Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SARIF output #483

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Stan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import Stan.Inspection (Inspection (..), inspectionsMd, prettyShowInspection,
import Stan.Inspection.All (getInspectionById, inspections, lookupInspectionById)
import Stan.Observation (Observation (..), prettyShowIgnoredObservations)
import Stan.Report (generateReport)
import Stan.SARIF (toSARIF)
import Stan.Severity (Severity (Error))
import Stan.Toml (configCodec, getTomlConfig, usedTomlFiles)

Expand All @@ -55,6 +56,7 @@ run = runStanCli >>= \case
runStan :: StanArgs -> IO ()
runStan StanArgs{..} = do
let notJson = not stanArgsJsonOut
&& not stanArgsSARIF
-- ENV vars
env@EnvVars{..} <- getEnvVars
let defConfTrial = envVarsUseDefaultConfigFile <> stanArgsUseDefaultConfigFile
Expand Down Expand Up @@ -89,7 +91,10 @@ runStan StanArgs{..} = do
then successMessage "All clean! Stan did not find any observations at the moment."
else warningMessage "Stan found the following observations for the project:\n"
putTextLn $ prettyShowAnalysis analysis stanArgsOutputSettings
else putLBSLn $ encode analysis
else
if stanArgsSARIF
then putLBSLn $ toSARIF analysis
else putLBSLn $ encode analysis

-- report generation
whenJust stanArgsReport $ \ReportArgs{..} -> do
Expand Down
8 changes: 8 additions & 0 deletions src/Stan/Cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ data StanArgs = StanArgs
, stanArgsConfigFile :: !(Maybe FilePath) -- ^ Path to a custom configurations file.
, stanArgsConfig :: !PartialConfig
, stanArgsJsonOut :: !Bool -- ^ Output the machine-readable output in JSON format instead.
, stanArgsSARIF :: !Bool -- ^ Output the results as a SARIF file.
}

newtype ReportArgs = ReportArgs
Expand Down Expand Up @@ -122,6 +123,7 @@ stanP = do
stanArgsUseDefaultConfigFile <- useDefaultConfigFileP
stanArgsOutputSettings <- outputSettingsP
stanArgsJsonOut <- jsonOutputP
stanArgsSARIF <- sarifOutputP
pure $ Stan StanArgs{..}

-- | @stan inspection@ command parser.
Expand Down Expand Up @@ -210,6 +212,12 @@ jsonOutputP = switch $ mconcat
, help "Output the machine-readable output in JSON format instead"
]

sarifOutputP :: Parser Bool
sarifOutputP = switch $ mconcat
[ long "sarif"
, help "Output the results as a SARIF file"
]

reportP :: Parser (Maybe ReportArgs)
reportP = optional
$ hsubparser
Expand Down
127 changes: 127 additions & 0 deletions src/Stan/SARIF.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{- |
Copyright: (c) 2020 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <[email protected]>

Provides functions to convert @Stan@'s data types to equivalent SARIF ones.
-}

module Stan.SARIF
( toSARIF
) where

import qualified Data.ByteString.Lazy as LBS
import qualified Data.Map.Lazy as Map
import Data.SARIF as SARIF
import qualified Data.Text as T
import Data.Version (showVersion)

import Paths_stan (version)
import Stan.Analysis (Analysis (..))
import Stan.Core.Id
import Stan.FileInfo
import Stan.Ghc.Compat
import Stan.Inspection
import Stan.Inspection.All (inspections)
import Stan.Observation
import Stan.Severity as Stan

-- | Represents @Stan@ as a `SARIF.Tool` value.
stanTool :: SARIF.Tool
stanTool = MkTool{
toolDriver = defaultToolComponent{
toolComponentName = Just "Stan",
-- Haskell versions aren't valid semver versions
toolComponentVersion = Just $ T.pack $ showVersion version,
toolComponentInformationUri = Just "https://github.com/kowainik/stan/",
toolComponentRules = Map.elems reportingDescriptors
},
toolExtensions = []
}

-- | `fileMapToArtifacts` @fileMap@ converts @fileMap@ to a list of
-- `SARIF.Artifact` values.
fileMapToArtifacts :: FileMap -> [SARIF.Artifact]
fileMapToArtifacts fm = map toArtifact $ Map.keys fm
where toArtifact fp = MkArtifact{
artifactLocation = MkArtifactLocation{
artifactLocationUri = T.pack fp
},
artifactMimeType = Nothing
}

-- | `toLevel` @severity@ converts a @Stan@ `Severity` to a SARIF `Level`.
toLevel :: Severity -> Level
toLevel Style = Note
toLevel Performance = SARIF.Warning
toLevel PotentialBug = SARIF.Warning
toLevel Stan.Warning = SARIF.Warning
toLevel Stan.Error = SARIF.Error

-- | `toReportingDescriptor` @inspection@ converts @inspection@ to
-- a `SARIF.ReportingDescriptor`.
toReportingDescriptor :: Inspection -> SARIF.ReportingDescriptor
toReportingDescriptor Inspection{..} =
(defaultReportingDescriptor $ unId inspectionId){
rdName = Nothing,
rdShortDescription = Just $
defaultMultiformatMessageString inspectionName,
rdFullDescription = Just $
defaultMultiformatMessageString inspectionDescription,
-- TODO: make this useful
rdHelpUri = Just "https://github.com/kowainik/stan/",
rdHelp = Just $
defaultMultiformatMessageString (T.unlines inspectionSolution),
rdDefaultConfiguration = Just $ defaultReportingConfiguration{
rcLevel = Just $ toLevel inspectionSeverity
} -- ,
-- tricky, because Stan isn't currently using aeson
-- rdProperties = Map.singleton "tags" $ _ $ map (toJSON . _) inspectionCategory
}

-- | `reportingDescriptors` is a `Map.Map` of `SARIF.ReportingDescriptor` which
-- correspond to @Stan@ `Inspection`s, indexed by their Id.
reportingDescriptors :: Map.Map Text SARIF.ReportingDescriptor
reportingDescriptors = Map.fromList
[ (rdId rd, rd) | rd <- map toReportingDescriptor inspections ]

-- | `observationToResult` @observation@ converts an @observation@ to
-- a `SARIF.Result`.
observationToResult :: Observation -> SARIF.Result
observationToResult Observation{..} =
let mrd = Map.lookup (unId observationInspectionId) reportingDescriptors
in MkResult{
resultRuleId = unId observationInspectionId,
resultLevel = Nothing,
resultMessage = defaultMultiformatMessageString $
fromMaybe "A problem was detected here." $
mrd >>= fmap mmsText . rdFullDescription,
resultLocations = [
MkLocation{
locationPhysicalLocation = Just MkPhysicalLocation{
physicalLocationArtifactLocation = MkArtifactLocation{
artifactLocationUri = T.pack observationFile
},
physicalLocationRegion = MkRegion{
regionStartLine = srcSpanStartLine observationSrcSpan,
regionStartColumn = srcSpanStartCol observationSrcSpan,
regionEndLine = srcSpanEndLine observationSrcSpan,
regionEndColumn = srcSpanEndCol observationSrcSpan
}
}
}
]
}

-- | `toSARIF` @analysis@ converts an @analysis@ to a SARIF log and encodes it
-- as JSON which is returned as a `LBS.ByteString`.
toSARIF :: Analysis -> LBS.ByteString
toSARIF Analysis{..} = encodeSarifAsLBS $ defaultLog{
logRuns = [
MkRun{
runTool = stanTool,
runArtifacts = fileMapToArtifacts analysisFileMap,
runResults = map observationToResult $ toList analysisObservations
}
]
}
2 changes: 2 additions & 0 deletions stack.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
resolver: lts-18.18

extra-deps:
- git: https://github.com/mbg/sarif.git
commit: 08d1a9878de944b8de0a67dc4cf0f0634b23fd08
- colourista-0.1.0.1
- dir-traverse-0.2.2.3
- dlist-0.8.0.8
Expand Down
2 changes: 2 additions & 0 deletions stan.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ library
Stan.Report.Css
Stan.Report.Html
Stan.Report.Settings
Stan.SARIF
Stan.Severity
Stan.Toml

Expand All @@ -134,6 +135,7 @@ library
, optparse-applicative >= 0.15 && < 0.17
, pretty-simple ^>= 4.0
, process ^>= 1.6.8.0
, sarif ^>= 0.1
, slist ^>= 0.1
, tomland ^>= 1.3.0.0
, trial ^>= 0.0.0.0
Expand Down