glualint-1.27.0: src/GLuaFixer/LintMessage.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module GLuaFixer.LintMessage where
import Control.Monad
import Data.Aeson
import Data.List (sortOn)
import Data.Maybe (isJust)
import GLua.AG.PrettyPrint (renderPSError, renderPos, renderRegion)
import GLua.AG.Token (Token)
import GLua.Position (LineColPos (..), Region (..))
import System.Environment (lookupEnv)
import Text.Parsec (ParseError)
-- | Output formats for logging
data LogFormat = StandardLogFormat | GithubLogFormat
data LogFormatChoice = AutoLogFormatChoice | LogFormatChoice !LogFormat
instance Show LogFormat where
show StandardLogFormat = "standard"
show GithubLogFormat = "github"
instance Show LogFormatChoice where
show (LogFormatChoice choice) = show choice
show AutoLogFormatChoice = "auto"
instance ToJSON LogFormat where
toJSON StandardLogFormat = "standard"
toJSON GithubLogFormat = "github"
instance ToJSON LogFormatChoice where
toJSON (LogFormatChoice choice) = toJSON choice
toJSON AutoLogFormatChoice = "auto"
instance FromJSON LogFormatChoice where
parseJSON (String logFormat) = case logFormat of
"standard" -> pure $ LogFormatChoice StandardLogFormat
"github" -> pure $ LogFormatChoice GithubLogFormat
"auto" -> pure AutoLogFormatChoice
_ -> fail ("Please use either \"auto\" \"standard\" or \"github\" but was " ++ show logFormat)
parseJSON _ = mzero
data Severity = LintWarning | LintError
deriving (Eq)
-- | With the Space(Before|After)(Parenthesis|Bracket|Brace), it depends on the pretty print
-- settings whether the space is desired or not. This encodes what we ask the user to do.
data RemoveOrAddSpace
= RemoveSpace
| AddSpace
deriving (Eq)
-- | Representation of the different kinds of issues that can be raised. Many of the arguments are
-- 'String', because this data type is a rewrite of what was previously directly rendered Strings.
-- Many of these Strings can later be rewritten to their own types if necessary.
data Issue
= IssueParseError ParseError
| -- From BadSequenceFinder
-- | Reason
Deprecated !String
| Profanity
| -- | message
BeginnerMistake !String
| -- | message
WhitespaceStyle !String
| SpaceAfterParenthesis !RemoveOrAddSpace
| SpaceBeforeParenthesis !RemoveOrAddSpace
| SpaceAfterBracket !RemoveOrAddSpace
| SpaceBeforeBracket !RemoveOrAddSpace
| SpaceAfterBrace !RemoveOrAddSpace
| SpaceBeforeBrace !RemoveOrAddSpace
| SpaceAfterComma !RemoveOrAddSpace
| SpaceBeforeComma !RemoveOrAddSpace
| -- Issues found in the lexicon (see LexLint.ag)
TrailingWhitespace
| InconsistentTabsSpaces
| SyntaxInconsistency
!String
-- ^ First encountered
!String
-- ^ Second encountered
| -- Line length limit (see LineLimitParser.hs)
LineTooLong
| -- Issues found in the AST (see ASTLint.ag)
VariableShadows
!String
-- ^ Name of the variable being shadowed
!Region
-- ^ Definition location of variable being shadowed
| GotoAsIdentifier
| InconsistentVariableNaming
| ScopePyramids
| -- | Variable name
UnusedVariable !String
| AvoidGoto
| EmptyDoBlock
| EmptyWhileLoop
| EmptyRepeat
| EmptyIf
| DoubleIf
| EmptyFor
| EmptyElseIf
| EmptyElse
| SelfInNonMeta
| SelfEntity
| SelfWeapon
| UnnecessaryParentheses
| -- | Alternative to using the negation
SillyNegation !String
| -- | The key that is duplicated
DuplicateKeyInTable !Token
deriving (Eq)
-- | Represents lint messages
data LintMessage = LintMessage
{ lintmsg_severity :: !Severity
, lintmsg_region :: !Region
, lintmsg_message :: !Issue
, lintmsg_file :: !FilePath
}
deriving (Eq)
instance Show LintMessage where
show = formatLintMessageDefault
issueDescription :: Issue -> String
issueDescription = \case
IssueParseError parseError -> renderPSError parseError
Deprecated reason -> "Deprecated: " ++ reason
Profanity -> "Watch your profanity"
BeginnerMistake msg -> msg
WhitespaceStyle msg -> "Style: " ++ msg
SpaceAfterParenthesis RemoveSpace -> "Style: Please remove the space after the parenthesis"
SpaceAfterParenthesis AddSpace -> "Style: Please add a space after the parenthesis"
SpaceBeforeParenthesis RemoveSpace -> "Style: Please remove the space before the parenthesis"
SpaceBeforeParenthesis AddSpace -> "Style: Please add a space before the parenthesis"
SpaceAfterBracket RemoveSpace -> "Style: Please remove the space after the bracket"
SpaceAfterBracket AddSpace -> "Style: Please add a space after the bracket"
SpaceBeforeBracket RemoveSpace -> "Style: Please remove the space before the bracket"
SpaceBeforeBracket AddSpace -> "Style: Please add a space before the bracket"
SpaceAfterBrace RemoveSpace -> "Style: Please remove the space after the brace"
SpaceAfterBrace AddSpace -> "Style: Please add a space after the brace"
SpaceBeforeBrace RemoveSpace -> "Style: Please remove the space before the brace"
SpaceBeforeBrace AddSpace -> "Style: Please add a space before the brace"
SpaceAfterComma RemoveSpace -> "Style: Please remove the space after the comma"
SpaceAfterComma AddSpace -> "Style: Please add a space after the comma"
SpaceBeforeComma RemoveSpace -> "Style: Please remove the space before the comma"
SpaceBeforeComma AddSpace -> "Style: Please add a space before the comma"
TrailingWhitespace -> "Trailing whitespace"
InconsistentTabsSpaces -> "Inconsistent use of tabs and spaces for indentation"
SyntaxInconsistency firstEncountered secondEncountered ->
"Inconsistent use of '" ++ firstEncountered ++ "' and '" ++ secondEncountered ++ "'"
LineTooLong -> "Style: Line too long"
VariableShadows lbl (Region start _) ->
"Variable '" ++ lbl ++ "' shadows existing binding, defined at " ++ renderPos start
GotoAsIdentifier ->
"Don't use 'goto' as an identifier, later versions of Lua will confuse it with the goto keyword."
InconsistentVariableNaming ->
"Inconsistent variable naming! There are variables that start with a lowercase letter, as well as ones that start with an uppercase letter. Please decide on one style."
ScopePyramids ->
"Are you Egyptian? What's with these fucking scope pyramids!?"
UnusedVariable varName ->
"Unused variable: " ++ varName
AvoidGoto ->
"Don't use labels and gotos unless you're jumping out of multiple loops."
EmptyDoBlock -> "Empty do block"
EmptyWhileLoop -> "Empty while loop"
EmptyRepeat -> "Empty repeat statement"
EmptyIf -> "Empty if statement"
DoubleIf ->
"Double if statement. Please combine the condition of this if statement with that of the outer if statement using `and`."
EmptyFor -> "Empty for loop"
EmptyElseIf -> "Empty elseif statement"
EmptyElse -> "Empty else statement"
SelfInNonMeta ->
"Don't use self in a non-metafunction"
SelfEntity ->
"'self.Entity' is the same as just 'self' in SENTs"
SelfWeapon ->
"'self.Weapon' is the same as just 'self' in SWEPs"
UnnecessaryParentheses -> "Unnecessary parentheses"
SillyNegation alternative ->
"Silly negation. Use '" ++ alternative ++ "'"
DuplicateKeyInTable keyToken ->
"Duplicate key in table: '" ++ show keyToken ++ "'."
-- | Shorthand title of an issue. Several issues may share the same title.
issueTitle :: Issue -> String
issueTitle = \case
IssueParseError _ -> "Parse error"
Deprecated _ -> "Deprecated"
Profanity -> "Profanity"
BeginnerMistake _ -> "Beginner mistake"
WhitespaceStyle _ -> "Whitespace style"
TrailingWhitespace -> "Trailing whitespace"
SpaceAfterParenthesis _ -> "Space after parenthesis"
SpaceBeforeParenthesis _ -> "Space before parenthesis"
SpaceAfterBracket _ -> "Space after bracket"
SpaceBeforeBracket _ -> "Space before bracket"
SpaceAfterBrace _ -> "Space after brace"
SpaceBeforeBrace _ -> "Space before brace"
SpaceAfterComma _ -> "Space after comma"
SpaceBeforeComma _ -> "Space before comma"
InconsistentTabsSpaces -> "Syntax inconsistency"
SyntaxInconsistency _ _ -> "Syntax inconsistency"
LineTooLong -> "Line too long"
VariableShadows _ _ -> "Shadowing"
GotoAsIdentifier -> "Goto"
InconsistentVariableNaming -> "Variable inconsistency"
ScopePyramids -> "Scope depth"
UnusedVariable _ -> "Unused variable"
AvoidGoto -> "Goto"
EmptyDoBlock -> "Empty block"
EmptyWhileLoop -> "Empty block"
EmptyRepeat -> "Empty block"
EmptyIf -> "Empty block"
DoubleIf -> "Double if-statement"
EmptyFor -> "Empty block"
EmptyElseIf -> "Empty block"
EmptyElse -> "Empty block"
SelfInNonMeta -> "Bad self"
SelfEntity -> "Deprecated"
SelfWeapon -> "Deprecated"
UnnecessaryParentheses -> "Unnecessary parentheses"
SillyNegation _ -> "Unnecessary negation"
DuplicateKeyInTable _ -> "Duplicate key"
logFormatChoiceToLogFormat :: LogFormatChoice -> IO LogFormat
logFormatChoiceToLogFormat = \case
LogFormatChoice format -> pure format
AutoLogFormatChoice -> do
actionsExists <- isJust <$> lookupEnv "GITHUB_ACTIONS"
workflowExists <- isJust <$> lookupEnv "GITHUB_WORKFLOW"
if actionsExists && workflowExists
then pure GithubLogFormat
else pure StandardLogFormat
formatLintMessage :: LogFormat -> LintMessage -> String
formatLintMessage StandardLogFormat lintMsg = formatLintMessageDefault lintMsg
formatLintMessage GithubLogFormat lintMsg = formatLintMessageGithub lintMsg
formatLintMessageDefault :: LintMessage -> String
formatLintMessageDefault (LintMessage severity region msg file) =
let
level = case severity of
LintWarning -> "Warning"
LintError -> "Error"
in
showString file
. showString ": ["
. showString level
. showString "] "
. showString (renderRegion region)
. showString ": "
. showString (issueDescription msg)
$ ""
formatLintMessageGithub :: LintMessage -> String
formatLintMessageGithub (LintMessage severity (Region (LineColPos line col _) (LineColPos endLine endCol _)) msg file) =
let
level = case severity of
LintWarning -> "warning"
LintError -> "error"
in
showString "::"
. showString level
. showString " file="
. showString file
. showString ",line="
. shows (succ line)
. showString ",col="
. shows (succ col)
. showString ",endLine="
. shows (succ endLine)
. showString ",endColumn="
. shows (succ endCol)
. showString ",title="
. shows (issueTitle msg)
. showString "::"
. showString (issueDescription msg)
$ ""
-- | Sort lint messages on file and then region
sortLintMessages :: [LintMessage] -> [LintMessage]
sortLintMessages = sortOn (\(LintMessage _ rg _ f) -> (f, rg))