@@ -0,0 +1,232 @@
1+{-# LANGUAGE RecordWildCards #-}
2+{-# LANGUAGE ViewPatterns #-}
3+{-# LANGUAGE DataKinds #-}
4+
5+module TestDocs where
6+
7+import Prelude ()
8+import Prelude.Compat
9+
10+import Data.Version (Version(..))
11+
12+import Control.Monad hiding (forM_)
13+import Control.Applicative
14+import Control.Arrow
15+import Data.Maybe (fromMaybe)
16+import Data.List ((\\))
17+import Data.Foldable
18+import Data.Traversable
19+import System.Exit
20+import qualified Text.Parsec as Parsec
21+
22+import qualified Language.PureScript as P
23+import qualified Language.PureScript.Docs as Docs
24+import qualified Language.PureScript.Publish as Publish
25+
26+import qualified TestPscPublish
27+
28+publishOpts :: Publish.PublishOptions
29+publishOpts = Publish.defaultPublishOptions
30+ { Publish.publishGetVersion = return testVersion
31+ , Publish.publishWorkingTreeDirty = return ()
32+ }
33+ where testVersion = ("v999.0.0", Version [999,0,0] [])
34+
35+main :: IO ()
36+main = do
37+ TestPscPublish.pushd "examples/docs" $ do
38+ Docs.Package{..} <- Publish.preparePackage publishOpts
39+ forM_ testCases $ \(mn, pragmas) ->
40+ let mdl = takeJust ("module not found in docs: " ++ mn)
41+ (find ((==) mn . Docs.modName) pkgModules)
42+ in forM_ pragmas (flip runAssertionIO mdl)
43+
44+takeJust :: String -> Maybe a -> a
45+takeJust msg = maybe (error msg) id
46+
47+data Assertion
48+ -- | Assert that a particular declaration is documented with the given
49+ -- children
50+ = ShouldBeDocumented P.ModuleName String [String]
51+ -- | Assert that a particular declaration is not documented
52+ | ShouldNotBeDocumented P.ModuleName String
53+ -- | Assert that a particular declaration exists, but without a particular
54+ -- child.
55+ | ChildShouldNotBeDocumented P.ModuleName String String
56+ -- | Assert that a particular declaration has a particular type class
57+ -- constraint.
58+ | ShouldBeConstrained P.ModuleName String String
59+ deriving (Show)
60+
61+data AssertionFailure
62+ -- | A declaration was not documented, but should have been
63+ = NotDocumented P.ModuleName String
64+ -- | A child declaration was not documented, but should have been
65+ | ChildrenNotDocumented P.ModuleName String [String]
66+ -- | A declaration was documented, but should not have been
67+ | Documented P.ModuleName String
68+ -- | A child declaration was documented, but should not have been
69+ | ChildDocumented P.ModuleName String String
70+ -- | A constraint was missing.
71+ | ConstraintMissing P.ModuleName String String
72+ -- | A declaration had the wrong "type" (ie, value, type, type class)
73+ -- Fields: declaration title, expected "type", actual "type".
74+ | WrongDeclarationType P.ModuleName String String String
75+ deriving (Show)
76+
77+data AssertionResult
78+ = Pass
79+ | Fail AssertionFailure
80+ deriving (Show)
81+
82+runAssertion :: Assertion -> Docs.Module -> AssertionResult
83+runAssertion assertion Docs.Module{..} =
84+ case assertion of
85+ ShouldBeDocumented mn decl children ->
86+ case findChildren decl (declarationsFor mn) of
87+ Nothing ->
88+ Fail (NotDocumented mn decl)
89+ Just actualChildren ->
90+ case children \\ actualChildren of
91+ [] -> Pass
92+ cs -> Fail (ChildrenNotDocumented mn decl cs)
93+
94+ ShouldNotBeDocumented mn decl ->
95+ case findChildren decl (declarationsFor mn) of
96+ Just _ ->
97+ Fail (Documented mn decl)
98+ Nothing ->
99+ Pass
100+
101+ ChildShouldNotBeDocumented mn decl child ->
102+ case findChildren decl (declarationsFor mn) of
103+ Just children ->
104+ if child `elem` children
105+ then Fail (ChildDocumented mn decl child)
106+ else Pass
107+ Nothing ->
108+ Fail (NotDocumented mn decl)
109+
110+ ShouldBeConstrained mn decl tyClass ->
111+ case find ((==) decl . Docs.declTitle) (declarationsFor mn) of
112+ Nothing ->
113+ Fail (NotDocumented mn decl)
114+ Just Docs.Declaration{..} ->
115+ case declInfo of
116+ Docs.ValueDeclaration ty ->
117+ if checkConstrained ty tyClass
118+ then Pass
119+ else Fail (ConstraintMissing mn decl tyClass)
120+ _ ->
121+ Fail (WrongDeclarationType mn decl "value"
122+ (Docs.declInfoToString declInfo))
123+
124+ where
125+ declarationsFor mn =
126+ if P.runModuleName mn == modName
127+ then modDeclarations
128+ else fromMaybe [] (lookup mn modReExports)
129+
130+ findChildren title =
131+ fmap childrenTitles . find ((==) title . Docs.declTitle)
132+
133+ childrenTitles = map Docs.cdeclTitle . Docs.declChildren
134+
135+checkConstrained ty tyClass =
136+ -- Note that we don't recurse on ConstrainedType if none of the constraints
137+ -- match; this is by design, as constraints should be moved to the front
138+ -- anyway.
139+ case ty of
140+ P.ConstrainedType cs _ | any (matches tyClass) cs ->
141+ True
142+ P.ForAll _ ty' _ ->
143+ checkConstrained ty' tyClass
144+ _ ->
145+ False
146+ where
147+ matches className =
148+ (==) className . P.runProperName . P.disqualify . fst
149+
150+runAssertionIO :: Assertion -> Docs.Module -> IO ()
151+runAssertionIO assertion mdl = do
152+ putStrLn ("In " ++ Docs.modName mdl ++ ": " ++ show assertion)
153+ case runAssertion assertion mdl of
154+ Pass -> pure ()
155+ fail -> do
156+ putStrLn (show fail)
157+ exitFailure
158+
159+testCases :: [(String, [Assertion])]
160+testCases =
161+ [ ("Example",
162+ [ -- From dependencies
163+ ShouldBeDocumented (n "Prelude") "Unit" []
164+ , ShouldNotBeDocumented (n "Prelude") "unit"
165+
166+ -- From local files
167+ , ShouldBeDocumented (n "Example2") "one" []
168+ , ShouldNotBeDocumented (n "Example2") "two"
169+ ])
170+ , ("Example2",
171+ [ ShouldBeDocumented (n "Example2") "one" []
172+ , ShouldBeDocumented (n "Example2") "two" []
173+ ])
174+
175+ , ("UTF8",
176+ [ ShouldBeDocumented (n "UTF8") "thing" []
177+ ])
178+
179+ , ("Transitive1",
180+ [ ShouldBeDocumented (n "Transitive2") "transitive3" []
181+ ])
182+
183+ , ("NotAllCtors",
184+ [ ShouldBeDocumented (n "Prelude") "Boolean2" ["True"]
185+ , ChildShouldNotBeDocumented (n "Prelude") "Boolean2" "False"
186+ ])
187+
188+ , ("DuplicateNames",
189+ [ ShouldBeDocumented (n "Prelude") "Unit" []
190+ , ShouldBeDocumented (n "DuplicateNames") "unit" []
191+ , ShouldNotBeDocumented (n "Prelude") "unit"
192+ ])
193+
194+ , ("MultiVirtual",
195+ [ ShouldBeDocumented (n "MultiVirtual1") "foo" []
196+ , ShouldBeDocumented (n "MultiVirtual2") "bar" []
197+ , ShouldBeDocumented (n "MultiVirtual2") "baz" []
198+ ])
199+
200+ , ("Clash",
201+ [ ShouldBeDocumented (n "Clash1") "value" []
202+ , ShouldBeDocumented (n "Clash1") "Type" []
203+ , ShouldBeDocumented (n "Clash1") "TypeClass" ["typeClassMember"]
204+ ])
205+
206+ , ("SolitaryTypeClassMember",
207+ [ ShouldBeDocumented (n "SomeTypeClass") "member" []
208+ , ShouldNotBeDocumented (n "SomeTypeClass") "SomeClass"
209+ , ShouldBeConstrained (n "SomeTypeClass") "member" "SomeClass"
210+ ])
211+
212+ , ("ReExportedTypeClass",
213+ [ ShouldBeDocumented (n "SomeTypeClass") "SomeClass" ["member"]
214+ ])
215+
216+ , ("TypeClassWithoutMembers",
217+ [ ShouldBeDocumented (n "Intermediate") "SomeClass" []
218+ , ChildShouldNotBeDocumented (n "Intermediate") "SomeClass" "member"
219+ ])
220+
221+ -- Remove this after 0.9.
222+ , ("OldOperators",
223+ [ ShouldBeDocumented (n "OldOperators2") "(>>)" []
224+ ])
225+
226+ , ("NewOperators",
227+ [ ShouldBeDocumented (n "NewOperators2") "(>>>)" []
228+ ])
229+ ]
230+
231+ where
232+ n = P.moduleNameFromString