{-# 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