Skip to content

Commit

Permalink
Merge pull request #67 from ptwales/relative-imports
Browse files Browse the repository at this point in the history
Fix a bug with relative imports
  • Loading branch information
ptwales authored Jan 8, 2020
2 parents 6b1f4d8 + c92bb35 commit 3063629
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 42 deletions.
34 changes: 24 additions & 10 deletions src/Ccap/Codegen/Imports.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ module Ccap.Codegen.Imports
, Imported
, Includes
, importInScope
, importPath
, importsInScope
, importsInScopes
, parseImports
, possibleImportPaths
, resolveImport
, validateImports
) where

Expand All @@ -32,7 +35,7 @@ type FoundImport
= { imprt :: Import, filePath :: FilePath }

type Imported
= { imprt :: Import, mod :: Module }
= Source { imprt :: Import, mod :: Module }

data ImportError
= NotFound Module Import
Expand All @@ -42,18 +45,26 @@ data ImportError
instance importValidationError :: ValidationError ImportError where
printError = case _ of
NotFound mod imprt ->
mod.name <> " tried to import module: " <> imprt
mod.name
<> " tried to import module: "
<> imprt
<> " but it does not exist, or was not included."
ParseError imprt message -> "Parsing imported module, " <> imprt <> ", failed with error: " <> message
ParseError imprt message ->
"Parsing imported module, "
<> imprt
<> ", failed with error: "
<> message

importPath :: Import -> FilePath
importPath = String.replaceAll (Pattern ".") (Replacement Path.sep)

resolveImport :: FilePath -> Import -> FilePath
resolveImport filePath = FS.joinPaths filePath <<< flip append ".tmpl" <<< importPath

possibleImportPaths :: Includes -> FilePath -> Import -> Array FilePath
possibleImportPaths includes source imprt =
let
sourceDirs = Path.dirname source : includes

importPath = String.replaceAll (Pattern ".") (Replacement Path.sep) imprt
in
sourceDirs <#> flip FS.joinPaths importPath <#> flip append ".tmpl"
(Path.dirname source : includes)
<#> (flip resolveImport $ importPath imprt)

importInScope :: Includes -> Source Module -> Import -> Effect (Either ImportError FoundImport)
importInScope included { source, contents } imprt =
Expand Down Expand Up @@ -90,7 +101,10 @@ importsInScopes includes sources =
parseImports :: Array FoundImport -> Effect (Either (Array ImportError) (Array Imported))
parseImports imports = traverse parse imports <#> toValidation
where
parse { imprt, filePath } = bimap (ParseError imprt) (\source -> { imprt, mod: source.contents }) <$> FS.sourceFile filePath
parse { imprt, filePath } =
bimap (ParseError imprt)
(\source -> { source: filePath, contents: { imprt, mod: source.contents } })
<$> FS.sourceFile filePath

-- | Validate that the imports of the given modules exist and parse the imported modules
-- | Note: Does not validate the contents of the imported files.
Expand Down
65 changes: 38 additions & 27 deletions src/Ccap/Codegen/Module.purs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ module Ccap.Codegen.Module
) where

import Prelude
import Ccap.Codegen.Imports (Imported, Includes, validateImports)
import Ccap.Codegen.Imports (Imported, Includes, possibleImportPaths, validateImports)
import Ccap.Codegen.TypeRef (validateAllTypeRefs)
import Ccap.Codegen.Types (Module, Source, ValidatedModule)
import Ccap.Codegen.ValidationError (class ValidationError, printError)
import Ccap.Codegen.ValidationError (printError)
import Control.Monad.Except (ExceptT(..), except, runExceptT, withExceptT)
import Control.MonadPlus (guard)
import Data.Array as Array
import Data.Bifunctor (lmap)
import Data.Either (Either)
import Data.Foldable (any)
import Data.Traversable (for)
import Effect (Effect)

Expand All @@ -21,27 +23,36 @@ validateModules ::
Effect (Either (Array String) (Array (Source ValidatedModule)))
validateModules includes sources =
runExceptT do
allImports <- withErrors $ ExceptT $ validateImports includes sources
withErrors $ except
$ for sources \source -> do
let
mod = source.contents

imports = importsForModule mod allImports
_ <- validateAllTypeRefs mod imports
pure $ source { contents = mod { imports = imports } }

withErrors ::
forall f e a.
Functor f =>
ValidationError e =>
ExceptT (Array e) f a ->
ExceptT (Array String) f a
withErrors = withExceptT $ map printError

importsForModule :: Module -> Array Imported -> Array Module
importsForModule mod imports = do
imported <- imports
imprt <- mod.imports
guard $ imprt == imported.imprt
pure $ imported.mod
allImports <- withExceptT (map printError) $ ExceptT $ validateImports includes sources
except $ for sources $ validateModule includes allImports

validateModule ::
Includes ->
Array Imported ->
Source Module ->
Either (Array String) (Source ValidatedModule)
validateModule includes allImports source =
let
imports = importsForModule includes source allImports

validatedSource = source { contents = source.contents { imports = imports } }

validations = lmap (map printError) $ validateAllTypeRefs source.contents imports
in
validations *> pure validatedSource

importsForModule :: Includes -> Source Module -> Array Imported -> Array Module
importsForModule includes source =
map _.contents.mod
<<< Array.filter (isImportedBy includes source)

-- This was already done by validateImports, we should adjust the return type of that so we don't
-- have to do this again.
isImportedBy :: Includes -> Source Module -> Imported -> Boolean
isImportedBy includes source imported =
let
{ source: modulePath, contents: { imports } } = source

{ source: importPath, contents: { imprt, mod: { name } } } = imported
in -- TODO: this is almost identical to importInScope
any (eq importPath) $ possibleImportPaths includes modulePath =<< imports
38 changes: 33 additions & 5 deletions test/Ccap/Codegen/Imports.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,34 @@ module Test.Ccap.Codegen.Imports
import Prelude
import Ccap.Codegen.FileSystem as FS
import Ccap.Codegen.Imports (importsInScope, validateImports)
import Ccap.Codegen.Module (validateModules)
import Ccap.Codegen.TypeRef (validateAllTypeRefs)
import Ccap.Codegen.ValidationError (class ValidationError, printError)
import Control.Monad.Except (ExceptT(..), except, runExceptT, withExceptT)
import Data.Either (either)
import Data.Foldable (fold)
import Data.Traversable (intercalate, sequence, traverse)
import Effect.Class (liftEffect)
import Node.Path (FilePath)
import Node.Path as Path
import Test.Ccap.Codegen.Util (eqElems, shouldBeLeft, shouldBeRight)
import Test.Spec (Spec, describe, it)
import Test.Spec.Assertions (shouldSatisfy)

root :: FilePath
root = "./test/resources/includes/"
resources :: FilePath
resources = "./test/resources/"

imports_ :: FilePath
imports_ = Path.concat [ resources, "imports" ]

includes_ :: FilePath
includes_ = Path.concat [ resources, "includes" ]

internal_ :: FilePath
internal_ = Path.concat [ root, "internal" ]
internal_ = Path.concat [ includes_, "internal" ]

external_ :: FilePath
external_ = Path.concat [ root, "external" ]
external_ = Path.concat [ includes_, "external" ]

internal :: FilePath -> FilePath
internal fileName = Path.concat [ internal_, fileName ]
Expand All @@ -44,6 +52,12 @@ externalSource = internal "SourceExternal.tmpl"
externalSubmoduleSource :: FilePath
externalSubmoduleSource = internal "SourceExternalSubmodule.tmpl"

app1 :: FilePath
app1 = Path.concat [ imports_, "app1", "Main.tmpl" ]

app2 :: FilePath
app2 = Path.concat [ imports_, "app2", "Main.tmpl" ]

withPrintErrors e m a. ValidationError e Monad m ExceptT (Array e) m a ExceptT String m a
withPrintErrors = withExceptT $ fold <<< map printError

Expand Down Expand Up @@ -77,6 +91,15 @@ specs =
withPrintErrors $ ExceptT $ validateImports includes [ source ]
shouldBeRight imports

itCanValidateAllModules filePaths includes =
it "Can validate all modules" do
modules <-
liftEffect
$ runExceptT do
sources <- ExceptT $ map sequence $ traverse FS.sourceFile filePaths
withExceptT (intercalate "|") $ ExceptT $ validateModules includes sources
shouldBeRight modules

itFailsWithoutIncludes filePath =
it "Fails validation without including the external folder" do
let
Expand All @@ -95,7 +118,7 @@ specs =
$ runExceptT do
source <- ExceptT $ FS.sourceFile filePath
imports <- withPrintErrors $ ExceptT $ validateImports includes [ source ]
withPrintErrors $ except $ validateAllTypeRefs source.contents (imports <#> _.mod)
withPrintErrors $ except $ validateAllTypeRefs source.contents (imports <#> _.contents.mod)
shouldBeRight typeDecls
in
describe "template include syntax" do
Expand All @@ -115,17 +138,22 @@ specs =
itCanFindImports submoduleSource []
itCanValidateImports submoduleSource []
itHasValidTypeReferences submoduleSource []
itCanValidateAllModules [ submoduleSource ] []
describe "a file with an external reference" do
itCanBeParsed externalSource
itHasImports externalSource [ "External" ]
itCanFindImports externalSource [ external_ ]
itCanValidateImports externalSource [ external_ ]
itFailsWithoutIncludes externalSource
itHasValidTypeReferences externalSource [ external_ ]
itCanValidateAllModules [ externalSource ] [ external_ ]
describe "a file with an external reference to a submodule" do
itCanBeParsed externalSubmoduleSource
itHasImports externalSubmoduleSource [ "submodule.ExternalSubmodule" ]
itCanFindImports externalSubmoduleSource [ external_ ]
itCanValidateImports externalSubmoduleSource [ external_ ]
itFailsWithoutIncludes externalSubmoduleSource
itHasValidTypeReferences externalSubmoduleSource [ external_ ]
itCanValidateAllModules [ externalSubmoduleSource ] [ external_ ]
describe "two files with identical relative imports"
$ itCanValidateAllModules [ app1, app2 ] []
6 changes: 6 additions & 0 deletions test/resources/imports/app1/Main.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
scala: test.App1
purs: Test.App1

import Types

type App: Types.Foo
4 changes: 4 additions & 0 deletions test/resources/imports/app1/Types.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
scala: test.Lib1
purs: Test.Lib1

type Foo: Int
6 changes: 6 additions & 0 deletions test/resources/imports/app2/Main.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
scala: test.App2
purs: Test.App2

import Types

type App: Types.Bar
4 changes: 4 additions & 0 deletions test/resources/imports/app2/Types.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
scala: test.Lib2
purs: Test.Lib2

type Bar: Int

0 comments on commit 3063629

Please sign in to comment.