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.
- package/README.md +80 -649
- package/dist/index.js +7387 -6211
- package/dist/index.js.map +4 -4
- package/grammar/C.g4 +9 -3
- package/package.json +1 -2
- package/src/__tests__/index.test.ts +1 -1
- package/src/cli/CleanCommand.ts +8 -12
- package/src/cli/Cli.ts +29 -6
- package/src/cli/Runner.ts +42 -62
- package/src/cli/__tests__/CleanCommand.test.ts +10 -10
- package/src/cli/__tests__/Cli.test.ts +59 -7
- package/src/cli/__tests__/ConfigPrinter.test.ts +12 -12
- package/src/cli/__tests__/PathNormalizer.test.ts +5 -5
- package/src/cli/__tests__/Runner.test.ts +108 -82
- package/src/cli/serve/ServeCommand.ts +1 -1
- package/src/cli/types/ICliConfig.ts +2 -2
- package/src/lib/parseWithSymbols.ts +21 -21
- package/src/transpiler/Transpiler.ts +88 -43
- package/src/transpiler/__tests__/DualCodePaths.test.ts +29 -29
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +244 -72
- package/src/transpiler/__tests__/Transpiler.test.ts +32 -72
- package/src/transpiler/__tests__/determineProjectRoot.test.ts +30 -28
- package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +1 -1
- package/src/transpiler/data/CNextMarkerDetector.ts +34 -0
- package/src/transpiler/data/CppEntryPointScanner.ts +174 -0
- package/src/transpiler/data/FileDiscovery.ts +2 -105
- package/src/transpiler/data/InputExpansion.ts +37 -81
- package/src/transpiler/data/__tests__/CNextMarkerDetector.test.ts +62 -0
- package/src/transpiler/data/__tests__/CppEntryPointScanner.test.ts +239 -0
- package/src/transpiler/data/__tests__/FileDiscovery.test.ts +45 -191
- package/src/transpiler/data/__tests__/InputExpansion.test.ts +36 -204
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +2 -2
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +4 -5
- package/src/transpiler/logic/parser/c/grammar/C.interp +19 -1
- package/src/transpiler/logic/parser/c/grammar/C.tokens +231 -213
- package/src/transpiler/logic/parser/c/grammar/CLexer.interp +28 -1
- package/src/transpiler/logic/parser/c/grammar/CLexer.tokens +231 -213
- package/src/transpiler/logic/parser/c/grammar/CLexer.ts +654 -600
- package/src/transpiler/logic/parser/c/grammar/CParser.ts +1175 -1099
- package/src/transpiler/logic/symbols/SymbolTable.ts +19 -7
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +78 -0
- package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +6 -6
- package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +28 -27
- package/src/transpiler/logic/symbols/cnext/index.ts +4 -4
- package/src/transpiler/logic/symbols/cnext/utils/SymbolNameUtils.ts +5 -5
- package/src/transpiler/output/codegen/CodeGenerator.ts +7 -1
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +15 -0
- package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +3 -3
- package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +14 -14
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
- package/src/transpiler/output/codegen/utils/QualifiedNameGenerator.ts +7 -7
- package/src/transpiler/output/codegen/utils/__tests__/QualifiedNameGenerator.test.ts +3 -3
- package/src/transpiler/output/headers/BaseHeaderGenerator.ts +10 -1
- package/src/transpiler/output/headers/HeaderGenerator.ts +3 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +6 -2
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +16 -0
- package/src/transpiler/output/headers/adapters/HeaderSymbolAdapter.ts +19 -19
- package/src/transpiler/output/headers/adapters/__tests__/HeaderSymbolAdapter.test.ts +5 -5
- package/src/transpiler/state/SymbolRegistry.ts +10 -12
- package/src/transpiler/state/__tests__/SymbolRegistry.test.ts +11 -13
- package/src/transpiler/types/IPipelineFile.ts +3 -0
- package/src/transpiler/types/ITranspilerConfig.ts +2 -2
- package/src/transpiler/types/symbols/IScopeSymbol.ts +1 -1
- package/src/utils/FunctionUtils.ts +3 -3
- package/src/utils/__tests__/FunctionUtils.test.ts +6 -4
- 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.
|
|
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.
|
|
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.
|
|
301
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
});
|