Skip to content

Commit 059bc80

Browse files
committed
create SarifExporter
1 parent bf2ec91 commit 059bc80

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Compiler } from "./main/libs/Compiler";
55
import { fix } from "./main/libs/FixFlows";
66
import { getBetaRules, getRules } from "./main/libs/GetRuleDefinitions";
77
import { parse } from "./main/libs/ParseFlows";
8+
import { exportSarif } from "./main/libs/SARIFExporter";
89
import { scan } from "./main/libs/ScanFlows";
910
import { AdvancedRule } from "./main/models/AdvancedRule";
1011
import { Flow } from "./main/models/Flow";
@@ -22,6 +23,7 @@ import { ScanResult } from "./main/models/ScanResult";
2223
export {
2324
AdvancedRule,
2425
Compiler,
26+
exportSarif,
2527
fix,
2628
Flow,
2729
FlowAttribute,
@@ -39,4 +41,4 @@ export {
3941
scan,
4042
ScanResult,
4143
};
42-
export type { IRuleDefinition, IRulesConfig };
44+
export type { IRuleDefinition, IRulesConfig };

src/main/libs/SarifExporter.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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

Comments
 (0)