@boyingliu01/xp-gate 0.8.9 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/init.js +36 -0
- package/mock-policy/AGENTS.md +94 -0
- package/mock-policy/__tests__/config.test.ts +292 -0
- package/mock-policy/__tests__/integration.test.ts +600 -0
- package/mock-policy/__tests__/mock-decision-engine.test.ts +231 -0
- package/mock-policy/__tests__/scope-scanner.test.ts +535 -0
- package/mock-policy/config.ts +106 -0
- package/mock-policy/gate-m3.ts +233 -0
- package/mock-policy/mock-decision-engine.ts +155 -0
- package/mock-policy/schema.ts +22 -0
- package/mock-policy/scope-scanner.ts +288 -0
- package/mock-policy/types.ts +65 -0
- package/mutation/AGENTS.md +73 -0
- package/mutation/__tests__/detect-ai-test.test.ts +152 -0
- package/mutation/__tests__/gate-m.test.ts +194 -0
- package/mutation/detect-ai-test.ts +128 -0
- package/mutation/gate-m.ts +619 -0
- package/mutation/init-baseline.ts +192 -0
- package/mutation/stryker-types.ts +14 -0
- package/mutation/types.ts +70 -0
- package/mutation/update-baseline.ts +160 -0
- package/package.json +5 -2
- package/plugins/claude-code/.claude-plugin/plugin.json +2 -2
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +7 -7
- package/plugins/claude-code/skills/delphi-review/INSTALL.md +1 -1
- package/plugins/claude-code/skills/delphi-review/SKILL.md +12 -12
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +7 -7
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/phase-3-review.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +7 -7
- package/plugins/opencode/skills/delphi-review/INSTALL.md +1 -1
- package/plugins/opencode/skills/delphi-review/SKILL.md +12 -12
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +1 -1
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +7 -7
- package/plugins/opencode/skills/sprint-flow/SKILL.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/force-levels.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/phase-3-review.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +7 -7
- package/plugins/qoder/skills/delphi-review/INSTALL.md +1 -1
- package/plugins/qoder/skills/delphi-review/SKILL.md +12 -12
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +1 -1
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +7 -7
- package/plugins/qoder/skills/sprint-flow/SKILL.md +51 -1
- package/plugins/qoder/skills/sprint-flow/references/force-levels.md +1 -1
- package/plugins/qoder/skills/sprint-flow/references/phase-3-review.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +111 -0
- package/principles/__tests__/analyzer.test.ts +343 -0
- package/principles/__tests__/baseline-storage.test.ts +187 -0
- package/principles/__tests__/baseline.test.ts +328 -0
- package/principles/__tests__/boy-scout-integration.test.ts +264 -0
- package/principles/__tests__/boy-scout.test.ts +646 -0
- package/principles/__tests__/config.test.ts +78 -0
- package/principles/__tests__/index.test.ts +81 -0
- package/principles/__tests__/lint-baseline.test.ts +314 -0
- package/principles/__tests__/reporter.test.ts +832 -0
- package/principles/__tests__/types.test.ts +101 -0
- package/principles/adapters/__tests__/base.test.ts +95 -0
- package/principles/adapters/__tests__/cpp.test.ts +290 -0
- package/principles/adapters/__tests__/dart.test.ts +84 -0
- package/principles/adapters/__tests__/go.test.ts +68 -0
- package/principles/adapters/__tests__/java.test.ts +68 -0
- package/principles/adapters/__tests__/kotlin.test.ts +76 -0
- package/principles/adapters/__tests__/objectivec.test.ts +453 -0
- package/principles/adapters/__tests__/python.test.ts +84 -0
- package/principles/adapters/__tests__/swift.test.ts +68 -0
- package/principles/adapters/__tests__/typescript.test.ts +116 -0
- package/principles/adapters/base.ts +124 -0
- package/principles/adapters/cpp.ts +115 -0
- package/principles/adapters/dart.ts +44 -0
- package/principles/adapters/go.ts +55 -0
- package/principles/adapters/java.ts +56 -0
- package/principles/adapters/kotlin.ts +54 -0
- package/principles/adapters/objectivec.ts +82 -0
- package/principles/adapters/python.ts +83 -0
- package/principles/adapters/swift.ts +40 -0
- package/principles/adapters/typescript.ts +66 -0
- package/principles/analyzer.ts +190 -0
- package/principles/baseline.ts +273 -0
- package/principles/boy-scout.ts +416 -0
- package/principles/config.ts +118 -0
- package/principles/index.ts +87 -0
- package/principles/lint-baseline.ts +385 -0
- package/principles/reporter.ts +188 -0
- package/principles/rules/__tests__/clean-code/code-duplication.test.ts +84 -0
- package/principles/rules/__tests__/clean-code/deep-nesting.test.ts +74 -0
- package/principles/rules/__tests__/clean-code/god-class.test.ts +111 -0
- package/principles/rules/__tests__/clean-code/large-file.test.ts +73 -0
- package/principles/rules/__tests__/clean-code/long-function.test.ts +83 -0
- package/principles/rules/__tests__/clean-code/magic-numbers.test.ts +68 -0
- package/principles/rules/__tests__/clean-code/many-exports.test.ts +118 -0
- package/principles/rules/__tests__/clean-code/missing-error-handling.test.ts +173 -0
- package/principles/rules/__tests__/clean-code/too-many-params.test.ts +60 -0
- package/principles/rules/__tests__/clean-code/unused-imports.test.ts +107 -0
- package/principles/rules/__tests__/index.test.ts +49 -0
- package/principles/rules/__tests__/solid/dip.test.ts +121 -0
- package/principles/rules/__tests__/solid/isp.test.ts +60 -0
- package/principles/rules/__tests__/solid/lsp.test.ts +184 -0
- package/principles/rules/__tests__/solid/ocp.test.ts +69 -0
- package/principles/rules/__tests__/solid/srp.test.ts +89 -0
- package/principles/rules/clean-code/code-duplication.ts +31 -0
- package/principles/rules/clean-code/deep-nesting.ts +38 -0
- package/principles/rules/clean-code/god-class.ts +54 -0
- package/principles/rules/clean-code/large-file.ts +34 -0
- package/principles/rules/clean-code/long-function.ts +47 -0
- package/principles/rules/clean-code/magic-numbers.ts +59 -0
- package/principles/rules/clean-code/many-exports.ts +40 -0
- package/principles/rules/clean-code/missing-error-handling.ts +33 -0
- package/principles/rules/clean-code/too-many-params.ts +35 -0
- package/principles/rules/clean-code/unused-imports.ts +33 -0
- package/principles/rules/index.ts +61 -0
- package/principles/rules/solid/dip.ts +47 -0
- package/principles/rules/solid/isp.ts +37 -0
- package/principles/rules/solid/lsp.ts +79 -0
- package/principles/rules/solid/ocp.ts +40 -0
- package/principles/rules/solid/srp.ts +44 -0
- package/principles/types.ts +27 -0
- package/skills/delphi-review/AGENTS.md +7 -7
- package/skills/delphi-review/INSTALL.md +1 -1
- package/skills/delphi-review/SKILL.md +12 -12
- package/skills/delphi-review/references/code-walkthrough.md +1 -1
- package/skills/sprint-flow/AGENTS.md +7 -7
- package/skills/sprint-flow/SKILL.md +1 -1
- package/skills/sprint-flow/references/force-levels.md +1 -1
- package/skills/sprint-flow/references/phase-3-review.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +3 -3
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test reporter.ts - Output formatting
|
|
3
|
+
* @intent Verify reporter formats violations for console and other outputs
|
|
4
|
+
* @covers clean-code-solid-checker-design Section 3 (CORE ENGINE)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { formatConsole, formatJSON, formatSARIF, formatSummary } from '../reporter';
|
|
9
|
+
import { AnalysisResult, Violation } from '../analyzer';
|
|
10
|
+
|
|
11
|
+
describe('reporter.ts - Output Formatting', () => {
|
|
12
|
+
const sampleViolation: Violation = {
|
|
13
|
+
file: 'src/test.ts',
|
|
14
|
+
line: 10,
|
|
15
|
+
column: 5,
|
|
16
|
+
ruleId: 'clean-code.long-function',
|
|
17
|
+
message: 'Function "processData" is too long: 65 lines (maximum: 50)',
|
|
18
|
+
severity: 'warning'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const sampleResult: AnalysisResult = {
|
|
22
|
+
violations: [
|
|
23
|
+
sampleViolation,
|
|
24
|
+
{
|
|
25
|
+
file: 'src/utils.ts',
|
|
26
|
+
line: 1,
|
|
27
|
+
ruleId: 'clean-code.large-file',
|
|
28
|
+
message: 'File is too large: 600 lines (maximum: 500)',
|
|
29
|
+
severity: 'warning'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
file: 'src/api.ts',
|
|
33
|
+
line: 20,
|
|
34
|
+
ruleId: 'solid.dip',
|
|
35
|
+
message: 'Direct instantiation detected: new UserRepository()',
|
|
36
|
+
severity: 'warning'
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
summary: {
|
|
40
|
+
totalViolations: 3,
|
|
41
|
+
errorCount: 0,
|
|
42
|
+
warningCount: 3,
|
|
43
|
+
infoCount: 0,
|
|
44
|
+
filesChecked: 3,
|
|
45
|
+
rulesRun: 3
|
|
46
|
+
},
|
|
47
|
+
fileResults: {
|
|
48
|
+
'src/test.ts': { violations: [sampleViolation], ruleIds: ['clean-code.long-function'] }
|
|
49
|
+
},
|
|
50
|
+
ruleResults: {
|
|
51
|
+
'clean-code.long-function': { violationCount: 1, filesChecked: 3 }
|
|
52
|
+
},
|
|
53
|
+
executionTimeMs: 150,
|
|
54
|
+
errors: []
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
describe('formatConsole', () => {
|
|
58
|
+
it('should format violations as human-readable console output', () => {
|
|
59
|
+
const output = formatConsole(sampleResult);
|
|
60
|
+
|
|
61
|
+
expect(output).toContain('clean-code.long-function');
|
|
62
|
+
expect(output).toContain('Function "processData" is too long');
|
|
63
|
+
expect(output).toContain('src/test.ts');
|
|
64
|
+
expect(output).toContain('line 10');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should group violations by file', () => {
|
|
68
|
+
const output = formatConsole(sampleResult);
|
|
69
|
+
|
|
70
|
+
expect(output).toContain('src/test.ts');
|
|
71
|
+
expect(output).toContain('src/utils.ts');
|
|
72
|
+
expect(output).toContain('src/api.ts');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should show severity indicators', () => {
|
|
76
|
+
const output = formatConsole(sampleResult);
|
|
77
|
+
|
|
78
|
+
expect(output).toContain('⚠');
|
|
79
|
+
expect(output).toContain('WARNING');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should show execution time', () => {
|
|
83
|
+
const output = formatConsole(sampleResult);
|
|
84
|
+
|
|
85
|
+
expect(output).toContain('150ms');
|
|
86
|
+
expect(output).toContain('Execution time');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle empty violations', () => {
|
|
90
|
+
const emptyResult: AnalysisResult = {
|
|
91
|
+
violations: [],
|
|
92
|
+
summary: {
|
|
93
|
+
totalViolations: 0,
|
|
94
|
+
errorCount: 0,
|
|
95
|
+
warningCount: 0,
|
|
96
|
+
infoCount: 0,
|
|
97
|
+
filesChecked: 5,
|
|
98
|
+
rulesRun: 3
|
|
99
|
+
},
|
|
100
|
+
fileResults: {},
|
|
101
|
+
ruleResults: {},
|
|
102
|
+
executionTimeMs: 50,
|
|
103
|
+
errors: []
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const output = formatConsole(emptyResult);
|
|
107
|
+
|
|
108
|
+
expect(output).toContain('✓');
|
|
109
|
+
expect(output).toContain('No violations found');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('formatJSON', () => {
|
|
114
|
+
it('should output valid JSON structure', () => {
|
|
115
|
+
const output = formatJSON(sampleResult);
|
|
116
|
+
const parsed = JSON.parse(output);
|
|
117
|
+
|
|
118
|
+
expect(parsed.violations).toBeDefined();
|
|
119
|
+
expect(parsed.summary).toBeDefined();
|
|
120
|
+
expect(parsed.executionTimeMs).toBe(150);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should include all violation details', () => {
|
|
124
|
+
const output = formatJSON(sampleResult);
|
|
125
|
+
const parsed = JSON.parse(output);
|
|
126
|
+
|
|
127
|
+
expect(parsed.violations[0].file).toBe('src/test.ts');
|
|
128
|
+
expect(parsed.violations[0].ruleId).toBe('clean-code.long-function');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should be parseable for downstream tools', () => {
|
|
132
|
+
const output = formatJSON(sampleResult);
|
|
133
|
+
|
|
134
|
+
expect(() => JSON.parse(output)).not.toThrow();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('formatSummary', () => {
|
|
139
|
+
it('should show total violations count', () => {
|
|
140
|
+
const output = formatSummary(sampleResult);
|
|
141
|
+
|
|
142
|
+
expect(output).toContain('3 violations');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should show breakdown by severity', () => {
|
|
146
|
+
const output = formatSummary(sampleResult);
|
|
147
|
+
|
|
148
|
+
expect(output).toContain('0 errors');
|
|
149
|
+
expect(output).toContain('3 warnings');
|
|
150
|
+
expect(output).toContain('0 info');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should show files and rules checked', () => {
|
|
154
|
+
const output = formatSummary(sampleResult);
|
|
155
|
+
|
|
156
|
+
expect(output).toContain('3 files');
|
|
157
|
+
expect(output).toContain('3 rules');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should indicate pass/fail status', () => {
|
|
161
|
+
const output = formatSummary(sampleResult);
|
|
162
|
+
|
|
163
|
+
expect(output).toContain('FAIL');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should show PASS when no violations', () => {
|
|
167
|
+
const emptyResult: AnalysisResult = {
|
|
168
|
+
violations: [],
|
|
169
|
+
summary: {
|
|
170
|
+
totalViolations: 0,
|
|
171
|
+
errorCount: 0,
|
|
172
|
+
warningCount: 0,
|
|
173
|
+
infoCount: 0,
|
|
174
|
+
filesChecked: 5,
|
|
175
|
+
rulesRun: 3
|
|
176
|
+
},
|
|
177
|
+
fileResults: {},
|
|
178
|
+
ruleResults: {},
|
|
179
|
+
executionTimeMs: 50,
|
|
180
|
+
errors: []
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const output = formatSummary(emptyResult);
|
|
184
|
+
|
|
185
|
+
expect(output).toContain('PASS');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should show error count when severity has errors', () => {
|
|
189
|
+
const errorResult: AnalysisResult = {
|
|
190
|
+
violations: [
|
|
191
|
+
{
|
|
192
|
+
file: 'src/auth.ts',
|
|
193
|
+
line: 5,
|
|
194
|
+
ruleId: 'solid.srp',
|
|
195
|
+
message: 'Class handles both auth and logging',
|
|
196
|
+
severity: 'error'
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
summary: {
|
|
200
|
+
totalViolations: 1,
|
|
201
|
+
errorCount: 1,
|
|
202
|
+
warningCount: 0,
|
|
203
|
+
infoCount: 0,
|
|
204
|
+
filesChecked: 1,
|
|
205
|
+
rulesRun: 1
|
|
206
|
+
},
|
|
207
|
+
fileResults: {},
|
|
208
|
+
ruleResults: {},
|
|
209
|
+
executionTimeMs: 30,
|
|
210
|
+
errors: []
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const output = formatSummary(errorResult);
|
|
214
|
+
|
|
215
|
+
expect(output).toContain('1 errors');
|
|
216
|
+
expect(output).toContain('0 warnings');
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('formatSARIF', () => {
|
|
221
|
+
it('should return valid SARIF JSON with version 2.1.0', () => {
|
|
222
|
+
const result: AnalysisResult = {
|
|
223
|
+
violations: [sampleViolation],
|
|
224
|
+
summary: {
|
|
225
|
+
totalViolations: 1,
|
|
226
|
+
errorCount: 0,
|
|
227
|
+
warningCount: 1,
|
|
228
|
+
infoCount: 0,
|
|
229
|
+
filesChecked: 1,
|
|
230
|
+
rulesRun: 1
|
|
231
|
+
},
|
|
232
|
+
fileResults: {},
|
|
233
|
+
ruleResults: {
|
|
234
|
+
'clean-code.long-function': { violationCount: 1, filesChecked: 1 }
|
|
235
|
+
},
|
|
236
|
+
executionTimeMs: 100,
|
|
237
|
+
errors: []
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const output = formatSARIF(result);
|
|
241
|
+
const parsed = JSON.parse(output);
|
|
242
|
+
|
|
243
|
+
expect(parsed.version).toBe('2.1.0');
|
|
244
|
+
expect(parsed.$schema).toContain('sarif-schema-2.1.0');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should include runs array with tool driver info', () => {
|
|
248
|
+
const result: AnalysisResult = {
|
|
249
|
+
violations: [sampleViolation],
|
|
250
|
+
summary: {
|
|
251
|
+
totalViolations: 1,
|
|
252
|
+
errorCount: 0,
|
|
253
|
+
warningCount: 1,
|
|
254
|
+
infoCount: 0,
|
|
255
|
+
filesChecked: 3,
|
|
256
|
+
rulesRun: 2
|
|
257
|
+
},
|
|
258
|
+
fileResults: {},
|
|
259
|
+
ruleResults: {
|
|
260
|
+
'clean-code.long-function': { violationCount: 1, filesChecked: 3 }
|
|
261
|
+
},
|
|
262
|
+
executionTimeMs: 95,
|
|
263
|
+
errors: []
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const output = formatSARIF(result);
|
|
267
|
+
const parsed = JSON.parse(output);
|
|
268
|
+
|
|
269
|
+
expect(parsed.runs).toBeDefined();
|
|
270
|
+
expect(parsed.runs).toHaveLength(1);
|
|
271
|
+
expect(parsed.runs[0].tool.driver.name).toBe('Principles Checker');
|
|
272
|
+
expect(parsed.runs[0].tool.driver.version).toBe('1.0.0');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should map violations to SARIF results with correct levels', () => {
|
|
276
|
+
const result: AnalysisResult = {
|
|
277
|
+
violations: [
|
|
278
|
+
{
|
|
279
|
+
file: 'src/auth.ts',
|
|
280
|
+
line: 5,
|
|
281
|
+
column: 10,
|
|
282
|
+
ruleId: 'solid.srp',
|
|
283
|
+
message: 'Multiple responsibilities',
|
|
284
|
+
severity: 'error'
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
file: 'src/utils.ts',
|
|
288
|
+
line: 20,
|
|
289
|
+
ruleId: 'clean-code.long-function',
|
|
290
|
+
message: 'Function too long',
|
|
291
|
+
severity: 'warning'
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
file: 'src/config.ts',
|
|
295
|
+
line: 3,
|
|
296
|
+
ruleId: 'clean-code.magic-numbers',
|
|
297
|
+
message: 'Magic number 42',
|
|
298
|
+
severity: 'info'
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
summary: {
|
|
302
|
+
totalViolations: 3,
|
|
303
|
+
errorCount: 1,
|
|
304
|
+
warningCount: 1,
|
|
305
|
+
infoCount: 1,
|
|
306
|
+
filesChecked: 3,
|
|
307
|
+
rulesRun: 3
|
|
308
|
+
},
|
|
309
|
+
fileResults: {},
|
|
310
|
+
ruleResults: {
|
|
311
|
+
'solid.srp': { violationCount: 1, filesChecked: 1 },
|
|
312
|
+
'clean-code.long-function': { violationCount: 1, filesChecked: 1 },
|
|
313
|
+
'clean-code.magic-numbers': { violationCount: 1, filesChecked: 1 }
|
|
314
|
+
},
|
|
315
|
+
executionTimeMs: 200,
|
|
316
|
+
errors: []
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const output = formatSARIF(result);
|
|
320
|
+
const parsed = JSON.parse(output);
|
|
321
|
+
const results = parsed.runs[0].results;
|
|
322
|
+
|
|
323
|
+
expect(results).toHaveLength(3);
|
|
324
|
+
expect(results[0].level).toBe('error');
|
|
325
|
+
expect(results[1].level).toBe('warning');
|
|
326
|
+
expect(results[2].level).toBe('note');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should include file, line, and column in result locations', () => {
|
|
330
|
+
const result: AnalysisResult = {
|
|
331
|
+
violations: [
|
|
332
|
+
{
|
|
333
|
+
file: 'src/api.ts',
|
|
334
|
+
line: 42,
|
|
335
|
+
column: 15,
|
|
336
|
+
ruleId: 'clean-code.deep-nesting',
|
|
337
|
+
message: 'Nesting depth 6 exceeds threshold 4',
|
|
338
|
+
severity: 'warning'
|
|
339
|
+
}
|
|
340
|
+
],
|
|
341
|
+
summary: {
|
|
342
|
+
totalViolations: 1,
|
|
343
|
+
errorCount: 0,
|
|
344
|
+
warningCount: 1,
|
|
345
|
+
infoCount: 0,
|
|
346
|
+
filesChecked: 1,
|
|
347
|
+
rulesRun: 1
|
|
348
|
+
},
|
|
349
|
+
fileResults: {},
|
|
350
|
+
ruleResults: {
|
|
351
|
+
'clean-code.deep-nesting': { violationCount: 1, filesChecked: 1 }
|
|
352
|
+
},
|
|
353
|
+
executionTimeMs: 80,
|
|
354
|
+
errors: []
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const output = formatSARIF(result);
|
|
358
|
+
const parsed = JSON.parse(output);
|
|
359
|
+
const location = parsed.runs[0].results[0].locations[0].physicalLocation;
|
|
360
|
+
|
|
361
|
+
expect(location.artifactLocation.uri).toBe('src/api.ts');
|
|
362
|
+
expect(location.region.startLine).toBe(42);
|
|
363
|
+
expect(location.region.startColumn).toBe(15);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should default column to 1 when violation has no column', () => {
|
|
367
|
+
const result: AnalysisResult = {
|
|
368
|
+
violations: [
|
|
369
|
+
{
|
|
370
|
+
file: 'src/big.ts',
|
|
371
|
+
line: 500,
|
|
372
|
+
ruleId: 'clean-code.large-file',
|
|
373
|
+
message: 'File too large',
|
|
374
|
+
severity: 'warning'
|
|
375
|
+
}
|
|
376
|
+
],
|
|
377
|
+
summary: {
|
|
378
|
+
totalViolations: 1,
|
|
379
|
+
errorCount: 0,
|
|
380
|
+
warningCount: 1,
|
|
381
|
+
infoCount: 0,
|
|
382
|
+
filesChecked: 1,
|
|
383
|
+
rulesRun: 1
|
|
384
|
+
},
|
|
385
|
+
fileResults: {},
|
|
386
|
+
ruleResults: {
|
|
387
|
+
'clean-code.large-file': { violationCount: 1, filesChecked: 1 }
|
|
388
|
+
},
|
|
389
|
+
executionTimeMs: 50,
|
|
390
|
+
errors: []
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const output = formatSARIF(result);
|
|
394
|
+
const parsed = JSON.parse(output);
|
|
395
|
+
const location = parsed.runs[0].results[0].locations[0].physicalLocation;
|
|
396
|
+
|
|
397
|
+
expect(location.region.startColumn).toBe(1);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should include ruleDescriptors for all rules in ruleResults', () => {
|
|
401
|
+
const result: AnalysisResult = {
|
|
402
|
+
violations: [sampleViolation],
|
|
403
|
+
summary: {
|
|
404
|
+
totalViolations: 1,
|
|
405
|
+
errorCount: 0,
|
|
406
|
+
warningCount: 1,
|
|
407
|
+
infoCount: 0,
|
|
408
|
+
filesChecked: 2,
|
|
409
|
+
rulesRun: 2
|
|
410
|
+
},
|
|
411
|
+
fileResults: {},
|
|
412
|
+
ruleResults: {
|
|
413
|
+
'clean-code.long-function': { violationCount: 1, filesChecked: 2 },
|
|
414
|
+
'clean-code.missing-error-handling': { violationCount: 0, filesChecked: 2 }
|
|
415
|
+
},
|
|
416
|
+
executionTimeMs: 60,
|
|
417
|
+
errors: []
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const output = formatSARIF(result);
|
|
421
|
+
const parsed = JSON.parse(output);
|
|
422
|
+
const rules = parsed.runs[0].tool.driver.rules;
|
|
423
|
+
|
|
424
|
+
expect(rules).toHaveLength(2);
|
|
425
|
+
expect(rules[0].id).toBe('clean-code.long-function');
|
|
426
|
+
expect(rules[0].shortDescription.text).toContain('Function');
|
|
427
|
+
expect(rules[1].id).toBe('clean-code.missing-error-handling');
|
|
428
|
+
expect(rules[1].shortDescription.text).toContain('error handling');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should include execution properties (filesChecked, rulesRun, executionTimeMs)', () => {
|
|
432
|
+
const result: AnalysisResult = {
|
|
433
|
+
violations: [],
|
|
434
|
+
summary: {
|
|
435
|
+
totalViolations: 0,
|
|
436
|
+
errorCount: 0,
|
|
437
|
+
warningCount: 0,
|
|
438
|
+
infoCount: 0,
|
|
439
|
+
filesChecked: 10,
|
|
440
|
+
rulesRun: 5
|
|
441
|
+
},
|
|
442
|
+
fileResults: {},
|
|
443
|
+
ruleResults: {
|
|
444
|
+
'clean-code.long-function': { violationCount: 0, filesChecked: 10 }
|
|
445
|
+
},
|
|
446
|
+
executionTimeMs: 150,
|
|
447
|
+
errors: []
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const output = formatSARIF(result);
|
|
451
|
+
const parsed = JSON.parse(output);
|
|
452
|
+
const props = parsed.runs[0].properties;
|
|
453
|
+
|
|
454
|
+
expect(props.filesChecked).toBe(10);
|
|
455
|
+
expect(props.rulesRun).toBe(5);
|
|
456
|
+
expect(props.executionTimeMs).toBe(150);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should return empty results array when no violations', () => {
|
|
460
|
+
const result: AnalysisResult = {
|
|
461
|
+
violations: [],
|
|
462
|
+
summary: {
|
|
463
|
+
totalViolations: 0,
|
|
464
|
+
errorCount: 0,
|
|
465
|
+
warningCount: 0,
|
|
466
|
+
infoCount: 0,
|
|
467
|
+
filesChecked: 5,
|
|
468
|
+
rulesRun: 3
|
|
469
|
+
},
|
|
470
|
+
fileResults: {},
|
|
471
|
+
ruleResults: {
|
|
472
|
+
'clean-code.long-function': { violationCount: 0, filesChecked: 5 }
|
|
473
|
+
},
|
|
474
|
+
executionTimeMs: 30,
|
|
475
|
+
errors: []
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const output = formatSARIF(result);
|
|
479
|
+
const parsed = JSON.parse(output);
|
|
480
|
+
|
|
481
|
+
expect(parsed.runs[0].results).toEqual([]);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('should map DIP violation to error level when violation severity is error', () => {
|
|
485
|
+
const result: AnalysisResult = {
|
|
486
|
+
violations: [
|
|
487
|
+
{
|
|
488
|
+
file: 'src/service.ts',
|
|
489
|
+
line: 8,
|
|
490
|
+
column: 1,
|
|
491
|
+
ruleId: 'solid.dip',
|
|
492
|
+
message: 'Direct instantiation detected',
|
|
493
|
+
severity: 'error'
|
|
494
|
+
}
|
|
495
|
+
],
|
|
496
|
+
summary: {
|
|
497
|
+
totalViolations: 1,
|
|
498
|
+
errorCount: 1,
|
|
499
|
+
warningCount: 0,
|
|
500
|
+
infoCount: 0,
|
|
501
|
+
filesChecked: 1,
|
|
502
|
+
rulesRun: 1
|
|
503
|
+
},
|
|
504
|
+
fileResults: {},
|
|
505
|
+
ruleResults: {
|
|
506
|
+
'solid.dip': { violationCount: 1, filesChecked: 1 }
|
|
507
|
+
},
|
|
508
|
+
executionTimeMs: 25,
|
|
509
|
+
errors: []
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const output = formatSARIF(result);
|
|
513
|
+
const parsed = JSON.parse(output);
|
|
514
|
+
|
|
515
|
+
expect(parsed.runs[0].results[0].level).toBe('error');
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('should include ruleDescriptors with defaultConfiguration levels', () => {
|
|
519
|
+
const result: AnalysisResult = {
|
|
520
|
+
violations: [],
|
|
521
|
+
summary: {
|
|
522
|
+
totalViolations: 0,
|
|
523
|
+
errorCount: 0,
|
|
524
|
+
warningCount: 0,
|
|
525
|
+
infoCount: 0,
|
|
526
|
+
filesChecked: 1,
|
|
527
|
+
rulesRun: 3
|
|
528
|
+
},
|
|
529
|
+
fileResults: {},
|
|
530
|
+
ruleResults: {
|
|
531
|
+
'solid.srp': { violationCount: 0, filesChecked: 1 },
|
|
532
|
+
'clean-code.long-function': { violationCount: 0, filesChecked: 1 },
|
|
533
|
+
'clean-code.magic-numbers': { violationCount: 0, filesChecked: 1 }
|
|
534
|
+
},
|
|
535
|
+
executionTimeMs: 40,
|
|
536
|
+
errors: []
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const output = formatSARIF(result);
|
|
540
|
+
const parsed = JSON.parse(output);
|
|
541
|
+
const rules = parsed.runs[0].tool.driver.rules;
|
|
542
|
+
|
|
543
|
+
const srpRule = rules.find((r: { id: string }) => r.id === 'solid.srp');
|
|
544
|
+
const longFuncRule = rules.find((r: { id: string }) => r.id === 'clean-code.long-function');
|
|
545
|
+
const magicNumRule = rules.find((r: { id: string }) => r.id === 'clean-code.magic-numbers');
|
|
546
|
+
|
|
547
|
+
expect(srpRule.defaultConfiguration.level).toBe('error');
|
|
548
|
+
expect(longFuncRule.defaultConfiguration.level).toBe('warning');
|
|
549
|
+
expect(magicNumRule.defaultConfiguration.level).toBe('note');
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe('formatConsole - additional cases', () => {
|
|
554
|
+
it('should show error severity with ✗ icon', () => {
|
|
555
|
+
const errorResult: AnalysisResult = {
|
|
556
|
+
violations: [
|
|
557
|
+
{
|
|
558
|
+
file: 'src/auth.ts',
|
|
559
|
+
line: 5,
|
|
560
|
+
column: 1,
|
|
561
|
+
ruleId: 'solid.srp',
|
|
562
|
+
message: 'Multiple responsibilities',
|
|
563
|
+
severity: 'error'
|
|
564
|
+
}
|
|
565
|
+
],
|
|
566
|
+
summary: {
|
|
567
|
+
totalViolations: 1,
|
|
568
|
+
errorCount: 1,
|
|
569
|
+
warningCount: 0,
|
|
570
|
+
infoCount: 0,
|
|
571
|
+
filesChecked: 1,
|
|
572
|
+
rulesRun: 1
|
|
573
|
+
},
|
|
574
|
+
fileResults: {},
|
|
575
|
+
ruleResults: {},
|
|
576
|
+
executionTimeMs: 30,
|
|
577
|
+
errors: []
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const output = formatConsole(errorResult);
|
|
581
|
+
|
|
582
|
+
expect(output).toContain('✗');
|
|
583
|
+
expect(output).toContain('ERROR');
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('should show info severity with ℹ icon', () => {
|
|
587
|
+
const infoResult: AnalysisResult = {
|
|
588
|
+
violations: [
|
|
589
|
+
{
|
|
590
|
+
file: 'src/config.ts',
|
|
591
|
+
line: 3,
|
|
592
|
+
column: 10,
|
|
593
|
+
ruleId: 'clean-code.magic-numbers',
|
|
594
|
+
message: 'Magic number 42',
|
|
595
|
+
severity: 'info'
|
|
596
|
+
}
|
|
597
|
+
],
|
|
598
|
+
summary: {
|
|
599
|
+
totalViolations: 1,
|
|
600
|
+
errorCount: 0,
|
|
601
|
+
warningCount: 0,
|
|
602
|
+
infoCount: 1,
|
|
603
|
+
filesChecked: 1,
|
|
604
|
+
rulesRun: 1
|
|
605
|
+
},
|
|
606
|
+
fileResults: {},
|
|
607
|
+
ruleResults: {},
|
|
608
|
+
executionTimeMs: 20,
|
|
609
|
+
errors: []
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const output = formatConsole(infoResult);
|
|
613
|
+
|
|
614
|
+
expect(output).toContain('ℹ');
|
|
615
|
+
expect(output).toContain('INFO');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('should handle violations without column number', () => {
|
|
619
|
+
const noColumnResult: AnalysisResult = {
|
|
620
|
+
violations: [
|
|
621
|
+
{
|
|
622
|
+
file: 'src/big.ts',
|
|
623
|
+
line: 600,
|
|
624
|
+
ruleId: 'clean-code.large-file',
|
|
625
|
+
message: 'File is too large: 600 lines (maximum: 500)',
|
|
626
|
+
severity: 'warning'
|
|
627
|
+
}
|
|
628
|
+
],
|
|
629
|
+
summary: {
|
|
630
|
+
totalViolations: 1,
|
|
631
|
+
errorCount: 0,
|
|
632
|
+
warningCount: 1,
|
|
633
|
+
infoCount: 0,
|
|
634
|
+
filesChecked: 1,
|
|
635
|
+
rulesRun: 1
|
|
636
|
+
},
|
|
637
|
+
fileResults: {},
|
|
638
|
+
ruleResults: {},
|
|
639
|
+
executionTimeMs: 25,
|
|
640
|
+
errors: []
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const output = formatConsole(noColumnResult);
|
|
644
|
+
|
|
645
|
+
expect(output).toContain('line 600');
|
|
646
|
+
expect(output).not.toContain('col');
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it('should include analysis errors only when violations exist', () => {
|
|
650
|
+
const resultWithErrors: AnalysisResult = {
|
|
651
|
+
violations: [
|
|
652
|
+
{
|
|
653
|
+
file: 'src/test.ts',
|
|
654
|
+
line: 5,
|
|
655
|
+
column: 1,
|
|
656
|
+
ruleId: 'clean-code.long-function',
|
|
657
|
+
message: 'Function too long',
|
|
658
|
+
severity: 'warning'
|
|
659
|
+
}
|
|
660
|
+
],
|
|
661
|
+
summary: {
|
|
662
|
+
totalViolations: 1,
|
|
663
|
+
errorCount: 0,
|
|
664
|
+
warningCount: 1,
|
|
665
|
+
infoCount: 0,
|
|
666
|
+
filesChecked: 2,
|
|
667
|
+
rulesRun: 1
|
|
668
|
+
},
|
|
669
|
+
fileResults: {},
|
|
670
|
+
ruleResults: {},
|
|
671
|
+
executionTimeMs: 50,
|
|
672
|
+
errors: [
|
|
673
|
+
'Rule clean-code.long-function failed on src/corrupt.ts: Parse error',
|
|
674
|
+
'Rule solid.srp failed on src/broken.ts: Timeout'
|
|
675
|
+
]
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const output = formatConsole(resultWithErrors);
|
|
679
|
+
|
|
680
|
+
expect(output).toContain('Analysis errors');
|
|
681
|
+
expect(output).toContain('Parse error');
|
|
682
|
+
expect(output).toContain('Timeout');
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it('should not show analysis errors section when errors array is empty', () => {
|
|
686
|
+
const emptyResult: AnalysisResult = {
|
|
687
|
+
violations: [],
|
|
688
|
+
summary: {
|
|
689
|
+
totalViolations: 0,
|
|
690
|
+
errorCount: 0,
|
|
691
|
+
warningCount: 0,
|
|
692
|
+
infoCount: 0,
|
|
693
|
+
filesChecked: 5,
|
|
694
|
+
rulesRun: 3
|
|
695
|
+
},
|
|
696
|
+
fileResults: {},
|
|
697
|
+
ruleResults: {},
|
|
698
|
+
executionTimeMs: 50,
|
|
699
|
+
errors: []
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const output = formatConsole(emptyResult);
|
|
703
|
+
|
|
704
|
+
expect(output).not.toContain('Analysis errors');
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('should show files checked in empty violations output', () => {
|
|
708
|
+
const emptyResult: AnalysisResult = {
|
|
709
|
+
violations: [],
|
|
710
|
+
summary: {
|
|
711
|
+
totalViolations: 0,
|
|
712
|
+
errorCount: 0,
|
|
713
|
+
warningCount: 0,
|
|
714
|
+
infoCount: 0,
|
|
715
|
+
filesChecked: 15,
|
|
716
|
+
rulesRun: 8
|
|
717
|
+
},
|
|
718
|
+
fileResults: {},
|
|
719
|
+
ruleResults: {},
|
|
720
|
+
executionTimeMs: 75,
|
|
721
|
+
errors: []
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const output = formatConsole(emptyResult);
|
|
725
|
+
|
|
726
|
+
expect(output).toContain('Files checked: 15');
|
|
727
|
+
expect(output).toContain('Rules run: 8');
|
|
728
|
+
expect(output).toContain('75ms');
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it('should include summary section with violation details', () => {
|
|
732
|
+
const mixedResult: AnalysisResult = {
|
|
733
|
+
violations: [
|
|
734
|
+
{
|
|
735
|
+
file: 'src/a.ts',
|
|
736
|
+
line: 5,
|
|
737
|
+
column: 1,
|
|
738
|
+
ruleId: 'solid.srp',
|
|
739
|
+
message: 'SRP violation',
|
|
740
|
+
severity: 'error'
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
file: 'src/b.ts',
|
|
744
|
+
line: 10,
|
|
745
|
+
ruleId: 'clean-code.long-function',
|
|
746
|
+
message: 'Too long',
|
|
747
|
+
severity: 'warning'
|
|
748
|
+
}
|
|
749
|
+
],
|
|
750
|
+
summary: {
|
|
751
|
+
totalViolations: 2,
|
|
752
|
+
errorCount: 1,
|
|
753
|
+
warningCount: 1,
|
|
754
|
+
infoCount: 0,
|
|
755
|
+
filesChecked: 2,
|
|
756
|
+
rulesRun: 2
|
|
757
|
+
},
|
|
758
|
+
fileResults: {},
|
|
759
|
+
ruleResults: {},
|
|
760
|
+
executionTimeMs: 60,
|
|
761
|
+
errors: []
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
const output = formatConsole(mixedResult);
|
|
765
|
+
|
|
766
|
+
expect(output).toContain('2 violations total');
|
|
767
|
+
expect(output).toContain('1 errors');
|
|
768
|
+
expect(output).toContain('1 warnings');
|
|
769
|
+
expect(output).toContain('2 files checked');
|
|
770
|
+
expect(output).toContain('2 rules run');
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
describe('formatJSON - additional cases', () => {
|
|
775
|
+
it('should include empty arrays for no violations', () => {
|
|
776
|
+
const emptyResult: AnalysisResult = {
|
|
777
|
+
violations: [],
|
|
778
|
+
summary: {
|
|
779
|
+
totalViolations: 0,
|
|
780
|
+
errorCount: 0,
|
|
781
|
+
warningCount: 0,
|
|
782
|
+
infoCount: 0,
|
|
783
|
+
filesChecked: 5,
|
|
784
|
+
rulesRun: 3
|
|
785
|
+
},
|
|
786
|
+
fileResults: {},
|
|
787
|
+
ruleResults: {},
|
|
788
|
+
executionTimeMs: 30,
|
|
789
|
+
errors: []
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const output = formatJSON(emptyResult);
|
|
793
|
+
const parsed = JSON.parse(output);
|
|
794
|
+
|
|
795
|
+
expect(parsed.violations).toEqual([]);
|
|
796
|
+
expect(parsed.errors).toEqual([]);
|
|
797
|
+
expect(parsed.executionTimeMs).toBe(30);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
it('should include all fields: violations, summary, fileResults, ruleResults, executionTimeMs, errors', () => {
|
|
801
|
+
const result: AnalysisResult = {
|
|
802
|
+
violations: [sampleViolation],
|
|
803
|
+
summary: {
|
|
804
|
+
totalViolations: 1,
|
|
805
|
+
errorCount: 0,
|
|
806
|
+
warningCount: 1,
|
|
807
|
+
infoCount: 0,
|
|
808
|
+
filesChecked: 3,
|
|
809
|
+
rulesRun: 2
|
|
810
|
+
},
|
|
811
|
+
fileResults: {
|
|
812
|
+
'src/test.ts': { violations: [sampleViolation], ruleIds: ['clean-code.long-function'] }
|
|
813
|
+
},
|
|
814
|
+
ruleResults: {
|
|
815
|
+
'clean-code.long-function': { violationCount: 1, filesChecked: 3 }
|
|
816
|
+
},
|
|
817
|
+
executionTimeMs: 120,
|
|
818
|
+
errors: ['Some analysis error']
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const output = formatJSON(result);
|
|
822
|
+
const parsed = JSON.parse(output);
|
|
823
|
+
|
|
824
|
+
expect(parsed.violations).toHaveLength(1);
|
|
825
|
+
expect(parsed.summary.totalViolations).toBe(1);
|
|
826
|
+
expect(parsed.fileResults['src/test.ts'].ruleIds).toContain('clean-code.long-function');
|
|
827
|
+
expect(parsed.ruleResults['clean-code.long-function'].violationCount).toBe(1);
|
|
828
|
+
expect(parsed.executionTimeMs).toBe(120);
|
|
829
|
+
expect(parsed.errors).toEqual(['Some analysis error']);
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
});
|