@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.
Files changed (132) hide show
  1. package/lib/init.js +36 -0
  2. package/mock-policy/AGENTS.md +94 -0
  3. package/mock-policy/__tests__/config.test.ts +292 -0
  4. package/mock-policy/__tests__/integration.test.ts +600 -0
  5. package/mock-policy/__tests__/mock-decision-engine.test.ts +231 -0
  6. package/mock-policy/__tests__/scope-scanner.test.ts +535 -0
  7. package/mock-policy/config.ts +106 -0
  8. package/mock-policy/gate-m3.ts +233 -0
  9. package/mock-policy/mock-decision-engine.ts +155 -0
  10. package/mock-policy/schema.ts +22 -0
  11. package/mock-policy/scope-scanner.ts +288 -0
  12. package/mock-policy/types.ts +65 -0
  13. package/mutation/AGENTS.md +73 -0
  14. package/mutation/__tests__/detect-ai-test.test.ts +152 -0
  15. package/mutation/__tests__/gate-m.test.ts +194 -0
  16. package/mutation/detect-ai-test.ts +128 -0
  17. package/mutation/gate-m.ts +619 -0
  18. package/mutation/init-baseline.ts +192 -0
  19. package/mutation/stryker-types.ts +14 -0
  20. package/mutation/types.ts +70 -0
  21. package/mutation/update-baseline.ts +160 -0
  22. package/package.json +5 -2
  23. package/plugins/claude-code/.claude-plugin/plugin.json +2 -2
  24. package/plugins/claude-code/skills/delphi-review/AGENTS.md +7 -7
  25. package/plugins/claude-code/skills/delphi-review/INSTALL.md +1 -1
  26. package/plugins/claude-code/skills/delphi-review/SKILL.md +12 -12
  27. package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +1 -1
  28. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +7 -7
  29. package/plugins/claude-code/skills/sprint-flow/SKILL.md +1 -1
  30. package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +1 -1
  31. package/plugins/claude-code/skills/sprint-flow/references/phase-3-review.md +2 -2
  32. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  33. package/plugins/opencode/package.json +1 -1
  34. package/plugins/opencode/skills/delphi-review/AGENTS.md +7 -7
  35. package/plugins/opencode/skills/delphi-review/INSTALL.md +1 -1
  36. package/plugins/opencode/skills/delphi-review/SKILL.md +12 -12
  37. package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +1 -1
  38. package/plugins/opencode/skills/sprint-flow/AGENTS.md +7 -7
  39. package/plugins/opencode/skills/sprint-flow/SKILL.md +1 -1
  40. package/plugins/opencode/skills/sprint-flow/references/force-levels.md +1 -1
  41. package/plugins/opencode/skills/sprint-flow/references/phase-3-review.md +2 -2
  42. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  43. package/plugins/qoder/plugin.json +1 -1
  44. package/plugins/qoder/skills/delphi-review/AGENTS.md +7 -7
  45. package/plugins/qoder/skills/delphi-review/INSTALL.md +1 -1
  46. package/plugins/qoder/skills/delphi-review/SKILL.md +12 -12
  47. package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +1 -1
  48. package/plugins/qoder/skills/sprint-flow/AGENTS.md +7 -7
  49. package/plugins/qoder/skills/sprint-flow/SKILL.md +51 -1
  50. package/plugins/qoder/skills/sprint-flow/references/force-levels.md +1 -1
  51. package/plugins/qoder/skills/sprint-flow/references/phase-3-review.md +2 -2
  52. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  53. package/principles/AGENTS.md +111 -0
  54. package/principles/__tests__/analyzer.test.ts +343 -0
  55. package/principles/__tests__/baseline-storage.test.ts +187 -0
  56. package/principles/__tests__/baseline.test.ts +328 -0
  57. package/principles/__tests__/boy-scout-integration.test.ts +264 -0
  58. package/principles/__tests__/boy-scout.test.ts +646 -0
  59. package/principles/__tests__/config.test.ts +78 -0
  60. package/principles/__tests__/index.test.ts +81 -0
  61. package/principles/__tests__/lint-baseline.test.ts +314 -0
  62. package/principles/__tests__/reporter.test.ts +832 -0
  63. package/principles/__tests__/types.test.ts +101 -0
  64. package/principles/adapters/__tests__/base.test.ts +95 -0
  65. package/principles/adapters/__tests__/cpp.test.ts +290 -0
  66. package/principles/adapters/__tests__/dart.test.ts +84 -0
  67. package/principles/adapters/__tests__/go.test.ts +68 -0
  68. package/principles/adapters/__tests__/java.test.ts +68 -0
  69. package/principles/adapters/__tests__/kotlin.test.ts +76 -0
  70. package/principles/adapters/__tests__/objectivec.test.ts +453 -0
  71. package/principles/adapters/__tests__/python.test.ts +84 -0
  72. package/principles/adapters/__tests__/swift.test.ts +68 -0
  73. package/principles/adapters/__tests__/typescript.test.ts +116 -0
  74. package/principles/adapters/base.ts +124 -0
  75. package/principles/adapters/cpp.ts +115 -0
  76. package/principles/adapters/dart.ts +44 -0
  77. package/principles/adapters/go.ts +55 -0
  78. package/principles/adapters/java.ts +56 -0
  79. package/principles/adapters/kotlin.ts +54 -0
  80. package/principles/adapters/objectivec.ts +82 -0
  81. package/principles/adapters/python.ts +83 -0
  82. package/principles/adapters/swift.ts +40 -0
  83. package/principles/adapters/typescript.ts +66 -0
  84. package/principles/analyzer.ts +190 -0
  85. package/principles/baseline.ts +273 -0
  86. package/principles/boy-scout.ts +416 -0
  87. package/principles/config.ts +118 -0
  88. package/principles/index.ts +87 -0
  89. package/principles/lint-baseline.ts +385 -0
  90. package/principles/reporter.ts +188 -0
  91. package/principles/rules/__tests__/clean-code/code-duplication.test.ts +84 -0
  92. package/principles/rules/__tests__/clean-code/deep-nesting.test.ts +74 -0
  93. package/principles/rules/__tests__/clean-code/god-class.test.ts +111 -0
  94. package/principles/rules/__tests__/clean-code/large-file.test.ts +73 -0
  95. package/principles/rules/__tests__/clean-code/long-function.test.ts +83 -0
  96. package/principles/rules/__tests__/clean-code/magic-numbers.test.ts +68 -0
  97. package/principles/rules/__tests__/clean-code/many-exports.test.ts +118 -0
  98. package/principles/rules/__tests__/clean-code/missing-error-handling.test.ts +173 -0
  99. package/principles/rules/__tests__/clean-code/too-many-params.test.ts +60 -0
  100. package/principles/rules/__tests__/clean-code/unused-imports.test.ts +107 -0
  101. package/principles/rules/__tests__/index.test.ts +49 -0
  102. package/principles/rules/__tests__/solid/dip.test.ts +121 -0
  103. package/principles/rules/__tests__/solid/isp.test.ts +60 -0
  104. package/principles/rules/__tests__/solid/lsp.test.ts +184 -0
  105. package/principles/rules/__tests__/solid/ocp.test.ts +69 -0
  106. package/principles/rules/__tests__/solid/srp.test.ts +89 -0
  107. package/principles/rules/clean-code/code-duplication.ts +31 -0
  108. package/principles/rules/clean-code/deep-nesting.ts +38 -0
  109. package/principles/rules/clean-code/god-class.ts +54 -0
  110. package/principles/rules/clean-code/large-file.ts +34 -0
  111. package/principles/rules/clean-code/long-function.ts +47 -0
  112. package/principles/rules/clean-code/magic-numbers.ts +59 -0
  113. package/principles/rules/clean-code/many-exports.ts +40 -0
  114. package/principles/rules/clean-code/missing-error-handling.ts +33 -0
  115. package/principles/rules/clean-code/too-many-params.ts +35 -0
  116. package/principles/rules/clean-code/unused-imports.ts +33 -0
  117. package/principles/rules/index.ts +61 -0
  118. package/principles/rules/solid/dip.ts +47 -0
  119. package/principles/rules/solid/isp.ts +37 -0
  120. package/principles/rules/solid/lsp.ts +79 -0
  121. package/principles/rules/solid/ocp.ts +40 -0
  122. package/principles/rules/solid/srp.ts +44 -0
  123. package/principles/types.ts +27 -0
  124. package/skills/delphi-review/AGENTS.md +7 -7
  125. package/skills/delphi-review/INSTALL.md +1 -1
  126. package/skills/delphi-review/SKILL.md +12 -12
  127. package/skills/delphi-review/references/code-walkthrough.md +1 -1
  128. package/skills/sprint-flow/AGENTS.md +7 -7
  129. package/skills/sprint-flow/SKILL.md +1 -1
  130. package/skills/sprint-flow/references/force-levels.md +1 -1
  131. package/skills/sprint-flow/references/phase-3-review.md +2 -2
  132. package/skills/test-specification-alignment/AGENTS.md +3 -3
@@ -0,0 +1,188 @@
1
+ import { AnalysisResult, Violation } from './analyzer';
2
+
3
+ export function formatSARIF(result: AnalysisResult): string {
4
+ const ruleDescriptors = Object.keys(result.ruleResults).map(ruleId => ({
5
+ id: ruleId,
6
+ shortDescription: {
7
+ text: getRuleDescription(ruleId)
8
+ },
9
+ defaultConfiguration: {
10
+ level: getRuleLevel(ruleId)
11
+ }
12
+ }));
13
+
14
+ const sarifResults = result.violations.map(v => ({
15
+ ruleId: v.ruleId,
16
+ level: v.severity === 'error' ? 'error' : v.severity === 'warning' ? 'warning' : 'note',
17
+ message: {
18
+ text: v.message
19
+ },
20
+ locations: [
21
+ {
22
+ physicalLocation: {
23
+ artifactLocation: {
24
+ uri: v.file
25
+ },
26
+ region: {
27
+ startLine: v.line,
28
+ startColumn: v.column || 1
29
+ }
30
+ }
31
+ }
32
+ ]
33
+ }));
34
+
35
+ const sarif = {
36
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
37
+ version: '2.1.0',
38
+ runs: [
39
+ {
40
+ tool: {
41
+ driver: {
42
+ name: 'Principles Checker',
43
+ version: '1.0.0',
44
+ informationUri: 'https://github.com/boyingliu01/xp-gate',
45
+ rules: ruleDescriptors
46
+ }
47
+ },
48
+ results: sarifResults,
49
+ properties: {
50
+ filesChecked: result.summary.filesChecked,
51
+ rulesRun: result.summary.rulesRun,
52
+ executionTimeMs: result.executionTimeMs
53
+ }
54
+ }
55
+ ]
56
+ };
57
+
58
+ return JSON.stringify(sarif, null, 2);
59
+ }
60
+
61
+ function getRuleDescription(ruleId: string): string {
62
+ const descriptions: Record<string, string> = {
63
+ 'clean-code.long-function': 'Function exceeds maximum line threshold',
64
+ 'clean-code.large-file': 'File exceeds maximum line threshold',
65
+ 'clean-code.god-class': 'Class has too many methods',
66
+ 'clean-code.deep-nesting': 'Nested depth exceeds threshold',
67
+ 'clean-code.too-many-params': 'Function has too many parameters',
68
+ 'clean-code.magic-numbers': 'Magic number found without named constant',
69
+ 'clean-code.missing-error-handling': 'Missing error handling for I/O operations',
70
+ 'clean-code.unused-imports': 'Unused import detected',
71
+ 'clean-code.code-duplication': 'Code duplication detected',
72
+ 'solid.srp': 'Single Responsibility Principle violation',
73
+ 'solid.ocp': 'Open/Closed Principle violation',
74
+ 'solid.lsp': 'Liskov Substitution Principle violation',
75
+ 'solid.isp': 'Interface Segregation Principle violation',
76
+ 'solid.dip': 'Dependency Inversion Principle violation'
77
+ };
78
+
79
+ return descriptions[ruleId] || ruleId;
80
+ }
81
+
82
+ function getRuleLevel(ruleId: string): string {
83
+ const errorRules = ['clean-code.missing-error-handling', 'solid.srp', 'solid.dip'];
84
+ const warningRules = [
85
+ 'clean-code.long-function', 'clean-code.large-file', 'clean-code.god-class',
86
+ 'clean-code.deep-nesting', 'clean-code.code-duplication'
87
+ ];
88
+
89
+ if (errorRules.includes(ruleId)) return 'error';
90
+ if (warningRules.includes(ruleId)) return 'warning';
91
+ return 'note';
92
+ }
93
+
94
+ function formatEmptyResult(result: AnalysisResult, lines: string[]): string {
95
+ lines.push('✓ No violations found');
96
+ lines.push('');
97
+ lines.push(`Files checked: ${result.summary.filesChecked}`);
98
+ lines.push(`Rules run: ${result.summary.rulesRun}`);
99
+ lines.push(`Execution time: ${result.executionTimeMs}ms`);
100
+ return lines.join('\n');
101
+ }
102
+
103
+ function formatViolationLines(violation: Violation): string[] {
104
+ const severityIcon = violation.severity === 'error' ? '✗' :
105
+ violation.severity === 'warning' ? '⚠' : 'ℹ';
106
+ const severityLabel = violation.severity.toUpperCase();
107
+ return [
108
+ ` ${severityIcon} [${severityLabel}] ${violation.ruleId}`,
109
+ ` line ${violation.line}${violation.column ? `, col ${violation.column}` : ''}: ${violation.message}`
110
+ ];
111
+ }
112
+
113
+ function formatFileViolations(lines: string[], filesMap: Record<string, Violation[]>): void {
114
+ for (const [file, violations] of Object.entries(filesMap)) {
115
+ lines.push(`\n📁 ${file}`);
116
+ lines.push('─'.repeat(40));
117
+ for (const v of violations) {
118
+ lines.push(...formatViolationLines(v));
119
+ }
120
+ }
121
+ }
122
+
123
+ function formatErrors(lines: string[], result: AnalysisResult): void {
124
+ if (result.errors.length === 0) return;
125
+ lines.push('');
126
+ lines.push('⚠ Analysis errors:');
127
+ for (const err of result.errors) {
128
+ lines.push(` - ${err}`);
129
+ }
130
+ }
131
+
132
+ export function formatConsole(result: AnalysisResult): string {
133
+ const lines: string[] = [];
134
+
135
+ if (result.violations.length === 0) {
136
+ return formatEmptyResult(result, lines);
137
+ }
138
+
139
+ const filesMap: Record<string, Violation[]> = {};
140
+ for (const v of result.violations) {
141
+ if (!filesMap[v.file]) {
142
+ filesMap[v.file] = [];
143
+ }
144
+ filesMap[v.file].push(v);
145
+ }
146
+
147
+ formatFileViolations(lines, filesMap);
148
+
149
+ lines.push('');
150
+ lines.push('─'.repeat(40));
151
+ lines.push(formatSummary(result));
152
+ lines.push(`Execution time: ${result.executionTimeMs}ms`);
153
+
154
+ formatErrors(lines, result);
155
+
156
+ return lines.join('\n');
157
+ }
158
+
159
+ export function formatJSON(result: AnalysisResult): string {
160
+ const output = {
161
+ violations: result.violations,
162
+ summary: result.summary,
163
+ fileResults: result.fileResults,
164
+ ruleResults: result.ruleResults,
165
+ executionTimeMs: result.executionTimeMs,
166
+ errors: result.errors
167
+ };
168
+
169
+ return JSON.stringify(output, null, 2);
170
+ }
171
+
172
+ export function formatSummary(result: AnalysisResult): string {
173
+ const status = result.violations.length === 0 ? 'PASS' : 'FAIL';
174
+ const statusIcon = result.violations.length === 0 ? '✓' : '✗';
175
+
176
+ const lines: string[] = [];
177
+ lines.push(`${statusIcon} ${status}`);
178
+ lines.push('');
179
+ lines.push(`${result.summary.totalViolations} violations total`);
180
+ lines.push(` ${result.summary.errorCount} errors`);
181
+ lines.push(` ${result.summary.warningCount} warnings`);
182
+ lines.push(` ${result.summary.infoCount} info`);
183
+ lines.push('');
184
+ lines.push(`${result.summary.filesChecked} files checked`);
185
+ lines.push(`${result.summary.rulesRun} rules run`);
186
+
187
+ return lines.join('\n');
188
+ }
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { codeDuplicationRule } from '../../clean-code/code-duplication';
3
+
4
+ const mockAdapter = {
5
+ detectLanguage: () => 'typescript',
6
+ parseAST: () => undefined,
7
+ extractFunctions: () => [],
8
+ extractClasses: () => [],
9
+ countLines: () => 0
10
+ };
11
+
12
+ describe('code-duplication.ts - Code Duplication Rule', () => {
13
+ it('should detect high duplication percentage above threshold', () => {
14
+ const mockAdapterWithDuplication = {
15
+ ...mockAdapter,
16
+ duplicationPercentage: 25
17
+ };
18
+
19
+ const violations = codeDuplicationRule.check('test.ts', mockAdapterWithDuplication as never);
20
+
21
+ expect(violations.length).toBe(1);
22
+ expect(violations[0].ruleId).toBe('clean-code.code-duplication');
23
+ expect(violations[0].message).toContain('25%');
24
+ });
25
+
26
+ it('should pass for low duplication percentage below threshold', () => {
27
+ const mockAdapterWithLowDuplication = {
28
+ ...mockAdapter,
29
+ duplicationPercentage: 10
30
+ };
31
+
32
+ const violations = codeDuplicationRule.check('test.ts', mockAdapterWithLowDuplication as never);
33
+
34
+ expect(violations.length).toBe(0);
35
+ });
36
+
37
+ it('should pass for duplication exactly at threshold (15%)', () => {
38
+ const mockAdapterAtThreshold = {
39
+ ...mockAdapter,
40
+ duplicationPercentage: 15
41
+ };
42
+
43
+ const violations = codeDuplicationRule.check('test.ts', mockAdapterAtThreshold as never);
44
+
45
+ expect(violations.length).toBe(0);
46
+ });
47
+
48
+ it('should use threshold and severity from config', () => {
49
+ expect(codeDuplicationRule.threshold).toBe(15);
50
+ expect(codeDuplicationRule.severity).toBe('warning');
51
+ });
52
+
53
+ it('should return empty array when adapter.duplicationPercentage is undefined', () => {
54
+ const violations = codeDuplicationRule.check('test.ts', mockAdapter as never);
55
+
56
+ expect(violations).toEqual([]);
57
+ });
58
+
59
+ it('should return empty array when adapter throws', () => {
60
+ const mockAdapterThrowing = {
61
+ ...mockAdapter,
62
+ duplicationPercentage: () => { throw new Error('jscpd error'); }
63
+ };
64
+
65
+ const violations = codeDuplicationRule.check('test.ts', mockAdapterThrowing as never);
66
+
67
+ expect(violations).toEqual([]);
68
+ });
69
+
70
+ it('should handle multiple files with duplication', () => {
71
+ const mockAdapterWithDuplication = {
72
+ ...mockAdapter,
73
+ duplicationPercentage: 30,
74
+ duplicatedBlocks: [
75
+ { lines: 10, tokens: 50, files: ['test.ts', 'other.ts'] }
76
+ ]
77
+ };
78
+
79
+ const violations = codeDuplicationRule.check('test.ts', mockAdapterWithDuplication as never);
80
+
81
+ expect(violations.length).toBe(1);
82
+ expect(violations[0].message).toContain('30%');
83
+ });
84
+ });
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { deepNestingRule } from '../../clean-code/deep-nesting';
3
+
4
+ const mockAdapter = {
5
+ detectLanguage: () => 'typescript',
6
+ parseAST: () => undefined,
7
+ extractFunctions: () => [],
8
+ extractClasses: () => [],
9
+ countLines: () => 0
10
+ };
11
+
12
+ describe('deep-nesting.ts - Deep Nesting Rule', () => {
13
+ it('should detect function with nesting deeper than 4 levels', () => {
14
+ const mockAdapterWithDeepNesting = {
15
+ ...mockAdapter,
16
+ extractFunctions: () => [{
17
+ name: 'deeplyNested',
18
+ startLine: 1,
19
+ code: `
20
+ function deeplyNested() {
21
+ if (a) {
22
+ if (b) {
23
+ if (c) {
24
+ if (d) {
25
+ if (e) {
26
+ return true;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ `,
34
+ nestingDepth: 5
35
+ }]
36
+ };
37
+
38
+ const violations = deepNestingRule.check('test.ts', mockAdapterWithDeepNesting as never);
39
+
40
+ expect(violations.length).toBe(1);
41
+ expect(violations[0].ruleId).toBe('clean-code.deep-nesting');
42
+ });
43
+
44
+ it('should pass for function with acceptable nesting', () => {
45
+ const mockAdapterWithNormalNesting = {
46
+ ...mockAdapter,
47
+ extractFunctions: () => [{
48
+ name: 'normalNesting',
49
+ startLine: 1,
50
+ nestingDepth: 3
51
+ }]
52
+ };
53
+
54
+ const violations = deepNestingRule.check('test.ts', mockAdapterWithNormalNesting as never);
55
+
56
+ expect(violations.length).toBe(0);
57
+ });
58
+
59
+ it('should use threshold from config', () => {
60
+ expect(deepNestingRule.threshold).toBe(4);
61
+ expect(deepNestingRule.severity).toBe('warning');
62
+ });
63
+
64
+ it('should return empty violations when adapter throws error', () => {
65
+ const mockAdapterThatThrows = {
66
+ ...mockAdapter,
67
+ extractFunctions: () => { throw new Error('Adapter failed'); }
68
+ };
69
+
70
+ const violations = deepNestingRule.check('test.ts', mockAdapterThatThrows as never);
71
+
72
+ expect(violations.length).toBe(0);
73
+ });
74
+ });
@@ -0,0 +1,111 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { godClassRule } from '../../clean-code/god-class';
3
+
4
+ const mockAdapter = {
5
+ detectLanguage: () => 'typescript',
6
+ parseAST: () => undefined,
7
+ extractFunctions: () => [],
8
+ extractClasses: () => [],
9
+ countLines: () => 0
10
+ };
11
+
12
+ describe('god-class.ts - God Class Rule', () => {
13
+ it('should detect class with more than 15 methods', () => {
14
+ const mockAdapterWithGodClass = {
15
+ ...mockAdapter,
16
+ extractClasses: () => [{
17
+ name: 'GodClass',
18
+ line: 1,
19
+ code: `
20
+ class GodClass {
21
+ method1() {}
22
+ method2() {}
23
+ method3() {}
24
+ method4() {}
25
+ method5() {}
26
+ method6() {}
27
+ method7() {}
28
+ method8() {}
29
+ method9() {}
30
+ method10() {}
31
+ method11() {}
32
+ method12() {}
33
+ method13() {}
34
+ method14() {}
35
+ method15() {}
36
+ method16() {}
37
+ }
38
+ `
39
+ }]
40
+ };
41
+
42
+ const violations = godClassRule.check('test.ts', mockAdapterWithGodClass as never);
43
+
44
+ expect(violations.length).toBe(1);
45
+ expect(violations[0].ruleId).toBe('clean-code.god-class');
46
+ expect(violations[0].message).toContain('GodClass');
47
+ expect(violations[0].message).toContain('16');
48
+ });
49
+
50
+ it('should pass for class with fewer than threshold methods', () => {
51
+ const mockAdapterWithSmallClass = {
52
+ ...mockAdapter,
53
+ extractClasses: () => [{
54
+ name: 'SmallClass',
55
+ line: 1,
56
+ code: `
57
+ class SmallClass {
58
+ method1() {}
59
+ method2() {}
60
+ method3() {}
61
+ }
62
+ `
63
+ }]
64
+ };
65
+
66
+ const violations = godClassRule.check('test.ts', mockAdapterWithSmallClass as never);
67
+
68
+ expect(violations.length).toBe(0);
69
+ });
70
+
71
+ it('should exclude getters and setters from count', () => {
72
+ const mockAdapterWithGetters = {
73
+ ...mockAdapter,
74
+ extractClasses: () => [{
75
+ name: 'DataClass',
76
+ line: 1,
77
+ code: `
78
+ class DataClass {
79
+ get name() { return this._name; }
80
+ set name(value) { this._name = value; }
81
+ get age() { return this._age; }
82
+ set age(value) { this._age = value; }
83
+ method1() {}
84
+ method2() {}
85
+ method3() {}
86
+ }
87
+ `
88
+ }]
89
+ };
90
+
91
+ const violations = godClassRule.check('test.ts', mockAdapterWithGetters as never);
92
+
93
+ expect(violations.length).toBe(0);
94
+ });
95
+
96
+ it('should use threshold from config', () => {
97
+ expect(godClassRule.threshold).toBe(15);
98
+ expect(godClassRule.severity).toBe('warning');
99
+ });
100
+
101
+ it('should return empty violations when adapter throws error', () => {
102
+ const mockAdapterThatThrows = {
103
+ ...mockAdapter,
104
+ extractClasses: () => { throw new Error('Adapter failed'); }
105
+ };
106
+
107
+ const violations = godClassRule.check('test.ts', mockAdapterThatThrows as never);
108
+
109
+ expect(violations.length).toBe(0);
110
+ });
111
+ });
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { largeFileRule } from '../../clean-code/large-file';
3
+
4
+ // Mock adapter for testing
5
+ const mockAdapter = {
6
+ detectLanguage: () => 'typescript',
7
+ parseAST: () => undefined,
8
+ extractFunctions: () => [],
9
+ extractClasses: () => [],
10
+ countLines: (_fileName: string) => 10 // Default to small file
11
+ };
12
+
13
+ describe('largeFileRule', () => {
14
+ it('should return an empty array when file has fewer than 500 lines', () => {
15
+ const shortFileAdapter = {
16
+ ...mockAdapter,
17
+ countLines: () => 499
18
+ } as never;
19
+
20
+ const violations = largeFileRule.check('test-short.ts', shortFileAdapter);
21
+ expect(violations).toHaveLength(0);
22
+ });
23
+
24
+ it('should detect files exceeding 650 lines', () => {
25
+ const largeFileAdapter: never = {
26
+ ...mockAdapter,
27
+ countLines: () => 651
28
+ };
29
+
30
+ const violations = largeFileRule.check('test-large.ts', largeFileAdapter);
31
+ expect(violations).toHaveLength(1);
32
+ expect(violations[0]).toEqual({
33
+ file: 'test-large.ts',
34
+ line: 1,
35
+ ruleId: 'clean-code.large-file',
36
+ message: 'File is too large: 651 lines (maximum: 650)',
37
+ severity: 'warning'
38
+ });
39
+ });
40
+
41
+ it('should handle exactly threshold lines as not violating', () => {
42
+ const thresholdFileAdapter: never = {
43
+ ...mockAdapter,
44
+ countLines: () => 650
45
+ };
46
+
47
+ const violations = largeFileRule.check('test-threshold.ts', thresholdFileAdapter);
48
+ expect(violations).toHaveLength(0);
49
+ });
50
+
51
+ it('should use the correct rule identifier', () => {
52
+ expect(largeFileRule.id).toEqual('clean-code.large-file');
53
+ });
54
+
55
+ it('should have the correct severity', () => {
56
+ expect(largeFileRule.severity).toEqual('warning');
57
+ });
58
+
59
+ it('should use the correct threshold', () => {
60
+ expect(largeFileRule.threshold).toEqual(650);
61
+ });
62
+
63
+ it('should return empty violations when adapter throws error', () => {
64
+ const mockAdapterThatThrows: never = {
65
+ ...mockAdapter,
66
+ countLines: () => { throw new Error('Adapter failed'); }
67
+ };
68
+
69
+ const violations = largeFileRule.check('test.ts', mockAdapterThatThrows);
70
+
71
+ expect(violations).toHaveLength(0);
72
+ });
73
+ });
@@ -0,0 +1,83 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { longFunctionRule } from '../../clean-code/long-function';
3
+
4
+ // Mock adapter for testing
5
+ const mockAdapter = {
6
+ detectLanguage: () => 'typescript',
7
+ parseAST: () => undefined,
8
+ extractFunctions: () => [],
9
+ extractClasses: () => [],
10
+ countLines: (content: string) => content.split('\n').length
11
+ };
12
+
13
+ describe('longFunctionRule', () => {
14
+ it('should return an empty array when no long function is detected', () => {
15
+ const mockShortAdapter = {
16
+ ...mockAdapter,
17
+ extractFunctions: () => [
18
+ { name: 'shortFunction', startLine: 1, endLine: 40, length: 40 }
19
+ ]
20
+ };
21
+
22
+ const violations = longFunctionRule.check('test.ts', mockShortAdapter as never);
23
+ expect(violations).toHaveLength(0);
24
+ });
25
+
26
+ it('should detect long functions exceeding 50 lines', () => {
27
+ const mockLongAdapter = {
28
+ ...mockAdapter,
29
+ extractFunctions: () => [
30
+ { name: 'veryLongFunction', startLine: 1, endLine: 55, length: 55 }
31
+ ]
32
+ };
33
+
34
+ const violations = longFunctionRule.check('test.ts', mockLongAdapter as never);
35
+ expect(violations).toHaveLength(1);
36
+ expect(violations[0]).toEqual({
37
+ file: 'test.ts',
38
+ line: 1,
39
+ ruleId: 'clean-code.long-function',
40
+ message: 'Function "veryLongFunction" is too long: 55 lines (maximum: 50)',
41
+ severity: 'warning'
42
+ });
43
+ });
44
+
45
+ it('should handle multiple functions in a file', () => {
46
+ const mockMultiAdapter = {
47
+ ...mockAdapter,
48
+ extractFunctions: () => [
49
+ { name: 'shortFunction', startLine: 1, endLine: 4, length: 4 },
50
+ { name: 'alsoLongFunction', startLine: 6, endLine: 62, length: 57 }
51
+ ]
52
+ };
53
+
54
+ const violations = longFunctionRule.check('test.ts', mockMultiAdapter as never);
55
+ expect(violations).toHaveLength(1);
56
+ expect(violations[0].ruleId).toEqual('clean-code.long-function');
57
+ expect(violations[0].message).toContain('alsoLongFunction');
58
+ expect(violations[0].message).toContain('57 lines');
59
+ });
60
+
61
+ it('should use the correct rule identifier', () => {
62
+ expect(longFunctionRule.id).toEqual('clean-code.long-function');
63
+ });
64
+
65
+ it('should have the correct severity', () => {
66
+ expect(longFunctionRule.severity).toEqual('warning');
67
+ });
68
+
69
+ it('should use the correct threshold', () => {
70
+ expect(longFunctionRule.threshold).toEqual(50);
71
+ });
72
+
73
+ it('should return empty violations when adapter throws error', () => {
74
+ const mockAdapterThatThrows = {
75
+ ...mockAdapter,
76
+ extractFunctions: () => { throw new Error('Adapter failed'); }
77
+ };
78
+
79
+ const violations = longFunctionRule.check('test.ts', mockAdapterThatThrows as never);
80
+
81
+ expect(violations).toHaveLength(0);
82
+ });
83
+ });
@@ -0,0 +1,68 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { magicNumbersRule } from '../../clean-code/magic-numbers';
3
+
4
+ // Mock adapter for testing
5
+ const mockAdapter = {
6
+ detectLanguage: () => 'typescript',
7
+ parseAST: () => undefined,
8
+ extractFunctions: () => [],
9
+ extractClasses: () => [],
10
+ countLines: (_fileName: string) => 10
11
+ };
12
+
13
+ describe('magicNumbersRule', () => {
14
+ it('should return an empty array for code with only excluded safe values', () => {
15
+ const mockSafeAdapter = {
16
+ ...mockAdapter,
17
+ parseAST: () => undefined,
18
+ extract: () => []
19
+ };
20
+
21
+ const violations = magicNumbersRule.check('test-safe.ts', mockSafeAdapter as never);
22
+ expect(violations).toHaveLength(0);
23
+ });
24
+
25
+ it('should detect non-safe magic numbers', () => {
26
+ const mockUnsafeAdapter = {
27
+ ...mockAdapter,
28
+ parseAST: () => undefined,
29
+ extract: () => [
30
+ { value: 0.0875, line: 1 },
31
+ { value: 42, line: 2 },
32
+ { value: 99, line: 3 },
33
+ { value: 73, line: 4 }
34
+ ]
35
+ };
36
+
37
+ const violations = magicNumbersRule.check('test-unsafe.ts', mockUnsafeAdapter as never);
38
+ expect(violations).toHaveLength(4);
39
+
40
+ expect(violations[0].message).toContain('0.0875');
41
+ expect(violations[1].message).toContain('42');
42
+ expect(violations[2].message).toContain('99');
43
+ expect(violations[3].message).toContain('73');
44
+ expect(violations[0].severity).toEqual('info');
45
+ });
46
+
47
+ it('should have the correct rule identifier', () => {
48
+ expect(magicNumbersRule.id).toEqual('clean-code.magic-numbers');
49
+ });
50
+
51
+ it('should have the correct severity', () => {
52
+ expect(magicNumbersRule.severity).toEqual('info');
53
+ });
54
+
55
+ it('should use the correct exclusion values', () => {
56
+ expect(magicNumbersRule.threshold).toEqual(10);
57
+ });
58
+ });
59
+ it('should return empty violations when adapter throws error', () => {
60
+ const mockAdapterThatThrows = {
61
+ ...mockAdapter,
62
+ extractFunctions: () => { throw new Error('Adapter failed'); }
63
+ };
64
+
65
+ const violations = magicNumbersRule.check('test.ts', mockAdapterThatThrows as never);
66
+
67
+ expect(violations).toHaveLength(0);
68
+ });