packages feed

glualint-1.27.0: 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 GLua.Lexer (pPos)
import GLua.Position (Region (..))
import GLuaFixer.LintMessage (Issue (LineTooLong), LintMessage (..), Severity (LintWarning))
import Text.Parsec (endOfLine, eof, parse, satisfy, skipMany1)
import Text.Parsec.String (Parser)

-- | 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 ()