@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,68 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { JavaAdapter } from '../java';
|
|
3
|
+
|
|
4
|
+
vi.mock('fs', () => ({
|
|
5
|
+
readFileSync: vi.fn(),
|
|
6
|
+
existsSync: vi.fn(() => true),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
describe('JavaAdapter', () => {
|
|
12
|
+
it('should implement the Adapter interface', () => {
|
|
13
|
+
(readFileSync as vi.Mock).mockReturnValue('public class Test {\n public void testFn() {}\n}');
|
|
14
|
+
const adapter = new JavaAdapter('Test.java');
|
|
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 java for .java files', () => {
|
|
24
|
+
(readFileSync as vi.Mock).mockReturnValue('public class Test {}');
|
|
25
|
+
const adapter = new JavaAdapter('Test.java');
|
|
26
|
+
const detected = adapter.detectLanguage();
|
|
27
|
+
expect(detected).toBe('java');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should parse Java file AST correctly', () => {
|
|
31
|
+
(readFileSync as vi.Mock).mockReturnValue('public class Test {\n public void testFn() {}\n}');
|
|
32
|
+
const adapter = new JavaAdapter('Test.java');
|
|
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('java');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should extract methods from Java AST', () => {
|
|
41
|
+
(readFileSync as vi.Mock).mockReturnValue('public class Test {\n public void testFn() {}\n}');
|
|
42
|
+
const adapter = new JavaAdapter('Test.java');
|
|
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 Java AST', () => {
|
|
49
|
+
(readFileSync as vi.Mock).mockReturnValue('public class Test {\n public void testFn() {}\n}');
|
|
50
|
+
const adapter = new JavaAdapter('Test.java');
|
|
51
|
+
const classes = adapter.extractClasses();
|
|
52
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
53
|
+
expect(classes.some(cls => (cls as {name: string}).name === 'Test')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should count Java file physical lines', () => {
|
|
57
|
+
(readFileSync as vi.Mock).mockReturnValue('public class Test {\n}\nclass Test2 {}');
|
|
58
|
+
const adapter = new JavaAdapter('Test.java');
|
|
59
|
+
const lineCount = adapter.countLines();
|
|
60
|
+
expect(lineCount).toBe(3);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should fall back to super.detectLanguage for non-java extensions', () => {
|
|
64
|
+
(readFileSync as vi.Mock).mockReturnValue('content');
|
|
65
|
+
const adapter = new JavaAdapter('test.ts');
|
|
66
|
+
expect(adapter.detectLanguage()).toBe('typescript');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { KotlinAdapter } from '../kotlin';
|
|
3
|
+
|
|
4
|
+
vi.mock('fs', () => ({
|
|
5
|
+
readFileSync: vi.fn(),
|
|
6
|
+
existsSync: vi.fn(() => true),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
describe('KotlinAdapter', () => {
|
|
12
|
+
it('should implement the Adapter interface', () => {
|
|
13
|
+
(readFileSync as vi.Mock).mockReturnValue('fun testFn() {}\nclass TestClass');
|
|
14
|
+
const adapter = new KotlinAdapter('test.kt');
|
|
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 kotlin for .kt files', () => {
|
|
24
|
+
(readFileSync as vi.Mock).mockReturnValue('fun testFn() {}');
|
|
25
|
+
const adapter = new KotlinAdapter('test.kt');
|
|
26
|
+
const detected = adapter.detectLanguage();
|
|
27
|
+
expect(detected).toBe('kotlin');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should parse Kotlin file AST correctly', () => {
|
|
31
|
+
(readFileSync as vi.Mock).mockReturnValue('fun testFn() {}\nclass TestClass');
|
|
32
|
+
const adapter = new KotlinAdapter('test.kt');
|
|
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('kotlin');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should extract functions from Kotlin AST', () => {
|
|
41
|
+
(readFileSync as vi.Mock).mockReturnValue('fun testFn() {}\nclass TestClass');
|
|
42
|
+
const adapter = new KotlinAdapter('test.kt');
|
|
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 Kotlin AST', () => {
|
|
49
|
+
(readFileSync as vi.Mock).mockReturnValue('fun testFn() {}\nclass TestClass');
|
|
50
|
+
const adapter = new KotlinAdapter('test.kt');
|
|
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 Kotlin file physical lines', () => {
|
|
57
|
+
(readFileSync as vi.Mock).mockReturnValue('fun testFn() {}\nfun testFn2() {}');
|
|
58
|
+
const adapter = new KotlinAdapter('test.kt');
|
|
59
|
+
const lineCount = adapter.countLines();
|
|
60
|
+
expect(lineCount).toBe(2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should fall back to super.detectLanguage for non-kotlin extensions', () => {
|
|
64
|
+
(readFileSync as vi.Mock).mockReturnValue('content');
|
|
65
|
+
const adapter = new KotlinAdapter('test.ts');
|
|
66
|
+
expect(adapter.detectLanguage()).toBe('typescript');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should handle suspend functions in Kotlin', () => {
|
|
70
|
+
(readFileSync as vi.Mock).mockReturnValue('suspend fun fetchData(): String { return "data" }');
|
|
71
|
+
const adapter = new KotlinAdapter('test.kt');
|
|
72
|
+
const functions = adapter.extractFunctions();
|
|
73
|
+
const suspendFns = functions.filter(fn => (fn as {type: string}).type === 'suspend_function');
|
|
74
|
+
expect(suspendFns.length).toBeGreaterThan(0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { ObjectiveCAdapter } from '../objectivec';
|
|
3
|
+
|
|
4
|
+
vi.mock('fs', () => ({
|
|
5
|
+
readFileSync: vi.fn(),
|
|
6
|
+
existsSync: vi.fn(() => true),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @test REQ-QG-001 Language Coverage Extension
|
|
13
|
+
* @intent Verify Objective-C adapter implementation for quality gates
|
|
14
|
+
* @covers AC-QG-001-05, AC-QG-001-06, AC-QG-001-08
|
|
15
|
+
*/
|
|
16
|
+
describe('ObjectiveCAdapter', () => {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @test REQ-QG-001
|
|
20
|
+
* @covers AC-QG-001-05
|
|
21
|
+
*/
|
|
22
|
+
it('should implement the Adapter interface', () => {
|
|
23
|
+
(readFileSync as vi.Mock).mockReturnValue('@implementation Test @end');
|
|
24
|
+
const adapter = new ObjectiveCAdapter('test.m');
|
|
25
|
+
|
|
26
|
+
expect(adapter).toHaveProperty('detectLanguage');
|
|
27
|
+
expect(adapter).toHaveProperty('parseAST');
|
|
28
|
+
expect(adapter).toHaveProperty('extractFunctions');
|
|
29
|
+
expect(adapter).toHaveProperty('extractClasses');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @test REQ-QG-001
|
|
34
|
+
* @covers AC-QG-001-08
|
|
35
|
+
*/
|
|
36
|
+
it('should detect language as objectivec for .m files', () => {
|
|
37
|
+
(readFileSync as vi.Mock).mockReturnValue('@implementation Test @end');
|
|
38
|
+
const adapter = new ObjectiveCAdapter('test.m');
|
|
39
|
+
const detected = adapter.detectLanguage();
|
|
40
|
+
expect(detected).toBe('objectivec');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @test REQ-QG-001
|
|
45
|
+
* @covers AC-QG-001-08
|
|
46
|
+
*/
|
|
47
|
+
it('should detect language as objectivec for .mm files (Objective-C++)', () => {
|
|
48
|
+
(readFileSync as vi.Mock).mockReturnValue('@implementation Test @end');
|
|
49
|
+
const adapter = new ObjectiveCAdapter('test.mm');
|
|
50
|
+
const detected = adapter.detectLanguage();
|
|
51
|
+
expect(detected).toBe('objectivec');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @test REQ-QG-001
|
|
56
|
+
* @covers AC-QG-001-06
|
|
57
|
+
*/
|
|
58
|
+
it('should parse Objective-C file AST correctly', () => {
|
|
59
|
+
(readFileSync as vi.Mock).mockReturnValue('@implementation Test @end');
|
|
60
|
+
const adapter = new ObjectiveCAdapter('test.m');
|
|
61
|
+
const ast = adapter.parseAST();
|
|
62
|
+
expect(ast).toHaveProperty('content');
|
|
63
|
+
expect(ast).toHaveProperty('language');
|
|
64
|
+
expect(ast).toHaveProperty('filePath');
|
|
65
|
+
expect(ast.language).toBe('objectivec');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @test REQ-QG-001
|
|
70
|
+
* @covers AC-QG-001-06
|
|
71
|
+
*/
|
|
72
|
+
it('should extract Objective-C instance methods', () => {
|
|
73
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
74
|
+
@implementation Calculator
|
|
75
|
+
- (int)add:(int)a to:(int)b {
|
|
76
|
+
return a + b;
|
|
77
|
+
}
|
|
78
|
+
- (void)printResult {
|
|
79
|
+
NSLog(@"Result");
|
|
80
|
+
}
|
|
81
|
+
@end
|
|
82
|
+
`);
|
|
83
|
+
const adapter = new ObjectiveCAdapter('Calculator.m');
|
|
84
|
+
const functions = adapter.extractFunctions();
|
|
85
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
86
|
+
expect(functions.length).toBeGreaterThan(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @test REQ-QG-001
|
|
91
|
+
* @covers AC-QG-001-06
|
|
92
|
+
*/
|
|
93
|
+
it('should extract Objective-C class methods', () => {
|
|
94
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
95
|
+
@implementation StringUtils
|
|
96
|
+
+ (NSString *)trim:(NSString *)str {
|
|
97
|
+
return [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
98
|
+
}
|
|
99
|
+
+ (BOOL)isEmpty:(NSString *)str {
|
|
100
|
+
return [str length] == 0;
|
|
101
|
+
}
|
|
102
|
+
@end
|
|
103
|
+
`);
|
|
104
|
+
const adapter = new ObjectiveCAdapter('StringUtils.m');
|
|
105
|
+
const functions = adapter.extractFunctions();
|
|
106
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @test REQ-QG-001
|
|
111
|
+
* @covers AC-QG-001-06
|
|
112
|
+
*/
|
|
113
|
+
it('should extract C functions from Objective-C file', () => {
|
|
114
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
115
|
+
int helperFunction(int x) {
|
|
116
|
+
return x * 2;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void logDebug(const char *msg) {
|
|
120
|
+
printf("%s\\n", msg);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@implementation Helper
|
|
124
|
+
- (void)process {
|
|
125
|
+
int result = helperFunction(5);
|
|
126
|
+
}
|
|
127
|
+
@end
|
|
128
|
+
`);
|
|
129
|
+
const adapter = new ObjectiveCAdapter('Helper.m');
|
|
130
|
+
const functions = adapter.extractFunctions();
|
|
131
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
132
|
+
expect(functions.some(fn => (fn as {type: string}).type === 'function')).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @test REQ-QG-001
|
|
137
|
+
* @covers AC-QG-001-06
|
|
138
|
+
*/
|
|
139
|
+
it('should extract @implementation declarations', () => {
|
|
140
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
141
|
+
@implementation MyClass
|
|
142
|
+
- (void)doSomething {}
|
|
143
|
+
@end
|
|
144
|
+
`);
|
|
145
|
+
const adapter = new ObjectiveCAdapter('MyClass.m');
|
|
146
|
+
const classes = adapter.extractClasses();
|
|
147
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
148
|
+
expect(classes.some(cls => (cls as {name: string}).name === 'MyClass')).toBe(true);
|
|
149
|
+
expect(classes.some(cls => (cls as {type: string}).type === 'implementation')).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @test REQ-QG-001
|
|
154
|
+
* @covers AC-QG-001-06
|
|
155
|
+
*/
|
|
156
|
+
it('should extract @interface declarations', () => {
|
|
157
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
158
|
+
@interface MyProtocol
|
|
159
|
+
- (void)requiredMethod;
|
|
160
|
+
@end
|
|
161
|
+
`);
|
|
162
|
+
const adapter = new ObjectiveCAdapter('MyProtocol.m');
|
|
163
|
+
const classes = adapter.extractClasses();
|
|
164
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
165
|
+
expect(classes.some(cls => (cls as {name: string}).name === 'MyProtocol')).toBe(true);
|
|
166
|
+
expect(classes.some(cls => (cls as {type: string}).type === 'interface')).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @test REQ-QG-001
|
|
171
|
+
* @covers AC-QG-001-06
|
|
172
|
+
*/
|
|
173
|
+
it('should handle multiple @implementation blocks', () => {
|
|
174
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
175
|
+
@implementation FirstClass
|
|
176
|
+
- (void)method1 {}
|
|
177
|
+
@end
|
|
178
|
+
|
|
179
|
+
@implementation SecondClass
|
|
180
|
+
- (void)method2 {}
|
|
181
|
+
@end
|
|
182
|
+
`);
|
|
183
|
+
const adapter = new ObjectiveCAdapter('Multiple.m');
|
|
184
|
+
const classes = adapter.extractClasses();
|
|
185
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
186
|
+
expect(classes.length).toBeGreaterThanOrEqual(2);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @test REQ-QG-001
|
|
191
|
+
* @covers AC-QG-001-06
|
|
192
|
+
*/
|
|
193
|
+
it('should handle Objective-C inheritance syntax', () => {
|
|
194
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
195
|
+
@interface ChildClass : ParentClass
|
|
196
|
+
- (void)overrideMethod;
|
|
197
|
+
@end
|
|
198
|
+
`);
|
|
199
|
+
const adapter = new ObjectiveCAdapter('ChildClass.m');
|
|
200
|
+
const classes = adapter.extractClasses();
|
|
201
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
202
|
+
expect(classes.some(cls => (cls as {name: string}).name === 'ChildClass')).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @test REQ-QG-001
|
|
207
|
+
* @covers AC-QG-001-06
|
|
208
|
+
*/
|
|
209
|
+
it('should handle Objective-C categories', () => {
|
|
210
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
211
|
+
@implementation NSString (Extensions)
|
|
212
|
+
- (NSString *)reversed {
|
|
213
|
+
// implementation
|
|
214
|
+
}
|
|
215
|
+
@end
|
|
216
|
+
`);
|
|
217
|
+
const adapter = new ObjectiveCAdapter('Extensions.m');
|
|
218
|
+
const classes = adapter.extractClasses();
|
|
219
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @test REQ-QG-001
|
|
224
|
+
* @covers AC-QG-001-06
|
|
225
|
+
*/
|
|
226
|
+
it('should handle Objective-C properties', () => {
|
|
227
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
228
|
+
@interface Person
|
|
229
|
+
@property (nonatomic, strong) NSString *name;
|
|
230
|
+
@property (nonatomic) NSInteger age;
|
|
231
|
+
- (void)introduce;
|
|
232
|
+
@end
|
|
233
|
+
|
|
234
|
+
@implementation Person
|
|
235
|
+
- (void)introduce {
|
|
236
|
+
NSLog(@"Hi, I'm %@", self.name);
|
|
237
|
+
}
|
|
238
|
+
@end
|
|
239
|
+
`);
|
|
240
|
+
const adapter = new ObjectiveCAdapter('Person.m');
|
|
241
|
+
const classes = adapter.extractClasses();
|
|
242
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
243
|
+
const functions = adapter.extractFunctions();
|
|
244
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @test REQ-QG-001
|
|
249
|
+
* @covers AC-QG-001-06, AC-QG-001-08
|
|
250
|
+
*/
|
|
251
|
+
it('should handle Objective-C++ (.mm) files with C++ content', () => {
|
|
252
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
253
|
+
#import <Foundation/Foundation.h>
|
|
254
|
+
#include <vector>
|
|
255
|
+
|
|
256
|
+
@implementation MixedClass {
|
|
257
|
+
std::vector<int> _data;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
- (void)processCpp {
|
|
261
|
+
_data.push_back(42);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@end
|
|
265
|
+
`);
|
|
266
|
+
const adapter = new ObjectiveCAdapter('Mixed.mm');
|
|
267
|
+
expect(adapter.detectLanguage()).toBe('objectivec');
|
|
268
|
+
const classes = adapter.extractClasses();
|
|
269
|
+
expect(Array.isArray(classes)).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @test REQ-QG-001
|
|
274
|
+
* @covers AC-QG-001-05
|
|
275
|
+
*/
|
|
276
|
+
it('should count physical lines correctly', () => {
|
|
277
|
+
(readFileSync as vi.Mock).mockReturnValue('@implementation Test\n- (void)method {}\n@end');
|
|
278
|
+
const adapter = new ObjectiveCAdapter('test.m');
|
|
279
|
+
const lineCount = adapter.countLines();
|
|
280
|
+
expect(lineCount).toBe(3);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @test REQ-QG-001
|
|
285
|
+
* @covers AC-QG-001-06
|
|
286
|
+
*/
|
|
287
|
+
it('should handle empty file', () => {
|
|
288
|
+
(readFileSync as vi.Mock).mockReturnValue('');
|
|
289
|
+
const adapter = new ObjectiveCAdapter('empty.m');
|
|
290
|
+
const functions = adapter.extractFunctions();
|
|
291
|
+
const classes = adapter.extractClasses();
|
|
292
|
+
expect(functions.length).toBe(0);
|
|
293
|
+
expect(classes.length).toBe(0);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @test REQ-QG-001
|
|
298
|
+
* @covers AC-QG-001-06
|
|
299
|
+
*/
|
|
300
|
+
it('should handle Objective-C literals and modern syntax', () => {
|
|
301
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
302
|
+
@implementation ModernObjC
|
|
303
|
+
- (NSArray *)getItems {
|
|
304
|
+
return @[ @"one", @"two", @"three" ];
|
|
305
|
+
}
|
|
306
|
+
- (NSDictionary *)getDict {
|
|
307
|
+
return @{ @"key": @"value" };
|
|
308
|
+
}
|
|
309
|
+
- (NSNumber *)getNumber {
|
|
310
|
+
return @42;
|
|
311
|
+
}
|
|
312
|
+
@end
|
|
313
|
+
`);
|
|
314
|
+
const adapter = new ObjectiveCAdapter('ModernObjC.m');
|
|
315
|
+
const functions = adapter.extractFunctions();
|
|
316
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @test REQ-QG-001
|
|
321
|
+
* @covers AC-QG-001-06
|
|
322
|
+
*/
|
|
323
|
+
it('should handle Objective-C blocks', () => {
|
|
324
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
325
|
+
@implementation BlockUser
|
|
326
|
+
- (void)useBlock {
|
|
327
|
+
void (^myBlock)(void) = ^{
|
|
328
|
+
NSLog(@"Block executed");
|
|
329
|
+
};
|
|
330
|
+
myBlock();
|
|
331
|
+
}
|
|
332
|
+
@end
|
|
333
|
+
`);
|
|
334
|
+
const adapter = new ObjectiveCAdapter('BlockUser.m');
|
|
335
|
+
const functions = adapter.extractFunctions();
|
|
336
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @test REQ-QG-001
|
|
341
|
+
* @covers AC-QG-001-08
|
|
342
|
+
*/
|
|
343
|
+
it('should handle preprocessor directives', () => {
|
|
344
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
345
|
+
#import <Foundation/Foundation.h>
|
|
346
|
+
#define MAX_SIZE 100
|
|
347
|
+
|
|
348
|
+
#ifdef DEBUG
|
|
349
|
+
@implementation DebugHelper
|
|
350
|
+
- (void)logDebug {}
|
|
351
|
+
@end
|
|
352
|
+
#endif
|
|
353
|
+
`);
|
|
354
|
+
const adapter = new ObjectiveCAdapter('Preprocessor.m');
|
|
355
|
+
expect(adapter.detectLanguage()).toBe('objectivec');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @test REQ-QG-001
|
|
360
|
+
* @covers AC-QG-001-05
|
|
361
|
+
*/
|
|
362
|
+
it('should throw error when file cannot be read', () => {
|
|
363
|
+
(readFileSync as vi.Mock).mockImplementation(() => {
|
|
364
|
+
throw new Error('Could not read file');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(() => {
|
|
368
|
+
new ObjectiveCAdapter('nonexistent-file.m');
|
|
369
|
+
}).toThrow('Could not read file:');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* @test REQ-QG-001
|
|
374
|
+
* @covers AC-QG-001-06
|
|
375
|
+
*/
|
|
376
|
+
it('should extract method with multiple parameters', () => {
|
|
377
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
378
|
+
@implementation MultiParam
|
|
379
|
+
- (void)drawRect:(CGRect)rect withColor:(UIColor *)color andAlpha:(float)alpha {
|
|
380
|
+
// drawing code
|
|
381
|
+
}
|
|
382
|
+
@end
|
|
383
|
+
`);
|
|
384
|
+
const adapter = new ObjectiveCAdapter('MultiParam.m');
|
|
385
|
+
const functions = adapter.extractFunctions();
|
|
386
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* @test REQ-QG-001
|
|
391
|
+
* @covers AC-QG-001-05, AC-QG-001-06
|
|
392
|
+
*/
|
|
393
|
+
it('should handle static inline C functions', () => {
|
|
394
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
395
|
+
static inline int fastAdd(int a, int b) {
|
|
396
|
+
return a + b;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
@implementation FastMath
|
|
400
|
+
- (int)compute {
|
|
401
|
+
return fastAdd(1, 2);
|
|
402
|
+
}
|
|
403
|
+
@end
|
|
404
|
+
`);
|
|
405
|
+
const adapter = new ObjectiveCAdapter('FastMath.m');
|
|
406
|
+
const functions = adapter.extractFunctions();
|
|
407
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
408
|
+
expect(functions.some(fn => (fn as {type: string}).type === 'function')).toBe(true);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* @test REQ-QG-001
|
|
413
|
+
* @covers AC-QG-001-05
|
|
414
|
+
*/
|
|
415
|
+
it('should extract line number for extracted elements', () => {
|
|
416
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
417
|
+
@implementation LineTest
|
|
418
|
+
- (void)firstMethod {}
|
|
419
|
+
- (void)secondMethod {}
|
|
420
|
+
@end
|
|
421
|
+
`);
|
|
422
|
+
const adapter = new ObjectiveCAdapter('LineTest.m');
|
|
423
|
+
const functions = adapter.extractFunctions();
|
|
424
|
+
expect(functions.every(fn => typeof (fn as {line: number}).line === 'number')).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @test REQ-QG-001
|
|
429
|
+
* @covers AC-QG-001-05
|
|
430
|
+
*/
|
|
431
|
+
it('should extract code block for methods', () => {
|
|
432
|
+
(readFileSync as vi.Mock).mockReturnValue(`
|
|
433
|
+
@implementation CodeBlockTest
|
|
434
|
+
- (int)calculate {
|
|
435
|
+
int result = 0;
|
|
436
|
+
for (int i = 0; i < 10; i++) {
|
|
437
|
+
result += i;
|
|
438
|
+
}
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
@end
|
|
442
|
+
`);
|
|
443
|
+
const adapter = new ObjectiveCAdapter('CodeBlockTest.m');
|
|
444
|
+
const functions = adapter.extractFunctions();
|
|
445
|
+
expect(functions.every(fn => typeof (fn as {code: string}).code === 'string')).toBe(true);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('should fall back to super.detectLanguage for non-m/non-mm extensions', () => {
|
|
449
|
+
(readFileSync as vi.Mock).mockReturnValue('content');
|
|
450
|
+
const adapter = new ObjectiveCAdapter('test.ts');
|
|
451
|
+
expect(adapter.detectLanguage()).toBe('typescript');
|
|
452
|
+
});
|
|
453
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { PythonAdapter } from '../python';
|
|
3
|
+
|
|
4
|
+
vi.mock('fs', () => ({
|
|
5
|
+
readFileSync: vi.fn(),
|
|
6
|
+
existsSync: vi.fn(() => true),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
describe('PythonAdapter', () => {
|
|
12
|
+
it('should implement the Adapter interface', () => {
|
|
13
|
+
(readFileSync as vi.Mock).mockReturnValue('def test_fn(): pass\nclass TestClass:');
|
|
14
|
+
const adapter = new PythonAdapter('test.py');
|
|
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 python for .py files', () => {
|
|
24
|
+
(readFileSync as vi.Mock).mockReturnValue('def test_fn(): pass');
|
|
25
|
+
const adapter = new PythonAdapter('test.py');
|
|
26
|
+
const detected = adapter.detectLanguage();
|
|
27
|
+
expect(detected).toBe('python');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should parse Python file AST correctly', () => {
|
|
31
|
+
(readFileSync as vi.Mock).mockReturnValue('def test_fn(): pass\nclass TestClass:');
|
|
32
|
+
const adapter = new PythonAdapter('test.py');
|
|
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('python');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should extract functions from Python AST', () => {
|
|
41
|
+
(readFileSync as vi.Mock).mockReturnValue('def test_fn(): pass\nclass TestClass:');
|
|
42
|
+
const adapter = new PythonAdapter('test.py');
|
|
43
|
+
const functions = adapter.extractFunctions();
|
|
44
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
45
|
+
expect(functions.some(fn => (fn as {name: string}).name === 'test_fn')).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should extract classes from Python AST', () => {
|
|
49
|
+
(readFileSync as vi.Mock).mockReturnValue('def test_fn(): pass\nclass TestClass:');
|
|
50
|
+
const adapter = new PythonAdapter('test.py');
|
|
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 Python file physical lines', () => {
|
|
57
|
+
(readFileSync as vi.Mock).mockReturnValue('def test_fn(): pass\ndef test_fn2(): pass');
|
|
58
|
+
const adapter = new PythonAdapter('test.py');
|
|
59
|
+
const lineCount = adapter.countLines();
|
|
60
|
+
expect(lineCount).toBe(2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should handle async functions in Python', () => {
|
|
64
|
+
(readFileSync as vi.Mock).mockReturnValue('async def async_fn(): pass');
|
|
65
|
+
const adapter = new PythonAdapter('test.py');
|
|
66
|
+
const functions = adapter.extractFunctions();
|
|
67
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should fall back to super.detectLanguage for non-py extensions', () => {
|
|
71
|
+
(readFileSync as vi.Mock).mockReturnValue('content');
|
|
72
|
+
const adapter = new PythonAdapter('test.ts');
|
|
73
|
+
expect(adapter.detectLanguage()).toBe('typescript');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should extract code block with indented body', () => {
|
|
77
|
+
(readFileSync as vi.Mock).mockReturnValue('def outer():\n inner()\n return 0');
|
|
78
|
+
const adapter = new PythonAdapter('test.py');
|
|
79
|
+
const functions = adapter.extractFunctions();
|
|
80
|
+
expect(Array.isArray(functions)).toBe(true);
|
|
81
|
+
expect(functions.length).toBe(1);
|
|
82
|
+
expect((functions[0] as {name: string}).name).toBe('outer');
|
|
83
|
+
});
|
|
84
|
+
});
|