@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.
Files changed (141) hide show
  1. package/bin/xp-gate.js +18 -0
  2. package/hooks/pre-commit +565 -120
  3. package/lib/arch.js +49 -0
  4. package/lib/check.js +50 -0
  5. package/lib/init.js +36 -0
  6. package/lib/principles.js +48 -0
  7. package/mock-policy/AGENTS.md +94 -0
  8. package/mock-policy/__tests__/config.test.ts +292 -0
  9. package/mock-policy/__tests__/integration.test.ts +600 -0
  10. package/mock-policy/__tests__/mock-decision-engine.test.ts +231 -0
  11. package/mock-policy/__tests__/scope-scanner.test.ts +535 -0
  12. package/mock-policy/config.ts +106 -0
  13. package/mock-policy/gate-m3.ts +233 -0
  14. package/mock-policy/mock-decision-engine.ts +155 -0
  15. package/mock-policy/schema.ts +22 -0
  16. package/mock-policy/scope-scanner.ts +288 -0
  17. package/mock-policy/types.ts +65 -0
  18. package/mutation/AGENTS.md +73 -0
  19. package/mutation/__tests__/detect-ai-test.test.ts +152 -0
  20. package/mutation/__tests__/gate-m.test.ts +194 -0
  21. package/mutation/detect-ai-test.ts +128 -0
  22. package/mutation/gate-m.ts +619 -0
  23. package/mutation/init-baseline.ts +192 -0
  24. package/mutation/stryker-types.ts +14 -0
  25. package/mutation/types.ts +70 -0
  26. package/mutation/update-baseline.ts +160 -0
  27. package/package.json +5 -2
  28. package/plugins/claude-code/.claude-plugin/plugin.json +2 -2
  29. package/plugins/claude-code/skills/delphi-review/AGENTS.md +7 -7
  30. package/plugins/claude-code/skills/delphi-review/INSTALL.md +1 -1
  31. package/plugins/claude-code/skills/delphi-review/SKILL.md +12 -12
  32. package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +1 -1
  33. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +83 -36
  34. package/plugins/claude-code/skills/sprint-flow/SKILL.md +1 -1
  35. package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +1 -1
  36. package/plugins/claude-code/skills/sprint-flow/references/phase-3-review.md +2 -2
  37. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  38. package/plugins/opencode/README.md +23 -7
  39. package/plugins/opencode/index.ts +36 -21
  40. package/plugins/opencode/package.json +1 -1
  41. package/plugins/opencode/skills/delphi-review/AGENTS.md +7 -7
  42. package/plugins/opencode/skills/delphi-review/INSTALL.md +1 -1
  43. package/plugins/opencode/skills/delphi-review/SKILL.md +12 -12
  44. package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +1 -1
  45. package/plugins/opencode/skills/sprint-flow/AGENTS.md +83 -36
  46. package/plugins/opencode/skills/sprint-flow/SKILL.md +1 -1
  47. package/plugins/opencode/skills/sprint-flow/references/force-levels.md +1 -1
  48. package/plugins/opencode/skills/sprint-flow/references/phase-3-review.md +2 -2
  49. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  50. package/plugins/qoder/plugin.json +20 -0
  51. package/plugins/qoder/skills/delphi-review/AGENTS.md +7 -7
  52. package/plugins/qoder/skills/delphi-review/INSTALL.md +1 -1
  53. package/plugins/qoder/skills/delphi-review/SKILL.md +12 -12
  54. package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +1 -1
  55. package/plugins/qoder/skills/sprint-flow/AGENTS.md +83 -36
  56. package/plugins/qoder/skills/sprint-flow/SKILL.md +51 -1
  57. package/plugins/qoder/skills/sprint-flow/references/force-levels.md +1 -1
  58. package/plugins/qoder/skills/sprint-flow/references/phase-3-review.md +2 -2
  59. package/plugins/qoder/skills/test-driven-development/SKILL.md +371 -0
  60. package/plugins/qoder/skills/test-driven-development/testing-anti-patterns.md +299 -0
  61. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  62. package/principles/AGENTS.md +111 -0
  63. package/principles/__tests__/analyzer.test.ts +343 -0
  64. package/principles/__tests__/baseline-storage.test.ts +187 -0
  65. package/principles/__tests__/baseline.test.ts +328 -0
  66. package/principles/__tests__/boy-scout-integration.test.ts +264 -0
  67. package/principles/__tests__/boy-scout.test.ts +646 -0
  68. package/principles/__tests__/config.test.ts +78 -0
  69. package/principles/__tests__/index.test.ts +81 -0
  70. package/principles/__tests__/lint-baseline.test.ts +314 -0
  71. package/principles/__tests__/reporter.test.ts +832 -0
  72. package/principles/__tests__/types.test.ts +101 -0
  73. package/principles/adapters/__tests__/base.test.ts +95 -0
  74. package/principles/adapters/__tests__/cpp.test.ts +290 -0
  75. package/principles/adapters/__tests__/dart.test.ts +84 -0
  76. package/principles/adapters/__tests__/go.test.ts +68 -0
  77. package/principles/adapters/__tests__/java.test.ts +68 -0
  78. package/principles/adapters/__tests__/kotlin.test.ts +76 -0
  79. package/principles/adapters/__tests__/objectivec.test.ts +453 -0
  80. package/principles/adapters/__tests__/python.test.ts +84 -0
  81. package/principles/adapters/__tests__/swift.test.ts +68 -0
  82. package/principles/adapters/__tests__/typescript.test.ts +116 -0
  83. package/principles/adapters/base.ts +124 -0
  84. package/principles/adapters/cpp.ts +115 -0
  85. package/principles/adapters/dart.ts +44 -0
  86. package/principles/adapters/go.ts +55 -0
  87. package/principles/adapters/java.ts +56 -0
  88. package/principles/adapters/kotlin.ts +54 -0
  89. package/principles/adapters/objectivec.ts +82 -0
  90. package/principles/adapters/python.ts +83 -0
  91. package/principles/adapters/swift.ts +40 -0
  92. package/principles/adapters/typescript.ts +66 -0
  93. package/principles/analyzer.ts +190 -0
  94. package/principles/baseline.ts +273 -0
  95. package/principles/boy-scout.ts +416 -0
  96. package/principles/config.ts +118 -0
  97. package/principles/index.ts +87 -0
  98. package/principles/lint-baseline.ts +385 -0
  99. package/principles/reporter.ts +188 -0
  100. package/principles/rules/__tests__/clean-code/code-duplication.test.ts +84 -0
  101. package/principles/rules/__tests__/clean-code/deep-nesting.test.ts +74 -0
  102. package/principles/rules/__tests__/clean-code/god-class.test.ts +111 -0
  103. package/principles/rules/__tests__/clean-code/large-file.test.ts +73 -0
  104. package/principles/rules/__tests__/clean-code/long-function.test.ts +83 -0
  105. package/principles/rules/__tests__/clean-code/magic-numbers.test.ts +68 -0
  106. package/principles/rules/__tests__/clean-code/many-exports.test.ts +118 -0
  107. package/principles/rules/__tests__/clean-code/missing-error-handling.test.ts +173 -0
  108. package/principles/rules/__tests__/clean-code/too-many-params.test.ts +60 -0
  109. package/principles/rules/__tests__/clean-code/unused-imports.test.ts +107 -0
  110. package/principles/rules/__tests__/index.test.ts +49 -0
  111. package/principles/rules/__tests__/solid/dip.test.ts +121 -0
  112. package/principles/rules/__tests__/solid/isp.test.ts +60 -0
  113. package/principles/rules/__tests__/solid/lsp.test.ts +184 -0
  114. package/principles/rules/__tests__/solid/ocp.test.ts +69 -0
  115. package/principles/rules/__tests__/solid/srp.test.ts +89 -0
  116. package/principles/rules/clean-code/code-duplication.ts +31 -0
  117. package/principles/rules/clean-code/deep-nesting.ts +38 -0
  118. package/principles/rules/clean-code/god-class.ts +54 -0
  119. package/principles/rules/clean-code/large-file.ts +34 -0
  120. package/principles/rules/clean-code/long-function.ts +47 -0
  121. package/principles/rules/clean-code/magic-numbers.ts +59 -0
  122. package/principles/rules/clean-code/many-exports.ts +40 -0
  123. package/principles/rules/clean-code/missing-error-handling.ts +33 -0
  124. package/principles/rules/clean-code/too-many-params.ts +35 -0
  125. package/principles/rules/clean-code/unused-imports.ts +33 -0
  126. package/principles/rules/index.ts +61 -0
  127. package/principles/rules/solid/dip.ts +47 -0
  128. package/principles/rules/solid/isp.ts +37 -0
  129. package/principles/rules/solid/lsp.ts +79 -0
  130. package/principles/rules/solid/ocp.ts +40 -0
  131. package/principles/rules/solid/srp.ts +44 -0
  132. package/principles/types.ts +27 -0
  133. package/skills/delphi-review/AGENTS.md +7 -7
  134. package/skills/delphi-review/INSTALL.md +1 -1
  135. package/skills/delphi-review/SKILL.md +12 -12
  136. package/skills/delphi-review/references/code-walkthrough.md +1 -1
  137. package/skills/sprint-flow/AGENTS.md +83 -36
  138. package/skills/sprint-flow/SKILL.md +1 -1
  139. package/skills/sprint-flow/references/force-levels.md +1 -1
  140. package/skills/sprint-flow/references/phase-3-review.md +2 -2
  141. 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
+ });