1- import { CodeLocation , RunResults , Violation } from "./results" ;
1+ import { CodeLocation , RunResults , Violation , EngineRunResults } from "./results" ;
22import { Rule , RuleType , SeverityLevel } from "./rules" ;
33import { stringify as stringifyToCsv } from "csv-stringify/sync" ;
44import { Options as CsvOptions } from "csv-stringify" ;
55import * as xmlbuilder from "xmlbuilder" ;
66import * as fs from 'fs' ;
77import path from "node:path" ;
88import { Clock , RealClock } from "./utils" ;
9+ import * as sarif from "sarif" ;
910
1011export enum OutputFormat {
1112 CSV = "CSV" ,
1213 JSON = "JSON" ,
1314 XML = "XML" ,
14- HTML = "HTML"
15+ HTML = "HTML" ,
16+ SARIF = "SARIF"
1517}
1618
1719export abstract class OutputFormatter {
@@ -27,6 +29,8 @@ export abstract class OutputFormatter {
2729 return new XmlOutputFormatter ( ) ;
2830 case OutputFormat . HTML :
2931 return new HtmlOutputFormatter ( clock ) ;
32+ case OutputFormat . SARIF :
33+ return new SarifOutputFormatter ( ) ;
3034 default :
3135 throw new Error ( `Unsupported output format: ${ format } ` ) ;
3236 }
@@ -231,6 +235,99 @@ class XmlOutputFormatter implements OutputFormatter {
231235 }
232236}
233237
238+ class SarifOutputFormatter implements OutputFormatter {
239+ format ( results : RunResults ) : string {
240+ const runDir = results . getRunDirectory ( ) ;
241+
242+ const sarifRuns : sarif . Run [ ] = results . getEngineNames ( )
243+ . map ( engineName => results . getEngineRunResults ( engineName ) )
244+ . filter ( engineRunResults => engineRunResults . getViolationCount ( ) > 0 )
245+ . map ( engineRunResults => toSarifRun ( engineRunResults , runDir ) ) ;
246+
247+ // Construct SARIF log
248+ const sarifLog : sarif . Log = {
249+ version : "2.1.0" ,
250+ $schema : 'http://json.schemastore.org/sarif-2.1.0' ,
251+ runs : sarifRuns ,
252+ } ;
253+
254+ // Return formatted SARIF JSON string
255+ return JSON . stringify ( sarifLog , null , 2 ) ;
256+ }
257+ }
258+
259+ function toSarifRun ( engineRunResults : EngineRunResults , runDir : string ) : sarif . Run {
260+ const violations : Violation [ ] = engineRunResults . getViolations ( ) ;
261+ const rules : Rule [ ] = [ ... new Set ( violations . map ( v => v . getRule ( ) ) ) ] ;
262+ const ruleNames : string [ ] = rules . map ( r => r . getName ( ) ) ;
263+
264+ return {
265+ tool : {
266+ driver : {
267+ name : engineRunResults . getEngineName ( ) ,
268+ informationUri : "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/version-5.html" ,
269+ rules : rules . map ( toSarifReportingDescriptor ) ,
270+ }
271+ } ,
272+ results : violations . map ( v => toSarifResult ( v , ruleNames . indexOf ( v . getRule ( ) . getName ( ) ) ) ) ,
273+ invocations : [
274+ {
275+ executionSuccessful : true ,
276+ workingDirectory : {
277+ uri : runDir ,
278+ } ,
279+ } ,
280+ ] ,
281+ } ;
282+ }
283+
284+ function toSarifResult ( violation : Violation , ruleIndex : number ) : sarif . Result {
285+ const primaryCodeLocation = violation . getCodeLocations ( ) [ violation . getPrimaryLocationIndex ( ) ] ;
286+ const result : sarif . Result = {
287+ ruleId : violation . getRule ( ) . getName ( ) ,
288+ ruleIndex : ruleIndex ,
289+ message : { text : violation . getMessage ( ) } ,
290+ locations : [ toSarifLocation ( primaryCodeLocation ) ] ,
291+ } ;
292+ if ( typeSupportsMultipleLocations ( violation . getRule ( ) . getType ( ) ) ) {
293+ result . relatedLocations = violation . getCodeLocations ( ) . map ( toSarifLocation ) ;
294+ }
295+ result . level = toSarifNotificationLevel ( violation . getRule ( ) . getSeverityLevel ( ) ) ;
296+ return result ;
297+ }
298+
299+ function toSarifLocation ( codeLocation : CodeLocation ) : sarif . Location {
300+ return {
301+ physicalLocation : {
302+ artifactLocation : {
303+ uri : codeLocation . getFile ( ) ,
304+ } ,
305+ region : {
306+ startLine : codeLocation . getStartLine ( ) ,
307+ startColumn : codeLocation . getStartColumn ( ) ,
308+ endLine : codeLocation . getEndLine ( ) ,
309+ endColumn : codeLocation . getEndColumn ( )
310+ } as sarif . Region
311+ }
312+ }
313+ }
314+
315+ function toSarifReportingDescriptor ( rule : Rule ) : sarif . ReportingDescriptor {
316+ return {
317+ id : rule . getName ( ) ,
318+ properties : {
319+ category : rule . getTags ( ) ,
320+ severity : rule . getSeverityLevel ( )
321+ } ,
322+ ...( rule . getResourceUrls ( ) ?. [ 0 ] && { helpUri : rule . getResourceUrls ( ) [ 0 ] } )
323+ }
324+ }
325+
326+ function toSarifNotificationLevel ( severity : SeverityLevel ) : sarif . Notification . level {
327+ return severity < 3 ? 'error' : 'warning' ; // IF satif.Notification.level is an enum then please return the num instead of the string.
328+ }
329+
330+
234331const HTML_TEMPLATE_VERSION : string = '0.0.1' ;
235332const HTML_TEMPLATE_FILE : string = path . resolve ( __dirname , '..' , 'output-templates' , `html-template-${ HTML_TEMPLATE_VERSION } .txt` ) ;
236333class HtmlOutputFormatter implements OutputFormatter {
@@ -293,13 +390,17 @@ function createViolationOutput(violation: Violation, runDir: string, sanitizeFcn
293390 column : primaryLocation . getStartColumn ( ) ,
294391 endLine : primaryLocation . getEndLine ( ) ,
295392 endColumn : primaryLocation . getEndColumn ( ) ,
296- primaryLocationIndex : [ RuleType . DataFlow , RuleType . Flow ] . includes ( rule . getType ( ) ) ? violation . getPrimaryLocationIndex ( ) : undefined ,
297- locations : [ RuleType . DataFlow , RuleType . Flow ] . includes ( rule . getType ( ) ) ? createCodeLocationOutputs ( codeLocations , runDir ) : undefined ,
393+ primaryLocationIndex : typeSupportsMultipleLocations ( rule . getType ( ) ) ? violation . getPrimaryLocationIndex ( ) : undefined ,
394+ locations : typeSupportsMultipleLocations ( rule . getType ( ) ) ? createCodeLocationOutputs ( codeLocations , runDir ) : undefined ,
298395 message : sanitizeFcn ( violation . getMessage ( ) ) ,
299396 resources : violation . getResourceUrls ( )
300397 } ;
301398}
302399
400+ function typeSupportsMultipleLocations ( ruleType : RuleType ) {
401+ return [ RuleType . DataFlow , RuleType . Flow , RuleType . MultiLocation ] . includes ( ruleType ) ;
402+ }
403+
303404function createCodeLocationOutputs ( codeLocations : CodeLocation [ ] , runDir : string ) : CodeLocationOutput [ ] {
304405 return codeLocations . map ( loc => {
305406 return new CodeLocationOutput ( loc , runDir ) ;
0 commit comments