c-next 0.2.9 → 0.2.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 (60) hide show
  1. package/dist/index.js +814 -206
  2. package/dist/index.js.map +4 -4
  3. package/package.json +1 -1
  4. package/src/transpiler/Transpiler.ts +97 -7
  5. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +170 -0
  6. package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +246 -0
  7. package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +12 -1
  8. package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +172 -0
  9. package/src/transpiler/logic/symbols/SymbolTable.ts +84 -0
  10. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +43 -0
  11. package/src/transpiler/logic/symbols/__tests__/TransitiveEnumCollector.test.ts +1 -0
  12. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +98 -1
  13. package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +23 -5
  14. package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +69 -2
  15. package/src/transpiler/logic/symbols/c/index.ts +85 -30
  16. package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +18 -0
  17. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
  18. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
  19. package/src/transpiler/output/codegen/CodeGenerator.ts +23 -1
  20. package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
  21. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +19 -14
  22. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
  23. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
  24. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
  25. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
  26. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
  27. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
  28. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
  29. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
  30. package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
  31. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +14 -0
  32. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +17 -2
  33. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +49 -0
  34. package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
  35. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +5 -0
  36. package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +16 -0
  37. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +41 -5
  38. package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
  39. package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
  40. package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
  41. package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
  42. package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
  43. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -4
  44. package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
  45. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +1 -0
  46. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
  47. package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
  48. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
  49. package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
  50. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
  51. package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
  52. package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
  53. package/src/transpiler/state/CodeGenState.ts +49 -0
  54. package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
  55. package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
  56. package/src/transpiler/types/ICachedFileEntry.ts +2 -0
  57. package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
  58. package/src/utils/BitUtils.ts +33 -4
  59. package/src/utils/cache/CacheManager.ts +9 -1
  60. package/src/utils/cache/__tests__/CacheManager.test.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "packageManager": "npm@11.9.0",
6
6
  "type": "module",
@@ -214,7 +214,8 @@ class Transpiler {
214
214
  result: ITranspilerResult,
215
215
  ): Promise<void> {
216
216
  // Stage 2: Collect symbols from C/C++ headers and build analyzer context
217
- this._collectAllHeaderSymbols(input.headerFiles, result);
217
+ // Issue #945: Now async for preprocessing support
218
+ await this._collectAllHeaderSymbols(input.headerFiles, result);
218
219
  CodeGenState.buildExternalStructFields();
219
220
 
220
221
  // Stage 3: Collect symbols from C-Next files
@@ -365,6 +366,16 @@ class Transpiler {
365
366
  );
366
367
  }
367
368
 
369
+ // Issue #948: Merge opaque types from C/C++ headers
370
+ // These are forward-declared struct types that need pointer semantics
371
+ const externalOpaqueTypes = CodeGenState.symbolTable.getAllOpaqueTypes();
372
+ if (externalOpaqueTypes.length > 0) {
373
+ symbolInfo = TSymbolInfoAdapter.mergeOpaqueTypes(
374
+ symbolInfo,
375
+ externalOpaqueTypes,
376
+ );
377
+ }
378
+
368
379
  // Make symbols available to analyzers (CodeGenerator.generate() sets this too)
369
380
  CodeGenState.symbols = symbolInfo;
370
381
 
@@ -596,14 +607,15 @@ class Transpiler {
596
607
 
597
608
  /**
598
609
  * Stage 2: Collect symbols from all C/C++ headers
610
+ * Issue #945: Made async for preprocessing support.
599
611
  */
600
- private _collectAllHeaderSymbols(
612
+ private async _collectAllHeaderSymbols(
601
613
  headerFiles: IDiscoveredFile[],
602
614
  result: ITranspilerResult,
603
- ): void {
615
+ ): Promise<void> {
604
616
  for (const file of headerFiles) {
605
617
  try {
606
- this.doCollectHeaderSymbols(file);
618
+ await this.doCollectHeaderSymbols(file);
607
619
  result.filesProcessed++;
608
620
  } catch (err) {
609
621
  this.warnings.push(`Failed to process header ${file.path}: ${err}`);
@@ -989,9 +1001,10 @@ class Transpiler {
989
1001
  /**
990
1002
  * Stage 2: Collect symbols from a single C/C++ header
991
1003
  * Issue #592: Recursive include processing moved to IncludeResolver.resolveHeadersTransitively()
1004
+ * Issue #945: Added preprocessing support for conditional compilation
992
1005
  * SonarCloud S3776: Refactored to use helper methods for reduced complexity.
993
1006
  */
994
- private doCollectHeaderSymbols(file: IDiscoveredFile): void {
1007
+ private async doCollectHeaderSymbols(file: IDiscoveredFile): Promise<void> {
995
1008
  // Track as processed (for cycle detection)
996
1009
  const absolutePath = resolve(file.path);
997
1010
  this.state.markHeaderProcessed(absolutePath);
@@ -1001,8 +1014,8 @@ class Transpiler {
1001
1014
  return; // Cache hit - skip full parsing
1002
1015
  }
1003
1016
 
1004
- // Read content and parse
1005
- const content = this.fs.readFile(file.path);
1017
+ // Issue #945: Preprocess header to evaluate #if/#ifdef directives
1018
+ const content = await this.getHeaderContent(file);
1006
1019
  this.parseHeaderFile(file, content);
1007
1020
 
1008
1021
  // Debug: Show symbols found
@@ -1043,12 +1056,89 @@ class Transpiler {
1043
1056
  );
1044
1057
  CodeGenState.symbolTable.restoreEnumBitWidths(cached.enumBitWidth);
1045
1058
 
1059
+ // Issue #948: Restore opaque types (forward-declared structs)
1060
+ CodeGenState.symbolTable.restoreOpaqueTypes(cached.opaqueTypes);
1061
+
1046
1062
  // Issue #211: Still check for C++ syntax even on cache hit
1047
1063
  this.detectCppFromFileType(file);
1048
1064
 
1049
1065
  return true;
1050
1066
  }
1051
1067
 
1068
+ /**
1069
+ * Get header content, optionally preprocessed.
1070
+ * Issue #945: Evaluates #if/#ifdef directives using system preprocessor.
1071
+ *
1072
+ * Only preprocesses when necessary to avoid side effects from full expansion.
1073
+ * Preprocessing is needed when the file has conditional compilation patterns
1074
+ * like #if MACRO != 0 that require expression evaluation.
1075
+ */
1076
+ private async getHeaderContent(file: IDiscoveredFile): Promise<string> {
1077
+ const rawContent = this.fs.readFile(file.path);
1078
+
1079
+ // Check if preprocessing is disabled
1080
+ if (this.config.preprocess === false) {
1081
+ return rawContent;
1082
+ }
1083
+
1084
+ // Check if preprocessing is available
1085
+ if (!this.preprocessor.isAvailable()) {
1086
+ return rawContent;
1087
+ }
1088
+
1089
+ // Issue #945: Only preprocess if file has conditional compilation patterns
1090
+ // that require expression evaluation (e.g., #if MACRO != 0, #if MACRO == 1)
1091
+ // Simple #ifdef/#ifndef patterns are already handled by the parser
1092
+ if (!this.needsConditionalPreprocessing(rawContent)) {
1093
+ return rawContent;
1094
+ }
1095
+
1096
+ // Preprocess the header file
1097
+ const result = await this.preprocessor.preprocess(file.path, {
1098
+ defines: this.config.defines,
1099
+ includePaths: this.config.includeDirs,
1100
+ keepLineDirectives: false, // We don't need line mappings for symbol collection
1101
+ });
1102
+
1103
+ if (!result.success) {
1104
+ // Log warning but fall back to raw content
1105
+ this.warnings.push(
1106
+ `Preprocessing failed for ${file.path}: ${result.error}. Using raw content.`,
1107
+ );
1108
+ return rawContent;
1109
+ }
1110
+
1111
+ return result.content;
1112
+ }
1113
+
1114
+ /**
1115
+ * Check if a header file needs conditional preprocessing.
1116
+ * Issue #945: Only preprocess files with #if expressions that need evaluation.
1117
+ */
1118
+ private needsConditionalPreprocessing(content: string): boolean {
1119
+ // Patterns that require the preprocessor for expression evaluation:
1120
+ // - #if MACRO != 0
1121
+ // - #if MACRO == 1
1122
+ // - #if MACRO > 0
1123
+ // - #if MACRO (bare macro as truthy check)
1124
+ // - #elif MACRO != 0
1125
+ // - #if defined(X) && MACRO
1126
+ // - etc.
1127
+ //
1128
+ // Simple patterns handled by the parser without preprocessing:
1129
+ // - #ifdef MACRO
1130
+ // - #ifndef MACRO
1131
+ // - #if defined(MACRO) (single defined check)
1132
+ // - #if 1
1133
+ // - #if 0
1134
+ //
1135
+ // Look for #if/#elif followed by an expression (not just defined() or 0/1)
1136
+ // Also match bare macro names used as truthy checks (common in config headers)
1137
+ const ifExpressionPattern =
1138
+ /#(?:if|elif)\s+(?!defined\s*\()(?![01]\s*(?:$|\n|\/\*|\/\/))\w+/m;
1139
+ return ifExpressionPattern.test(content);
1140
+ }
1141
+
1052
1142
  /**
1053
1143
  * Restore cached symbols to the symbol table.
1054
1144
  * ADR-055 Phase 7: Converts ISerializedSymbol[] from cache to typed symbols.
@@ -1239,4 +1239,174 @@ describe("Transpiler coverage integration tests", () => {
1239
1239
  expect(result.outputFiles.some((f) => f.endsWith(".c"))).toBe(true);
1240
1240
  expect(result.outputFiles.some((f) => f.endsWith(".h"))).toBe(true);
1241
1241
  });
1242
+
1243
+ // ==========================================================================
1244
+ // Issue #945: getHeaderContent preprocessing tests
1245
+ // ==========================================================================
1246
+
1247
+ it("skips preprocessing when preprocess config is false", async () => {
1248
+ const srcDir = join(testDir, "src");
1249
+ const includeDir = join(testDir, "include");
1250
+ mkdirSync(srcDir, { recursive: true });
1251
+ mkdirSync(includeDir, { recursive: true });
1252
+
1253
+ // Create a header with conditional that would trigger preprocessing
1254
+ writeFileSync(
1255
+ join(includeDir, "config.h"),
1256
+ `
1257
+ #if FEATURE_ENABLED != 0
1258
+ void enabled_func(void);
1259
+ #endif
1260
+ void always_available(void);
1261
+ `,
1262
+ );
1263
+
1264
+ writeFileSync(
1265
+ join(srcDir, "main.cnx"),
1266
+ `
1267
+ #include "config.h"
1268
+ void test() { always_available(); }
1269
+ `,
1270
+ );
1271
+
1272
+ const transpiler = new Transpiler({
1273
+ inputs: [join(srcDir, "main.cnx")],
1274
+ includeDirs: [includeDir],
1275
+ preprocess: false, // Explicitly disable preprocessing
1276
+ noCache: true,
1277
+ });
1278
+
1279
+ const result = await transpiler.transpile({ kind: "files" });
1280
+ expect(result.success).toBe(true);
1281
+ });
1282
+
1283
+ it("handles header without conditional patterns (no preprocessing needed)", async () => {
1284
+ const srcDir = join(testDir, "src");
1285
+ const includeDir = join(testDir, "include");
1286
+ mkdirSync(srcDir, { recursive: true });
1287
+ mkdirSync(includeDir, { recursive: true });
1288
+
1289
+ // Create a simple header without #if MACRO patterns
1290
+ writeFileSync(
1291
+ join(includeDir, "simple.h"),
1292
+ `
1293
+ #ifndef SIMPLE_H
1294
+ #define SIMPLE_H
1295
+ void simple_func(void);
1296
+ #endif
1297
+ `,
1298
+ );
1299
+
1300
+ writeFileSync(
1301
+ join(srcDir, "main.cnx"),
1302
+ `
1303
+ #include "simple.h"
1304
+ void test() { simple_func(); }
1305
+ `,
1306
+ );
1307
+
1308
+ const transpiler = new Transpiler({
1309
+ inputs: [join(srcDir, "main.cnx")],
1310
+ includeDirs: [includeDir],
1311
+ noCache: true,
1312
+ });
1313
+
1314
+ const result = await transpiler.transpile({ kind: "files" });
1315
+ expect(result.success).toBe(true);
1316
+ });
1317
+
1318
+ it("preprocesses header with conditional patterns when preprocessor available", async () => {
1319
+ const srcDir = join(testDir, "src");
1320
+ const includeDir = join(testDir, "include");
1321
+ mkdirSync(srcDir, { recursive: true });
1322
+ mkdirSync(includeDir, { recursive: true });
1323
+
1324
+ // Create a config header that defines the feature
1325
+ writeFileSync(
1326
+ join(includeDir, "config.h"),
1327
+ `
1328
+ #ifndef CONFIG_H
1329
+ #define CONFIG_H
1330
+ #define MY_FEATURE 1
1331
+ #endif
1332
+ `,
1333
+ );
1334
+
1335
+ // Create a header with #if MACRO != 0 pattern that needs preprocessing
1336
+ writeFileSync(
1337
+ join(includeDir, "feature.h"),
1338
+ `
1339
+ #ifndef FEATURE_H
1340
+ #define FEATURE_H
1341
+ #include "config.h"
1342
+
1343
+ void always_available(void);
1344
+
1345
+ #if MY_FEATURE != 0
1346
+ void feature_func(void);
1347
+ #endif
1348
+ #endif
1349
+ `,
1350
+ );
1351
+
1352
+ writeFileSync(
1353
+ join(srcDir, "main.cnx"),
1354
+ `
1355
+ #include "feature.h"
1356
+ void test() { always_available(); }
1357
+ `,
1358
+ );
1359
+
1360
+ const transpiler = new Transpiler({
1361
+ inputs: [join(srcDir, "main.cnx")],
1362
+ includeDirs: [includeDir],
1363
+ noCache: true,
1364
+ });
1365
+
1366
+ const result = await transpiler.transpile({ kind: "files" });
1367
+ expect(result.success).toBe(true);
1368
+ });
1369
+
1370
+ it("falls back to raw content when preprocessing fails (invalid include)", async () => {
1371
+ const srcDir = join(testDir, "src");
1372
+ const includeDir = join(testDir, "include");
1373
+ mkdirSync(srcDir, { recursive: true });
1374
+ mkdirSync(includeDir, { recursive: true });
1375
+
1376
+ // Create a header that includes a non-existent file (will cause preprocessing to fail)
1377
+ writeFileSync(
1378
+ join(includeDir, "broken.h"),
1379
+ `
1380
+ #ifndef BROKEN_H
1381
+ #define BROKEN_H
1382
+ #include "nonexistent_config.h"
1383
+
1384
+ #if SOME_MACRO != 0
1385
+ void some_func(void);
1386
+ #endif
1387
+
1388
+ void available_func(void);
1389
+ #endif
1390
+ `,
1391
+ );
1392
+
1393
+ writeFileSync(
1394
+ join(srcDir, "main.cnx"),
1395
+ `
1396
+ #include "broken.h"
1397
+ void test() { available_func(); }
1398
+ `,
1399
+ );
1400
+
1401
+ const transpiler = new Transpiler({
1402
+ inputs: [join(srcDir, "main.cnx")],
1403
+ includeDirs: [includeDir],
1404
+ noCache: true,
1405
+ });
1406
+
1407
+ // Should succeed even if preprocessing fails (falls back to raw content)
1408
+ const result = await transpiler.transpile({ kind: "files" });
1409
+ // The transpiler should still succeed and add a warning
1410
+ expect(result.success).toBe(true);
1411
+ });
1242
1412
  });
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Unit tests for needsConditionalPreprocessing detection logic
3
+ * Issue #945: Tests the regex pattern that determines when header files
4
+ * need preprocessing for conditional compilation evaluation.
5
+ *
6
+ * Since needsConditionalPreprocessing is a private method, we test it
7
+ * via a subclass that exposes it for testing purposes.
8
+ */
9
+
10
+ import { describe, it, expect } from "vitest";
11
+ import Transpiler from "../Transpiler";
12
+ import MockFileSystem from "./MockFileSystem";
13
+
14
+ /**
15
+ * Test subclass that exposes the private needsConditionalPreprocessing method
16
+ */
17
+ class TestableTranspiler extends Transpiler {
18
+ testNeedsConditionalPreprocessing(content: string): boolean {
19
+ // Access private method via type casting
20
+ return (
21
+ this as unknown as {
22
+ needsConditionalPreprocessing: (s: string) => boolean;
23
+ }
24
+ ).needsConditionalPreprocessing(content);
25
+ }
26
+ }
27
+
28
+ describe("needsConditionalPreprocessing", () => {
29
+ let transpiler: TestableTranspiler;
30
+
31
+ beforeEach(() => {
32
+ transpiler = new TestableTranspiler(
33
+ { inputs: [], noCache: true },
34
+ new MockFileSystem(),
35
+ );
36
+ });
37
+
38
+ describe("patterns that SHOULD trigger preprocessing", () => {
39
+ it("detects #if MACRO != 0", () => {
40
+ const content = `
41
+ #if FEATURE_LABEL != 0
42
+ void create_label(void);
43
+ #endif
44
+ `;
45
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
46
+ });
47
+
48
+ it("detects #if MACRO == 1", () => {
49
+ const content = `
50
+ #if DEBUG_ENABLED == 1
51
+ void debug_log(const char* msg);
52
+ #endif
53
+ `;
54
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
55
+ });
56
+
57
+ it("detects #if MACRO > 0", () => {
58
+ const content = `
59
+ #if LOG_LEVEL > 0
60
+ void log_debug(const char* msg);
61
+ #endif
62
+ `;
63
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
64
+ });
65
+
66
+ it("detects bare #if MACRO (truthy check)", () => {
67
+ const content = `
68
+ #if FEATURE_ENABLED
69
+ void do_feature(void);
70
+ #endif
71
+ `;
72
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
73
+ });
74
+
75
+ it("detects #elif MACRO != 0", () => {
76
+ const content = `
77
+ #if FEATURE_A
78
+ void feature_a(void);
79
+ #elif FEATURE_B != 0
80
+ void feature_b(void);
81
+ #endif
82
+ `;
83
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
84
+ });
85
+
86
+ it("detects #elif with bare macro", () => {
87
+ const content = `
88
+ #ifdef FEATURE_A
89
+ void feature_a(void);
90
+ #elif FEATURE_B
91
+ void feature_b(void);
92
+ #endif
93
+ `;
94
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
95
+ });
96
+
97
+ it("detects #if with arithmetic expression", () => {
98
+ const content = `
99
+ #if MAJOR_VERSION >= 2
100
+ void new_api(void);
101
+ #endif
102
+ `;
103
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
104
+ });
105
+
106
+ it("does not trigger for #if defined(X) && macro (starts with defined)", () => {
107
+ // The regex is conservative: if the line starts with defined(),
108
+ // we assume the parser can handle it. Complex expressions like
109
+ // defined(X) && MACRO > 1 are rare enough that this trade-off is acceptable.
110
+ const content = `
111
+ #if defined(FEATURE_X) && FEATURE_LEVEL > 1
112
+ void advanced_feature(void);
113
+ #endif
114
+ `;
115
+ // Current regex doesn't match this because it starts with defined()
116
+ // This is a known limitation, but acceptable for most real-world headers
117
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
118
+ });
119
+ });
120
+
121
+ describe("patterns that should NOT trigger preprocessing", () => {
122
+ it("does not trigger for #ifdef MACRO", () => {
123
+ const content = `
124
+ #ifdef FEATURE_ENABLED
125
+ void do_feature(void);
126
+ #endif
127
+ `;
128
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
129
+ });
130
+
131
+ it("does not trigger for #ifndef MACRO", () => {
132
+ const content = `
133
+ #ifndef HEADER_GUARD_H
134
+ #define HEADER_GUARD_H
135
+ void foo(void);
136
+ #endif
137
+ `;
138
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
139
+ });
140
+
141
+ it("does not trigger for #if defined(MACRO)", () => {
142
+ const content = `
143
+ #if defined(FEATURE_X)
144
+ void feature_x(void);
145
+ #endif
146
+ `;
147
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
148
+ });
149
+
150
+ it("does not trigger for #if 0", () => {
151
+ const content = `
152
+ #if 0
153
+ // disabled code
154
+ void old_api(void);
155
+ #endif
156
+ `;
157
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
158
+ });
159
+
160
+ it("does not trigger for #if 1", () => {
161
+ const content = `
162
+ #if 1
163
+ void always_enabled(void);
164
+ #endif
165
+ `;
166
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
167
+ });
168
+
169
+ it("does not trigger for plain declarations without conditionals", () => {
170
+ const content = `
171
+ void foo(void);
172
+ void bar(int x);
173
+ typedef struct widget_s widget_t;
174
+ `;
175
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
176
+ });
177
+
178
+ it("does not trigger for #if 0 with comment", () => {
179
+ const content = `
180
+ #if 0 // disabled
181
+ void disabled(void);
182
+ #endif
183
+ `;
184
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
185
+ });
186
+
187
+ it("does not trigger for #if 1 with comment", () => {
188
+ const content = `
189
+ #if 1 /* always on */
190
+ void enabled(void);
191
+ #endif
192
+ `;
193
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
194
+ });
195
+ });
196
+
197
+ describe("edge cases", () => {
198
+ it("handles mixed ifdef and if MACRO patterns", () => {
199
+ const content = `
200
+ #ifdef PLATFORM_LINUX
201
+ void linux_init(void);
202
+ #endif
203
+
204
+ #if DEBUG_LEVEL != 0
205
+ void debug_init(void);
206
+ #endif
207
+ `;
208
+ // Should trigger because of the #if DEBUG_LEVEL != 0 pattern
209
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
210
+ });
211
+
212
+ it("handles LVGL-style config patterns", () => {
213
+ // Real-world pattern from LVGL headers
214
+ const content = `
215
+ #if LV_USE_LABEL != 0
216
+ lv_obj_t* lv_label_create(lv_obj_t* parent);
217
+ #endif
218
+ `;
219
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
220
+ });
221
+
222
+ it("handles FreeRTOS-style config patterns", () => {
223
+ // Real-world pattern from FreeRTOS headers
224
+ const content = `
225
+ #if configUSE_MUTEXES == 1
226
+ SemaphoreHandle_t xSemaphoreCreateMutex(void);
227
+ #endif
228
+ `;
229
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
230
+ });
231
+
232
+ it("handles whitespace variations in #if", () => {
233
+ const content = `
234
+ #if FEATURE_X
235
+ void feature(void);
236
+ #endif
237
+ `;
238
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
239
+ });
240
+
241
+ it("handles tab in #if directive", () => {
242
+ const content = "#if\tFEATURE_X\nvoid feature(void);\n#endif";
243
+ expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
244
+ });
245
+ });
246
+ });
@@ -127,6 +127,11 @@ class IndexTypeListener extends CNextListener {
127
127
  continue;
128
128
  }
129
129
 
130
+ // Enum types are valid indices (ADR-054: transpile to unsigned constants)
131
+ if (CodeGenState.isKnownEnum(resolvedType)) {
132
+ continue;
133
+ }
134
+
130
135
  // Other non-integer types (e.g., string, struct) - E0852
131
136
  const { line, column } = ParserUtils.getPosition(ctx);
132
137
  this.analyzer.addError(line, column, "E0852", resolvedType);
@@ -277,7 +282,13 @@ class IndexTypeListener extends CNextListener {
277
282
  if (TypeConstants.SIGNED_TYPES.includes(currentType)) {
278
283
  return "bool";
279
284
  }
280
- // Array element type — strip array suffix
285
+ // Array element type — strip rightmost array dimension
286
+ // e.g., "u8[8]" → "u8", "u8[8][4]" → "u8[8]", "u8[CONST]" → "u8"
287
+ const strippedType = currentType.replace(/\[[^\]]*\]$/, "");
288
+ if (strippedType !== currentType) {
289
+ return strippedType;
290
+ }
291
+ // Not an array type (e.g., struct), return as-is
281
292
  return currentType;
282
293
  }
283
294