purescript-0.13.0: tests/Language/PureScript/Ide/ImportsSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Language.PureScript.Ide.ImportsSpec where
import Protolude hiding (moduleName)
import Data.Maybe (fromJust)
import qualified Data.Set as Set
import qualified Language.PureScript as P
import Language.PureScript.Ide.Command as Command
import Language.PureScript.Ide.Error
import Language.PureScript.Ide.Imports
import Language.PureScript.Ide.Filter (moduleFilter)
import qualified Language.PureScript.Ide.Test as Test
import Language.PureScript.Ide.Types
import System.FilePath
import Test.Hspec
noImportsFile :: [Text]
noImportsFile =
[ "module Main where"
, ""
, "myFunc x y = x + y"
]
simpleFile :: [Text]
simpleFile =
[ "module Main where"
, "import Prelude"
, ""
, "myFunc x y = x + y"
]
hidingFile :: [Text]
hidingFile =
[ "module Main where"
, "import Prelude"
, "import Data.Maybe hiding (maybe)"
, ""
, "myFunc x y = x + y"
]
syntaxErrorFile :: [Text]
syntaxErrorFile =
[ "module Main where"
, "import Prelude"
, ""
, "myFunc ="
]
testSliceImportSection :: [Text] -> (P.ModuleName, [Text], [Import], [Text])
testSliceImportSection = unsafeFromRight . sliceImportSection
where
unsafeFromRight = fromJust . rightToMaybe
withImports :: [Text] -> [Text]
withImports is =
take 2 simpleFile ++ [""] ++ is ++ drop 2 simpleFile
testParseImport :: Text -> Import
testParseImport = fromJust . parseImport
preludeImport, arrayImport, listImport, consoleImport, maybeImport :: Import
preludeImport = testParseImport "import Prelude"
arrayImport = testParseImport "import Data.Array (head, cons)"
listImport = testParseImport "import Data.List as List"
consoleImport = testParseImport "import Effect.Console (log) as Console"
maybeImport = testParseImport "import Data.Maybe (Maybe(Just))"
spec :: Spec
spec = do
describe "determining the importsection" $ do
let moduleSkeleton imports =
Right (P.moduleNameFromString "Main", take 1 simpleFile, imports, drop 2 simpleFile)
it "slices a file without imports" $
shouldBe (sliceImportSection noImportsFile)
(Right (P.moduleNameFromString "Main", take 1 noImportsFile, [], drop 1 noImportsFile))
it "handles a file with syntax errors just fine" $
shouldBe (sliceImportSection syntaxErrorFile)
(Right (P.moduleNameFromString "Main", take 1 syntaxErrorFile, [preludeImport], drop 2 syntaxErrorFile))
it "finds a simple import" $
shouldBe (sliceImportSection simpleFile) (moduleSkeleton [preludeImport])
it "allows multiline import statements" $
shouldBe
(sliceImportSection (withImports [ "import Data.Array (head,"
, " cons)"
]))
(moduleSkeleton [preludeImport, arrayImport])
it "allows multiline import statements with hanging parens" $
shouldBe
(sliceImportSection (withImports [ "import Data.Array ("
, " head,"
, " cons"
, ")"
]))
(moduleSkeleton [preludeImport, arrayImport])
describe "pretty printing imports" $ do
it "pretty prints a simple import" $
shouldBe (prettyPrintImport' preludeImport) "import Prelude"
it "pretty prints an explicit import" $
shouldBe (prettyPrintImport' arrayImport) "import Data.Array (head, cons)"
it "pretty prints a qualified import" $
shouldBe (prettyPrintImport' listImport) "import Data.List as List"
it "pretty prints a qualified explicit import" $
shouldBe (prettyPrintImport' consoleImport) "import Effect.Console (log) as Console"
it "pretty prints an import with a datatype (and PositionedRef's for the dtors)" $
shouldBe (prettyPrintImport' maybeImport) "import Data.Maybe (Maybe(Just))"
describe "import commands" $ do
let simpleFileImports = let (_, _, i, _) = testSliceImportSection simpleFile in i
hidingFileImports = let (_, _, i, _) = testSliceImportSection hidingFile in i
addValueImport i mn q is =
prettyPrintImportSection (addExplicitImport' (_idaDeclaration (Test.ideValue i Nothing)) mn q is)
addOpImport op mn q is =
prettyPrintImportSection (addExplicitImport' (_idaDeclaration (Test.ideValueOp op (P.Qualified q (Left "")) 2 Nothing Nothing)) mn q is)
addDtorImport i t mn q is =
prettyPrintImportSection (addExplicitImport' (_idaDeclaration (Test.ideDtor i t Nothing)) mn q is)
addTypeImport i mn q is =
prettyPrintImportSection (addExplicitImport' (_idaDeclaration (Test.ideType i Nothing [])) mn q is)
addKindImport i mn q is =
prettyPrintImportSection (addExplicitImport' (_idaDeclaration (Test.ideKind i)) mn q is)
qualify s = Just (Test.mn s)
it "adds an implicit unqualified import to a file without any imports" $
shouldBe
(addImplicitImport' [] (P.moduleNameFromString "Data.Map"))
["import Data.Map"]
it "adds an implicit unqualified import" $
shouldBe
(addImplicitImport' simpleFileImports (P.moduleNameFromString "Data.Map"))
[ "import Data.Map"
, "import Prelude"
]
it "adds a qualified import" $
shouldBe
(addQualifiedImport' simpleFileImports (Test.mn "Data.Map") (Test.mn "Map"))
[ "import Prelude"
, ""
, "import Data.Map as Map"
]
it "adds a qualified import and maintains proper grouping for implicit hiding imports" $
shouldBe
(addQualifiedImport' hidingFileImports (Test.mn "Data.Map") (Test.mn "Map"))
[ "import Data.Maybe hiding (maybe)"
, "import Prelude"
, ""
, "import Data.Map as Map"
]
it "adds an explicit unqualified import to a file without any imports" $
shouldBe
(addValueImport "head" (P.moduleNameFromString "Data.Array") Nothing [])
["import Data.Array (head)"]
it "adds an explicit qualified import to a file without any imports" $
shouldBe
(addValueImport "head" (P.moduleNameFromString "Data.Array") (qualify "Array") [])
["import Data.Array (head) as Array"]
it "adds an explicit unqualified import" $
shouldBe
(addValueImport "head" (P.moduleNameFromString "Data.Array") Nothing simpleFileImports)
[ "import Prelude"
, ""
, "import Data.Array (head)"
]
it "adds an explicit qualified import" $
shouldBe
(addValueImport "head" (P.moduleNameFromString "Data.Array") (qualify "Array") simpleFileImports)
[ "import Prelude"
, ""
, "import Data.Array (head) as Array"
]
it "doesn't add an import if the containing module is imported implicitly" $
shouldBe
(addValueImport "const" (P.moduleNameFromString "Prelude") Nothing simpleFileImports)
["import Prelude"]
let Right (_, _, qualifiedImports, _) = sliceImportSection (withImports ["import Data.Array as Array"])
it "doesn't add a qualified explicit import if the containing module is imported qualified" $
shouldBe
(addValueImport "length" (P.moduleNameFromString "Data.Array") (qualify "Array") qualifiedImports)
["import Prelude"
, ""
, "import Data.Array as Array"]
let Right (_, _, explicitImports, _) = sliceImportSection (withImports ["import Data.Array (tail)"])
it "adds an identifier to an explicit import list" $
shouldBe
(addValueImport "head" (P.moduleNameFromString "Data.Array") Nothing explicitImports)
[ "import Prelude"
, ""
, "import Data.Array (head, tail)"
]
let Right (_, _, explicitQualImports, _) = sliceImportSection (withImports ["import Data.Array (tail) as Array"])
it "adds an identifier to an explicit qualified import list" $
shouldBe
(addValueImport "head" (P.moduleNameFromString "Data.Array") (qualify "Array") explicitQualImports)
[ "import Prelude"
, ""
, "import Data.Array (head, tail) as Array"
]
it "adds a kind to an explicit import list" $
shouldBe
(addKindImport "Effect" (P.moduleNameFromString "Control.Monad.Eff") Nothing simpleFileImports)
[ "import Prelude"
, ""
, "import Control.Monad.Eff (kind Effect)"
]
it "adds a kind to an explicit qualified import list" $
shouldBe
(addKindImport "Effect" (P.moduleNameFromString "Control.Monad.Eff") (qualify "Eff") simpleFileImports)
[ "import Prelude"
, ""
, "import Control.Monad.Eff (kind Effect) as Eff"
]
it "adds an operator to an explicit import list" $
shouldBe
(addOpImport "<~>" (P.moduleNameFromString "Data.Array") Nothing explicitImports)
[ "import Prelude"
, ""
, "import Data.Array (tail, (<~>))"
]
it "adds an operator to an explicit qualified import list" $
shouldBe
(addOpImport "<~>" (P.moduleNameFromString "Data.Array") (qualify "Array") explicitQualImports)
[ "import Prelude"
, ""
, "import Data.Array (tail, (<~>)) as Array"
]
it "adds a type with constructors without automatically adding an open import of said constructors " $
shouldBe
(addTypeImport "Maybe" (P.moduleNameFromString "Data.Maybe") Nothing simpleFileImports)
[ "import Prelude"
, ""
, "import Data.Maybe (Maybe)"
]
it "adds the type for a given DataConstructor" $
shouldBe
(addDtorImport "Just" "Maybe" (P.moduleNameFromString "Data.Maybe") Nothing simpleFileImports)
[ "import Prelude"
, ""
, "import Data.Maybe (Maybe(..))"
]
it "adds the type for a given DataConstructor qualified" $
shouldBe
(addDtorImport "Just" "Maybe" (P.moduleNameFromString "Data.Maybe") (qualify "M") simpleFileImports)
[ "import Prelude"
, ""
, "import Data.Maybe (Maybe(..)) as M"
]
it "adds a dataconstructor to an existing type import" $ do
let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe)"])
shouldBe
(addDtorImport "Just" "Maybe" (P.moduleNameFromString "Data.Maybe") Nothing typeImports)
[ "import Prelude"
, ""
, "import Data.Maybe (Maybe(..))"
]
it "adding a type to an existing import of that type is noop" $ do
let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe)"])
shouldBe
(addTypeImport "Maybe" (P.moduleNameFromString "Data.Maybe") Nothing typeImports)
[ "import Prelude"
, ""
, "import Data.Maybe (Maybe)"
]
it "adding a type to an existing import of that type with its constructors is noop" $ do
let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe (..))"])
shouldBe
(addTypeImport "Maybe" (P.moduleNameFromString "Data.Maybe") Nothing typeImports)
[ "import Prelude"
, ""
, "import Data.Maybe (Maybe(..))"
]
it "adds a dataconstructor to an existing qualified type import" $ do
let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe) as M"])
shouldBe
(addDtorImport "Just" "Maybe" (P.moduleNameFromString "Data.Maybe") (qualify "M") typeImports)
[ "import Prelude"
, ""
, "import Data.Maybe (Maybe(..)) as M"
]
it "doesn't add a dataconstructor to an existing type import with open dtors" $ do
let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe(..))"])
shouldBe
(addDtorImport "Just" "Maybe" (P.moduleNameFromString "Data.Maybe") Nothing typeImports)
[ "import Prelude"
, ""
, "import Data.Maybe (Maybe(..))"
]
it "doesn't add an identifier to an explicit import list if it's already imported" $
shouldBe
(addValueImport "tail" (P.moduleNameFromString "Data.Array") Nothing explicitImports)
[ "import Prelude"
, ""
, "import Data.Array (tail)"
]
it "doesn't add an identifier to an explicit qualified import list if it's already imported qualified" $
shouldBe
(addValueImport "tail" (P.moduleNameFromString "Data.Array") (qualify "Array") explicitQualImports)
[ "import Prelude"
, ""
, "import Data.Array (tail) as Array"
]
describe "explicit import sorting" $ do
-- given some basic import skeleton
let Right (_, _, baseImports, _) = sliceImportSection $ withImports ["import Control.Monad (ap)"]
moduleName = (P.moduleNameFromString "Control.Monad")
addImport imports import' = addExplicitImport' import' moduleName Nothing imports
valueImport ident = _idaDeclaration (Test.ideValue ident Nothing)
typeImport name = _idaDeclaration (Test.ideType name Nothing [])
classImport name = _idaDeclaration (Test.ideTypeClass name P.kindType [])
dtorImport name typeName = _idaDeclaration (Test.ideDtor name typeName Nothing)
-- expect any list of provided identifiers, when imported, to come out as specified
expectSorted imports expected = shouldBe
(ordNub $ map
(prettyPrintImportSection . foldl addImport baseImports)
(permutations imports))
[expected]
it "sorts class" $
expectSorted (map classImport ["Applicative", "Bind"])
["import Prelude", "", "import Control.Monad (class Applicative, class Bind, ap)"]
it "sorts value" $
expectSorted (map valueImport ["unless", "where"])
["import Prelude", "", "import Control.Monad (ap, unless, where)"]
it "sorts type, value" $
expectSorted
((map valueImport ["unless", "where"]) ++ (map typeImport ["Foo", "Bar"]))
["import Prelude", "", "import Control.Monad (Bar, Foo, ap, unless, where)"]
it "sorts class, type, value" $
expectSorted
((map valueImport ["unless", "where"]) ++ (map typeImport ["Foo", "Bar"]) ++ (map classImport ["Applicative", "Bind"]))
["import Prelude", "", "import Control.Monad (class Applicative, class Bind, Bar, Foo, ap, unless, where)"]
it "sorts types with constructors, using open imports for the constructors" $
expectSorted
-- the imported names don't actually have to exist!
(map (uncurry dtorImport) [("Just", "Maybe"), ("Nothing", "Maybe"), ("SomeOtherConstructor", "SomeDataType")])
["import Prelude", "", "import Control.Monad (Maybe(..), SomeDataType(..), ap)"]
describe "importing from a loaded IdeState" importFromIdeState
implImport :: Text -> Command
implImport mn =
Command.Import ("src" </> "ImportsSpec.purs") Nothing [] (Command.AddImplicitImport (Test.mn mn))
addExplicitImport :: Text -> Command
addExplicitImport i =
Command.Import ("src" </> "ImportsSpec.purs") Nothing [] (Command.AddImportForIdentifier i Nothing)
addExplicitImportFiltered :: Text -> [P.ModuleName] -> Command
addExplicitImportFiltered i ms =
Command.Import ("src" </> "ImportsSpec.purs") Nothing [moduleFilter (Set.fromList ms)] (Command.AddImportForIdentifier i Nothing)
importShouldBe :: [Text] -> [Text] -> Expectation
importShouldBe res importSection =
res `shouldBe`
[ "module ImportsSpec where" ]
++ (if null importSection then [] else "" : importSection)
++ [ ""
, "myId x = x"
]
runIdeLoaded :: Command -> IO (Either IdeError Success)
runIdeLoaded c = do
([_, result], _) <- Test.inProject $ Test.runIde [Command.LoadSync [] , c]
pure result
importFromIdeState :: Spec
importFromIdeState = do
it "adds an implicit import" $ do
Right (MultilineTextResult result) <-
runIdeLoaded (implImport "ImportsSpec1")
result `importShouldBe` [ "import ImportsSpec1" ]
it "adds an explicit unqualified import" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "exportedFunction")
result `importShouldBe` [ "import ImportsSpec1 (exportedFunction)" ]
it "adds an explicit unqualified import (type)" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "MyType")
result `importShouldBe` [ "import ImportsSpec1 (MyType)" ]
it "adds an explicit unqualified import (parameterized type)" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "MyParamType")
result `importShouldBe` [ "import ImportsSpec1 (MyParamType)" ]
it "adds an explicit unqualified import (typeclass)" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "ATypeClass")
result `importShouldBe` [ "import ImportsSpec1 (class ATypeClass)" ]
it "adds an explicit unqualified import (dataconstructor)" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "MyJust")
result `importShouldBe` [ "import ImportsSpec1 (MyMaybe(..))" ]
it "adds an explicit unqualified import (newtype)" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "MyNewtype")
result `importShouldBe` [ "import ImportsSpec1 (MyNewtype(..))" ]
it "adds an explicit unqualified import (typeclass member function)" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "typeClassFun")
result `importShouldBe` [ "import ImportsSpec1 (typeClassFun)" ]
it "doesn't add a newtypes constructor if only the type is exported" $ do
Right (MultilineTextResult result) <-
runIdeLoaded (addExplicitImport "OnlyTypeExported")
result `importShouldBe` [ "import ImportsSpec1 (OnlyTypeExported)" ]
it "doesn't add an import if the identifier is defined in the module itself" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "myId")
result `importShouldBe` []
it "responds with an error if it's undecidable whether we want a type or constructor" $ do
result <- runIdeLoaded (addExplicitImport "SpecialCase")
result `shouldSatisfy` isLeft
it "responds with an error if the identifier cannot be found and doesn't \
\write to the output file" $ do
result <- runIdeLoaded (addExplicitImport "doesnExist")
result `shouldSatisfy` isLeft
it "doesn't import things from the Prim modules" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImport "String")
result `importShouldBe` []
it "imports classes from Prim.* modules" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImportFiltered "Cons" [Test.mn "Prim.Row"])
result `importShouldBe` ["import Prim.Row (class Cons)"]
it "imports types from Prim.* modules" $ do
Right (MultilineTextResult result) <- runIdeLoaded (addExplicitImportFiltered "Cons" [Test.mn "Prim.RowList"])
result `importShouldBe` ["import Prim.RowList (Cons)"]