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

View file

@ -83,6 +83,7 @@ library
ShellCheck.Formatter.GCC ShellCheck.Formatter.GCC
ShellCheck.Formatter.JSON ShellCheck.Formatter.JSON
ShellCheck.Formatter.JSON1 ShellCheck.Formatter.JSON1
ShellCheck.Formatter.SARIF
ShellCheck.Formatter.TTY ShellCheck.Formatter.TTY
ShellCheck.Formatter.Quiet ShellCheck.Formatter.Quiet
ShellCheck.Interface 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 : 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. comments, and all offsets have a tab stop of 8.
**sarif**
: Static Analysis Results Interchange Format (SARIF) output.
**quiet** **quiet**
: Suppress all normal output. Exit with zero if no issues are found, : 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.GCC
import qualified ShellCheck.Formatter.JSON import qualified ShellCheck.Formatter.JSON
import qualified ShellCheck.Formatter.JSON1 import qualified ShellCheck.Formatter.JSON1
import qualified ShellCheck.Formatter.SARIF
import qualified ShellCheck.Formatter.TTY import qualified ShellCheck.Formatter.TTY
import qualified ShellCheck.Formatter.Quiet import qualified ShellCheck.Formatter.Quiet
@ -146,6 +147,7 @@ formats options = Map.fromList [
("gcc", ShellCheck.Formatter.GCC.format), ("gcc", ShellCheck.Formatter.GCC.format),
("json", ShellCheck.Formatter.JSON.format), ("json", ShellCheck.Formatter.JSON.format),
("json1", ShellCheck.Formatter.JSON1.format), ("json1", ShellCheck.Formatter.JSON1.format),
("sarif", ShellCheck.Formatter.SARIF.format),
("tty", ShellCheck.Formatter.TTY.format options), ("tty", ShellCheck.Formatter.TTY.format options),
("quiet", ShellCheck.Formatter.Quiet.format options) ("quiet", ShellCheck.Formatter.Quiet.format options)
] ]
@ -576,7 +578,7 @@ printVersion = do
putStrLn "ShellCheck - shell script analysis tool" putStrLn "ShellCheck - shell script analysis tool"
putStrLn $ "version: " ++ shellcheckVersion putStrLn $ "version: " ++ shellcheckVersion
putStrLn "license: GNU General Public License, version 3" putStrLn "license: GNU General Public License, version 3"
putStrLn "website: https://www.shellcheck.net" putStrLn $ "website: " ++ shellcheckWebsite
printOptional = do printOptional = do
mapM f list mapM f list

View file

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