krank-0.2.0: src/Krank/Checkers/Ignore.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Krank.Checkers.Ignore
( IgnoreCommand (..),
filterViolations,
)
where
import qualified Data.ByteString.Char8 as ByteString
import Data.ByteString.Char8 (ByteString)
import Data.HashMap.Strict as HashM
import qualified Data.List as DataL
import Krank.Types
import PyF (fmt)
import qualified Text.Regex.PCRE.Heavy as RE
data IgnoreCommand = IgnoreLine deriving (Show, Eq)
-- | This regex represents a krank ignore marker
ignoreRe :: RE.Regex
ignoreRe = [RE.re|krank:ignore-(line)|]
-- TODO: support more "ignore" (checker specific, all file, next line)
-- | Extract all issues on one line and returns a list of ignore keyword
extractIssuesOnALine :: ByteString -> [(Int, IgnoreCommand)]
extractIssuesOnALine lineContent = Prelude.map f (RE.scan ignoreRe lineContent)
where
f (match, [command]) = (colNo, ignoreCommand)
where
colNo = 1 + ByteString.length (fst $ ByteString.breakSubstring match lineContent)
ignoreCommand
| command == "line" = IgnoreLine
| otherwise = error [fmt|Impossible case, update the guard with: {ByteString.unpack command}|]
-- This case seems impossible, the reasons for pattern match issues are:
-- A number of items different than 1 in the list: there is only 1 matching groups in the regex
f res = error ("Error: impossible match" <> show res)
-- | Extract all ignore markers correctly localized
-- Note: we use 'ByteString' internally. This way we do not have to
-- care about the possible encoding of the input files.
-- In programming world, we mostly use ascii variants. This gives a
-- few performance improvement compared to initially converting
-- everything to 'Text' and search on it.
extractIgnores ::
-- | Path of the file
FilePath ->
-- | Content of the file
ByteString ->
[Localized IgnoreCommand]
extractIgnores filePath toCheck = concat (zipWith extract [1 ..] (ByteString.lines toCheck))
where
extract lineNo lineContent = Prelude.map f (extractIssuesOnALine lineContent)
where
f (colNo, gitIssue) = Localized (SourcePos filePath lineNo colNo) gitIssue
-- | Takes a list of Violation, some ignore commands and remove all those that are ignored due to an
-- ignore marker
filterViolations ::
-- | List of Violation to filter
[Violation] ->
-- | Path of the file
FilePath ->
-- | Content of the file
ByteString ->
[Violation]
filterViolations violations filePath content =
DataL.filter isNotIgnored violations
where
ignoreCommands = extractIgnores filePath content
f hashMap ignoreCommand = HashM.insert (lineNumber . getLocation $ ignoreCommand) (unLocalized ignoreCommand) hashMap
ignoreIndex = foldl f HashM.empty ignoreCommands
isIgnored violation = HashM.lookup (lineNumber . location $ violation) ignoreIndex == Just IgnoreLine
isNotIgnored = not . isIgnored