@boyingliu01/xp-gate 0.8.8 → 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/bin/xp-gate.js +18 -0
- package/hooks/pre-commit +565 -120
- package/lib/arch.js +49 -0
- package/lib/check.js +50 -0
- package/lib/init.js +36 -0
- package/lib/principles.js +48 -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 +83 -36
- 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/README.md +23 -7
- package/plugins/opencode/index.ts +36 -21
- 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 +83 -36
- 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 +20 -0
- 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 +83 -36
- 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-driven-development/SKILL.md +371 -0
- package/plugins/qoder/skills/test-driven-development/testing-anti-patterns.md +299 -0
- 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 +83 -36
- 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,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test types.ts - Core type definitions
|
|
3
|
+
* @intent Verify Rule and Violation interfaces match design spec
|
|
4
|
+
* @covers clean-code-solid-checker-design Section 6-7
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
|
|
9
|
+
// These imports will fail until implementation exists - that's the RED phase!
|
|
10
|
+
import type { Rule, Violation, Severity, Adapter } from '../types';
|
|
11
|
+
|
|
12
|
+
describe('types.ts - Core Interfaces', () => {
|
|
13
|
+
describe('Rule interface', () => {
|
|
14
|
+
it('should have required fields: id, name, threshold, severity, check', () => {
|
|
15
|
+
// This test will fail until types.ts exists
|
|
16
|
+
const rule: Rule = {
|
|
17
|
+
id: 'clean-code.long-function',
|
|
18
|
+
name: 'Long Function',
|
|
19
|
+
threshold: 50,
|
|
20
|
+
severity: 'warning',
|
|
21
|
+
check: () => [],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
expect(rule.id).toBeDefined();
|
|
25
|
+
expect(rule.name).toBeDefined();
|
|
26
|
+
expect(rule.threshold).toBeDefined();
|
|
27
|
+
expect(rule.severity).toBeDefined();
|
|
28
|
+
expect(rule.check).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should support all severity levels', () => {
|
|
32
|
+
const severities: Severity[] = ['error', 'warning', 'info'];
|
|
33
|
+
|
|
34
|
+
expect(severities).toContain('error');
|
|
35
|
+
expect(severities).toContain('warning');
|
|
36
|
+
expect(severities).toContain('info');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should have correct rule ID format', () => {
|
|
40
|
+
// Rule IDs should follow pattern: category.rule-name
|
|
41
|
+
const validIds = [
|
|
42
|
+
'clean-code.long-function',
|
|
43
|
+
'clean-code.magic-numbers',
|
|
44
|
+
'solid.srp',
|
|
45
|
+
'solid.dip',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
validIds.forEach(id => {
|
|
49
|
+
expect(id).toMatch(/^[a-z-]+\.[a-z-]+$/);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('Violation interface', () => {
|
|
55
|
+
it('should have required fields: file, line, ruleId, message, severity', () => {
|
|
56
|
+
const violation: Violation = {
|
|
57
|
+
file: 'src/test.ts',
|
|
58
|
+
line: 42,
|
|
59
|
+
ruleId: 'clean-code.long-function',
|
|
60
|
+
message: 'Function exceeds 50 lines',
|
|
61
|
+
severity: 'warning',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
expect(violation.file).toBeDefined();
|
|
65
|
+
expect(violation.line).toBeDefined();
|
|
66
|
+
expect(violation.ruleId).toBeDefined();
|
|
67
|
+
expect(violation.message).toBeDefined();
|
|
68
|
+
expect(violation.severity).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should support optional column field', () => {
|
|
72
|
+
const violationWithColumn: Violation = {
|
|
73
|
+
file: 'src/test.ts',
|
|
74
|
+
line: 42,
|
|
75
|
+
column: 10,
|
|
76
|
+
ruleId: 'clean-code.magic-numbers',
|
|
77
|
+
message: 'Magic number 47 detected',
|
|
78
|
+
severity: 'info',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
expect(violationWithColumn.column).toBe(10);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Adapter interface', () => {
|
|
86
|
+
it('should have required methods: detectLanguage, parseAST, extractFunctions', () => {
|
|
87
|
+
// This will fail until Adapter interface is defined
|
|
88
|
+
const adapter: Adapter = {
|
|
89
|
+
detectLanguage: () => 'typescript',
|
|
90
|
+
parseAST: () => null,
|
|
91
|
+
extractFunctions: () => [],
|
|
92
|
+
extractClasses: () => [],
|
|
93
|
+
countLines: () => 0,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
expect(adapter.detectLanguage).toBeDefined();
|
|
97
|
+
expect(adapter.parseAST).toBeDefined();
|
|
98
|
+
expect(adapter.extractFunctions).toBeDefined();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import type { Adapter } from '../../types';
|
|
3
|
+
import { BaseAdapter } from '../base';
|
|
4
|
+
|
|
5
|
+
class MockAdapter extends BaseAdapter {
|
|
6
|
+
parseAST(): unknown {
|
|
7
|
+
return { content: this.fileContent, astType: 'mock' };
|
|
8
|
+
}
|
|
9
|
+
extractFunctions(): unknown[] {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
extractClasses(): unknown[] {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
vi.mock('fs', () => ({
|
|
18
|
+
readFileSync: vi.fn(),
|
|
19
|
+
existsSync: vi.fn(() => true),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import { readFileSync } from 'fs';
|
|
23
|
+
|
|
24
|
+
describe('BaseAdapter - Default Implementation', () => {
|
|
25
|
+
|
|
26
|
+
it('should implement the Adapter interface', () => {
|
|
27
|
+
(readFileSync as vi.Mock).mockReturnValue('mock file content\ntest line 2');
|
|
28
|
+
const adapter = new MockAdapter('./test.ts');
|
|
29
|
+
const implemented: Adapter = adapter as unknown as Adapter;
|
|
30
|
+
|
|
31
|
+
expect(implemented).toBeDefined();
|
|
32
|
+
expect(implemented.detectLanguage).toBeDefined();
|
|
33
|
+
expect(implemented.parseAST).toBeDefined();
|
|
34
|
+
expect(implemented.extractFunctions).toBeDefined();
|
|
35
|
+
expect(implemented.extractClasses).toBeDefined();
|
|
36
|
+
expect(implemented.countLines).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should detect language based on file extension', () => {
|
|
40
|
+
(readFileSync as vi.Mock).mockReturnValue('file content');
|
|
41
|
+
|
|
42
|
+
const adapterTs = new MockAdapter('./test.ts');
|
|
43
|
+
expect(adapterTs.detectLanguage()).toBe('typescript');
|
|
44
|
+
|
|
45
|
+
const adapterTsx = new MockAdapter('./test.tsx');
|
|
46
|
+
expect(adapterTsx.detectLanguage()).toBe('typescript');
|
|
47
|
+
|
|
48
|
+
const adapterJs = new MockAdapter('./test.js');
|
|
49
|
+
expect(adapterJs.detectLanguage()).toBe('javascript');
|
|
50
|
+
|
|
51
|
+
const adapterPy = new MockAdapter('./test.py');
|
|
52
|
+
expect(adapterPy.detectLanguage()).toBe('python');
|
|
53
|
+
|
|
54
|
+
const adapterUnknown = new MockAdapter('./test.unknown');
|
|
55
|
+
expect(adapterUnknown.detectLanguage()).toBe('unknown');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should parse file AST correctly', () => {
|
|
59
|
+
(readFileSync as vi.Mock).mockReturnValue('mock file content\ntest line 2');
|
|
60
|
+
const adapter = new MockAdapter('./test.ts');
|
|
61
|
+
const ast = adapter.parseAST();
|
|
62
|
+
expect(ast).toEqual({ content: 'mock file content\ntest line 2', astType: 'mock' });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should extract functions from AST', () => {
|
|
66
|
+
(readFileSync as vi.Mock).mockReturnValue('');
|
|
67
|
+
const adapter = new MockAdapter('./test.ts');
|
|
68
|
+
const functions = adapter.extractFunctions();
|
|
69
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should extract classes from AST', () => {
|
|
73
|
+
(readFileSync as vi.Mock).mockReturnValue('');
|
|
74
|
+
const adapter = new MockAdapter('./test.ts');
|
|
75
|
+
const classes = adapter.extractClasses();
|
|
76
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should count physical lines correctly', () => {
|
|
80
|
+
(readFileSync as vi.Mock).mockReturnValue('line 1\nline 2\nline 3');
|
|
81
|
+
const adapter = new MockAdapter('./test.ts');
|
|
82
|
+
const lineCount = adapter.countLines();
|
|
83
|
+
expect(lineCount).toBe(3);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should throw error for unsupported file operations', () => {
|
|
87
|
+
(readFileSync as vi.Mock).mockImplementation(() => {
|
|
88
|
+
throw new Error('Could not read file');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(() => {
|
|
92
|
+
new MockAdapter('./nonexistent-file.txt');
|
|
93
|
+
}).toThrow('Could not read file:');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { CppAdapter } from '../cpp';
|
|
3
|
+
|
|
4
|
+
type MockFunction = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
vi.mock('fs', () => ({
|
|
7
|
+
readFileSync: vi.fn(),
|
|
8
|
+
existsSync: vi.fn(() => true),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'fs';
|
|
12
|
+
|
|
13
|
+
describe('CppAdapter', () => {
|
|
14
|
+
|
|
15
|
+
it('should implement the Adapter interface', () => {
|
|
16
|
+
(readFileSync as vi.Mock).mockReturnValue('int main() { return 0; }');
|
|
17
|
+
const adapter = new CppAdapter('test.cpp');
|
|
18
|
+
|
|
19
|
+
expect(adapter).toHaveProperty('detectLanguage');
|
|
20
|
+
expect(adapter).toHaveProperty('parseAST');
|
|
21
|
+
expect(adapter).toHaveProperty('extractFunctions');
|
|
22
|
+
expect(adapter).toHaveProperty('extractClasses');
|
|
23
|
+
expect(adapter).toHaveProperty('countLines');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should detect language as cpp for .cpp files', () => {
|
|
27
|
+
(readFileSync as vi.Mock).mockReturnValue('int main() { return 0; }');
|
|
28
|
+
const adapter = new CppAdapter('test.cpp');
|
|
29
|
+
const detected = adapter.detectLanguage();
|
|
30
|
+
expect(detected).toBe('cpp');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should detect language as cpp for .cxx files', () => {
|
|
34
|
+
(readFileSync as vi.Mock).mockReturnValue('int main() { return 0; }');
|
|
35
|
+
const adapter = new CppAdapter('test.cxx');
|
|
36
|
+
const detected = adapter.detectLanguage();
|
|
37
|
+
expect(detected).toBe('cpp');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should detect language as cpp for .cc files', () => {
|
|
41
|
+
(readFileSync as vi.Mock).mockReturnValue('int main() { return 0; }');
|
|
42
|
+
const adapter = new CppAdapter('test.cc');
|
|
43
|
+
const detected = adapter.detectLanguage();
|
|
44
|
+
expect(detected).toBe('cpp');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should detect language as cpp for .c files', () => {
|
|
48
|
+
(readFileSync as vi.Mock).mockReturnValue('int main() { return 0; }');
|
|
49
|
+
const adapter = new CppAdapter('test.c');
|
|
50
|
+
const detected = adapter.detectLanguage();
|
|
51
|
+
expect(detected).toBe('cpp');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should detect language as cpp for .hpp files', () => {
|
|
55
|
+
(readFileSync as vi.Mock).mockReturnValue('#pragma once');
|
|
56
|
+
const adapter = new CppAdapter('test.hpp');
|
|
57
|
+
const detected = adapter.detectLanguage();
|
|
58
|
+
expect(detected).toBe('cpp');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should detect language as cpp for .h files', () => {
|
|
62
|
+
(readFileSync as vi.Mock).mockReturnValue('#pragma once');
|
|
63
|
+
const adapter = new CppAdapter('test.h');
|
|
64
|
+
const detected = adapter.detectLanguage();
|
|
65
|
+
expect(detected).toBe('cpp');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should parse C++ file AST correctly', () => {
|
|
69
|
+
(readFileSync as vi.Mock).mockReturnValue('int main() { return 0; }');
|
|
70
|
+
const adapter = new CppAdapter('test.cpp');
|
|
71
|
+
const ast = adapter.parseAST();
|
|
72
|
+
expect(ast).toHaveProperty('content');
|
|
73
|
+
expect(ast).toHaveProperty('language');
|
|
74
|
+
expect(ast).toHaveProperty('filePath');
|
|
75
|
+
expect(ast.language).toBe('cpp');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should extract functions from C++ code', () => {
|
|
79
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
80
|
+
int add(int a, int b) {
|
|
81
|
+
return a + b;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
int main() {
|
|
85
|
+
return add(1, 2);
|
|
86
|
+
}
|
|
87
|
+
`);
|
|
88
|
+
const adapter = new CppAdapter('test.cpp');
|
|
89
|
+
const functions = adapter.extractFunctions();
|
|
90
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
91
|
+
expect(functions.some(fn => (fn as MockFunction).name === 'main')).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle C++ strings with special characters', () => {
|
|
95
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
96
|
+
int main() {
|
|
97
|
+
const char* str = "Hello \\"World\\"";
|
|
98
|
+
char c = '\\'';
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
`);
|
|
102
|
+
|
|
103
|
+
const adapter = new CppAdapter('test.cpp');
|
|
104
|
+
const functions = adapter.extractFunctions();
|
|
105
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
106
|
+
expect(functions.some(fn => (fn as {name: string}).name === 'main')).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should throw error when file cannot be read', () => {
|
|
110
|
+
(readFileSync as vi.Mock).mockImplementation(() => {
|
|
111
|
+
throw new Error('Could not read file');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(() => {
|
|
115
|
+
new CppAdapter('nonexistent-file.cpp');
|
|
116
|
+
}).toThrow('Could not read file:');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @test REQ-COV-004 CppAdapter extractCodeBlock branch coverage
|
|
122
|
+
* @intent Verify block-comment handling and nested-brace counting inside extractCodeBlock
|
|
123
|
+
* @covers AC-COV-004
|
|
124
|
+
*/
|
|
125
|
+
describe('CppAdapter - extractCodeBlock edge cases', () => {
|
|
126
|
+
it('should skip single-line block comment before the opening brace', () => {
|
|
127
|
+
const src = `int foo() /* inline comment */ { return 1; }`;
|
|
128
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
129
|
+
const adapter = new CppAdapter('test.cpp');
|
|
130
|
+
const block = adapter.extractCodeBlock(0);
|
|
131
|
+
expect(block).toContain('{ return 1; }');
|
|
132
|
+
expect(block.endsWith('}')).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should skip multi-line block comment spanning multiple lines', () => {
|
|
136
|
+
const src = [
|
|
137
|
+
'int foo()',
|
|
138
|
+
'/* this is a',
|
|
139
|
+
' multi-line block',
|
|
140
|
+
' comment */',
|
|
141
|
+
'{',
|
|
142
|
+
' return 42;',
|
|
143
|
+
'}',
|
|
144
|
+
].join('\n');
|
|
145
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
146
|
+
const adapter = new CppAdapter('test.cpp');
|
|
147
|
+
const block = adapter.extractCodeBlock(0);
|
|
148
|
+
expect(block).toContain('return 42;');
|
|
149
|
+
expect(block.trim().endsWith('}')).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should ignore braces inside block comments', () => {
|
|
153
|
+
const src = `int foo() /* fake { brace } here */ { return 7; }`;
|
|
154
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
155
|
+
const adapter = new CppAdapter('test.cpp');
|
|
156
|
+
const block = adapter.extractCodeBlock(0);
|
|
157
|
+
expect(block).toContain('return 7;');
|
|
158
|
+
expect(block.trim().endsWith('}')).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should count nested braces correctly via braceCount++ path', () => {
|
|
162
|
+
const src = `void foo() { if (x) { return; } }`;
|
|
163
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
164
|
+
const adapter = new CppAdapter('test.cpp');
|
|
165
|
+
const block = adapter.extractCodeBlock(0);
|
|
166
|
+
expect(block).toBe('void foo() { if (x) { return; } }');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should handle deeply nested braces (triple nesting)', () => {
|
|
170
|
+
const src = `int bar() { if (a) { while (b) { c++; } } return 0; }`;
|
|
171
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
172
|
+
const adapter = new CppAdapter('test.cpp');
|
|
173
|
+
const block = adapter.extractCodeBlock(0);
|
|
174
|
+
expect(block.trim().endsWith('}')).toBe(true);
|
|
175
|
+
expect(block).toContain('c++;');
|
|
176
|
+
const opens = (block.match(/\{/g) || []).length;
|
|
177
|
+
const closes = (block.match(/\}/g) || []).length;
|
|
178
|
+
expect(opens).toBe(closes);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should combine block comments and nested braces in same body', () => {
|
|
182
|
+
const src = [
|
|
183
|
+
'int compute() {',
|
|
184
|
+
' /* outer block comment',
|
|
185
|
+
' with text { and } symbols */',
|
|
186
|
+
' if (a) {',
|
|
187
|
+
' /* nested comment */',
|
|
188
|
+
' return 1;',
|
|
189
|
+
' }',
|
|
190
|
+
' return 0;',
|
|
191
|
+
'}',
|
|
192
|
+
].join('\n');
|
|
193
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
194
|
+
const adapter = new CppAdapter('test.cpp');
|
|
195
|
+
const block = adapter.extractCodeBlock(0);
|
|
196
|
+
expect(block.trim().endsWith('}')).toBe(true);
|
|
197
|
+
expect(block).toContain('return 1;');
|
|
198
|
+
expect(block).toContain('return 0;');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should skip line comments (// ...) before brace', () => {
|
|
202
|
+
const src = [
|
|
203
|
+
'int foo() {',
|
|
204
|
+
' // line comment with { fake brace }',
|
|
205
|
+
' return 5;',
|
|
206
|
+
'}',
|
|
207
|
+
].join('\n');
|
|
208
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
209
|
+
const adapter = new CppAdapter('test.cpp');
|
|
210
|
+
const block = adapter.extractCodeBlock(0);
|
|
211
|
+
expect(block).toContain('return 5;');
|
|
212
|
+
expect(block.trim().endsWith('}')).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should extract constructors with member initializer lists', () => {
|
|
216
|
+
const src = [
|
|
217
|
+
'class Foo {',
|
|
218
|
+
'public:',
|
|
219
|
+
' Foo(int x) : value_(x) {',
|
|
220
|
+
' init();',
|
|
221
|
+
' }',
|
|
222
|
+
'private:',
|
|
223
|
+
' int value_;',
|
|
224
|
+
'};',
|
|
225
|
+
].join('\n');
|
|
226
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
227
|
+
const adapter = new CppAdapter('test.cpp');
|
|
228
|
+
const functions = adapter.extractFunctions();
|
|
229
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
230
|
+
expect(functions.some(fn => (fn as { type: string }).type === 'constructor')).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should extract classes from C++ source', () => {
|
|
234
|
+
const src = [
|
|
235
|
+
'class MyClass {',
|
|
236
|
+
'public:',
|
|
237
|
+
' void doStuff() { return; }',
|
|
238
|
+
'};',
|
|
239
|
+
'struct MyStruct : public Base {',
|
|
240
|
+
' int x;',
|
|
241
|
+
'};',
|
|
242
|
+
].join('\n');
|
|
243
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
244
|
+
const adapter = new CppAdapter('test.cpp');
|
|
245
|
+
const classes = adapter.extractClasses();
|
|
246
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
247
|
+
expect(classes.length).toBeGreaterThanOrEqual(2);
|
|
248
|
+
expect(classes.some(c => (c as { name: string }).name === 'MyClass')).toBe(true);
|
|
249
|
+
expect(classes.some(c => (c as { name: string }).name === 'MyStruct')).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should count lines correctly', () => {
|
|
253
|
+
(readFileSync as vi.Mock).mockReturnValue('line1\nline2\nline3');
|
|
254
|
+
const adapter = new CppAdapter('test.cpp');
|
|
255
|
+
expect(adapter.countLines()).toBe(3);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should fall back to super.detectLanguage for non-cpp extensions', () => {
|
|
259
|
+
(readFileSync as vi.Mock).mockReturnValue('content');
|
|
260
|
+
const adapter = new CppAdapter('test.txt');
|
|
261
|
+
expect(adapter.detectLanguage()).toBe('unknown');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should extract functions correctly when source has block comments and nested braces', () => {
|
|
265
|
+
const src = [
|
|
266
|
+
'/* file header comment */',
|
|
267
|
+
'int outer(int x) {',
|
|
268
|
+
' /* explain logic */',
|
|
269
|
+
' if (x > 0) {',
|
|
270
|
+
' return x;',
|
|
271
|
+
' }',
|
|
272
|
+
' return -x;',
|
|
273
|
+
'}',
|
|
274
|
+
].join('\n');
|
|
275
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
276
|
+
const adapter = new CppAdapter('test.cpp');
|
|
277
|
+
const functions = adapter.extractFunctions();
|
|
278
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
279
|
+
expect(functions.some(fn => (fn as { name: string }).name === 'outer')).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should extract functions with scope prefix (e.g. MyClass::method)', () => {
|
|
283
|
+
const src = 'void MyClass::method() { return; }';
|
|
284
|
+
(readFileSync as vi.Mock).mockReturnValue(src);
|
|
285
|
+
const adapter = new CppAdapter('test.cpp');
|
|
286
|
+
const functions = adapter.extractFunctions();
|
|
287
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
288
|
+
expect(functions.some(fn => (fn as { name: string }).name === 'MyClass::method')).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { DartAdapter } from '../dart';
|
|
3
|
+
|
|
4
|
+
vi.mock('fs', () => ({
|
|
5
|
+
readFileSync: vi.fn(),
|
|
6
|
+
existsSync: vi.fn(() => true),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
describe('DartAdapter', () => {
|
|
12
|
+
it('should implement the Adapter interface', () => {
|
|
13
|
+
(readFileSync as vi.Mock).mockReturnValue('void testFn() {}\nclass TestClass {}');
|
|
14
|
+
const adapter = new DartAdapter('test.dart');
|
|
15
|
+
|
|
16
|
+
expect(adapter).toHaveProperty('detectLanguage');
|
|
17
|
+
expect(adapter).toHaveProperty('parseAST');
|
|
18
|
+
expect(adapter).toHaveProperty('extractFunctions');
|
|
19
|
+
expect(adapter).toHaveProperty('extractClasses');
|
|
20
|
+
expect(adapter).toHaveProperty('countLines');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should detect language as dart for .dart files', () => {
|
|
24
|
+
(readFileSync as vi.Mock).mockReturnValue('void testFn() {}');
|
|
25
|
+
const adapter = new DartAdapter('test.dart');
|
|
26
|
+
const detected = adapter.detectLanguage();
|
|
27
|
+
expect(detected).toBe('dart');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should parse Dart file AST correctly', () => {
|
|
31
|
+
(readFileSync as vi.Mock).mockReturnValue('void testFn() {}\nclass TestClass {}');
|
|
32
|
+
const adapter = new DartAdapter('test.dart');
|
|
33
|
+
const ast = adapter.parseAST();
|
|
34
|
+
expect(ast).toHaveProperty('content');
|
|
35
|
+
expect(ast).toHaveProperty('language');
|
|
36
|
+
expect(ast).toHaveProperty('filePath');
|
|
37
|
+
expect(ast.language).toBe('dart');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should extract functions from Dart AST', () => {
|
|
41
|
+
(readFileSync as vi.Mock).mockReturnValue('void testFn() {}\nclass TestClass {}');
|
|
42
|
+
const adapter = new DartAdapter('test.dart');
|
|
43
|
+
const functions = adapter.extractFunctions();
|
|
44
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
45
|
+
expect(functions.some(fn => (fn as {name: string}).name === 'testFn')).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should extract classes from Dart AST', () => {
|
|
49
|
+
(readFileSync as vi.Mock).mockReturnValue('void testFn() {}\nclass TestClass {}');
|
|
50
|
+
const adapter = new DartAdapter('test.dart');
|
|
51
|
+
const classes = adapter.extractClasses();
|
|
52
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
53
|
+
expect(classes.some(cls => (cls as {name: string}).name === 'TestClass')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should count Dart file physical lines', () => {
|
|
57
|
+
(readFileSync as vi.Mock).mockReturnValue('void testFn() {}\nvoid testFn2() {}');
|
|
58
|
+
const adapter = new DartAdapter('test.dart');
|
|
59
|
+
const lineCount = adapter.countLines();
|
|
60
|
+
expect(lineCount).toBe(2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should fall back to super.detectLanguage for non-dart extensions', () => {
|
|
64
|
+
(readFileSync as vi.Mock).mockReturnValue('content');
|
|
65
|
+
const adapter = new DartAdapter('test.py');
|
|
66
|
+
expect(adapter.detectLanguage()).toBe('python');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should handle async functions in Dart', () => {
|
|
70
|
+
(readFileSync as vi.Mock).mockReturnValue('Future<void> fetchData() async {}\nasync void fireAndForget() {}');
|
|
71
|
+
const adapter = new DartAdapter('test.dart');
|
|
72
|
+
const functions = adapter.extractFunctions();
|
|
73
|
+
const asyncFns = functions.filter(fn => (fn as {type: string}).type === 'async_function');
|
|
74
|
+
expect(asyncFns.length).toBeGreaterThan(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle abstract class declarations in Dart', () => {
|
|
78
|
+
(readFileSync as vi.Mock).mockReturnValue('abstract class Repository {\n void save();\n}\n');
|
|
79
|
+
const adapter = new DartAdapter('test.dart');
|
|
80
|
+
const classes = adapter.extractClasses();
|
|
81
|
+
const abstractClasses = classes.filter(cls => (cls as {type: string}).type === 'abstract_class');
|
|
82
|
+
expect(abstractClasses.length).toBeGreaterThan(0);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { GoAdapter } from '../go';
|
|
3
|
+
|
|
4
|
+
vi.mock('fs', () => ({
|
|
5
|
+
readFileSync: vi.fn(),
|
|
6
|
+
existsSync: vi.fn(() => true),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
describe('GoAdapter', () => {
|
|
12
|
+
it('should implement the Adapter interface', () => {
|
|
13
|
+
(readFileSync as vi.Mock).mockReturnValue('func testFn() {}\ntype TestClass struct {}');
|
|
14
|
+
const adapter = new GoAdapter('test.go');
|
|
15
|
+
|
|
16
|
+
expect(adapter).toHaveProperty('detectLanguage');
|
|
17
|
+
expect(adapter).toHaveProperty('parseAST');
|
|
18
|
+
expect(adapter).toHaveProperty('extractFunctions');
|
|
19
|
+
expect(adapter).toHaveProperty('extractClasses');
|
|
20
|
+
expect(adapter).toHaveProperty('countLines');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should detect language as go for .go files', () => {
|
|
24
|
+
(readFileSync as vi.Mock).mockReturnValue('func testFn() {}');
|
|
25
|
+
const adapter = new GoAdapter('test.go');
|
|
26
|
+
const detected = adapter.detectLanguage();
|
|
27
|
+
expect(detected).toBe('go');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should parse Go file AST correctly', () => {
|
|
31
|
+
(readFileSync as vi.Mock).mockReturnValue('func testFn() {}\ntype TestClass struct {}');
|
|
32
|
+
const adapter = new GoAdapter('test.go');
|
|
33
|
+
const ast = adapter.parseAST();
|
|
34
|
+
expect(ast).toHaveProperty('content');
|
|
35
|
+
expect(ast).toHaveProperty('language');
|
|
36
|
+
expect(ast).toHaveProperty('filePath');
|
|
37
|
+
expect(ast.language).toBe('go');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should extract functions from Go AST', () => {
|
|
41
|
+
(readFileSync as vi.Mock).mockReturnValue('func testFn() {}\nfunc (t *TestClass) method() {}');
|
|
42
|
+
const adapter = new GoAdapter('test.go');
|
|
43
|
+
const functions = adapter.extractFunctions();
|
|
44
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
45
|
+
expect(functions.some(fn => (fn as {name: string}).name === 'testFn')).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should extract structs from Go AST', () => {
|
|
49
|
+
(readFileSync as vi.Mock).mockReturnValue('func testFn() {}\ntype TestClass struct {}');
|
|
50
|
+
const adapter = new GoAdapter('test.go');
|
|
51
|
+
const classes = adapter.extractClasses();
|
|
52
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
53
|
+
expect(classes.some(cls => (cls as {name: string}).name === 'TestClass')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should count Go file physical lines', () => {
|
|
57
|
+
(readFileSync as vi.Mock).mockReturnValue('func testFn() {}\nfunc testFn2() {}');
|
|
58
|
+
const adapter = new GoAdapter('test.go');
|
|
59
|
+
const lineCount = adapter.countLines();
|
|
60
|
+
expect(lineCount).toBe(2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should fall back to super.detectLanguage for non-go extensions', () => {
|
|
64
|
+
(readFileSync as vi.Mock).mockReturnValue('content');
|
|
65
|
+
const adapter = new GoAdapter('test.ts');
|
|
66
|
+
expect(adapter.detectLanguage()).toBe('typescript');
|
|
67
|
+
});
|
|
68
|
+
});
|