packages feed

glualint-1.24.6: src/GLuaFixer/LintMessage.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
module GLuaFixer.LintMessage where

import Control.Monad
import Data.Aeson
import Data.List (sortOn)
import Data.Maybe (isJust)
import System.Environment (lookupEnv)
import Text.Parsec (ParseError)
import Text.ParserCombinators.UU.BasicInstances hiding (msgs)

import GLua.AG.Token
import GLua.AG.PrettyPrint

-- | 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
  | Deprecated !String -- ^ Reason
  | Profanity
  | BeginnerMistake !String -- ^ message
  | WhitespaceStyle !String -- ^ message
  | 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
  | UnusedVariable !String -- ^ Variable name
  | AvoidGoto
  | EmptyDoBlock
  | EmptyWhileLoop
  | EmptyRepeat
  | EmptyIf
  | DoubleIf
  | EmptyFor
  | EmptyElseIf
  | EmptyElse
  | SelfInNonMeta
  | SelfEntity
  | SelfWeapon
  | UnnecessaryParentheses
  | SillyNegation !String -- ^ Alternative to using the negation
  | DuplicateKeyInTable !Token -- ^ The key that is duplicated
  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))