{-# language QuasiQuotes #-} module AstSpec where import Control.Monad import Data.String.Interpolate import GHC import Outputable import System.Directory import System.IO import System.IO.Silently import Test.Hspec import Ast import Graph import Helper showAst :: Ast -> String showAst = showSDocUnsafe . ppr spec :: Spec spec = do describe "parse" $ do it "parses a simple module" $ do withFooHeader "foo = 3 -- bar" $ do ast <- parse ["Foo.hs"] fmap showAst ast `shouldBe` Right "[Module Foo Nothing [foo = 3]]" it "handles an invalid module gracefully" $ do withFooHeader "foo = bar" $ do result <- parse ["Foo.hs"] void result `shouldBe` Left "\nFoo.hs:2:7: Not in scope: ‘bar’\n" context "preprocessor errors" $ do it "gives Lefts, not Exceptions" $ do withFoo "{-# LANGUAGE CPP #-}\n#invalid" $ do Left err <- parse ["Foo.hs"] err `shouldContain` "lexical error" it "includes source locations" $ do withFoo "{-# LANGUAGE CPP #-}\n#invalid" $ do Left err <- parse ["Foo.hs"] err `shouldContain` "Foo.hs:2:2:" it "handles missing modules gracefully" $ do withFooHeader "import Bar" $ do Left message <- parse ["Foo.hs"] message `shouldContain` "Could not find module ‘Bar’" it "doesn't output error messages" $ do withFoo "foo = bar" $ do output <- hCapture_ [stdout, stderr] $ parse ["Foo.hs"] output `shouldBe` "" it "doesn't follow imports" $ do let a = ("A", [i| module A where foo = () |]) b = ("B", [i| module B where import A bar = foo |]) withModules [a, b] $ do parseStringGraph ["B.hs"] `shouldReturn` Graph [("B.bar", ["A.foo"])] [] it "can be used to parse multiple files" $ do let a = ("A", [i| module A where foo = foo |]) b = ("B", [i| module B where bar = bar |]) withModules [a, b] $ do parseStringGraph ["A.hs", "B.hs"] `shouldReturn` Graph [("A.foo", ["A.foo"]), ("B.bar", ["B.bar"])] [] it "does not create any files" $ do withFooHeader [i| foo = () bar = () |] $ do _ <- parse ["Foo.hs"] files <- getDirectoryContents "." files `shouldMatchList` (words ". .. Foo.hs") describe "findExports" $ do let find moduleFiles moduleName = do ast <- either error id <$> parse moduleFiles let exports = either error id $ findExports ast moduleName return $ map showName exports it "finds the names exported by a given module" $ do withFooHeader [i| foo = () bar = () |] $ do exports <- find ["Foo.hs"] [mkModuleName "Foo"] exports `shouldMatchList` ["Foo.foo", "Foo.bar"] it "does not include local variables" $ do withFooHeader [i| foo = let bar = () in bar |] $ do exports <- find ["Foo.hs"] [mkModuleName "Foo"] exports `shouldMatchList` ["Foo.foo"] context "when given a module with an export list" $ do it "returns the explicit exports" $ do let a = ("A", [i| module A (foo) where foo = () bar = () |]) withModules [a] $ do exports <- find ["A.hs"] [mkModuleName "A"] exports `shouldBe` ["A.foo"] it "includes identifiers exported by module" $ do let a = ("A", [i| module A (module B) where import B |]) b = ("B", [i| module B where b = () |]) withModules [a, b] $ do exports <- find ["A.hs", "B.hs"] [mkModuleName "A"] exports `shouldBe` ["B.b"] describe "usedTopLevelNames" $ do it "returns the graph of identifier usage" $ do withFooHeader [i| foo = bar bar = () |] $ do g <- usageGraph <$> parseStringGraph ["Foo.hs"] g `shouldMatchList` [("Foo.foo", ["Foo.bar"]), ("Foo.bar", ["GHC.Tuple.()"])] it "detects usage in ViewPatterns" $ do withFoo [i| {-# LANGUAGE ViewPatterns #-} module Foo where x y = () bar (x -> y) = () |] $ do g <- usageGraph <$> parseStringGraph ["Foo.hs"] let Just used = lookup "Foo.bar" g used `shouldContain` ["Foo.x"] it "doesn't return local variables" $ do withFooHeader [i| foo = let x = x in x |] $ do parseStringGraph ["Foo.hs"] `shouldReturn` Graph [("Foo.foo", [])] [] context "data type declarations" $ do it "does not include constructor names" $ do withFooHeader [i| data A = A |] $ do parseStringGraph ["Foo.hs"] `shouldReturn` Graph [("Foo.A", [])] [] it "ignores selectors" $ do withFooHeader [i| data A = A { foo :: () } |] $ do boundNames <- map fst <$> usageGraph <$> parseStringGraph ["Foo.hs"] boundNames `shouldBe` ["Foo.A", "Foo.foo"] it "doesn't return bound names for instance methods" $ do withFooHeader [i| instance Show (a -> b) where show _ = "" |] $ do (usageGraph <$> parseStringGraph ["Foo.hs"]) `shouldReturn` [] it "ignores standalone deriving instances" $ do withFoo [i| {-# LANGUAGE StandaloneDeriving #-} module Foo where data Foo = Foo deriving instance Show Foo |] $ do (usageGraph <$> parseStringGraph ["Foo.hs"]) `shouldReturn` [("Foo.Foo", [])] it "ignores annotations" $ do withFooHeader [i| {-# ANN foo "annotation" #-} foo = 42 |] $ do (usageGraph <$> parseStringGraph ["Foo.hs"]) `shouldReturn` [("Foo.foo",[])] context "PatBind" $ do it "can parse pattern binding" $ do withFooHeader [i| (Just foo) = let x = x in x |] $ do parseStringGraph ["Foo.hs"] `shouldReturn` Graph [("Foo.foo", ["GHC.Base.Just"])] [] it "can parse tuple pattern binding" $ do withFooHeader [i| (a, b) = let x = x in x |] $ do parseStringGraph ["Foo.hs"] `shouldReturn` Graph [("Foo.a", []), ("Foo.b", [])] [] it "can parse lazy patterns" $ do withFooHeader [i| ~(a, b) = let x = x in x |] $ do parseStringGraph ["Foo.hs"] `shouldReturn` Graph [("Foo.a", []), ("Foo.b", [])] [] context "local variables" $ do it "recognizes recursive definitions" $ do withFooHeader [i| foo = foo bar = () |] $ do parseStringGraph ["Foo.hs"] `shouldReturn` Graph [("Foo.bar", ["GHC.Tuple.()"]), ("Foo.foo", ["Foo.foo"])] []