content: {-
    Copyright (c) Meta Platforms, Inc. and affiliates.
    All rights reserved.
  
    This source code is licensed under the BSD-style license found in the
    LICENSE file in the root directory of this source tree.
  -}
  
  module Util.Linter
    ( LintError(..)
    , Severity(..)
    , LintFormat(..)
    , parseLintFormat
    , reportLintErrors
    ) where
  
  import Data.Aeson (ToJSON(..))
  import qualified Data.Aeson as Aeson
  import qualified Data.ByteString.Lazy.Char8 as ByteString
  import Data.Text (Text)
  import qualified Data.Text as Text
  import qualified Data.Text.IO as Text
  import GHC.Generics
  import Options.Applicative
  import TextShow
  
  import Util.Text
  
  -- Lint Error Types ------------------------------------------------------------
  
  data LintError = LintError
    { source_file :: FilePath
    , source_line :: Int
    , source_col  :: Int
    , severity    :: Severity
    , message     :: Text
    } deriving (Show, Eq, Generic)
  
  instance ToJSON LintError
  
  data Severity = Warning | Error
    deriving (Show, Eq, Generic)
  
  instance ToJSON Severity where
    toJSON Warning = Aeson.String "warning"
    toJSON Error = Aeson.String "error"
  
  -- Options and Parsers ---------------------------------------------------------
  
  data LintFormat
    = HumanReadable
    | JsonDump
  
  parseLintFormat :: Parser LintFormat
  parseLintFormat = flag HumanReadable JsonDump $ mconcat
    [ long "json"
    , help "Dump output as JSON"
    ]
  
  -- Outputs ---------------------------------------------------------------------
  
  reportLintErrors :: LintFormat -> [LintError] -> IO ()
  reportLintErrors HumanReadable errors
    | null errors = Text.putStrLn "No errors."
    | otherwise = mapM_ (Text.putStrLn . renderLintError) errors
  reportLintErrors JsonDump errors = ByteString.putStrLn $ Aeson.encode errors
  
  renderLintError :: LintError -> Text
  renderLintError LintError{..} = Text.unlines $
    colorize
    (Text.pack source_file <> ":" <> showt source_line <> ":" <>
     showt source_col <> ": " <> Text.pack (show severity)) :
    wrapText 2 80 message
    where
      colorize x = case severity of
        -- Warning -> Yellow
        Warning -> "\ESC[33m" <> x <> "\ESC[0m"
        -- Error -> Red
        Error   -> "\ESC[31m" <> x <> "\ESC[0m"
  