Skip to content
Snippets Groups Projects
Unverified Commit 9a8d793f authored by stuebinm's avatar stuebinm
Browse files

rebuilding the core LintWriter monad

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