1+ import { Flow } from "../models/Flow" ;
2+ import { ResultDetails } from "../models/ResultDetails" ;
3+ import { ScanResult } from "../models/ScanResult" ;
4+
5+ /**
6+ * Export scan results to SARIF v2.1.0
7+ * Uses real fsPath → GitHub clickable
8+ * Falls back to virtual URI in browser
9+ */
10+ export function exportSarif ( results : ScanResult [ ] ) : string {
11+ const runs = results . map ( ( result ) => {
12+ const flow = result . flow ;
13+ const uri = getUri ( flow ) ;
14+
15+ return {
16+ artifacts : [ { location : { uri } , sourceLanguage : "xml" } ] ,
17+ results : result . ruleResults
18+ . filter ( r => r . occurs )
19+ . flatMap ( r => r . details . map ( d => ( {
20+ level : mapSeverity ( r . severity ) ,
21+ locations : [ {
22+ physicalLocation : {
23+ artifactLocation : { index : 0 , uri } ,
24+ region : mapRegion ( d ) ,
25+ } ,
26+ } ] ,
27+ message : { text : r . errorMessage || `${ r . ruleName } in ${ d . name } ` } ,
28+ properties : {
29+ element : d . name ,
30+ flow : flow . name ,
31+ type : d . type ,
32+ ...d . details ,
33+ } ,
34+ ruleId : r . ruleName ,
35+ } ) ) ) ,
36+ tool : {
37+ driver : {
38+ informationUri : "https://github.com/Flow-Scanner/lightning-flow-scanner-core" ,
39+ name : "Lightning Flow Scanner" ,
40+ rules : result . ruleResults
41+ . filter ( r => r . occurs )
42+ . map ( r => ( {
43+ defaultConfiguration : { level : mapSeverity ( r . severity ) } ,
44+ fullDescription : { text : r . ruleDefinition . description || "" } ,
45+ helpUri : r . ruleDefinition . helpUrl ,
46+ id : r . ruleName ,
47+ shortDescription : { text : r . ruleDefinition . description || r . ruleName } ,
48+ } ) ) ,
49+ version : "1.0.0" ,
50+ } ,
51+ } ,
52+ } ;
53+ } ) ;
54+
55+ return JSON . stringify ( {
56+ $schema : "https://json.schemastore.org/sarif-2.1.0.json" ,
57+ runs,
58+ version : "2.1.0" ,
59+ } , null , 2 ) ;
60+ }
61+
62+ // ─── Private Helpers ───
63+ function getUri ( flow : Flow ) : string {
64+ return flow . fsPath
65+ ? flow . fsPath . replace ( / \\ / g, "/" )
66+ : `flows/${ flow . name } .flow-meta.xml` ;
67+ }
68+
69+ function mapRegion ( detail : ResultDetails ) : any {
70+ if ( detail . metaType === "node" && ( detail . details as any ) . locationY != null ) {
71+ return {
72+ startColumn : ( detail . details as any ) . locationX || 1 ,
73+ startLine : Math . max ( 1 , ( detail . details as any ) . locationY ) ,
74+ } ;
75+ }
76+ return { startColumn : 1 , startLine : 1 } ;
77+ }
78+
79+ function mapSeverity ( sev : string ) : "error" | "note" | "warning" {
80+ switch ( sev ?. toLowerCase ( ) ) {
81+ case "info" :
82+ case "note" : return "note" ; case "warning" : return "warning" ;
83+ default : return "error" ;
84+ }
85+ }
0 commit comments