packages feed

glualint-1.24.6: src/GLua/LineLimitParser.hs

-- | A parser that takes a maximum line length as input, and emits warnings for any line that
-- exceeds it.
module GLua.LineLimitParser where

import Control.Applicative ((<|>))
import Control.Monad (void)
import Text.Parsec (eof, satisfy, skipMany1, endOfLine, parse)
import Text.Parsec.String (Parser)
import GLua.AG.Token (Region(Region))
import GLua.PSLexer (pPos)
import GLuaFixer.LintMessage (LintMessage(..), Issue (LineTooLong), Severity (LintWarning))


-- | The maximum line length
newtype LineLimit = LineLimit Int

execParseLineLimits :: FilePath -> LineLimit -> String -> [LintMessage]
execParseLineLimits filePath lineLimit@(LineLimit limit) contents =
  -- No point in parsing anything if the limit is 0
  if limit <= 0 then []
  else case parse (lineLimitParser filePath lineLimit) "input" contents of
    Left parseError -> error $ "Parse error while checking line limit: " ++ show parseError
    Right lintWarnings -> lintWarnings

-- | Parser that produces warnings about lines being too long
lineLimitParser :: FilePath -> LineLimit -> Parser [LintMessage]
lineLimitParser filePath lineLimit =
  [] <$ eof <|>
  recurse filePath lineLimit

-- | Recursive case of 'lineLimitParser',
recurse :: FilePath -> LineLimit -> Parser [LintMessage]
recurse filePath lineLimit@(LineLimit limit) = do
  countAtMost limit notNewline
  -- If it finds line endings, it recurses without producing warnings. Otherwise, if it finds
  -- anything else, produce a warning and still recurse.
  endOfLines *> lineLimitParser filePath lineLimit <|>
    (:) <$> lineLimitWarning filePath <*> lineLimitParser filePath lineLimit

-- | Succeeds when a character is not a line
notNewline :: Parser Char
notNewline = satisfy $ \c -> c /= '\n' && c /= '\r'

-- | end of lines. Consumes all \r and \n characters it finds
endOfLines :: Parser ()
endOfLines = skipMany1 endOfLine <|> void eof

-- | Produces a line limit warning
lineLimitWarning :: FilePath -> Parser LintMessage
lineLimitWarning filePath = do
  startPos <- pPos
  skipMany1 notNewline
  endPos <- pPos

  let warnRegion = Region startPos endPos

  pure $ LintMessage LintWarning warnRegion LineTooLong filePath

-- | Consume the parser until either it has been parsed the Int amount of times, or the parser fails
countAtMost :: Int -> Parser a -> Parser ()
countAtMost 0 _ = pure ()
countAtMost i p = p *> countAtMost (i - 1) p <|> pure ()