summaryrefslogtreecommitdiff
path: root/walint/LintWriter.hs
diff options
context:
space:
mode:
Diffstat (limited to 'walint/LintWriter.hs')
-rw-r--r--walint/LintWriter.hs192
1 files changed, 192 insertions, 0 deletions
diff --git a/walint/LintWriter.hs b/walint/LintWriter.hs
new file mode 100644
index 0000000..40d54bb
--- /dev/null
+++ b/walint/LintWriter.hs
@@ -0,0 +1,192 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+{-# OPTIONS_GHC -Wno-missing-signatures #-}
+
+-- | a monad that collects warnings, outputs, etc,
+module LintWriter
+ ( runLintWriter
+ , LintWriter
+ , LintWriter'
+ , LintResult
+ , invertLintResult
+ , zoom
+ -- * working with lint results
+ , resultToDeps
+ , resultToOffers
+ , resultToBadges
+ , resultToLints
+ , resultToAdjusted
+ -- * Add lints to a linter
+ , info
+ , suggest
+ , warn
+ , forbid
+ , complain
+ -- * add other information to the linter
+ , offersEntrypoint
+ , offersBadge
+ , dependsOn
+ -- * get information about the linter's context
+ , askContext
+ , askFileDepth
+ , lintConfig
+ -- * adjust the linter's context
+ , adjust
+ ,offersCWs,resultToCWs,offersJitsi,resultToJitsis) where
+
+import Universum
+
+
+import Badges (Badge)
+import Data.Map (fromListWith)
+import Data.Tiled.Abstract (HasName (getName))
+import LintConfig (LintConfig')
+import Types (Dep, Hint, Level (..), Lint (..), hint,
+ lintsToHints)
+
+
+-- | A monad modelling the main linter features
+type LintWriter ctxt = LintWriter' ctxt ()
+-- | A linter that can use pure / return things monadically
+type LintWriter' ctxt res =
+ StateT (LinterState ctxt) (Reader (Context, ctxt, LintConfig')) res
+
+-- | A Linter's state: some context (which it may adjust), and a list of lints
+-- | it already collected.
+newtype LinterState ctxt = LinterState
+ { fromLinterState :: ([Lint], ctxt)}
+ deriving Functor
+
+-- | The result of running a linter: an adjusted context, and a list of lints.
+-- | This is actually just a type synonym of LinterState, but kept seperately
+-- | for largely historic reasons since I don't think I'll change it again
+type LintResult ctxt = LinterState ctxt
+
+-- | for now, all context we have is how "deep" in the directory tree
+-- we currently are
+type Context = Int
+
+-- | run a linter. Returns the adjusted context, and a list of lints
+runLintWriter
+ :: LintConfig' -> ctxt -> Context -> LintWriter ctxt -> LintResult ctxt
+runLintWriter config context depth linter = LinterState
+ . fromLinterState
+ . snd
+ . runReader runstate
+ $ (depth, context, config)
+ where runstate = runStateT linter (LinterState ([], context))
+
+
+zoom :: (a -> b) -> (b -> a) -> LintWriter a -> LintWriter' b a
+zoom embed extract operation = do
+ config <- lintConfig id
+ depth <- askFileDepth
+ let result ctxt = runLintWriter config ctxt depth operation
+ LinterState (lints,a) <- get
+ let res = result . extract $ a
+ put $ LinterState
+ . (resultToLints res <> lints,)
+ . embed
+ . resultToAdjusted
+ $ res
+ pure $ resultToAdjusted res
+
+
+-- | "invert" a linter's result, grouping lints by their messages
+invertLintResult :: HasName ctxt => LintResult ctxt -> Map Hint [Text]
+invertLintResult (LinterState (lints, ctxt)) =
+ fmap (sortNub . map getName) . fromListWith (<>) $ (, [ctxt]) <$> lintsToHints lints
+
+resultToDeps :: LintResult a -> [Dep]
+resultToDeps (LinterState (lints,_)) = mapMaybe lintToDep lints
+ where lintToDep = \case
+ Depends dep -> Just dep
+ _ -> Nothing
+
+resultToOffers :: LintResult a -> [Text]
+resultToOffers (LinterState a) = mapMaybe lintToOffer $ fst a
+ where lintToOffer = \case
+ Offers frag -> Just frag
+ _ -> Nothing
+
+resultToBadges :: LintResult a -> [Badge]
+resultToBadges (LinterState a) = mapMaybe lintToBadge $ fst a
+ where lintToBadge (Badge badge) = Just badge
+ lintToBadge _ = Nothing
+
+resultToCWs :: LintResult a -> [Text]
+resultToCWs (LinterState a) = fold $ mapMaybe lintToCW $ fst a
+ where lintToCW = \case (CW cw) -> Just cw; _ -> Nothing
+
+resultToJitsis :: LintResult a -> [Text]
+resultToJitsis (LinterState a) = mapMaybe lintToJitsi $ fst a
+ where lintToJitsi = \case (Jitsi room) -> Just room; _ -> Nothing
+
+-- | convert a lint result into a flat list of lints
+resultToLints :: LintResult a -> [Lint]
+resultToLints (LinterState res) = fst res
+
+-- | extract the adjusted context from a lint result
+resultToAdjusted :: LintResult a -> a
+resultToAdjusted (LinterState res) = snd res
+
+
+
+
+-- | fundamental linter operations: add a lint of some severity
+info = lint Info
+suggest = lint Suggestion
+warn = lint Warning
+forbid = lint Forbidden
+complain = lint Error
+
+-- | add a dependency to the linter
+dependsOn :: Dep -> LintWriter a
+dependsOn dep = tell' $ Depends dep
+
+-- | add an offer for an entrypoint to the linter
+offersEntrypoint :: Text -> LintWriter a
+offersEntrypoint text = tell' $ Offers text
+
+-- | add an offer for a badge to the linter
+offersBadge :: Badge -> LintWriter a
+offersBadge badge = tell' $ Badge badge
+
+offersCWs :: [Text] -> LintWriter a
+offersCWs = tell' . CW
+
+offersJitsi :: Text -> LintWriter a
+offersJitsi = tell' . Jitsi
+
+
+-- | get the context as it was initially, without any modifications
+askContext :: LintWriter' a a
+askContext = lift $ asks (\(_,a,_) -> a)
+
+-- | ask for the file depth within the repository tree of the current map.
+-- | This function brings in a lot more conceptual baggage than I'd like, but
+-- | it's needed to check if relative paths lie outside the repository
+askFileDepth :: LintWriter' a Int
+askFileDepth = lift $ asks (\(a,_,_) -> a)
+
+-- | ask for a specific part of the linter's global config
+lintConfig :: (LintConfig' -> a) -> LintWriter' ctxt a
+lintConfig get = lift $ asks (\(_,_,config) -> get config)
+
+
+
+
+-- | tell, but for a singular lint. Leaves the context unchanged
+tell' :: Lint -> LintWriter ctxt
+tell' l = modify $ \(LinterState (lints, ctxt)) -> LinterState (l:lints, ctxt)
+
+-- | small helper to tell a singlular proper lint
+lint :: Level -> Text -> LintWriter a
+lint level text = tell' $ hint level text
+
+-- | adjusts the context. Gets a copy of the /current/ context,
+-- | i.e. one which might have already been changed by other adjustments
+adjust :: (a -> a) -> LintWriter a
+adjust f = modify $ LinterState . second f . fromLinterState