Skip to content

Commit c5b338f

Browse files
joeldicksondicko2
andauthored
Add Sarif Parser (#164)
* Add Sarif parser * fix errors * Update README.md --------- Co-authored-by: jdickson <[email protected]>
1 parent a05ccf3 commit c5b338f

File tree

5 files changed

+446
-0
lines changed

5 files changed

+446
-0
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,19 @@ Use `-o <filename>` on output lint result created by command `dart analyze > <fi
155155
Use `--output <filename>` to output lint result to file and `--reporter json` to format logs as JSON.
156156
(_[ref.](https://github.com/realm/SwiftLint#command-line)_)
157157

158+
#### Kotlin Detekt
159+
In gradle config
160+
(_[ref.](https://detekt.dev/docs/gettingstarted/gradle#reports)_)
161+
162+
```kotlin
163+
tasks.named("detekt").configure {
164+
reports {
165+
// Enable/Disable SARIF report (default: false)
166+
sarif.required.set(true)
167+
sarif.outputLocation.set(file("build/reports/detekt.sarif"))
168+
}
169+
}
170+
```
171+
158172
### Contribute
159173
For contribution guidelines and project dev setup. Please see [CONTRIBUTING.md](CONTRIBUTING.md)

src/Config/@enums/projectType.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export enum ProjectType {
88
androidlint = 'androidlint',
99
dartlint = 'dartlint',
1010
swiftlint = 'swiftlint',
11+
sarif = 'sarif',
1112

1213
// copy paste detector
1314
jscpd = 'jscpd',

src/Parser/SarifParser.spec.ts

+280
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import { LintSeverity } from './@enums/LintSeverity';
2+
import { LintItem } from './@types';
3+
import { SarifParser } from './SarifParser';
4+
5+
describe('SarifParser tests', () => {
6+
const cwdWin = 'C:\\source';
7+
const cwdUnix = '/dir';
8+
9+
const basicSarifLog = {
10+
version: '2.1.0',
11+
runs: [
12+
{
13+
tool: {
14+
driver: {
15+
name: 'TestAnalyzer',
16+
rules: [
17+
{
18+
id: 'TEST001',
19+
shortDescription: {
20+
text: 'Test rule description',
21+
},
22+
},
23+
],
24+
},
25+
},
26+
results: [
27+
{
28+
ruleId: 'TEST001',
29+
level: 'warning',
30+
message: {
31+
text: 'This is a test warning',
32+
},
33+
locations: [
34+
{
35+
physicalLocation: {
36+
artifactLocation: {
37+
uri: 'C:\\source\\Test.cs',
38+
},
39+
region: {
40+
startLine: 42,
41+
startColumn: 13,
42+
},
43+
},
44+
},
45+
],
46+
},
47+
],
48+
},
49+
],
50+
};
51+
52+
const sarifLogNoLocation = {
53+
version: '2.1.0',
54+
runs: [
55+
{
56+
tool: {
57+
driver: {
58+
name: 'TestAnalyzer',
59+
},
60+
},
61+
results: [
62+
{
63+
ruleId: 'TEST002',
64+
level: 'error',
65+
message: {
66+
text: 'Error without location',
67+
},
68+
},
69+
],
70+
},
71+
],
72+
};
73+
74+
const sarifLogMultipleResults = {
75+
version: '2.1.0',
76+
runs: [
77+
{
78+
tool: {
79+
driver: {
80+
name: 'TestAnalyzer',
81+
},
82+
},
83+
results: [
84+
{
85+
ruleId: 'TEST003',
86+
level: 'warning',
87+
message: {
88+
text: 'First warning',
89+
},
90+
locations: [
91+
{
92+
physicalLocation: {
93+
artifactLocation: {
94+
uri: 'C:\\source\\Test1.cs',
95+
},
96+
region: {
97+
startLine: 10,
98+
startColumn: 5,
99+
},
100+
},
101+
},
102+
],
103+
},
104+
{
105+
ruleId: 'TEST004',
106+
level: 'error',
107+
message: {
108+
text: 'Second error',
109+
},
110+
locations: [
111+
{
112+
physicalLocation: {
113+
artifactLocation: {
114+
uri: 'C:\\source\\Test2.cs',
115+
},
116+
region: {
117+
startLine: 20,
118+
startColumn: 8,
119+
},
120+
},
121+
},
122+
],
123+
},
124+
],
125+
},
126+
],
127+
};
128+
129+
const sarifLogUnrelatedPath = {
130+
version: '2.1.0',
131+
runs: [
132+
{
133+
tool: {
134+
driver: {
135+
name: 'TestAnalyzer',
136+
},
137+
},
138+
results: [
139+
{
140+
ruleId: 'TEST005',
141+
level: 'warning',
142+
message: {
143+
text: 'Warning with unrelated path',
144+
},
145+
locations: [
146+
{
147+
physicalLocation: {
148+
artifactLocation: {
149+
uri: '/usr/share/test/Unrelated.cs',
150+
},
151+
region: {
152+
startLine: 15,
153+
startColumn: 3,
154+
},
155+
},
156+
},
157+
],
158+
},
159+
],
160+
},
161+
],
162+
};
163+
164+
it('Should parse basic SARIF log correctly', () => {
165+
const result = new SarifParser(cwdWin).parse(JSON.stringify(basicSarifLog));
166+
expect(result).toHaveLength(1);
167+
expect(result[0]).toEqual({
168+
ruleId: 'TEST001',
169+
source: 'Test.cs',
170+
severity: LintSeverity.warning,
171+
line: 42,
172+
lineOffset: 13,
173+
msg: 'TEST001: This is a test warning',
174+
log: expect.any(String),
175+
valid: true,
176+
type: 'sarif',
177+
} as LintItem);
178+
});
179+
180+
it('Should handle results without location information', () => {
181+
const result = new SarifParser(cwdWin).parse(JSON.stringify(sarifLogNoLocation));
182+
expect(result).toHaveLength(0);
183+
});
184+
185+
it('Should parse multiple results correctly', () => {
186+
const result = new SarifParser(cwdWin).parse(JSON.stringify(sarifLogMultipleResults));
187+
expect(result).toHaveLength(2);
188+
expect(result[0].severity).toBe(LintSeverity.warning);
189+
expect(result[1].severity).toBe(LintSeverity.error);
190+
expect(result[0].source).toBe('Test1.cs');
191+
expect(result[1].source).toBe('Test2.cs');
192+
});
193+
194+
it('Should handle unrelated paths correctly and flag as invalid', () => {
195+
const result = new SarifParser(cwdUnix).parse(JSON.stringify(sarifLogUnrelatedPath));
196+
expect(result).toHaveLength(1);
197+
expect(result[0]).toEqual({
198+
ruleId: 'TEST005',
199+
source: 'Unrelated.cs',
200+
severity: LintSeverity.warning,
201+
line: 15,
202+
lineOffset: 3,
203+
msg: 'TEST005: Warning with unrelated path',
204+
log: expect.any(String),
205+
valid: false,
206+
type: 'sarif',
207+
} as LintItem);
208+
});
209+
210+
it('Should handle empty SARIF log', () => {
211+
const emptyLog = {
212+
version: '2.1.0',
213+
runs: [
214+
{
215+
tool: {
216+
driver: {
217+
name: 'TestAnalyzer',
218+
},
219+
},
220+
results: [],
221+
},
222+
],
223+
};
224+
const result = new SarifParser(cwdWin).parse(JSON.stringify(emptyLog));
225+
expect(result).toHaveLength(0);
226+
});
227+
228+
it('Should throw error on invalid JSON', () => {
229+
expect(() => new SarifParser(cwdWin).parse('{')).toThrowError();
230+
});
231+
232+
it('Should throw error on invalid SARIF format', () => {
233+
const invalidLog = {
234+
version: '2.1.0',
235+
// missing runs array
236+
};
237+
expect(() =>
238+
new SarifParser(cwdWin).parse(JSON.stringify(invalidLog)),
239+
).toThrowError();
240+
});
241+
242+
it('Should handle missing severity level and default to warning', () => {
243+
const logWithNoLevel = {
244+
version: '2.1.0',
245+
runs: [
246+
{
247+
tool: {
248+
driver: {
249+
name: 'TestAnalyzer',
250+
},
251+
},
252+
results: [
253+
{
254+
ruleId: 'TEST006',
255+
message: {
256+
text: 'Message with no severity level',
257+
},
258+
locations: [
259+
{
260+
physicalLocation: {
261+
artifactLocation: {
262+
uri: 'C:\\source\\Test.cs',
263+
},
264+
region: {
265+
startLine: 1,
266+
startColumn: 1,
267+
},
268+
},
269+
},
270+
],
271+
},
272+
],
273+
},
274+
],
275+
};
276+
const result = new SarifParser(cwdWin).parse(JSON.stringify(logWithNoLevel));
277+
expect(result).toHaveLength(1);
278+
expect(result[0].severity).toBe(LintSeverity.warning);
279+
});
280+
});

0 commit comments

Comments
 (0)