module Docs.Check (check) where import qualified Data.List as List import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.Traversable as T import Text.Parsec ((<|>), choice, many, setPosition, string) import Text.Parsec.Pos (newPos) import qualified AST.Variable as Var import qualified Docs.AST as Docs import qualified Elm.Compiler.Type as Type import Elm.Utils ((|>)) import Parse.Helpers ( addLocation, anyUntil, commaSep1, iParse, parens, simpleNewline, symOp , var, whitespace ) import qualified Reporting.Annotation as A import qualified Reporting.Error.Docs as Error import qualified Reporting.Error.Helpers as Error (nearbyNames) import qualified Reporting.Region as R import qualified Reporting.Result as R type Result w a = R.Result w Error.Error a -- CHECK DOCUMENTATION check :: [Var.Value] -> A.Located (Maybe Docs.Centralized) -> Result w Docs.Checked check exports (A.A region maybeDocs) = case maybeDocs of Nothing -> R.throw region Error.NoDocs Just docs -> checkHelp region exports docs checkHelp :: R.Region -> [Var.Value] -> Docs.Centralized -> Result w Docs.Checked checkHelp region exports (Docs.Docs comment aliases types values) = let docNames = commentedNames region comment docSet = Set.fromList (map A.drop docNames) checkCategory checkValue dict = dict |> Map.filterWithKey (\key _ -> Set.member key docSet) |> Map.mapWithKey checkValue |> T.traverse id exportedUnions = map (\(tipe, Var.Listing tags _) -> (tipe, tags)) (Var.getUnions exports) in (\() -> Docs.Docs comment) <$> checkModuleComment region exports docNames <*> checkCategory (checkComment Docs.aliasComment) aliases <*> checkCategory (checkUnion exportedUnions) types <*> checkCategory checkValue values checkComment :: (a -> Maybe String) -> String -> A.Located a -> Result w (A.Located a) checkComment getComment name (A.A region value) = case getComment value of Nothing -> R.throw region (Error.NoComment name) Just _ -> R.ok (A.A region value) checkUnion :: [(String, [String])] -> String -> A.Located Docs.Union -> Result w (A.Located Docs.Union) checkUnion exportedUnions name value = const <$> filterUnionTags exportedUnions name value <*> checkComment Docs.unionComment name value filterUnionTags :: [(String, [String])] -> String -> A.Located Docs.Union -> Result w (A.Located Docs.Union) filterUnionTags exportedUnions name (A.A region union@(Docs.Union _ _ ctors)) = let exportedTags = maybe [] id (List.lookup name exportedUnions) in R.ok $ A.A region $ union { Docs.unionCases = filter (\(tag, _) -> elem tag exportedTags) ctors } checkValue :: String -> A.Located (Docs.Value (Maybe Type.Type)) -> Result w (A.Located (Docs.Value Type.Type)) checkValue name value = const <$> hasType name value <*> checkComment Docs.valueComment name value hasType :: String -> A.Located (Docs.Value (Maybe Type.Type)) -> Result w (A.Located (Docs.Value Type.Type)) hasType name (A.A region value) = case Docs.valueType value of Just tipe -> R.ok (A.A region (value { Docs.valueType = tipe })) Nothing -> R.throw region (Error.NoType name) -- CHECK MODULE COMMENT checkModuleComment :: R.Region -> [Var.Value] -> [A.Located String] -> Result w () checkModuleComment docRegion exports locatedDocNames = let exportNames = map valueName exports exportDict = Map.fromList (map (\name -> (name, ())) exportNames) docDict = Map.fromList (map (\(A.A region name) -> (name, region)) locatedDocNames) extraNameError (name, region) = R.throw region (Error.OnlyInDocs name (Error.nearbyNames id name exportNames)) docErrors = Map.difference docDict exportDict |> Map.toList |> T.traverse extraNameError namesOnlyInExports = Map.difference exportDict docDict |> Map.toList |> map fst exportErrors = if null namesOnlyInExports then R.ok () else R.throw docRegion (Error.OnlyInExports namesOnlyInExports) in (\_ _ -> ()) <$> exportErrors <*> docErrors commentedNames :: R.Region -> String -> [A.Located String] commentedNames (R.Region start _) comment = let nameParser = choice [ do string "@docs" whitespace commaSep1 (addLocation (var <|> parens symOp)) , do anyUntil simpleNewline return [] ] docCommentParser = do setPosition (newPos "docs" (R.line start) (R.column start + 3)) concat <$> many nameParser in case iParse docCommentParser comment of Left _ -> [] Right names -> names valueName :: Var.Value -> String valueName value = case value of Var.Value name -> name Var.Alias name -> name Var.Union name _ -> name