summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorstuebinm2021-09-20 21:41:50 +0200
committerstuebinm2021-09-20 21:41:50 +0200
commit9a8d793f8f08fd5674bc6a917278ee7251bac56f (patch)
tree7fce0b5da0739a23af4c2f16794a3240d6c4080f /lib
parent727f2cbc5feb3cdd30df3c78f39ba4a58e6c4832 (diff)
rebuilding the core LintWriter monad
it is no longer an Either since that wasn't used anyways, but is now also a Reader.
Diffstat (limited to 'lib')
-rw-r--r--lib/CheckMap.hs59
-rw-r--r--lib/LintWriter.hs80
-rw-r--r--lib/Properties.hs22
3 files changed, 68 insertions, 93 deletions
diff --git a/lib/CheckMap.hs b/lib/CheckMap.hs
index 0de9094..b32bad6 100644
--- a/lib/CheckMap.hs
+++ b/lib/CheckMap.hs
@@ -5,33 +5,31 @@
{-# LANGUAGE OverloadedStrings #-}
-- | Module that contains the high-level checking functions
+{-# OPTIONS_GHC -Wno-deferred-out-of-scope-variables #-}
module CheckMap (loadAndLintMap) where
-import Control.Monad.Trans.Writer (WriterT (runWriterT))
-import Data.Aeson (ToJSON)
-import Data.Map (Map, fromList, toList)
-import Data.Maybe (mapMaybe)
-import Data.Text (Text)
-import qualified Data.Text as T
-import qualified Data.Vector as V
-import GHC.Generics (Generic)
-
-import LintWriter (LintResult (..), LintWriter,
- lintResultToDeps, lintToDep,
- runLintWriter)
-import Properties (checkLayerProperty, checkMap)
-import Tiled2 (Layer (layerName, layerProperties),
- Tiledmap (tiledmapLayers),
- loadTiledmap)
-import Types (Dep, Level (..), Lint (..), hint,
- lintLevel)
-import Util (PrettyPrint (prettyprint),
- prettyprint)
+import Data.Aeson (ToJSON)
+import Data.Map (Map, fromList, toList)
+import Data.Maybe (mapMaybe)
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Vector as V
+import GHC.Generics (Generic)
+
+import LintWriter (LayerContext (..), LintResult (..), LintWriter,
+ lintToDep, resultToDeps, resultToLints,
+ runLintWriter)
+import Properties (checkLayerProperty, checkMap)
+import Tiled2 (Layer (layerName, layerProperties),
+ Tiledmap (tiledmapLayers), loadTiledmap)
+import Types (Dep, Level (..), Lint (..), hint, lintLevel)
+import Util (PrettyPrint (prettyprint), prettyprint)
+
-- | What this linter produces: lints for a single map
data MapResult a = MapResult
- { mapresultLayer :: Maybe (Map Text (LintResult a))
+ { mapresultLayer :: Maybe (Map Text (LintResult LayerContext))
, mapresultGeneral :: [Lint]
, mapresultDepends :: [Dep]
} deriving (Generic, ToJSON)
@@ -57,21 +55,22 @@ runLinter :: Tiledmap -> MapResult ()
runLinter tiledmap = MapResult
{ mapresultLayer = Just layerMap
, mapresultGeneral = generalLints -- no general lints for now
- , mapresultDepends = concatMap (lintResultToDeps . snd) layer
+ , mapresultDepends = concatMap (resultToDeps . snd) layer
<> mapMaybe lintToDep generalLints
}
where
- layerMap :: Map Text (LintResult ())
+ layerMap :: Map Text (LintResult LayerContext)
layerMap = fromList layer
layer = V.toList . V.map runCheck $ tiledmapLayers tiledmap
- where runCheck l = (layerName l, LintResult $ runWriterT (checkLayer l))
+ where runCheck l = (layerName l, runLintWriter (LayerContext ()) (checkLayer l))
-- lints collected from properties
- generalLints = runLintWriter (checkMap tiledmap)
+ generalLints =
+ resultToLints $ runLintWriter () (checkMap tiledmap)
-- | collect lints on a single map layer
-checkLayer :: Layer -> LintWriter ()
+checkLayer :: Layer -> LintWriter LayerContext
checkLayer layer =
mapM_ (checkLayerProperty layer) (layerProperties layer)
@@ -95,11 +94,9 @@ showContext ctxt = " (in layer " <> ctxt <> ")\n"
-- | pretty-printer for a LintResult. Isn't an instance of PrettyPrint since
-- it needs to know about the result's context (yes, there could be
-- a wrapper type for that – but I wasn't really in the mood)
-showResult :: Text -> LintResult a -> Maybe Text
-showResult ctxt (LintResult res) = case res of
- Left hint -> Just $ "Fatal: " <> prettyprint hint
- Right (_, []) -> Nothing
- Right (_, hints) -> Just $ T.concat (mapMaybe showHint hints)
+showResult :: Text -> LintResult c -> Maybe Text
+showResult ctxt (LintResult (_, lints)) =
+ Just $ T.concat (mapMaybe showHint lints)
where
-- TODO: make the "log level" configurable
showHint hint = case lintLevel hint of
diff --git a/lib/LintWriter.hs b/lib/LintWriter.hs
index dd5ae7d..a6fa17e 100644
--- a/lib/LintWriter.hs
+++ b/lib/LintWriter.hs
@@ -3,88 +3,66 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
-- | a monad that collects warnings, outputs, etc,
module LintWriter where
-import Control.Monad.Trans.Maybe ()
-import Control.Monad.Writer (MonadTrans (lift),
- MonadWriter (tell), WriterT,
- runWriterT)
-import Data.Aeson (ToJSON (toJSON))
-import Data.Text (Text)
+import Control.Monad.Trans.Maybe ()
+import Control.Monad.Writer (MonadWriter (tell), WriterT,
+ runWriterT)
+import Data.Aeson (ToJSON (toJSON))
+import Data.Text (Text)
-import Data.Maybe (mapMaybe)
+import Control.Monad.Trans.Reader (Reader, runReader)
+import Data.Maybe (mapMaybe)
import Types
+import GHC.Generics (Generic)
--- | a monad to collect hints. If it yields Left, then the
--- map is flawed in some fundamental way which prevented us
--- from getting any hints at all except whatever broke it
-type LintWriter a = WriterT [Lint] (Either Lint) a
+-- | a monad to collect hints, with some context
+type LintWriter ctxt = WriterT [Lint] (Reader ctxt) ()
--- this is wrapped in a newtype because Aeson is silly and wants
--- to serialise Either as { "Right" : … } or { "Left" : … } ...
-type LintResult' a = Either Lint (a, [Lint])
-newtype LintResult a = LintResult (LintResult' a)
+-- wrapped to allow for manual writing of Aeson instances
+type LintResult' ctxt = (ctxt, [Lint]) -- Either Lint (a, [Lint])
+newtype LintResult ctxt = LintResult (LintResult' ctxt)
+
+data LayerContext = LayerContext ()
+ deriving (Generic, ToJSON)
-- better, less confusing serialisation of an Either Hint (a, [Hint]).
-- Note that Left hint is also serialised as a list to make the resulting
-- json schema more regular.
instance ToJSON a => ToJSON (LintResult a) where
- toJSON (LintResult r) = toJson' r
- where toJson' (Left hint) = toJSON [hint]
- toJson' (Right (_, hints)) = toJSON hints
+ toJSON (LintResult res) = toJSON $ snd res
lintToDep :: Lint -> Maybe Dep
lintToDep = \case
Depends dep -> Just dep
_ -> Nothing
-lintResultToDeps :: LintResult a -> [Dep]
-lintResultToDeps (LintResult a) = case a of
- Left (Depends dep) -> [dep]
- Left _ -> []
- Right (_, lints) -> mapMaybe lintToDep lints
+resultToDeps :: LintResult a -> [Dep]
+resultToDeps (LintResult a) = mapMaybe lintToDep $ snd a
-- | convert a lint result into a flat list of lints
-- (throwing away information on if a single error was fatal)
resultToLints :: LintResult a -> [Lint]
-resultToLints (LintResult res) = case res of
- Left l -> [l]
- Right (_, lints) -> lints
+resultToLints (LintResult res) = snd res
--- | Confusingly, this returns lints, not a …
-runLintWriter :: LintWriter a -> [Lint]
-runLintWriter = resultToLints . LintResult . runWriterT
+-- | run a linter
+runLintWriter :: ctxt -> LintWriter ctxt -> LintResult ctxt
+runLintWriter c linter = LintResult (c, lints)
+ where lints = snd $ flip runReader c $ runWriterT linter
-- | write a hint into the LintWriter monad
-lint :: Level -> Text -> LintWriter ()
+lint :: Level -> Text -> LintWriter a
lint level = tell . (: []) . hint level
-dependsOn :: Dep -> LintWriter ()
+dependsOn :: Dep -> LintWriter a
dependsOn dep = tell . (: []) $ Depends dep
-warn = lint Warning
info = lint Info
-forbid = lint Forbidden
suggest = lint Suggestion
+warn = lint Warning
+forbid = lint Forbidden
complain = lint Error
-
-
-
--- TODO: all these functions should probably also just operate on LintWriter
-
--- | converts a Maybe to an Either, with a default value for Left
-unwrap :: b -> Maybe a -> Either b a
-unwrap hint maybe = case maybe of
- Just a -> Right a
- Nothing -> Left hint
-
--- | unwrap and produce a warning if the value was Nothing
-unwrapWarn :: Text -> Maybe a -> Either Lint a
-unwrapWarn msg = unwrap $ hint Warning msg
-
--- | same as unwrapWarn, but for booleans
-assertWarn :: Text -> Bool -> LintWriter ()
-assertWarn msg cond = lift $ if cond then Right () else Left $ hint Warning msg
diff --git a/lib/Properties.hs b/lib/Properties.hs
index 320f132..68cf88a 100644
--- a/lib/Properties.hs
+++ b/lib/Properties.hs
@@ -13,7 +13,7 @@ import Tiled2 (Layer (..), Property (..), PropertyValue (..),
import Util (layerIsEmpty, prettyprint)
import LintWriter (LintWriter, complain, dependsOn, forbid, info,
- suggest, warn)
+ suggest, warn, LayerContext)
import Paths
import Types (Dep (Link, Local, LocalMap, MapLink))
@@ -83,7 +83,7 @@ checkTileset tileset = do
--
-- It gets a reference to its own layer since sometimes the presence
-- of one property implies the presence or absense of another.
-checkLayerProperty :: Layer -> Property -> LintWriter ()
+checkLayerProperty :: Layer -> Property -> LintWriter LayerContext
checkLayerProperty layer p@(Property name value) = case name of
"jitsiRoom" -> do
uselessEmptyLayer
@@ -182,18 +182,18 @@ containsProperty props name = any
(\(Property name' _) -> name' == name) props
-- | this property is forbidden and should not be used
-forbidProperty :: Text -> LintWriter ()
+forbidProperty :: Text -> LintWriter a
forbidProperty name = forbid $ "property " <> prettyprint name <> " should not be used"
-- | asserts that this property is a string, and unwraps it
-unwrapString :: Property -> (Text -> LintWriter ()) -> LintWriter ()
+unwrapString :: Property -> (Text -> LintWriter a) -> LintWriter a
unwrapString (Property name value) f = case value of
StrProp str -> f str
_ -> complain $ "type mismatch in property " <> name <> "; should be of type string"
-- | same as unwrapString, but also forbids http:// as prefix
-unwrapLink :: Property -> (Text -> LintWriter ()) -> LintWriter ()
+unwrapLink :: Property -> (Text -> LintWriter a) -> LintWriter a
unwrapLink (Property name value) f = case value of
StrProp str -> if "http://" `isPrefixOf` str
then complain "cannot access content via http; either use https or include it locally instead."
@@ -201,31 +201,31 @@ unwrapLink (Property name value) f = case value of
_ -> complain $ "type mismatch in property " <> name <> "; should be of typ string"
-- | asserts that this property is a boolean, and unwraps it
-unwrapBool :: Property -> (Bool -> LintWriter ()) -> LintWriter ()
+unwrapBool :: Property -> (Bool -> LintWriter a) -> LintWriter a
unwrapBool (Property name value) f = case value of
BoolProp b -> f b
_ -> complain $ "type mismatch in property " <> name <> "; should be of type bool"
-unwrapPath :: Text -> (RelPath -> LintWriter ()) -> LintWriter ()
+unwrapPath :: Text -> (RelPath -> LintWriter a) -> LintWriter a
unwrapPath str f = case parsePath str of
Just path -> f path
Nothing -> complain $ "path \"" <> str <> "\" is invalid"
-- | just asserts that this is a string
-isString :: Property -> LintWriter ()
+isString :: Property -> LintWriter a
isString = flip unwrapString (const $ pure ())
-- | just asserts that this is a boolean
-isBool :: Property -> LintWriter ()
+isBool :: Property -> LintWriter a
isBool = flip unwrapBool (const $ pure ())
-- | require some property
-requireProperty :: [Property] -> Text -> LintWriter ()
+requireProperty :: [Property] -> Text -> LintWriter a
requireProperty props name = unless (containsProperty props name)
$ complain $ "property "<>prettyprint name<>" requires property "<>prettyprint name
-- | suggest soem value for another property if that property does not
-- also already exist
-suggestPropertyValue :: [Property] -> Property -> LintWriter ()
+suggestPropertyValue :: [Property] -> Property -> LintWriter a
suggestPropertyValue props (Property name value) = unless (containsProperty props name)
$ suggest $ "set property " <> prettyprint name <> " to " <> prettyprint value