c-next 0.2.12 → 0.2.13

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 (66) hide show
  1. package/README.md +80 -649
  2. package/dist/index.js +7387 -6211
  3. package/dist/index.js.map +4 -4
  4. package/grammar/C.g4 +9 -3
  5. package/package.json +1 -2
  6. package/src/__tests__/index.test.ts +1 -1
  7. package/src/cli/CleanCommand.ts +8 -12
  8. package/src/cli/Cli.ts +29 -6
  9. package/src/cli/Runner.ts +42 -62
  10. package/src/cli/__tests__/CleanCommand.test.ts +10 -10
  11. package/src/cli/__tests__/Cli.test.ts +59 -7
  12. package/src/cli/__tests__/ConfigPrinter.test.ts +12 -12
  13. package/src/cli/__tests__/PathNormalizer.test.ts +5 -5
  14. package/src/cli/__tests__/Runner.test.ts +108 -82
  15. package/src/cli/serve/ServeCommand.ts +1 -1
  16. package/src/cli/types/ICliConfig.ts +2 -2
  17. package/src/lib/parseWithSymbols.ts +21 -21
  18. package/src/transpiler/Transpiler.ts +88 -43
  19. package/src/transpiler/__tests__/DualCodePaths.test.ts +29 -29
  20. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +244 -72
  21. package/src/transpiler/__tests__/Transpiler.test.ts +32 -72
  22. package/src/transpiler/__tests__/determineProjectRoot.test.ts +30 -28
  23. package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +1 -1
  24. package/src/transpiler/data/CNextMarkerDetector.ts +34 -0
  25. package/src/transpiler/data/CppEntryPointScanner.ts +174 -0
  26. package/src/transpiler/data/FileDiscovery.ts +2 -105
  27. package/src/transpiler/data/InputExpansion.ts +37 -81
  28. package/src/transpiler/data/__tests__/CNextMarkerDetector.test.ts +62 -0
  29. package/src/transpiler/data/__tests__/CppEntryPointScanner.test.ts +239 -0
  30. package/src/transpiler/data/__tests__/FileDiscovery.test.ts +45 -191
  31. package/src/transpiler/data/__tests__/InputExpansion.test.ts +36 -204
  32. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +2 -2
  33. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +4 -5
  34. package/src/transpiler/logic/parser/c/grammar/C.interp +19 -1
  35. package/src/transpiler/logic/parser/c/grammar/C.tokens +231 -213
  36. package/src/transpiler/logic/parser/c/grammar/CLexer.interp +28 -1
  37. package/src/transpiler/logic/parser/c/grammar/CLexer.tokens +231 -213
  38. package/src/transpiler/logic/parser/c/grammar/CLexer.ts +654 -600
  39. package/src/transpiler/logic/parser/c/grammar/CParser.ts +1175 -1099
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +19 -7
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +78 -0
  42. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +6 -6
  43. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +28 -27
  44. package/src/transpiler/logic/symbols/cnext/index.ts +4 -4
  45. package/src/transpiler/logic/symbols/cnext/utils/SymbolNameUtils.ts +5 -5
  46. package/src/transpiler/output/codegen/CodeGenerator.ts +7 -1
  47. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +15 -0
  48. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +3 -3
  49. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +14 -14
  50. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  51. package/src/transpiler/output/codegen/utils/QualifiedNameGenerator.ts +7 -7
  52. package/src/transpiler/output/codegen/utils/__tests__/QualifiedNameGenerator.test.ts +3 -3
  53. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +10 -1
  54. package/src/transpiler/output/headers/HeaderGenerator.ts +3 -0
  55. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +6 -2
  56. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +16 -0
  57. package/src/transpiler/output/headers/adapters/HeaderSymbolAdapter.ts +19 -19
  58. package/src/transpiler/output/headers/adapters/__tests__/HeaderSymbolAdapter.test.ts +5 -5
  59. package/src/transpiler/state/SymbolRegistry.ts +10 -12
  60. package/src/transpiler/state/__tests__/SymbolRegistry.test.ts +11 -13
  61. package/src/transpiler/types/IPipelineFile.ts +3 -0
  62. package/src/transpiler/types/ITranspilerConfig.ts +2 -2
  63. package/src/transpiler/types/symbols/IScopeSymbol.ts +1 -1
  64. package/src/utils/FunctionUtils.ts +3 -3
  65. package/src/utils/__tests__/FunctionUtils.test.ts +6 -4
  66. package/src/transpiler/data/types/IDiscoveryOptions.ts +0 -15
@@ -0,0 +1,239 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import CppEntryPointScanner from "../CppEntryPointScanner";
3
+ import IFileSystem from "../../types/IFileSystem";
4
+
5
+ describe("CppEntryPointScanner", () => {
6
+ let mockFs: IFileSystem;
7
+ let files: Map<string, string>;
8
+
9
+ beforeEach(() => {
10
+ files = new Map();
11
+ mockFs = {
12
+ readFile: (path: string) => {
13
+ const content = files.get(path);
14
+ if (!content) throw new Error(`File not found: ${path}`);
15
+ return content;
16
+ },
17
+ exists: (path: string) => files.has(path),
18
+ isDirectory: () => false,
19
+ isFile: (path: string) => files.has(path),
20
+ writeFile: () => {},
21
+ mkdir: () => {},
22
+ readdir: () => [],
23
+ stat: () => ({ mtimeMs: Date.now() }),
24
+ };
25
+ });
26
+
27
+ describe("scan", () => {
28
+ it("should find .cnx source from header with marker", () => {
29
+ files.set("/project/main.cpp", '#include "led.h"\nint main() {}');
30
+ files.set(
31
+ "/project/led.h",
32
+ `/**
33
+ * Generated by C-Next Transpiler from: led.cnx
34
+ */
35
+ void led_on(void);`,
36
+ );
37
+ files.set("/project/led.cnx", "scope LED { public void on() {} }");
38
+
39
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
40
+ const result = scanner.scan("/project/main.cpp");
41
+
42
+ expect(result.cnextSources).toHaveLength(1);
43
+ expect(result.cnextSources[0]).toBe("/project/led.cnx");
44
+ });
45
+
46
+ it("should return empty when no C-Next markers found", () => {
47
+ files.set("/project/main.cpp", '#include "utils.h"\nint main() {}');
48
+ files.set("/project/utils.h", "void utils_init(void);");
49
+
50
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
51
+ const result = scanner.scan("/project/main.cpp");
52
+
53
+ expect(result.cnextSources).toHaveLength(0);
54
+ expect(result.noCNextFound).toBe(true);
55
+ });
56
+
57
+ it("should follow transitive includes through .cnx sources", () => {
58
+ files.set("/project/main.cpp", '#include "middleware.h"');
59
+ files.set(
60
+ "/project/middleware.h",
61
+ `/**
62
+ * Generated by C-Next Transpiler from: middleware.cnx
63
+ */`,
64
+ );
65
+ files.set("/project/middleware.cnx", '#include "led.h"');
66
+ files.set(
67
+ "/project/led.h",
68
+ `/**
69
+ * Generated by C-Next Transpiler from: led.cnx
70
+ */`,
71
+ );
72
+ files.set("/project/led.cnx", "scope LED {}");
73
+
74
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
75
+ const result = scanner.scan("/project/main.cpp");
76
+
77
+ expect(result.cnextSources).toContain("/project/middleware.cnx");
78
+ expect(result.cnextSources).toContain("/project/led.cnx");
79
+ });
80
+
81
+ it("should report error when marker points to missing .cnx", () => {
82
+ files.set("/project/main.cpp", '#include "led.h"');
83
+ files.set(
84
+ "/project/led.h",
85
+ `/**
86
+ * Generated by C-Next Transpiler from: led.cnx
87
+ */`,
88
+ );
89
+ // led.cnx does NOT exist
90
+
91
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
92
+ const result = scanner.scan("/project/main.cpp");
93
+
94
+ expect(result.errors).toHaveLength(1);
95
+ expect(result.errors[0]).toContain("led.cnx");
96
+ expect(result.errors[0]).toContain("led.h");
97
+ });
98
+
99
+ it("should handle cycle detection", () => {
100
+ files.set("/project/main.cpp", '#include "a.h"');
101
+ files.set(
102
+ "/project/a.h",
103
+ `/**
104
+ * Generated by C-Next Transpiler from: a.cnx
105
+ */`,
106
+ );
107
+ files.set("/project/a.cnx", '#include "b.h"');
108
+ files.set(
109
+ "/project/b.h",
110
+ `/**
111
+ * Generated by C-Next Transpiler from: b.cnx
112
+ */`,
113
+ );
114
+ files.set("/project/b.cnx", '#include "a.h"'); // cycle back to a.h
115
+
116
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
117
+ const result = scanner.scan("/project/main.cpp");
118
+
119
+ // Should not infinite loop
120
+ expect(result.cnextSources).toContain("/project/a.cnx");
121
+ expect(result.cnextSources).toContain("/project/b.cnx");
122
+ });
123
+
124
+ it("should handle multiple includes in one file", () => {
125
+ files.set(
126
+ "/project/main.cpp",
127
+ `#include "led.h"
128
+ #include "motor.h"
129
+ int main() {}`,
130
+ );
131
+ files.set(
132
+ "/project/led.h",
133
+ `/**
134
+ * Generated by C-Next Transpiler from: led.cnx
135
+ */`,
136
+ );
137
+ files.set("/project/led.cnx", "scope LED {}");
138
+ files.set(
139
+ "/project/motor.h",
140
+ `/**
141
+ * Generated by C-Next Transpiler from: motor.cnx
142
+ */`,
143
+ );
144
+ files.set("/project/motor.cnx", "scope Motor {}");
145
+
146
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
147
+ const result = scanner.scan("/project/main.cpp");
148
+
149
+ expect(result.cnextSources).toHaveLength(2);
150
+ expect(result.cnextSources).toContain("/project/led.cnx");
151
+ expect(result.cnextSources).toContain("/project/motor.cnx");
152
+ });
153
+
154
+ it("should skip system includes", () => {
155
+ files.set(
156
+ "/project/main.cpp",
157
+ `#include <stdio.h>
158
+ #include "led.h"
159
+ int main() {}`,
160
+ );
161
+ files.set(
162
+ "/project/led.h",
163
+ `/**
164
+ * Generated by C-Next Transpiler from: led.cnx
165
+ */`,
166
+ );
167
+ files.set("/project/led.cnx", "scope LED {}");
168
+
169
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
170
+ const result = scanner.scan("/project/main.cpp");
171
+
172
+ // Should only find led.cnx, not fail on stdio.h
173
+ expect(result.cnextSources).toHaveLength(1);
174
+ expect(result.cnextSources[0]).toBe("/project/led.cnx");
175
+ });
176
+
177
+ it("should scan non-C-Next headers for nested includes", () => {
178
+ files.set("/project/main.cpp", '#include "wrapper.h"');
179
+ files.set("/project/wrapper.h", '#include "led.h"\nvoid wrapper_init();');
180
+ files.set(
181
+ "/project/led.h",
182
+ `/**
183
+ * Generated by C-Next Transpiler from: led.cnx
184
+ */`,
185
+ );
186
+ files.set("/project/led.cnx", "scope LED {}");
187
+
188
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
189
+ const result = scanner.scan("/project/main.cpp");
190
+
191
+ expect(result.cnextSources).toHaveLength(1);
192
+ expect(result.cnextSources[0]).toBe("/project/led.cnx");
193
+ });
194
+
195
+ it("should handle old-style C-Next markers without source path", () => {
196
+ files.set("/project/main.cpp", '#include "led.h"');
197
+ files.set(
198
+ "/project/led.h",
199
+ `/**
200
+ * Generated by C-Next Transpiler
201
+ */
202
+ void led_on(void);`,
203
+ );
204
+
205
+ const scanner = new CppEntryPointScanner(["/project"], mockFs);
206
+ const result = scanner.scan("/project/main.cpp");
207
+
208
+ // Old marker without source path - should be treated as C-Next generated
209
+ // but without a source path, we can't add it to cnextSources
210
+ expect(result.cnextSources).toHaveLength(0);
211
+ // Should not follow includes from old-style C-Next headers (they're leaf nodes)
212
+ });
213
+
214
+ it("should find .cnx source via search paths when --header-out is used", () => {
215
+ // Simulate --header-out: headers in include/, sources in src/
216
+ files.set("/project/main.cpp", '#include "led.h"');
217
+ files.set(
218
+ "/project/include/led.h",
219
+ `/**
220
+ * Generated by C-Next Transpiler from: led.cnx
221
+ */
222
+ void led_on(void);`,
223
+ );
224
+ // Source is in src/, not include/
225
+ files.set("/project/src/led.cnx", "scope LED { public void on() {} }");
226
+
227
+ // Scanner search paths include src/
228
+ const scanner = new CppEntryPointScanner(
229
+ ["/project/include", "/project/src"],
230
+ mockFs,
231
+ );
232
+ const result = scanner.scan("/project/main.cpp");
233
+
234
+ expect(result.cnextSources).toHaveLength(1);
235
+ expect(result.cnextSources[0]).toBe("/project/src/led.cnx");
236
+ expect(result.errors).toHaveLength(0);
237
+ });
238
+ });
239
+ });
@@ -2,7 +2,6 @@
2
2
  * Unit tests for FileDiscovery
3
3
  *
4
4
  * Tests file discovery functionality:
5
- * - discover: Scan directories for source files
6
5
  * - discoverFile: Discover single file
7
6
  * - discoverFiles: Discover multiple files
8
7
  * - filterByType, getCNextFiles, getHeaderFiles: Filter utilities
@@ -37,11 +36,6 @@ describe("FileDiscovery", () => {
37
36
  // Create source files
38
37
  writeFileSync(join(srcDir, "impl.c"), "int main() {}");
39
38
  writeFileSync(join(srcDir, "impl.cpp"), "int main() {}");
40
-
41
- // Create nested structure
42
- mkdirSync(join(srcDir, "modules"), { recursive: true });
43
- writeFileSync(join(srcDir, "modules", "display.cnx"), "scope Display {}");
44
- writeFileSync(join(srcDir, "modules", "io.cnx"), "scope IO {}");
45
39
  });
46
40
 
47
41
  afterEach(() => {
@@ -51,140 +45,6 @@ describe("FileDiscovery", () => {
51
45
  vi.restoreAllMocks();
52
46
  });
53
47
 
54
- // ==========================================================================
55
- // discover
56
- // ==========================================================================
57
-
58
- describe("discover", () => {
59
- it("discovers all files in directory recursively by default", () => {
60
- const files = FileDiscovery.discover([srcDir]);
61
-
62
- // Should find main.cnx, utils.cnx, legacy.cnext, impl.c, impl.cpp,
63
- // modules/display.cnx, modules/io.cnx
64
- expect(files.length).toBeGreaterThanOrEqual(5);
65
- expect(files.some((f) => f.path.endsWith("main.cnx"))).toBe(true);
66
- expect(files.some((f) => f.path.endsWith("display.cnx"))).toBe(true);
67
- });
68
-
69
- it("discovers files non-recursively when recursive=false", () => {
70
- const files = FileDiscovery.discover([srcDir], { recursive: false });
71
-
72
- // Should find files in srcDir but not in modules/
73
- expect(files.some((f) => f.path.endsWith("main.cnx"))).toBe(true);
74
- expect(files.some((f) => f.path.endsWith("display.cnx"))).toBe(false);
75
- });
76
-
77
- it("filters by extensions when specified", () => {
78
- const files = FileDiscovery.discover([srcDir], { extensions: [".cnx"] });
79
-
80
- // Should only find .cnx files
81
- expect(files.every((f) => f.extension === ".cnx")).toBe(true);
82
- expect(files.some((f) => f.path.endsWith("impl.c"))).toBe(false);
83
- });
84
-
85
- it("discovers files from multiple directories", () => {
86
- const files = FileDiscovery.discover([srcDir, includeDir]);
87
-
88
- expect(files.some((f) => f.path.endsWith("main.cnx"))).toBe(true);
89
- expect(files.some((f) => f.path.endsWith("types.h"))).toBe(true);
90
- });
91
-
92
- it("removes duplicates from overlapping directories (Issue #331)", () => {
93
- // srcDir is already under testDir
94
- const files = FileDiscovery.discover([testDir, srcDir]);
95
-
96
- // Count files with same path
97
- const paths = files.map((f) => f.path);
98
- const uniquePaths = new Set(paths);
99
-
100
- expect(paths.length).toBe(uniquePaths.size);
101
- });
102
-
103
- it("classifies C-Next files correctly", () => {
104
- const files = FileDiscovery.discover([srcDir], { extensions: [".cnx"] });
105
-
106
- const cnxFile = files.find((f) => f.path.endsWith("main.cnx"));
107
- expect(cnxFile).toBeDefined();
108
- expect(cnxFile!.type).toBe(EFileType.CNext);
109
- expect(cnxFile!.extension).toBe(".cnx");
110
- });
111
-
112
- it("classifies .cnext files as C-Next", () => {
113
- const files = FileDiscovery.discover([srcDir], {
114
- extensions: [".cnext"],
115
- });
116
-
117
- const cnextFile = files.find((f) => f.path.endsWith("legacy.cnext"));
118
- expect(cnextFile).toBeDefined();
119
- expect(cnextFile!.type).toBe(EFileType.CNext);
120
- });
121
-
122
- it("classifies header files correctly", () => {
123
- const files = FileDiscovery.discover([includeDir]);
124
-
125
- const hFile = files.find((f) => f.path.endsWith("types.h"));
126
- expect(hFile).toBeDefined();
127
- expect(hFile!.type).toBe(EFileType.CHeader);
128
-
129
- const hppFile = files.find((f) => f.path.endsWith("utils.hpp"));
130
- expect(hppFile).toBeDefined();
131
- expect(hppFile!.type).toBe(EFileType.CppHeader);
132
- });
133
-
134
- it("classifies source files correctly", () => {
135
- const files = FileDiscovery.discover([srcDir]);
136
-
137
- const cFile = files.find((f) => f.path.endsWith("impl.c"));
138
- expect(cFile).toBeDefined();
139
- expect(cFile!.type).toBe(EFileType.CSource);
140
-
141
- const cppFile = files.find((f) => f.path.endsWith("impl.cpp"));
142
- expect(cppFile).toBeDefined();
143
- expect(cppFile!.type).toBe(EFileType.CppSource);
144
- });
145
-
146
- it("warns when directory not found", () => {
147
- const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
148
-
149
- const files = FileDiscovery.discover([join(testDir, "nonexistent")]);
150
-
151
- expect(files).toHaveLength(0);
152
- expect(consoleSpy).toHaveBeenCalledWith(
153
- expect.stringContaining("Directory not found"),
154
- );
155
- });
156
-
157
- it("uses default ignore patterns", () => {
158
- // Create node_modules directory (should be ignored)
159
- mkdirSync(join(srcDir, "node_modules"), { recursive: true });
160
- writeFileSync(join(srcDir, "node_modules", "dep.cnx"), "// dep");
161
-
162
- const files = FileDiscovery.discover([srcDir]);
163
-
164
- expect(files.some((f) => f.path.includes("node_modules"))).toBe(false);
165
- });
166
-
167
- it("uses custom exclude patterns when provided", () => {
168
- // Create a test directory that would normally be included
169
- mkdirSync(join(srcDir, "excluded"), { recursive: true });
170
- writeFileSync(join(srcDir, "excluded", "skip.cnx"), "// skip");
171
-
172
- const files = FileDiscovery.discover([srcDir], {
173
- excludePatterns: [/excluded/],
174
- });
175
-
176
- expect(files.some((f) => f.path.includes("excluded"))).toBe(false);
177
- });
178
-
179
- it("returns absolute paths", () => {
180
- const files = FileDiscovery.discover([srcDir]);
181
-
182
- for (const file of files) {
183
- expect(file.path.startsWith("/")).toBe(true);
184
- }
185
- });
186
- });
187
-
188
48
  // ==========================================================================
189
49
  // discoverFile
190
50
  // ==========================================================================
@@ -282,7 +142,14 @@ describe("FileDiscovery", () => {
282
142
 
283
143
  describe("filterByType", () => {
284
144
  it("filters files by C-Next type", () => {
285
- const allFiles = FileDiscovery.discover([testDir]);
145
+ const allFiles = FileDiscovery.discoverFiles([
146
+ join(srcDir, "main.cnx"),
147
+ join(srcDir, "utils.cnx"),
148
+ join(srcDir, "legacy.cnext"),
149
+ join(srcDir, "impl.c"),
150
+ join(srcDir, "impl.cpp"),
151
+ join(includeDir, "types.h"),
152
+ ]);
286
153
  const cnxFiles = FileDiscovery.filterByType(allFiles, EFileType.CNext);
287
154
 
288
155
  expect(cnxFiles.every((f) => f.type === EFileType.CNext)).toBe(true);
@@ -290,16 +157,21 @@ describe("FileDiscovery", () => {
290
157
  });
291
158
 
292
159
  it("filters files by C header type", () => {
293
- const allFiles = FileDiscovery.discover([testDir]);
160
+ const allFiles = FileDiscovery.discoverFiles([
161
+ join(srcDir, "main.cnx"),
162
+ join(includeDir, "types.h"),
163
+ join(includeDir, "utils.hpp"),
164
+ ]);
294
165
  const hFiles = FileDiscovery.filterByType(allFiles, EFileType.CHeader);
295
166
 
296
167
  expect(hFiles.every((f) => f.type === EFileType.CHeader)).toBe(true);
297
168
  });
298
169
 
299
170
  it("returns empty array when no matches", () => {
300
- const cnxFiles = FileDiscovery.discover([srcDir], {
301
- extensions: [".cnx"],
302
- });
171
+ const cnxFiles = FileDiscovery.discoverFiles([
172
+ join(srcDir, "main.cnx"),
173
+ join(srcDir, "utils.cnx"),
174
+ ]);
303
175
  const hFiles = FileDiscovery.filterByType(cnxFiles, EFileType.CHeader);
304
176
 
305
177
  expect(hFiles).toHaveLength(0);
@@ -312,7 +184,13 @@ describe("FileDiscovery", () => {
312
184
 
313
185
  describe("getCNextFiles", () => {
314
186
  it("returns only C-Next files", () => {
315
- const allFiles = FileDiscovery.discover([testDir]);
187
+ const allFiles = FileDiscovery.discoverFiles([
188
+ join(srcDir, "main.cnx"),
189
+ join(srcDir, "utils.cnx"),
190
+ join(srcDir, "legacy.cnext"),
191
+ join(srcDir, "impl.c"),
192
+ join(includeDir, "types.h"),
193
+ ]);
316
194
  const cnxFiles = FileDiscovery.getCNextFiles(allFiles);
317
195
 
318
196
  expect(cnxFiles.every((f) => f.type === EFileType.CNext)).toBe(true);
@@ -320,7 +198,11 @@ describe("FileDiscovery", () => {
320
198
  });
321
199
 
322
200
  it("includes both .cnx and .cnext files", () => {
323
- const allFiles = FileDiscovery.discover([srcDir]);
201
+ const allFiles = FileDiscovery.discoverFiles([
202
+ join(srcDir, "main.cnx"),
203
+ join(srcDir, "utils.cnx"),
204
+ join(srcDir, "legacy.cnext"),
205
+ ]);
324
206
  const cnxFiles = FileDiscovery.getCNextFiles(allFiles);
325
207
 
326
208
  // Should include both main.cnx and legacy.cnext
@@ -335,7 +217,12 @@ describe("FileDiscovery", () => {
335
217
 
336
218
  describe("getHeaderFiles", () => {
337
219
  it("returns C and C++ header files", () => {
338
- const allFiles = FileDiscovery.discover([includeDir]);
220
+ const allFiles = FileDiscovery.discoverFiles([
221
+ join(includeDir, "types.h"),
222
+ join(includeDir, "utils.hpp"),
223
+ join(includeDir, "config.hxx"),
224
+ join(srcDir, "impl.c"),
225
+ ]);
339
226
  const headerFiles = FileDiscovery.getHeaderFiles(allFiles);
340
227
 
341
228
  expect(headerFiles.length).toBeGreaterThan(0);
@@ -347,7 +234,11 @@ describe("FileDiscovery", () => {
347
234
  });
348
235
 
349
236
  it("includes .h, .hpp, and .hxx files", () => {
350
- const allFiles = FileDiscovery.discover([includeDir]);
237
+ const allFiles = FileDiscovery.discoverFiles([
238
+ join(includeDir, "types.h"),
239
+ join(includeDir, "utils.hpp"),
240
+ join(includeDir, "config.hxx"),
241
+ ]);
351
242
  const headerFiles = FileDiscovery.getHeaderFiles(allFiles);
352
243
 
353
244
  expect(headerFiles.some((f) => f.extension === ".h")).toBe(true);
@@ -356,52 +247,15 @@ describe("FileDiscovery", () => {
356
247
  });
357
248
 
358
249
  it("excludes source files", () => {
359
- const allFiles = FileDiscovery.discover([srcDir]);
250
+ const allFiles = FileDiscovery.discoverFiles([
251
+ join(srcDir, "impl.c"),
252
+ join(srcDir, "impl.cpp"),
253
+ join(includeDir, "types.h"),
254
+ ]);
360
255
  const headerFiles = FileDiscovery.getHeaderFiles(allFiles);
361
256
 
362
257
  expect(headerFiles.some((f) => f.extension === ".c")).toBe(false);
363
258
  expect(headerFiles.some((f) => f.extension === ".cpp")).toBe(false);
364
259
  });
365
260
  });
366
-
367
- // ==========================================================================
368
- // Edge cases
369
- // ==========================================================================
370
-
371
- describe("edge cases", () => {
372
- it("handles empty directory", () => {
373
- const emptyDir = join(testDir, "empty");
374
- mkdirSync(emptyDir, { recursive: true });
375
-
376
- const files = FileDiscovery.discover([emptyDir]);
377
-
378
- expect(files).toHaveLength(0);
379
- });
380
-
381
- it("handles unknown file extensions", () => {
382
- writeFileSync(join(srcDir, "readme.txt"), "text file");
383
-
384
- const files = FileDiscovery.discover([srcDir], {
385
- extensions: [".txt"],
386
- });
387
-
388
- // File should be discovered but with Unknown type
389
- expect(files).toHaveLength(1);
390
- expect(files[0].type).toBe(EFileType.Unknown);
391
- });
392
-
393
- it("handles mixed case extensions", () => {
394
- // Note: fast-glob behavior varies by filesystem
395
- // On case-sensitive filesystems, .CNX won't match .cnx pattern
396
- writeFileSync(join(srcDir, "test-upper.cnx"), "// test file");
397
-
398
- const files = FileDiscovery.discover([srcDir]);
399
-
400
- // Extension should be lowercased
401
- const testFile = files.find((f) => f.path.endsWith("test-upper.cnx"));
402
- expect(testFile).toBeDefined();
403
- expect(testFile!.extension).toBe(".cnx");
404
- expect(testFile!.type).toBe(EFileType.CNext);
405
- });
406
- });
407
261
  });