Support for SARIF output format

This commit is contained in:
Arkadiusz Bokowy 2022-09-03 12:58:49 +00:00
commit f6cd40d4f4
6 changed files with 150 additions and 2 deletions

View file

@ -7,6 +7,7 @@
- SC2321: Suggest removing $((..)) in array[$((idx))]=val
- SC2322: Suggest collapsing double parentheses in arithmetic contexts
- SC2323: Suggest removing wrapping parentheses in a[(x+1)]=val
- support Static Analysis Results Interchange Format (SARIF) output
### Fixed
- SC2086: Now uses DFA to make more accurate predictions about values

View file

@ -83,6 +83,7 @@ library
ShellCheck.Formatter.GCC
ShellCheck.Formatter.JSON
ShellCheck.Formatter.JSON1
ShellCheck.Formatter.SARIF
ShellCheck.Formatter.TTY
ShellCheck.Formatter.Quiet
ShellCheck.Interface

View file

@ -197,6 +197,10 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts.
: This is a legacy version of the **json1** format. It's a raw array of
comments, and all offsets have a tab stop of 8.
**sarif**
: Static Analysis Results Interchange Format (SARIF) output.
**quiet**
: Suppress all normal output. Exit with zero if no issues are found,

View file

@ -29,6 +29,7 @@ import qualified ShellCheck.Formatter.Diff
import qualified ShellCheck.Formatter.GCC
import qualified ShellCheck.Formatter.JSON
import qualified ShellCheck.Formatter.JSON1
import qualified ShellCheck.Formatter.SARIF
import qualified ShellCheck.Formatter.TTY
import qualified ShellCheck.Formatter.Quiet
@ -146,6 +147,7 @@ formats options = Map.fromList [
("gcc", ShellCheck.Formatter.GCC.format),
("json", ShellCheck.Formatter.JSON.format),
("json1", ShellCheck.Formatter.JSON1.format),
("sarif", ShellCheck.Formatter.SARIF.format),
("tty", ShellCheck.Formatter.TTY.format options),
("quiet", ShellCheck.Formatter.Quiet.format options)
]
@ -576,7 +578,7 @@ printVersion = do
putStrLn "ShellCheck - shell script analysis tool"
putStrLn $ "version: " ++ shellcheckVersion
putStrLn "license: GNU General Public License, version 3"
putStrLn "website: https://www.shellcheck.net"
putStrLn $ "website: " ++ shellcheckWebsite
printOptional = do
mapM f list

View file

@ -22,7 +22,7 @@ Use:
import Paths_ShellCheck (version)
shellcheckVersion = showVersion version -- VERSIONSTRING
shellcheckWebsite = "https://www.shellcheck.net"
internalVariables = [
-- Generic

View file

@ -0,0 +1,140 @@
{-# LANGUAGE OverloadedStrings #-}
{-
Copyright 2012-2019 Vidar Holen
This file is part of ShellCheck.
https://www.shellcheck.net
ShellCheck is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ShellCheck is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
module ShellCheck.Formatter.SARIF (format) where
import ShellCheck.Data
import ShellCheck.Interface
import ShellCheck.Formatter.Format
import Control.DeepSeq
import Data.Aeson
import Data.IORef
import Data.Monoid
import GHC.Exts
import System.IO
import qualified Data.ByteString.Lazy.Char8 as BL
format :: IO Formatter
format = do
ref <- newIORef []
return Formatter {
header = return (),
onResult = collectResult ref,
onFailure = outputError,
footer = finish ref
}
data SarifOutput = SarifOutput {
comments :: [PositionedComment]
}
instance ToJSON SarifOutput where
toJSON result = object [
"version" .= ("2.1.0" :: String),
"$schema" .= ("http://json.schemastore.org/sarif-2.1.0" :: String),
"runs" .= [object [
"tool" .= object [
"driver" .= object [
"name" .= ("ShellCheck" :: String),
"semanticVersion" .= shellcheckVersion,
"informationUri" .= shellcheckWebsite
]
],
"results" .= comments result
]]
]
instance ToJSON Replacement where
toJSON replacement =
let start = repStartPos replacement
end = repEndPos replacement in
object [
"artifactLocation" .= object [
"uri" .= posFile start
],
"replacements" .= [object [
"deletedRegion" .= object [
"startLine" .= posLine start,
"startColumn" .= posColumn start,
"endLine" .= posLine end,
"endColumn" .= posColumn end
],
"insertedContent" .= object [
"text" .= repString replacement
]
]]
]
instance ToJSON PositionedComment where
toJSON comment =
let start = pcStartPos comment
end = pcEndPos comment
c = pcComment comment
replacements = maybe [] fixReplacements (pcFix comment) in
object [
"ruleId" .= code (cCode c),
"level" .= level (cSeverity c),
"message" .= object [
"text" .= cMessage c
],
"locations" .= [object [
"physicalLocation" .= object [
"artifactLocation" .= object [
"uri" .= posFile start
],
"region" .= object [
"startLine" .= posLine start,
"startColumn" .= posColumn start,
"endLine" .= posLine end,
"endColumn" .= posColumn end
]
]
]],
"fixes" .= [object [
"artifactChanges" .= replacements
] | not (null replacements)]
]
code num = "SC" ++ show num
level severity = case severity of
ErrorC -> "error" :: String
WarningC -> "warning"
InfoC -> "note"
StyleC -> "none"
outputError file msg = hPutStrLn stderr $ file ++ ": " ++ msg
collectResult ref cr sys = mapM_ f groups
where
comments = crComments cr
groups = groupWith sourceFile comments
f :: [PositionedComment] -> IO ()
f group = do
let filename = sourceFile (head group)
result <- siReadFile sys (Just True) filename
let contents = either (const "") id result
let comments' = makeNonVirtual comments contents
deepseq comments' $ modifyIORef ref (\x -> comments' ++ x)
finish ref = do
list <- readIORef ref
BL.putStrLn $ encode $ SarifOutput { comments = list }