c-next 0.2.2 → 0.2.4

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 (48) hide show
  1. package/README.md +59 -57
  2. package/dist/index.js +641 -191
  3. package/dist/index.js.map +4 -4
  4. package/package.json +1 -1
  5. package/src/cli/Runner.ts +1 -1
  6. package/src/cli/__tests__/Runner.test.ts +8 -8
  7. package/src/cli/serve/ServeCommand.ts +29 -9
  8. package/src/transpiler/Transpiler.ts +105 -200
  9. package/src/transpiler/__tests__/DualCodePaths.test.ts +117 -68
  10. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +87 -51
  11. package/src/transpiler/__tests__/Transpiler.test.ts +150 -48
  12. package/src/transpiler/__tests__/determineProjectRoot.test.ts +2 -2
  13. package/src/transpiler/data/IncludeResolver.ts +11 -3
  14. package/src/transpiler/data/__tests__/IncludeResolver.test.ts +2 -2
  15. package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +346 -0
  16. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +170 -14
  17. package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +545 -0
  18. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +327 -0
  19. package/src/transpiler/logic/analysis/runAnalyzers.ts +9 -2
  20. package/src/transpiler/logic/analysis/types/IArrayIndexTypeError.ts +15 -0
  21. package/src/transpiler/logic/symbols/TransitiveEnumCollector.ts +1 -1
  22. package/src/transpiler/logic/symbols/c/index.ts +50 -1
  23. package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +99 -2
  24. package/src/transpiler/logic/symbols/c/utils/__tests__/DeclaratorUtils.test.ts +128 -0
  25. package/src/transpiler/output/codegen/CodeGenerator.ts +31 -5
  26. package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
  27. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +49 -36
  28. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
  29. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +90 -25
  30. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +3 -1
  31. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +43 -29
  32. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -2
  33. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +23 -14
  34. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +10 -7
  35. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
  36. package/src/transpiler/output/codegen/generators/statements/SwitchGenerator.ts +10 -1
  37. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
  38. package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +6 -3
  39. package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +36 -22
  40. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +9 -1
  41. package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +8 -6
  42. package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +34 -18
  43. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
  44. package/src/transpiler/state/CodeGenState.ts +6 -0
  45. package/src/transpiler/types/IPipelineFile.ts +2 -2
  46. package/src/transpiler/types/IPipelineInput.ts +1 -1
  47. package/src/transpiler/types/TTranspileInput.ts +18 -0
  48. package/src/utils/constants/TypeConstants.ts +22 -0
@@ -16,8 +16,8 @@ describe("Transpiler", () => {
16
16
  mockFs = new MockFileSystem();
17
17
  });
18
18
 
19
- describe("transpileSource (primary unit testing target)", () => {
20
- // transpileSource is the main API that benefits from MockFileSystem
19
+ describe("transpile source mode (primary unit testing target)", () => {
20
+ // transpile({ kind: 'source' }) is the main API that benefits from MockFileSystem
21
21
  // because it doesn't rely on FileDiscovery for the main code path
22
22
 
23
23
  it("transpiles source string without file I/O", async () => {
@@ -26,9 +26,12 @@ describe("Transpiler", () => {
26
26
  mockFs,
27
27
  );
28
28
 
29
- const result = await transpiler.transpileSource(
30
- "u32 add(u32 a, u32 b) { return a + b; }",
31
- );
29
+ const result = (
30
+ await transpiler.transpile({
31
+ kind: "source",
32
+ source: "u32 add(u32 a, u32 b) { return a + b; }",
33
+ })
34
+ ).files[0];
32
35
 
33
36
  expect(result.success).toBe(true);
34
37
  expect(result.code).toContain("uint32_t");
@@ -41,7 +44,10 @@ describe("Transpiler", () => {
41
44
  mockFs,
42
45
  );
43
46
 
44
- const result = await transpiler.transpileSource("@@@invalid");
47
+ const result = await transpiler.transpile({
48
+ kind: "source",
49
+ source: "@@@invalid",
50
+ });
45
51
 
46
52
  expect(result.success).toBe(false);
47
53
  expect(result.errors.length).toBeGreaterThan(0);
@@ -53,11 +59,16 @@ describe("Transpiler", () => {
53
59
  mockFs,
54
60
  );
55
61
 
56
- const result = await transpiler.transpileSource(`
62
+ const result = (
63
+ await transpiler.transpile({
64
+ kind: "source",
65
+ source: `
57
66
  scope API {
58
67
  public void doSomething() { }
59
68
  }
60
- `);
69
+ `,
70
+ })
71
+ ).files[0];
61
72
 
62
73
  expect(result.success).toBe(true);
63
74
  expect(result.headerCode).toBeDefined();
@@ -70,9 +81,12 @@ describe("Transpiler", () => {
70
81
  mockFs,
71
82
  );
72
83
 
73
- const result = await transpiler.transpileSource(
74
- "void privateFunc() { }",
75
- );
84
+ const result = (
85
+ await transpiler.transpile({
86
+ kind: "source",
87
+ source: "void privateFunc() { }",
88
+ })
89
+ ).files[0];
76
90
 
77
91
  expect(result.success).toBe(true);
78
92
  expect(result.headerCode).toBeUndefined();
@@ -84,9 +98,12 @@ describe("Transpiler", () => {
84
98
  mockFs,
85
99
  );
86
100
 
87
- const result = await transpiler.transpileSource(
88
- "u32 test() { return 42; }",
89
- );
101
+ const result = (
102
+ await transpiler.transpile({
103
+ kind: "source",
104
+ source: "u32 test() { return 42; }",
105
+ })
106
+ ).files[0];
90
107
 
91
108
  expect(result.success).toBe(true);
92
109
  expect(result.code).toBe("");
@@ -99,9 +116,12 @@ describe("Transpiler", () => {
99
116
  );
100
117
 
101
118
  // This should parse but might have semantic issues
102
- const result = await transpiler.transpileSource(
103
- "void test() { undefinedVar <- 5; }",
104
- );
119
+ const result = (
120
+ await transpiler.transpile({
121
+ kind: "source",
122
+ source: "void test() { undefinedVar <- 5; }",
123
+ })
124
+ ).files[0];
105
125
 
106
126
  // Should still succeed (undefined vars become C identifiers)
107
127
  expect(result.success).toBe(true);
@@ -113,9 +133,12 @@ describe("Transpiler", () => {
113
133
  mockFs,
114
134
  );
115
135
 
116
- const result = await transpiler.transpileSource(
117
- `void test() {\n u32 large <- 1000;\n u8 small <- large;\n}`,
118
- );
136
+ const result = (
137
+ await transpiler.transpile({
138
+ kind: "source",
139
+ source: `void test() {\n u32 large <- 1000;\n u8 small <- large;\n}`,
140
+ })
141
+ ).files[0];
119
142
 
120
143
  expect(result.success).toBe(false);
121
144
  expect(result.errors).toHaveLength(1);
@@ -131,9 +154,12 @@ describe("Transpiler", () => {
131
154
  );
132
155
 
133
156
  // Ternary with bare variable produces error without line prefix
134
- const result = await transpiler.transpileSource(
135
- `void test() { u32 x <- 5; u32 r <- (x) ? 1 : 0; }`,
136
- );
157
+ const result = (
158
+ await transpiler.transpile({
159
+ kind: "source",
160
+ source: `void test() { u32 x <- 5; u32 r <- (x) ? 1 : 0; }`,
161
+ })
162
+ ).files[0];
137
163
 
138
164
  expect(result.success).toBe(false);
139
165
  expect(result.errors[0].line).toBe(1);
@@ -147,7 +173,10 @@ describe("Transpiler", () => {
147
173
  mockFs,
148
174
  );
149
175
 
150
- const result = await transpiler.transpileSource(`
176
+ const result = (
177
+ await transpiler.transpile({
178
+ kind: "source",
179
+ source: `
151
180
  u8 byte <- 255;
152
181
  u16 word <- 65535;
153
182
  u32 dword <- 0xFFFFFFFF;
@@ -156,7 +185,9 @@ describe("Transpiler", () => {
156
185
  i32 sdword <- -1;
157
186
  f32 floatVal <- 3.14;
158
187
  f64 doubleVal <- 2.718;
159
- `);
188
+ `,
189
+ })
190
+ ).files[0];
160
191
 
161
192
  expect(result.success).toBe(true);
162
193
  expect(result.code).toContain("uint8_t");
@@ -175,12 +206,17 @@ describe("Transpiler", () => {
175
206
  mockFs,
176
207
  );
177
208
 
178
- const result = await transpiler.transpileSource(`
209
+ const result = (
210
+ await transpiler.transpile({
211
+ kind: "source",
212
+ source: `
179
213
  void test() {
180
214
  u32 x;
181
215
  x <- 42;
182
216
  }
183
- `);
217
+ `,
218
+ })
219
+ ).files[0];
184
220
 
185
221
  expect(result.success).toBe(true);
186
222
  expect(result.code).toContain("x = 42");
@@ -192,11 +228,16 @@ describe("Transpiler", () => {
192
228
  mockFs,
193
229
  );
194
230
 
195
- const result = await transpiler.transpileSource(`
231
+ const result = (
232
+ await transpiler.transpile({
233
+ kind: "source",
234
+ source: `
196
235
  bool test(u32 a, u32 b) {
197
236
  return a = b;
198
237
  }
199
- `);
238
+ `,
239
+ })
240
+ ).files[0];
200
241
 
201
242
  expect(result.success).toBe(true);
202
243
  expect(result.code).toContain("a == b");
@@ -208,12 +249,17 @@ describe("Transpiler", () => {
208
249
  mockFs,
209
250
  );
210
251
 
211
- const result = await transpiler.transpileSource(`
252
+ const result = (
253
+ await transpiler.transpile({
254
+ kind: "source",
255
+ source: `
212
256
  struct Point {
213
257
  i32 x;
214
258
  i32 y;
215
259
  }
216
- `);
260
+ `,
261
+ })
262
+ ).files[0];
217
263
 
218
264
  expect(result.success).toBe(true);
219
265
  expect(result.code).toContain("typedef struct");
@@ -227,13 +273,18 @@ describe("Transpiler", () => {
227
273
  mockFs,
228
274
  );
229
275
 
230
- const result = await transpiler.transpileSource(`
276
+ const result = (
277
+ await transpiler.transpile({
278
+ kind: "source",
279
+ source: `
231
280
  enum Color {
232
281
  RED,
233
282
  GREEN,
234
283
  BLUE
235
284
  }
236
- `);
285
+ `,
286
+ })
287
+ ).files[0];
237
288
 
238
289
  expect(result.success).toBe(true);
239
290
  expect(result.code).toContain("typedef enum");
@@ -248,12 +299,17 @@ describe("Transpiler", () => {
248
299
  mockFs,
249
300
  );
250
301
 
251
- const result = await transpiler.transpileSource(`
302
+ const result = (
303
+ await transpiler.transpile({
304
+ kind: "source",
305
+ source: `
252
306
  scope LED {
253
307
  public void on() { }
254
308
  public void off() { }
255
309
  }
256
- `);
310
+ `,
311
+ })
312
+ ).files[0];
257
313
 
258
314
  expect(result.success).toBe(true);
259
315
  expect(result.code).toContain("LED_on");
@@ -277,7 +333,7 @@ describe("Transpiler", () => {
277
333
  mockFs,
278
334
  );
279
335
 
280
- const result = await transpiler.run();
336
+ const result = await transpiler.transpile({ kind: "files" });
281
337
 
282
338
  expect(result.success).toBe(true);
283
339
  expect(result.filesProcessed).toBe(1);
@@ -300,7 +356,7 @@ describe("Transpiler", () => {
300
356
  mockFs,
301
357
  );
302
358
 
303
- await transpiler.run();
359
+ await transpiler.transpile({ kind: "files" });
304
360
 
305
361
  const mkdirCalls = mockFs.getMkdirLog();
306
362
  expect(mkdirCalls.some((c) => c.path === "/project/build")).toBe(true);
@@ -319,7 +375,7 @@ describe("Transpiler", () => {
319
375
  mockFs,
320
376
  );
321
377
 
322
- await transpiler.run();
378
+ await transpiler.transpile({ kind: "files" });
323
379
 
324
380
  const mkdirCalls = mockFs.getMkdirLog();
325
381
  expect(mkdirCalls.some((c) => c.path === "/project/include")).toBe(
@@ -346,7 +402,7 @@ describe("Transpiler", () => {
346
402
  mockFs,
347
403
  );
348
404
 
349
- const result = await transpiler.run();
405
+ const result = await transpiler.transpile({ kind: "files" });
350
406
 
351
407
  expect(result.success).toBe(true);
352
408
 
@@ -365,7 +421,7 @@ describe("Transpiler", () => {
365
421
  mockFs,
366
422
  );
367
423
 
368
- const result = await transpiler.run();
424
+ const result = await transpiler.transpile({ kind: "files" });
369
425
 
370
426
  expect(result.success).toBe(false);
371
427
  expect(result.errors[0].message).toContain("Input not found");
@@ -382,7 +438,7 @@ describe("Transpiler", () => {
382
438
  mockFs,
383
439
  );
384
440
 
385
- const result = await transpiler.run();
441
+ const result = await transpiler.transpile({ kind: "files" });
386
442
 
387
443
  expect(result.warnings).toContain("No C-Next source files found");
388
444
  });
@@ -398,7 +454,7 @@ describe("Transpiler", () => {
398
454
  mockFs,
399
455
  );
400
456
 
401
- const result = await transpiler.run();
457
+ const result = await transpiler.transpile({ kind: "files" });
402
458
 
403
459
  expect(result.success).toBe(false);
404
460
  expect(result.errors.length).toBeGreaterThan(0);
@@ -429,7 +485,7 @@ describe("Transpiler", () => {
429
485
  noCache: true,
430
486
  });
431
487
 
432
- const result = await transpiler.run();
488
+ const result = await transpiler.transpile({ kind: "files" });
433
489
 
434
490
  expect(result.success).toBe(true);
435
491
  expect(result.filesProcessed).toBe(1);
@@ -445,7 +501,7 @@ describe("Transpiler", () => {
445
501
  noCache: true,
446
502
  });
447
503
 
448
- const result = await transpiler.run();
504
+ const result = await transpiler.transpile({ kind: "files" });
449
505
 
450
506
  expect(result.success).toBe(false);
451
507
  expect(result.errors.length).toBeGreaterThan(0);
@@ -461,7 +517,7 @@ describe("Transpiler", () => {
461
517
  noCache: true,
462
518
  });
463
519
 
464
- const result = await transpiler.run();
520
+ const result = await transpiler.transpile({ kind: "files" });
465
521
 
466
522
  expect(result.success).toBe(false);
467
523
  expect(result.errors.length).toBeGreaterThan(0);
@@ -478,7 +534,7 @@ describe("Transpiler", () => {
478
534
  noCache: true,
479
535
  });
480
536
 
481
- const result = await transpiler.run();
537
+ const result = await transpiler.transpile({ kind: "files" });
482
538
 
483
539
  expect(result.success).toBe(false);
484
540
  expect(result.errors.length).toBeGreaterThan(0);
@@ -495,7 +551,7 @@ describe("Transpiler", () => {
495
551
  noCache: true,
496
552
  });
497
553
 
498
- const result = await transpiler.run();
554
+ const result = await transpiler.transpile({ kind: "files" });
499
555
 
500
556
  expect(result.success).toBe(true);
501
557
  expect(result.outputFiles.length).toBeGreaterThan(0);
@@ -518,7 +574,7 @@ describe("Transpiler", () => {
518
574
  noCache: true,
519
575
  });
520
576
 
521
- const result = await transpiler.run();
577
+ const result = await transpiler.transpile({ kind: "files" });
522
578
 
523
579
  expect(result.success).toBe(true);
524
580
  // Should have both .c and .h output
@@ -527,4 +583,50 @@ describe("Transpiler", () => {
527
583
  });
528
584
  });
529
585
  });
586
+
587
+ describe("transpile() unified entry point", () => {
588
+ let mockFs: MockFileSystem;
589
+
590
+ beforeEach(() => {
591
+ mockFs = new MockFileSystem();
592
+ });
593
+
594
+ it("transpile({ kind: 'source' }) returns ITranspilerResult with files[]", async () => {
595
+ const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
596
+
597
+ const result = await transpiler.transpile({
598
+ kind: "source",
599
+ source: "void main() { }",
600
+ });
601
+ expect(result.success).toBe(true);
602
+ expect(result.files).toHaveLength(1);
603
+ expect(result.files[0].code).toContain("int main");
604
+ });
605
+
606
+ it("transpile({ kind: 'source' }) returns header in files[0].headerCode", async () => {
607
+ const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
608
+
609
+ const result = await transpiler.transpile({
610
+ kind: "source",
611
+ source: `
612
+ scope API {
613
+ public void doSomething() { }
614
+ }
615
+ `,
616
+ });
617
+ expect(result.success).toBe(true);
618
+ expect(result.files[0].headerCode).toContain("API_doSomething");
619
+ });
620
+
621
+ it("transpile({ kind: 'source' }) returns errors in result", async () => {
622
+ const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
623
+
624
+ const result = await transpiler.transpile({
625
+ kind: "source",
626
+ source: "@@@invalid",
627
+ });
628
+ expect(result.success).toBe(false);
629
+ expect(result.errors.length).toBeGreaterThan(0);
630
+ });
631
+ });
530
632
  });
@@ -422,7 +422,7 @@ describe("Transpiler.determineProjectRoot", () => {
422
422
  });
423
423
 
424
424
  // Run transpiler to trigger cache creation
425
- await transpiler.run();
425
+ await transpiler.transpile({ kind: "files" });
426
426
 
427
427
  // Cache directory should be in project root, not src dir
428
428
  expect(existsSync(join(projectDir, ".cnx"))).toBe(true);
@@ -440,7 +440,7 @@ describe("Transpiler.determineProjectRoot", () => {
440
440
  noCache: true,
441
441
  });
442
442
 
443
- await transpiler.run();
443
+ await transpiler.transpile({ kind: "files" });
444
444
 
445
445
  // No cache directory should be created
446
446
  expect(existsSync(join(projectDir, ".cnx"))).toBe(false);
@@ -37,7 +37,7 @@ interface IResolvedIncludes {
37
37
  * Unified include resolution for the C-Next Pipeline
38
38
  *
39
39
  * This class encapsulates the complete include resolution workflow,
40
- * used by both `run()` (CLI) and `transpileSource()` (API) paths.
40
+ * used by the unified `transpile()` entry point for both file and source modes.
41
41
  *
42
42
  * Key responsibilities:
43
43
  * - Extract #include directives from source content
@@ -161,6 +161,14 @@ class IncludeResolver {
161
161
 
162
162
  if (file.type === EFileType.CNext) {
163
163
  result.cnextIncludes.push(file);
164
+ // Issue #854: Track header directive for cnext includes so their types
165
+ // can be mapped by ExternalTypeHeaderBuilder, preventing duplicate
166
+ // forward declarations (MISRA Rule 5.6)
167
+ const headerPath = includeInfo.path.replace(/\.cnx$|\.cnext$/, ".h");
168
+ const directive = includeInfo.isLocal
169
+ ? `#include "${headerPath}"`
170
+ : `#include <${headerPath}>`;
171
+ result.headerIncludeDirectives.set(absolutePath, directive);
164
172
  }
165
173
  }
166
174
 
@@ -351,8 +359,8 @@ class IncludeResolver {
351
359
  /**
352
360
  * Build search paths from a source file location
353
361
  *
354
- * Consolidates the search path building logic used by both `run()` and
355
- * `transpileSource()` code paths.
362
+ * Consolidates the search path building logic used by the unified
363
+ * transpile() entry point.
356
364
  *
357
365
  * Search order (highest to lowest priority):
358
366
  * 1. Source file's directory (for relative includes)
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Unit tests for IncludeResolver
3
3
  *
4
- * Tests the unified include resolution logic used by both
5
- * Pipeline.run() and Pipeline.transpileSource().
4
+ * Tests the unified include resolution logic used by
5
+ * the transpile() entry point for both file and source modes.
6
6
  */
7
7
  import { describe, it, expect, beforeEach, afterEach } from "vitest";
8
8
  import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";