@boyingliu01/xp-gate 0.26.0 → 0.27.1
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/bin/xp-gate.js +161 -28
- package/hooks/pre-commit +27 -10
- package/lib/__tests__/check-alignment-cli.test.mjs +77 -0
- package/lib/__tests__/doctor-469-hook-env.test.js +12 -7
- package/lib/__tests__/doctor-exec-retry.test.js +152 -0
- package/lib/__tests__/doctor-module-resolution-parity.test.js +15 -6
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +16 -4
- package/lib/__tests__/test-alignment.test.ts +37 -0
- package/lib/__tests__/windows-bash-probe.test.js +123 -0
- package/lib/check-alignment-runner.cjs +74 -0
- package/lib/detect-deps.js +139 -20
- package/lib/doctor.js +45 -11
- package/lib/gate-runner.js +12 -5
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/sprint-status.js +5 -2
- package/lib/test-alignment.ts +136 -12
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/README.md +1 -1
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/scripts/prepack.cjs +2 -2
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/src/skills.test.ts +20 -7
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/scripts/prepack.cjs +0 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/principles/rules/__tests__/clean-code/code-duplication.test.ts +9 -8
- package/principles/rules/__tests__/clean-code/deep-nesting.test.ts +3 -4
- package/principles/rules/__tests__/clean-code/god-class.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/large-file.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/long-function.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/many-exports.test.ts +2 -3
- package/principles/rules/__tests__/clean-code/missing-error-handling.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/too-many-params.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/unused-imports.test.ts +4 -4
- package/principles/rules/__tests__/solid/dip.test.ts +2 -4
- package/principles/rules/__tests__/solid/isp.test.ts +2 -4
- package/principles/rules/__tests__/solid/lsp.test.ts +2 -4
- package/principles/rules/__tests__/solid/ocp.test.ts +2 -4
- package/principles/rules/__tests__/solid/srp.test.ts +2 -4
- package/principles/rules/clean-code/code-duplication.ts +12 -14
- package/principles/rules/clean-code/deep-nesting.ts +18 -20
- package/principles/rules/clean-code/god-class.ts +32 -34
- package/principles/rules/clean-code/large-file.ts +14 -16
- package/principles/rules/clean-code/long-function.ts +26 -28
- package/principles/rules/clean-code/magic-numbers.ts +39 -38
- package/principles/rules/clean-code/many-exports.ts +21 -23
- package/principles/rules/clean-code/missing-error-handling.ts +13 -15
- package/principles/rules/clean-code/too-many-params.ts +15 -17
- package/principles/rules/clean-code/unused-imports.ts +13 -15
- package/principles/rules/solid/dip.ts +16 -18
- package/principles/rules/solid/isp.ts +17 -19
- package/principles/rules/solid/lsp.ts +4 -6
- package/principles/rules/solid/ocp.ts +17 -19
- package/principles/rules/solid/srp.ts +20 -22
- package/scripts/__tests__/sync-package-content.test.js +0 -1
- package/scripts/sync-package-content.js +0 -1
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/claude-code/skills/clipboard-vision/SKILL.md +0 -48
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
- package/plugins/dsh/skills/clipboard-vision/SKILL.md +0 -48
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
- package/plugins/opencode/skills/clipboard-vision/SKILL.md +0 -48
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
- package/plugins/qoder/skills/clipboard-vision/SKILL.md +0 -48
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/plugins/qoder/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/plugins/qoder/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
- package/skills/clipboard-vision/SKILL.md +0 -48
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
|
@@ -111,14 +111,13 @@ describe('manyExportsRule', () => {
|
|
|
111
111
|
expect(violations).toHaveLength(0);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
it('
|
|
114
|
+
it('propagates adapter errors (no empty catch, #392)', () => {
|
|
115
115
|
const adapter: never = {
|
|
116
116
|
...baseAdapter,
|
|
117
117
|
extractExports: () => {
|
|
118
118
|
throw new Error('boom');
|
|
119
119
|
},
|
|
120
120
|
} as never;
|
|
121
|
-
|
|
122
|
-
expect(violations).toHaveLength(0);
|
|
121
|
+
expect(() => manyExportsRule.check('file.ts', adapter)).toThrow('boom');
|
|
123
122
|
});
|
|
124
123
|
});
|
|
@@ -134,15 +134,13 @@ async function fetchUser(id: string) {
|
|
|
134
134
|
expect(missingErrorHandlingRule.severity).toBe('warning');
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
it('should
|
|
137
|
+
it('should propagate adapter errors (no empty catch, #392)', () => {
|
|
138
138
|
const mockAdapterThrowing = {
|
|
139
139
|
...mockAdapter,
|
|
140
140
|
extractFunctions: () => { throw new Error('Parse error'); }
|
|
141
141
|
};
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
expect(violations).toEqual([]);
|
|
143
|
+
expect(() => missingErrorHandlingRule.check('test.ts', mockAdapterThrowing as never)).toThrow('Parse error');
|
|
146
144
|
});
|
|
147
145
|
|
|
148
146
|
it('should detect multiple IO operations in one function', () => {
|
|
@@ -47,14 +47,12 @@ describe('too-many-params.ts - Too Many Parameters Rule', () => {
|
|
|
47
47
|
expect(tooManyParamsRule.severity).toBe('info');
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it('should
|
|
50
|
+
it('should propagate adapter errors (no empty catch, #392)', () => {
|
|
51
51
|
const mockAdapterThatThrows = {
|
|
52
52
|
...mockAdapter,
|
|
53
53
|
extractFunctions: () => { throw new Error('Adapter failed'); }
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expect(violations.length).toBe(0);
|
|
56
|
+
expect(() => tooManyParamsRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
|
|
59
57
|
});
|
|
60
58
|
});
|
|
@@ -79,15 +79,15 @@ describe('unused-imports.ts - Unused Imports Rule', () => {
|
|
|
79
79
|
expect(violations).toEqual([]);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
it('should
|
|
82
|
+
it('should propagate adapter errors (no empty catch, #392)', () => {
|
|
83
83
|
const mockAdapterThrowing = {
|
|
84
84
|
...mockAdapter,
|
|
85
|
+
// Violate the adapter contract: `imports` must be an array. With the old
|
|
86
|
+
// empty catch this was swallowed; now it must bubble up to the analyzer.
|
|
85
87
|
imports: () => { throw new Error('Parse error'); }
|
|
86
88
|
};
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
expect(violations).toEqual([]);
|
|
90
|
+
expect(() => unusedImportsRule.check('test.ts', mockAdapterThrowing as never)).toThrow();
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
it('should handle namespace imports', () => {
|
|
@@ -82,15 +82,13 @@ class DateService {
|
|
|
82
82
|
expect(dipRule.severity).toBe('warning');
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it('should
|
|
85
|
+
it('should propagate adapter errors (no empty catch, #392)', () => {
|
|
86
86
|
const mockAdapterThatThrows = {
|
|
87
87
|
...mockAdapter,
|
|
88
88
|
extractClasses: () => { throw new Error('Adapter failed'); }
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
expect(violations.length).toBe(0);
|
|
91
|
+
expect(() => dipRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
|
|
94
92
|
});
|
|
95
93
|
|
|
96
94
|
it('should exclude built-in types (Date, Map, Set, etc.)', () => {
|
|
@@ -47,14 +47,12 @@ describe('isp.ts - Interface Segregation Principle Rule', () => {
|
|
|
47
47
|
expect(ispRule.severity).toBe('info');
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it('should
|
|
50
|
+
it('should propagate adapter errors (no empty catch, #392)', () => {
|
|
51
51
|
const mockAdapterThatThrows = {
|
|
52
52
|
...mockAdapter,
|
|
53
53
|
extractInterfaces: () => { throw new Error('Adapter failed'); }
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expect(violations.length).toBe(0);
|
|
56
|
+
expect(() => ispRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
|
|
59
57
|
});
|
|
60
58
|
});
|
|
@@ -49,15 +49,13 @@ class DerivedRepo extends BaseRepo {
|
|
|
49
49
|
expect(lspRule.severity).toBe('info');
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it('should
|
|
52
|
+
it('should propagate adapter errors (no empty catch, #392)', () => {
|
|
53
53
|
const mockAdapterThatThrows = {
|
|
54
54
|
...mockAdapter,
|
|
55
55
|
extractClasses: () => { throw new Error('Adapter failed'); }
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
expect(violations.length).toBe(0);
|
|
58
|
+
expect(() => lspRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
|
|
61
59
|
});
|
|
62
60
|
|
|
63
61
|
/**
|
|
@@ -56,14 +56,12 @@ class DerivedClass extends BaseClass {
|
|
|
56
56
|
expect(ocpRule.severity).toBe('info');
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
it('should
|
|
59
|
+
it('should propagate adapter errors (no empty catch, #392)', () => {
|
|
60
60
|
const mockAdapterThatThrows = {
|
|
61
61
|
...mockAdapter,
|
|
62
62
|
extractClasses: () => { throw new Error('Adapter failed'); }
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
expect(violations.length).toBe(0);
|
|
65
|
+
expect(() => ocpRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
|
|
68
66
|
});
|
|
69
67
|
});
|
|
@@ -76,14 +76,12 @@ describe('srp.ts - Single Responsibility Principle Rule', () => {
|
|
|
76
76
|
expect(srpRule.severity).toBe('warning');
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
it('should
|
|
79
|
+
it('should propagate adapter errors (no empty catch, #392)', () => {
|
|
80
80
|
const mockAdapterThatThrows = {
|
|
81
81
|
...mockAdapter,
|
|
82
82
|
extractClasses: () => { throw new Error('Adapter failed'); }
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
expect(violations.length).toBe(0);
|
|
85
|
+
expect(() => srpRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
|
|
88
86
|
});
|
|
89
87
|
});
|
|
@@ -11,20 +11,18 @@ export const codeDuplicationRule: Rule = {
|
|
|
11
11
|
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
12
|
const settings = getActiveConfig().rules['clean-code']['code-duplication'];
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
} catch { }
|
|
14
|
+
const typedAdapter = adapter as { duplicationPercentage?: number };
|
|
15
|
+
const duplicationPercentage = typedAdapter.duplicationPercentage;
|
|
16
|
+
|
|
17
|
+
if (duplicationPercentage && duplicationPercentage > (settings.threshold ?? 0)) {
|
|
18
|
+
violations.push({
|
|
19
|
+
file,
|
|
20
|
+
line: 1,
|
|
21
|
+
ruleId: 'clean-code.code-duplication',
|
|
22
|
+
message: `Code duplication detected: ${duplicationPercentage}% (threshold: ${settings.threshold}%). Consider refactoring duplicated code.`,
|
|
23
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
24
|
+
});
|
|
25
|
+
}
|
|
28
26
|
|
|
29
27
|
return violations;
|
|
30
28
|
}
|
|
@@ -11,27 +11,25 @@ export const deepNestingRule: Rule = {
|
|
|
11
11
|
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
12
|
const settings = getActiveConfig().rules['clean-code']['deep-nesting'];
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
32
|
-
}
|
|
14
|
+
interface TypedAdapter {
|
|
15
|
+
extractFunctions?: () => Array<{name?: string, nestingDepth?: number, startLine?: number, line?: number}> | undefined;
|
|
16
|
+
}
|
|
17
|
+
const typedAdapter = adapter as TypedAdapter;
|
|
18
|
+
const functions = typedAdapter.extractFunctions?.() || [];
|
|
19
|
+
|
|
20
|
+
for (const func of functions) {
|
|
21
|
+
if (func.nestingDepth && func.nestingDepth > (settings.threshold ?? 0)) {
|
|
22
|
+
violations.push({
|
|
23
|
+
file,
|
|
24
|
+
line: func.startLine ?? func.line ?? 1,
|
|
25
|
+
ruleId: 'clean-code.deep-nesting',
|
|
26
|
+
message: `Function "${func.name}" has deep nesting: ${func.nestingDepth} levels (maximum: ${
|
|
27
|
+
settings.threshold
|
|
28
|
+
})`,
|
|
29
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
30
|
+
});
|
|
33
31
|
}
|
|
34
|
-
}
|
|
32
|
+
}
|
|
35
33
|
|
|
36
34
|
return violations;
|
|
37
35
|
}
|
|
@@ -11,43 +11,41 @@ export const godClassRule: Rule = {
|
|
|
11
11
|
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
12
|
const settings = getActiveConfig().rules['clean-code']['god-class'];
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
interface ClassObject {
|
|
15
|
+
code?: string;
|
|
16
|
+
line: number;
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface TypedAdapter {
|
|
21
|
+
extractClasses?: () => ClassObject[] | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const typedAdapter = adapter as TypedAdapter;
|
|
25
|
+
const classes = typedAdapter.extractClasses?.() || [];
|
|
26
|
+
|
|
27
|
+
for (const cls of classes) {
|
|
28
|
+
const methodMatches = cls.code?.match(/(get|set)\s+\w+\s*\(|\w+\s*\([^)]*\)\s*{/g) || [];
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const methodCount = methodMatches.filter((m: string) => {
|
|
32
|
-
if (m.startsWith('get ') || m.startsWith('set ')) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
return true;
|
|
36
|
-
}).length;
|
|
37
|
-
|
|
38
|
-
if (methodCount > (settings.threshold ?? 0)) {
|
|
39
|
-
violations.push({
|
|
40
|
-
file,
|
|
41
|
-
line: cls.line,
|
|
42
|
-
ruleId: 'clean-code.god-class',
|
|
43
|
-
message: `Class "${cls.name}" has too many methods: ${methodCount} (maximum: ${
|
|
44
|
-
settings.threshold
|
|
45
|
-
})`,
|
|
46
|
-
severity: (settings.severity as Severity) ?? 'warning'
|
|
47
|
-
});
|
|
30
|
+
const methodCount = methodMatches.filter((m: string) => {
|
|
31
|
+
if (m.startsWith('get ') || m.startsWith('set ')) {
|
|
32
|
+
return false;
|
|
48
33
|
}
|
|
34
|
+
return true;
|
|
35
|
+
}).length;
|
|
36
|
+
|
|
37
|
+
if (methodCount > (settings.threshold ?? 0)) {
|
|
38
|
+
violations.push({
|
|
39
|
+
file,
|
|
40
|
+
line: cls.line,
|
|
41
|
+
ruleId: 'clean-code.god-class',
|
|
42
|
+
message: `Class "${cls.name}" has too many methods: ${methodCount} (maximum: ${
|
|
43
|
+
settings.threshold
|
|
44
|
+
})`,
|
|
45
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
46
|
+
});
|
|
49
47
|
}
|
|
50
|
-
}
|
|
48
|
+
}
|
|
51
49
|
|
|
52
50
|
return violations;
|
|
53
51
|
}
|
|
@@ -15,23 +15,21 @@ export const largeFileRule: Rule = {
|
|
|
15
15
|
const settings = getActiveConfig().rules['clean-code']['large-file'];
|
|
16
16
|
const threshold = settings.threshold ?? 500;
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const lineCount = typedAdapter.countLines?.(file) ?? 0;
|
|
18
|
+
interface TypedAdapter {
|
|
19
|
+
countLines?: (file: string) => number | undefined;
|
|
20
|
+
}
|
|
21
|
+
const typedAdapter = adapter as TypedAdapter;
|
|
22
|
+
const lineCount = typedAdapter.countLines?.(file) ?? 0;
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} catch { }
|
|
24
|
+
if (lineCount > threshold) {
|
|
25
|
+
violations.push({
|
|
26
|
+
file,
|
|
27
|
+
line: 1,
|
|
28
|
+
ruleId: 'clean-code.large-file',
|
|
29
|
+
message: `File is too large: ${lineCount} lines (maximum: ${threshold})`,
|
|
30
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
35
33
|
|
|
36
34
|
return violations;
|
|
37
35
|
}
|
|
@@ -11,36 +11,34 @@ export const longFunctionRule: Rule = {
|
|
|
11
11
|
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
12
|
const settings = getActiveConfig().rules['clean-code']['long-function'];
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
interface FunctionObj {
|
|
15
|
+
name: string;
|
|
16
|
+
startLine: number;
|
|
17
|
+
length: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface TypedAdapter {
|
|
21
|
+
extractFunctions?: () => FunctionObj[] | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const typedAdapter = adapter as TypedAdapter;
|
|
25
|
+
const functions = typedAdapter.extractFunctions?.() || [];
|
|
26
|
+
|
|
27
|
+
for (const func of functions) {
|
|
28
|
+
const { name, startLine, length } = func;
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})`,
|
|
39
|
-
severity: (settings.severity as Severity) ?? 'warning'
|
|
40
|
-
});
|
|
41
|
-
}
|
|
30
|
+
if (length > (settings.threshold ?? 0)) {
|
|
31
|
+
violations.push({
|
|
32
|
+
file,
|
|
33
|
+
line: startLine ?? 1,
|
|
34
|
+
ruleId: 'clean-code.long-function',
|
|
35
|
+
message: `Function "${name}" is too long: ${length} lines (maximum: ${
|
|
36
|
+
settings.threshold
|
|
37
|
+
})`,
|
|
38
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
39
|
+
});
|
|
42
40
|
}
|
|
43
|
-
}
|
|
41
|
+
}
|
|
44
42
|
|
|
45
43
|
return violations;
|
|
46
44
|
}
|
|
@@ -15,48 +15,49 @@ export const magicNumbersRule: Rule = {
|
|
|
15
15
|
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
16
16
|
const settings = getActiveConfig().rules['clean-code']['magic-numbers'];
|
|
17
17
|
|
|
18
|
+
// #392-#4: rules must not swallow their own errors. The outer try/catch was
|
|
19
|
+
// removed so a genuine rule bug bubbles up to analyzer.runRuleOnFile() and
|
|
20
|
+
// surfaces as a gate error. The inner try/catch below is kept deliberately:
|
|
21
|
+
// an adapter that cannot extract AST data is a legitimate "no data" case.
|
|
22
|
+
interface NumberObject {
|
|
23
|
+
value: number;
|
|
24
|
+
line: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface TypedAdapter {
|
|
28
|
+
parseAST?: () => unknown | undefined;
|
|
29
|
+
extract?: () => NumberObject[] | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const typedAdapter = adapter as TypedAdapter;
|
|
33
|
+
|
|
34
|
+
let magicNumbers: NumberObject[] = [];
|
|
35
|
+
|
|
18
36
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
interface TypedAdapter {
|
|
25
|
-
parseAST?: () => unknown | undefined;
|
|
26
|
-
extract?: () => NumberObject[] | undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const typedAdapter = adapter as TypedAdapter;
|
|
30
|
-
|
|
31
|
-
let magicNumbers: NumberObject[] = [];
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
if (typeof typedAdapter.extract !== 'undefined') {
|
|
35
|
-
const literals = typedAdapter.extract?.();
|
|
36
|
-
if (literals && Array.isArray(literals)) {
|
|
37
|
-
magicNumbers = literals;
|
|
38
|
-
}
|
|
37
|
+
if (typeof typedAdapter.extract !== 'undefined') {
|
|
38
|
+
const literals = typedAdapter.extract?.();
|
|
39
|
+
if (literals && Array.isArray(literals)) {
|
|
40
|
+
magicNumbers = literals;
|
|
39
41
|
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const excludedNumbers = settings.exclude ?? DEFAULT_EXCLUDED_NUMBERS;
|
|
43
|
-
const filteredNumbers = magicNumbers.filter(numObj => {
|
|
44
|
-
const numValue = numObj.value;
|
|
45
|
-
return !excludedNumbers.includes(numValue);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
filteredNumbers.forEach(numObj => {
|
|
49
|
-
violations.push({
|
|
50
|
-
file,
|
|
51
|
-
line: numObj.line,
|
|
52
|
-
ruleId: 'clean-code.magic-numbers',
|
|
53
|
-
message: `Potential magic number detected: ${numObj.value}. Consider using a named constant instead.`,
|
|
54
|
-
severity: (settings.severity as Severity) ?? 'info'
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
42
|
+
}
|
|
58
43
|
} catch { }
|
|
59
44
|
|
|
45
|
+
const excludedNumbers = settings.exclude ?? DEFAULT_EXCLUDED_NUMBERS;
|
|
46
|
+
const filteredNumbers = magicNumbers.filter(numObj => {
|
|
47
|
+
const numValue = numObj.value;
|
|
48
|
+
return !excludedNumbers.includes(numValue);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
filteredNumbers.forEach(numObj => {
|
|
52
|
+
violations.push({
|
|
53
|
+
file,
|
|
54
|
+
line: numObj.line,
|
|
55
|
+
ruleId: 'clean-code.magic-numbers',
|
|
56
|
+
message: `Potential magic number detected: ${numObj.value}. Consider using a named constant instead.`,
|
|
57
|
+
severity: (settings.severity as Severity) ?? 'info'
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
60
61
|
return violations;
|
|
61
62
|
}
|
|
62
63
|
};
|
|
@@ -11,29 +11,27 @@ export const manyExportsRule: Rule = {
|
|
|
11
11
|
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
12
|
const settings = getActiveConfig().rules['clean-code']['many-exports'];
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
} catch { }
|
|
14
|
+
interface ExportObj {
|
|
15
|
+
line: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface TypedAdapter {
|
|
19
|
+
extractExports?: () => ExportObj[] | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const typedAdapter = adapter as TypedAdapter;
|
|
23
|
+
const exports = typedAdapter.extractExports ? typedAdapter.extractExports() : [];
|
|
24
|
+
const threshold = settings.threshold ?? 10;
|
|
25
|
+
|
|
26
|
+
if (exports && exports.length > threshold) {
|
|
27
|
+
violations.push({
|
|
28
|
+
file,
|
|
29
|
+
line: exports[0]?.line || 1,
|
|
30
|
+
ruleId: 'clean-code.many-exports',
|
|
31
|
+
message: `Module has too many exports: ${exports.length} (maximum: ${threshold}). Consider splitting into focused sub-modules.`,
|
|
32
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
37
35
|
|
|
38
36
|
return violations;
|
|
39
37
|
}
|
|
@@ -11,22 +11,20 @@ export const missingErrorHandlingRule: Rule = {
|
|
|
11
11
|
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
12
|
const settings = getActiveConfig().rules['clean-code']['missing-error-handling'];
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
27
|
-
}
|
|
14
|
+
const typedAdapter = adapter as { extractFunctions?: () => Array<{name?: string; startLine?: number; line?: number; ioOperations?: string[]; hasTryCatch?: boolean;}> | undefined };
|
|
15
|
+
const functions = typedAdapter.extractFunctions?.() || [];
|
|
16
|
+
|
|
17
|
+
for (const func of functions) {
|
|
18
|
+
if (func.ioOperations && func.ioOperations.length > 0 && !func.hasTryCatch) {
|
|
19
|
+
violations.push({
|
|
20
|
+
file,
|
|
21
|
+
line: func.startLine ?? func.line ?? 1,
|
|
22
|
+
ruleId: 'clean-code.missing-error-handling',
|
|
23
|
+
message: `Function "${func.name}" has IO operations (${func.ioOperations.join(', ')}) without error handling`,
|
|
24
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
25
|
+
});
|
|
28
26
|
}
|
|
29
|
-
}
|
|
27
|
+
}
|
|
30
28
|
|
|
31
29
|
return violations;
|
|
32
30
|
}
|