@typecad/cuttlefish 1.0.0-alpha.3 → 1.0.0-alpha.7

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 (123) hide show
  1. package/README.md +4 -4
  2. package/dist/api/shared/display-adapter.d.ts +2 -1
  3. package/dist/api/shared/display-adapter.js +8 -1
  4. package/dist/api/shared/display-adapters/sdl.js +9 -2
  5. package/dist/api/shared/display-profile.d.ts +20 -3
  6. package/dist/api/shared/display-profile.js +21 -6
  7. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  8. package/dist/api/shared/framework-manifest-registry.js +25 -0
  9. package/dist/api/shared/framework-manifest.d.ts +462 -0
  10. package/dist/api/shared/framework-manifest.js +149 -0
  11. package/dist/api/shared/glcdfont.d.ts +12 -0
  12. package/dist/api/shared/glcdfont.js +124 -0
  13. package/dist/api/shared/graphics-strategy.d.ts +28 -0
  14. package/dist/api/shared/hal-op-ir.d.ts +427 -1
  15. package/dist/api/shared/hal-op-ir.js +95 -1
  16. package/dist/api/shared/index.d.ts +11 -1
  17. package/dist/api/shared/index.js +14 -0
  18. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  19. package/dist/api/shared/native-display-op-resolver.js +64 -0
  20. package/dist/api/shared/platform-strategy.d.ts +9 -0
  21. package/dist/api/shared/promise-runtime.js +78 -0
  22. package/dist/api/shared/types.d.ts +8 -0
  23. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  24. package/dist/api/shared/validate-framework-manifest.js +417 -0
  25. package/dist/cli-utils.d.ts +1 -0
  26. package/dist/cli-utils.js +3 -1
  27. package/dist/cli.js +175 -4
  28. package/dist/config-loader.js +20 -1
  29. package/dist/config-schema.d.ts +36 -36
  30. package/dist/create/board-spec.d.ts +4 -4
  31. package/dist/create/index.d.ts +1 -1
  32. package/dist/create/index.js +1 -1
  33. package/dist/create/init-scaffold.d.ts +3 -0
  34. package/dist/create/init-scaffold.js +74 -2
  35. package/dist/create/init-templates.d.ts +4 -0
  36. package/dist/create/init-templates.js +217 -17
  37. package/dist/create/init-wizard.js +20 -1
  38. package/dist/emit/cpp-emitter.js +4 -3
  39. package/dist/emit/emitters/emitter-context.d.ts +5 -0
  40. package/dist/emit/emitters/function-emitter-impl.js +69 -59
  41. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  42. package/dist/emit/emitters/output-finalizer.js +21 -11
  43. package/dist/emit/emitters/setup.js +155 -0
  44. package/dist/emit/emitters/top-level-prep.js +2 -0
  45. package/dist/emit/emitters/ui-emitter.js +23 -8
  46. package/dist/emit/expression-renderer.js +10 -1
  47. package/dist/emit/snprintf-helpers.js +8 -0
  48. package/dist/emit/statement-renderer.js +13 -0
  49. package/dist/emit/utils/async-state-machine.js +185 -116
  50. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  51. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  52. package/dist/ir/adc-range-validation.js +26 -25
  53. package/dist/ir/build-ir.js +5 -1
  54. package/dist/ir/expression-to-ir.js +57 -6
  55. package/dist/ir/feature-registry.js +7 -25
  56. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  57. package/dist/ir/hal/hal-emitter.js +40 -12
  58. package/dist/ir/hal/hal-parser.d.ts +6 -0
  59. package/dist/ir/hal/hal-parser.js +74 -0
  60. package/dist/ir/hal/hal-plugins.js +571 -0
  61. package/dist/ir/identifier-collector.js +18 -0
  62. package/dist/ir/interrupt-analysis.js +8 -3
  63. package/dist/ir/memory-budget-validation.js +1 -0
  64. package/dist/ir/network-validation.d.ts +4 -0
  65. package/dist/ir/network-validation.js +184 -0
  66. package/dist/ir/ownership-analysis.js +33 -1
  67. package/dist/ir/peripheral-ownership.js +5 -0
  68. package/dist/ir/peripheral-validation.d.ts +1 -1
  69. package/dist/ir/peripheral-validation.js +6 -3
  70. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  71. package/dist/ir/pin-alias-conflict.js +2 -1
  72. package/dist/ir/pin-capability-validation.js +34 -32
  73. package/dist/ir/pin-mode-validation.js +5 -0
  74. package/dist/ir/pin-safety.d.ts +1 -1
  75. package/dist/ir/pin-safety.js +2 -1
  76. package/dist/ir/program-analysis.d.ts +39 -0
  77. package/dist/ir/program-analysis.js +192 -0
  78. package/dist/ir/pulldown-validation.d.ts +1 -1
  79. package/dist/ir/pulldown-validation.js +2 -1
  80. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  81. package/dist/ir/pwm-timer-sharing.js +2 -1
  82. package/dist/ir/resource-analysis.js +2 -0
  83. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  84. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  85. package/dist/ir/timing-validation.d.ts +6 -1
  86. package/dist/ir/timing-validation.js +51 -12
  87. package/dist/ir/transformers/expressions.js +58 -0
  88. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  89. package/dist/ir/transformers/variables.js +86 -19
  90. package/dist/ir/try-catch-validation.js +2 -0
  91. package/dist/ir/type-resolution.js +2 -2
  92. package/dist/ir/unit-suspicion-validation.js +9 -7
  93. package/dist/ir/validation-orchestrator.js +9 -7
  94. package/dist/libdef/c-to-decl.d.ts +27 -0
  95. package/dist/libdef/c-to-decl.js +397 -0
  96. package/dist/libdef/component-decls.d.ts +2 -0
  97. package/dist/libdef/component-decls.js +6 -0
  98. package/dist/libdef/component-discovery.d.ts +43 -0
  99. package/dist/libdef/component-discovery.js +83 -0
  100. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  101. package/dist/libdef/cpp-to-decl.js +72 -0
  102. package/dist/libdef/idf-discovery.d.ts +7 -0
  103. package/dist/libdef/idf-discovery.js +59 -0
  104. package/dist/libdef/registry.js +5 -2
  105. package/dist/licenses.d.ts +185 -0
  106. package/dist/licenses.js +963 -0
  107. package/dist/lint-cache.d.ts +59 -0
  108. package/dist/lint-cache.js +257 -0
  109. package/dist/orchestrator/graph-builder.js +6 -2
  110. package/dist/stores/display-profile-store.d.ts +1 -0
  111. package/dist/stores/display-profile-store.js +1 -0
  112. package/dist/testing.d.ts +4 -2
  113. package/dist/testing.js +4 -2
  114. package/dist/transpile.d.ts +3 -0
  115. package/dist/transpile.js +78 -32
  116. package/dist/types.d.ts +5 -1
  117. package/dist/ui-hook.d.ts +17 -1
  118. package/dist/utils/cli.js +71 -1
  119. package/dist/utils/fs.d.ts +13 -0
  120. package/dist/utils/fs.js +50 -0
  121. package/package.json +9 -4
  122. package/dist/ir/heap-array-validation.d.ts +0 -24
  123. package/dist/ir/heap-array-validation.js +0 -29
@@ -80,7 +80,7 @@ function findPinsWithCapability(capability, boardConstants) {
80
80
  // ---------------------------------------------------------------------------
81
81
  // Capability check for a single HAL operation
82
82
  // ---------------------------------------------------------------------------
83
- function checkCapability(operation, sourceLine, sourceCol, boardConstants, diagnostics) {
83
+ function checkCapability(operation, sourceLine, sourceCol, filePath, boardConstants, diagnostics) {
84
84
  // Only operations with a pin field are capability-checked.
85
85
  const op = operation.operation;
86
86
  if (!('pin' in operation))
@@ -145,48 +145,49 @@ function checkCapability(operation, sourceLine, sourceCol, boardConstants, diagn
145
145
  hint,
146
146
  line: sourceLine,
147
147
  column: sourceCol,
148
+ filePath,
148
149
  source: 'pin-capability-validation',
149
150
  });
150
151
  }
151
152
  // ---------------------------------------------------------------------------
152
153
  // IR tree scanner
153
154
  // ---------------------------------------------------------------------------
154
- function scanExpression(expr, boardConstants, parentLine, parentCol, diagnostics) {
155
+ function scanExpression(expr, boardConstants, parentLine, parentCol, parentFilePath, diagnostics) {
155
156
  if (!expr || typeof expr !== 'object')
156
157
  return;
157
158
  switch (expr.kind) {
158
159
  case 'binary': {
159
- scanExpression(expr.left, boardConstants, parentLine, parentCol, diagnostics);
160
- scanExpression(expr.right, boardConstants, parentLine, parentCol, diagnostics);
160
+ scanExpression(expr.left, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
161
+ scanExpression(expr.right, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
161
162
  break;
162
163
  }
163
164
  case 'ternary': {
164
- scanExpression(expr.condition, boardConstants, parentLine, parentCol, diagnostics);
165
- scanExpression(expr.whenTrue, boardConstants, parentLine, parentCol, diagnostics);
166
- scanExpression(expr.whenFalse, boardConstants, parentLine, parentCol, diagnostics);
165
+ scanExpression(expr.condition, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
166
+ scanExpression(expr.whenTrue, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
167
+ scanExpression(expr.whenFalse, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
167
168
  break;
168
169
  }
169
170
  case 'property-access': {
170
- scanExpression(expr.object, boardConstants, parentLine, parentCol, diagnostics);
171
+ scanExpression(expr.object, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
171
172
  break;
172
173
  }
173
174
  case 'unary': {
174
- scanExpression(expr.operand, boardConstants, parentLine, parentCol, diagnostics);
175
+ scanExpression(expr.operand, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
175
176
  break;
176
177
  }
177
178
  case 'paren': {
178
- scanExpression(expr.inner, boardConstants, parentLine, parentCol, diagnostics);
179
+ scanExpression(expr.inner, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
179
180
  break;
180
181
  }
181
182
  case 'array': {
182
183
  for (const el of expr.elements) {
183
- scanExpression(el, boardConstants, parentLine, parentCol, diagnostics);
184
+ scanExpression(el, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
184
185
  }
185
186
  break;
186
187
  }
187
188
  case 'object': {
188
189
  for (const field of expr.fields) {
189
- scanExpression(field.value, boardConstants, parentLine, parentCol, diagnostics);
190
+ scanExpression(field.value, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
190
191
  }
191
192
  break;
192
193
  }
@@ -204,43 +205,43 @@ function scanExpression(expr, boardConstants, parentLine, parentCol, diagnostics
204
205
  }
205
206
  case 'method-call': {
206
207
  for (const arg of expr.args) {
207
- scanExpression(arg, boardConstants, parentLine, parentCol, diagnostics);
208
+ scanExpression(arg, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
208
209
  }
209
210
  break;
210
211
  }
211
212
  case 'element-access': {
212
- scanExpression(expr.object, boardConstants, parentLine, parentCol, diagnostics);
213
- scanExpression(expr.index, boardConstants, parentLine, parentCol, diagnostics);
213
+ scanExpression(expr.object, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
214
+ scanExpression(expr.index, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
214
215
  break;
215
216
  }
216
217
  case 'string_concat': {
217
218
  for (const part of expr.parts) {
218
- scanExpression(part, boardConstants, parentLine, parentCol, diagnostics);
219
+ scanExpression(part, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
219
220
  }
220
221
  break;
221
222
  }
222
223
  case 'template_string': {
223
- scanExpression(expr.expression, boardConstants, parentLine, parentCol, diagnostics);
224
+ scanExpression(expr.expression, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
224
225
  break;
225
226
  }
226
227
  case 'spread_array': {
227
- scanExpression(expr.spreadExpr, boardConstants, parentLine, parentCol, diagnostics);
228
+ scanExpression(expr.spreadExpr, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
228
229
  for (const el of expr.additionalElements) {
229
- scanExpression(el, boardConstants, parentLine, parentCol, diagnostics);
230
+ scanExpression(el, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
230
231
  }
231
232
  break;
232
233
  }
233
234
  case 'instanceof': {
234
- scanExpression(expr.object, boardConstants, parentLine, parentCol, diagnostics);
235
+ scanExpression(expr.object, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
235
236
  break;
236
237
  }
237
238
  case 'await': {
238
- scanExpression(expr.value, boardConstants, parentLine, parentCol, diagnostics);
239
+ scanExpression(expr.value, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
239
240
  break;
240
241
  }
241
242
  case 'hal-expr': {
242
243
  // Expression-form HAL operation (e.g. adc.read used as a value).
243
- checkCapability(expr.operation, parentLine, parentCol, boardConstants, diagnostics);
244
+ checkCapability(expr.operation, parentLine, parentCol, parentFilePath, boardConstants, diagnostics);
244
245
  break;
245
246
  }
246
247
  case 'number':
@@ -259,20 +260,21 @@ function scanStatement(stmt, boardConstants, diagnostics) {
259
260
  return;
260
261
  const line = stmt.sourceSpan?.startLine;
261
262
  const col = stmt.sourceSpan?.startColumn;
263
+ const filePath = stmt.sourceSpan?.filePath;
262
264
  switch (stmt.kind) {
263
265
  case 'var_decl': {
264
266
  if (stmt.initializer)
265
- scanExpression(stmt.initializer, boardConstants, line, col, diagnostics);
267
+ scanExpression(stmt.initializer, boardConstants, line, col, filePath, diagnostics);
266
268
  break;
267
269
  }
268
270
  case 'assign': {
269
271
  if (stmt.value)
270
- scanExpression(stmt.value, boardConstants, line, col, diagnostics);
272
+ scanExpression(stmt.value, boardConstants, line, col, filePath, diagnostics);
271
273
  break;
272
274
  }
273
275
  case 'if': {
274
276
  if (stmt.condition)
275
- scanExpression(stmt.condition, boardConstants, line, col, diagnostics);
277
+ scanExpression(stmt.condition, boardConstants, line, col, filePath, diagnostics);
276
278
  for (const s of stmt.thenBranch)
277
279
  scanStatement(s, boardConstants, diagnostics);
278
280
  if (stmt.elseBranch)
@@ -283,14 +285,14 @@ function scanStatement(stmt, boardConstants, diagnostics) {
283
285
  case 'while':
284
286
  case 'do_while': {
285
287
  if (stmt.condition)
286
- scanExpression(stmt.condition, boardConstants, line, col, diagnostics);
288
+ scanExpression(stmt.condition, boardConstants, line, col, filePath, diagnostics);
287
289
  for (const s of stmt.body)
288
290
  scanStatement(s, boardConstants, diagnostics);
289
291
  break;
290
292
  }
291
293
  case 'for': {
292
294
  if (stmt.condition)
293
- scanExpression(stmt.condition, boardConstants, line, col, diagnostics);
295
+ scanExpression(stmt.condition, boardConstants, line, col, filePath, diagnostics);
294
296
  if (stmt.initializer)
295
297
  scanStatement(stmt.initializer, boardConstants, diagnostics);
296
298
  if (stmt.increment)
@@ -309,23 +311,23 @@ function scanStatement(stmt, boardConstants, diagnostics) {
309
311
  }
310
312
  case 'return': {
311
313
  if (stmt.value)
312
- scanExpression(stmt.value, boardConstants, line, col, diagnostics);
314
+ scanExpression(stmt.value, boardConstants, line, col, filePath, diagnostics);
313
315
  break;
314
316
  }
315
317
  case 'call': {
316
318
  for (const arg of stmt.args) {
317
- scanExpression(arg, boardConstants, line, col, diagnostics);
319
+ scanExpression(arg, boardConstants, line, col, filePath, diagnostics);
318
320
  }
319
321
  break;
320
322
  }
321
323
  case 'hal-op': {
322
324
  // Statement-form HAL operation — the primary capability check target.
323
- checkCapability(stmt.operation, line, col, boardConstants, diagnostics);
325
+ checkCapability(stmt.operation, line, col, filePath, boardConstants, diagnostics);
324
326
  break;
325
327
  }
326
328
  case 'switch': {
327
329
  if (stmt.expression)
328
- scanExpression(stmt.expression, boardConstants, line, col, diagnostics);
330
+ scanExpression(stmt.expression, boardConstants, line, col, filePath, diagnostics);
329
331
  for (const c of stmt.cases) {
330
332
  for (const s of c.body)
331
333
  scanStatement(s, boardConstants, diagnostics);
@@ -355,7 +357,7 @@ function scanStatement(stmt, boardConstants, diagnostics) {
355
357
  }
356
358
  case 'throw': {
357
359
  if (stmt.value)
358
- scanExpression(stmt.value, boardConstants, line, col, diagnostics);
360
+ scanExpression(stmt.value, boardConstants, line, col, filePath, diagnostics);
359
361
  break;
360
362
  }
361
363
  case 'update':
@@ -43,6 +43,7 @@ export function validatePinModeConfig(program) {
43
43
  message: `Analog pin '${receiver}' used as digital output. ` +
44
44
  `Analog input capability (ADC) is lost while pin is in OUTPUT mode. ` +
45
45
  `Call ${receiver}.asInput() to restore analog reading.`,
46
+ filePath: program.fileName,
46
47
  code: 'analog-pin-as-output',
47
48
  source: 'pin-mode-validation',
48
49
  });
@@ -55,6 +56,7 @@ export function validatePinModeConfig(program) {
55
56
  severity: 'warning',
56
57
  message: `Pin '${receiver}' read via '${method}()' without prior mode configuration. ` +
57
58
  `Call ${receiver}.asInput() or ${receiver}.inputPullUp() first.`,
59
+ filePath: program.fileName,
58
60
  code: 'pin-mode-not-set',
59
61
  source: 'pin-mode-validation',
60
62
  });
@@ -64,6 +66,7 @@ export function validatePinModeConfig(program) {
64
66
  severity: 'info',
65
67
  message: `Pin '${receiver}' written via '${method}()' without explicit mode configuration. ` +
66
68
  `Arduino implicitly sets OUTPUT, but explicit ${receiver}.asOutput() is recommended.`,
69
+ filePath: program.fileName,
67
70
  code: 'pin-mode-not-set',
68
71
  source: 'pin-mode-validation',
69
72
  });
@@ -100,6 +103,7 @@ export function validatePinModeConfig(program) {
100
103
  severity: 'warning',
101
104
  message: `Pin ${op.pin} read without prior mode configuration. ` +
102
105
  `Call asInput() or inputPullUp() first — reading a floating pin is undefined behavior.`,
106
+ filePath: program.fileName,
103
107
  code: 'pin-mode-not-set',
104
108
  source: 'pin-mode-validation',
105
109
  });
@@ -109,6 +113,7 @@ export function validatePinModeConfig(program) {
109
113
  severity: 'info',
110
114
  message: `Pin ${op.pin} written without explicit mode configuration. ` +
111
115
  `Arduino implicitly sets OUTPUT, but explicit asOutput() is recommended.`,
116
+ filePath: program.fileName,
112
117
  code: 'pin-mode-not-set',
113
118
  source: 'pin-mode-validation',
114
119
  });
@@ -8,4 +8,4 @@ import type { Diagnostic } from '../types.js';
8
8
  * @param boardConstants - Board constants containing pins.unsafe array
9
9
  * @returns Array of diagnostic warnings for unsafe pin usage
10
10
  */
11
- export declare function validateUnsafePins(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
11
+ export declare function validateUnsafePins(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
@@ -30,7 +30,7 @@ function buildUnsafePinMessage(pinName, boardConstants) {
30
30
  * @param boardConstants - Board constants containing pins.unsafe array
31
31
  * @returns Array of diagnostic warnings for unsafe pin usage
32
32
  */
33
- export function validateUnsafePins(usage, boardConstants) {
33
+ export function validateUnsafePins(usage, boardConstants, filePath) {
34
34
  const diagnostics = [];
35
35
  if (!boardConstants)
36
36
  return diagnostics;
@@ -50,6 +50,7 @@ export function validateUnsafePins(usage, boardConstants) {
50
50
  severity: 'warning',
51
51
  message: buildUnsafePinMessage(pinName, boardConstants),
52
52
  code: 'unsafe-pin-usage',
53
+ filePath,
53
54
  source: 'pin-safety',
54
55
  });
55
56
  }
@@ -29,6 +29,22 @@ export interface ProgramAnalysisResult {
29
29
  usesTiming: boolean;
30
30
  usesWDT: boolean;
31
31
  usesStrPtr: boolean;
32
+ usesUart: boolean;
33
+ usesSPI: boolean;
34
+ usesI2C: boolean;
35
+ usesEEPROM: boolean;
36
+ usesTone: boolean;
37
+ /** map()/constrain() Arduino-API calls. framework-avr gates its native
38
+ * _native_map/_native_constrain helpers on these (they're dead code
39
+ * otherwise — no other internal caller references them). */
40
+ usesMap: boolean;
41
+ usesConstrain: boolean;
42
+ /** Comprehensive timing gate for the native millis() Timer0 ISR on AVR.
43
+ * True when the program directly uses millis/delay/micros, OR has hidden
44
+ * consumers of the soft clock: setInterval/setTimeout (timerCallCount),
45
+ * async functions (the runtime polls millis), or a mounted UI (per-frame
46
+ * tick injected by the emitter, not present in user source). */
47
+ usesNativeTiming: boolean;
32
48
  hasSerialBegin: boolean;
33
49
  hasGenerators: boolean;
34
50
  usesStdMap: boolean;
@@ -36,6 +52,29 @@ export interface ProgramAnalysisResult {
36
52
  * __tc_TimerRuntime::MAX_TIMERS to the observed count (floor 1) rather than
37
53
  * a blind constant, so a one-timer program links one slot, not eight. */
38
54
  timerCallCount: number;
55
+ /** ESP32 peripheral usage — framework-esp32 gates its IDF driver blocks and
56
+ * forced includes on these. Detected from HAL-op operation names, the same
57
+ * way usesUart/usesSPI/usesI2C are. Other frameworks have no CUTTLEFISH_*
58
+ * blocks with these marker names so the setup.ts filters are no-ops there. */
59
+ usesGPIO: boolean;
60
+ usesPWM: boolean;
61
+ usesRmt: boolean;
62
+ usesADC: boolean;
63
+ usesDAC: boolean;
64
+ usesPower: boolean;
65
+ usesWdt: boolean;
66
+ usesInterrupts: boolean;
67
+ usesPulse: boolean;
68
+ usesShift: boolean;
69
+ /** WiFi / HTTP / BLE usage — framework-esp32 gates the __tc_wifi/__tc_http/
70
+ * __tc_ble runtime shims and their esp_wifi/esp_http_client/nimble includes
71
+ * on these. Detected from wifi.* / http.* / ble.* HAL-op operation names. */
72
+ usesWifi: boolean;
73
+ usesHttp: boolean;
74
+ usesBle: boolean;
75
+ /** Preferences (NVS) usage — framework-esp32 gates the __tc_prefs runtime shim
76
+ * and its nvs_flash/nvs includes on this. Detected from preferences.* ops. */
77
+ usesPreferences: boolean;
39
78
  }
40
79
  /**
41
80
  * Analyze a program IR in a single pass to detect all features.
@@ -102,6 +102,30 @@ function analyzeExpression(expr, result, strategy) {
102
102
  if (expr.callee.startsWith("WDT.") || expr.callee === "WDT") {
103
103
  result.usesWDT = true;
104
104
  }
105
+ // Native AVR peripheral usage from namespace-prefixed method calls
106
+ // (Serial.* / SPI.* / Wire.* / EEPROM.*). The HAL resolver lowers these
107
+ // to structured hal-ops (detected in analyzeStatement) OR to bare
108
+ // lowered calls; these checks cover the pre-lowering and direct forms.
109
+ if (expr.callee.startsWith("Serial.") || expr.callee === "Serial") {
110
+ result.usesUart = true;
111
+ }
112
+ if (expr.callee.startsWith("SPI.") || expr.callee === "SPI") {
113
+ result.usesSPI = true;
114
+ }
115
+ if (expr.callee.startsWith("Wire.") || expr.callee === "Wire") {
116
+ result.usesI2C = true;
117
+ }
118
+ if (expr.callee.startsWith("EEPROM.") || expr.callee === "EEPROM") {
119
+ result.usesEEPROM = true;
120
+ }
121
+ // map()/constrain() Arduino-API calls appear as method-call exprs
122
+ // (callee "map"/"constrain"). framework-avr lowers these to _native_map/
123
+ // _native_constrain via symbol aliases, so the helpers are dead code
124
+ // unless the program actually calls them.
125
+ if (expr.callee === "map")
126
+ result.usesMap = true;
127
+ if (expr.callee === "constrain")
128
+ result.usesConstrain = true;
105
129
  // Count setInterval/setTimeout call sites (post-rename callee names) so
106
130
  // __tc_TimerRuntime::MAX_TIMERS can be sized to the observed count.
107
131
  if (expr.callee === "__tc_setInterval" || expr.callee === "__tc_setTimeout"
@@ -192,6 +216,23 @@ function analyzeExpression(expr, result, strategy) {
192
216
  analyzeExpression(expr.expression, result, strategy);
193
217
  }
194
218
  break;
219
+ case "hal-expr":
220
+ // Value-position HAL ops (e.g. `const ip = WiFi.localIP()` lowers to a
221
+ // hal-expr initializer) never pass through the statement-level hal-op
222
+ // detection above, so mirror the wifi./http. flag detection here. These
223
+ // gate the framework-esp32 __tc_wifi/__tc_http runtime shims.
224
+ if (expr.operation && typeof expr.operation.operation === "string") {
225
+ const opName = expr.operation.operation;
226
+ if (opName.startsWith("wifi."))
227
+ result.usesWifi = true;
228
+ if (opName.startsWith("http."))
229
+ result.usesHttp = true;
230
+ if (opName.startsWith("ble."))
231
+ result.usesBle = true;
232
+ if (opName.startsWith("preferences."))
233
+ result.usesPreferences = true;
234
+ }
235
+ break;
195
236
  }
196
237
  }
197
238
  /**
@@ -218,6 +259,11 @@ function analyzeStatement(statement, result, strategy) {
218
259
  if (statement.callee === "millis" || statement.callee === "delay") {
219
260
  result.usesMillis = true;
220
261
  }
262
+ // Awaited HAL wait markers become async state-machine poll states that
263
+ // arm deadlines via currentTimeMillis().
264
+ if (statement.callee === "__WIFI_WAIT__" || statement.callee === "__HTTP_WAIT__" || statement.callee === "__HAL_WAIT__") {
265
+ result.usesMillis = true;
266
+ }
221
267
  // Namespace-qualified polyfill entry points used as bare call statements
222
268
  // (e.g. `Timing.delay(5);`). The expression-level analyzer (case
223
269
  // "method-call") already checks these prefixes, but a statement-form call
@@ -234,6 +280,35 @@ function analyzeStatement(statement, result, strategy) {
234
280
  if (statement.callee.startsWith("WDT.") || statement.callee === "WDT") {
235
281
  result.usesWDT = true;
236
282
  }
283
+ // Native AVR peripheral usage from statement-form calls. tone()/noTone()
284
+ // are bare Arduino-API calls; console.* / Serial.* drive UART; the
285
+ // namespace prefixes mirror the method-call checks above.
286
+ if (statement.callee === "tone" || statement.callee === "noTone") {
287
+ result.usesTone = true;
288
+ }
289
+ // console.* is NOT UART: on ESP-IDF it lowers to printf, on Arduino to
290
+ // Serial via hasConsoleCalls. Only real UART HAL / Serial peripheral
291
+ // usage should gate the uart shim (avoids unused __tc_uart*_init).
292
+ if (statement.callee.startsWith("_uart_")) {
293
+ result.usesUart = true;
294
+ }
295
+ if (statement.callee.startsWith("Serial.") || statement.callee === "Serial") {
296
+ result.usesUart = true;
297
+ }
298
+ if (statement.callee.startsWith("SPI.") || statement.callee === "SPI") {
299
+ result.usesSPI = true;
300
+ }
301
+ if (statement.callee.startsWith("Wire.") || statement.callee === "Wire") {
302
+ result.usesI2C = true;
303
+ }
304
+ if (statement.callee.startsWith("EEPROM.") || statement.callee === "EEPROM") {
305
+ result.usesEEPROM = true;
306
+ }
307
+ // Statement-form map()/constrain() mirror the method-call checks above.
308
+ if (statement.callee === "map")
309
+ result.usesMap = true;
310
+ if (statement.callee === "constrain")
311
+ result.usesConstrain = true;
237
312
  // The HAL resolver lowers WDT.*/Timing.* namespace calls to bare AVR
238
313
  // library functions (WDT.reset() → wdt_reset(), Timing.delay() → delay(),
239
314
  // Timing.millis() → millis()). When that happens the `WDT.`/`Timing.`
@@ -357,6 +432,73 @@ function analyzeStatement(statement, result, strategy) {
357
432
  }
358
433
  break;
359
434
  case "hal-op":
435
+ // Structured HAL ops carry a typed operation name (e.g. "spi.begin",
436
+ // "i2c.read_byte", "tone.play", "uart.write") rather than raw code.
437
+ // Detect peripheral usage here so framework-avr's driver shims can be
438
+ // gated on actual use. Without this, SPI0.begin() → spi.begin hal-op
439
+ // would be invisible (no raw code to scan) and the SPI driver would
440
+ // always be emitted. Same blind spot the wdt.* fix below the raw-code
441
+ // block addresses for the watchdog.
442
+ if (statement.operation && typeof statement.operation.operation === "string") {
443
+ const opName = statement.operation.operation;
444
+ if (opName.startsWith("spi."))
445
+ result.usesSPI = true;
446
+ if (opName.startsWith("i2c."))
447
+ result.usesI2C = true;
448
+ if (opName.startsWith("tone."))
449
+ result.usesTone = true;
450
+ if (opName.startsWith("uart."))
451
+ result.usesUart = true;
452
+ // Display HAL ops (display.init from ui.mount, display.flush per frame)
453
+ // always imply GPIO usage (CS/DC/RST pins). Native SPI/I2C display
454
+ // adapters emit their own transport #includes (driver/spi_master.h,
455
+ // driver/i2c_master.h) — we don't force usesSPI/usesI2C here because
456
+ // that would pull headers even for non-display SPI/I2C code, and the
457
+ // display's transport type can't be determined from the op alone.
458
+ if (opName.startsWith("display.")) {
459
+ result.usesGPIO = true;
460
+ }
461
+ // ESP32 peripheral usage — framework-esp32 gates IDF driver blocks
462
+ // and forced includes on these. No-op for other frameworks (their
463
+ // shimLines emit no CUTTLEFISH_* blocks with these marker names).
464
+ if (opName.startsWith("gpio."))
465
+ result.usesGPIO = true;
466
+ if (opName.startsWith("pwm."))
467
+ result.usesPWM = true;
468
+ if (opName.startsWith("rmt."))
469
+ result.usesRmt = true;
470
+ if (opName.startsWith("adc."))
471
+ result.usesADC = true;
472
+ if (opName.startsWith("dac."))
473
+ result.usesDAC = true;
474
+ if (opName.startsWith("power."))
475
+ result.usesPower = true;
476
+ if (opName.startsWith("wdt."))
477
+ result.usesWdt = true;
478
+ if (opName.startsWith("interrupt."))
479
+ result.usesInterrupts = true;
480
+ if (opName.startsWith("pulse."))
481
+ result.usesPulse = true;
482
+ if (opName.startsWith("shift."))
483
+ result.usesShift = true;
484
+ if (opName.startsWith("wifi."))
485
+ result.usesWifi = true;
486
+ if (opName.startsWith("http."))
487
+ result.usesHttp = true;
488
+ if (opName.startsWith("ble."))
489
+ result.usesBle = true;
490
+ if (opName.startsWith("preferences."))
491
+ result.usesPreferences = true;
492
+ // Timing HAL ops (timing.delay/millis/micros) carry a typed operation
493
+ // name, not raw code, so the regex scans below miss them. Mirror the
494
+ // raw-code timing detection here so usesMillis/usesTiming (and thus
495
+ // usesNativeTiming) fire for `delay()`/`millis()` on AVR.
496
+ if (opName === "timing.delay" || opName === "timing.delay_microseconds"
497
+ || opName === "timing.millis" || opName === "timing.micros") {
498
+ result.usesTiming = true;
499
+ result.usesMillis = true;
500
+ }
501
+ }
360
502
  // Scan raw C++ code in HAL ops for polyfill helper usage
361
503
  if (statement.operation && statement.operation.operation === "raw" && typeof statement.operation.code === "string") {
362
504
  const code = statement.operation.code;
@@ -384,6 +526,21 @@ function analyzeStatement(statement, result, strategy) {
384
526
  result.usesTiming = true;
385
527
  result.usesMillis = true;
386
528
  }
529
+ // Native AVR peripheral usage inside raw hal-op code (e.g. the
530
+ // EEPROM namespace lowers to `EEPROM.write(...)` in a raw hal-op;
531
+ // Serial/SPI/Wire may appear as lowered library calls too).
532
+ if (/\bEEPROM\b/.test(code) || /\beeprom_(read|write|update)_byte\b/.test(code)) {
533
+ result.usesEEPROM = true;
534
+ }
535
+ if (/\bSerial\b/.test(code)) {
536
+ result.usesUart = true;
537
+ }
538
+ if (/\bSPI\b/.test(code)) {
539
+ result.usesSPI = true;
540
+ }
541
+ if (/\bWire\b/.test(code)) {
542
+ result.usesI2C = true;
543
+ }
387
544
  }
388
545
  break;
389
546
  case "update":
@@ -431,10 +588,32 @@ export function analyzeProgram(program, strategy) {
431
588
  usesTiming: false,
432
589
  usesWDT: false,
433
590
  usesStrPtr: false,
591
+ usesUart: false,
592
+ usesSPI: false,
593
+ usesI2C: false,
594
+ usesEEPROM: false,
595
+ usesTone: false,
596
+ usesMap: false,
597
+ usesConstrain: false,
598
+ usesNativeTiming: false,
434
599
  hasSerialBegin: false,
435
600
  hasGenerators: false,
436
601
  usesStdMap: false,
437
602
  timerCallCount: 0,
603
+ usesGPIO: false,
604
+ usesPWM: false,
605
+ usesRmt: false,
606
+ usesADC: false,
607
+ usesDAC: false,
608
+ usesPower: false,
609
+ usesWdt: false,
610
+ usesInterrupts: false,
611
+ usesPulse: false,
612
+ usesShift: false,
613
+ usesWifi: false,
614
+ usesHttp: false,
615
+ usesBle: false,
616
+ usesPreferences: false,
438
617
  };
439
618
  // Analyze type aliases
440
619
  for (const typeAlias of program.typeAliases) {
@@ -585,5 +764,18 @@ export function analyzeProgram(program, strategy) {
585
764
  if (loweredConsoleInCallback()) {
586
765
  result.hasConsoleCalls = true;
587
766
  }
767
+ // Derive the comprehensive native-timing gate for the AVR millis() Timer0
768
+ // ISR. The ISR is needed whenever the program touches the soft clock
769
+ // directly (millis/delay/micros) OR has a hidden consumer: setInterval/
770
+ // setTimeout (the scheduler polls millis), or async functions (the runtime
771
+ // polls millis). The per-frame UI tick is injected by the emitter, not
772
+ // present in user source — the setup emitter ORs entryHasUI() in at the
773
+ // consume site. Without this gate, every AVR program pulled in the Timer0
774
+ // ISR even when it never uses timing.
775
+ const hasAsync = program.functions.some(fn => fn.isAsync);
776
+ result.usesNativeTiming = result.usesMillis
777
+ || result.usesTiming
778
+ || result.timerCallCount > 0
779
+ || hasAsync;
588
780
  return result;
589
781
  }
@@ -4,4 +4,4 @@ import type { BoardConstants } from './board-resolver.js';
4
4
  /**
5
5
  * Validate that pins used with inputPullDown() support pulldown on this board.
6
6
  */
7
- export declare function validatePulldownSupport(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
7
+ export declare function validatePulldownSupport(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
@@ -7,7 +7,7 @@
7
7
  /**
8
8
  * Validate that pins used with inputPullDown() support pulldown on this board.
9
9
  */
10
- export function validatePulldownSupport(usage, boardConstants) {
10
+ export function validatePulldownSupport(usage, boardConstants, filePath) {
11
11
  const diagnostics = [];
12
12
  if (usage.inputPulldownPins.size === 0) {
13
13
  return diagnostics;
@@ -22,6 +22,7 @@ export function validatePulldownSupport(usage, boardConstants) {
22
22
  diagnostics.push({
23
23
  code: 'pulldown-not-supported',
24
24
  message: `${pinName ?? `pin ${pinNumber}`} does not support hardware pulldown${arch ? ` on ${arch.toUpperCase()} boards` : ''}. Use ${pinName ?? 'pin'}.asInput() or ${pinName ?? 'pin'}.inputPullUp() instead.`,
25
+ filePath,
25
26
  source: 'pulldown-validation',
26
27
  severity: 'error',
27
28
  });
@@ -1,4 +1,4 @@
1
1
  import type { Diagnostic } from '../types.js';
2
2
  import type { BoardConstants } from './board-resolver.js';
3
3
  import type { PeripheralUsage } from './peripheral-usage.js';
4
- export declare function validatePWMTimerSharing(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
4
+ export declare function validatePWMTimerSharing(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
@@ -52,7 +52,7 @@ function getPwmTimerPins(boardConstants) {
52
52
  }
53
53
  return Array.from(pinsByIndex.values()).filter(pin => pin.timerId.length > 0);
54
54
  }
55
- export function validatePWMTimerSharing(usage, boardConstants) {
55
+ export function validatePWMTimerSharing(usage, boardConstants, filePath) {
56
56
  if (usage.pwmPinsUsed.size < 2 || !boardConstants) {
57
57
  return [];
58
58
  }
@@ -85,6 +85,7 @@ export function validatePWMTimerSharing(usage, boardConstants) {
85
85
  diagnostics.push({
86
86
  severity: 'info',
87
87
  code: 'pwm-timer-sharing',
88
+ filePath,
88
89
  source: 'pwm-timer-sharing',
89
90
  message: `${references.join(', ')} share ${timerId}${boardLabel}. Duty cycle can differ per pin, but timer-wide PWM settings are shared across that group. Prefer pins on different timer groups if you need independent PWM timing behavior.`,
90
91
  });
@@ -34,6 +34,7 @@ export function analyzeResources(program, strategy) {
34
34
  diagnostics.push({
35
35
  severity: 'warning',
36
36
  message: `${pinRef} is configured as ${peripheralName} ${func.role.toUpperCase()}. Using it as GPIO may interfere with communication.\n → ${suggestion}`,
37
+ filePath: program.fileName,
37
38
  code: 'peripheral-pin-conflict',
38
39
  source: 'resource-analysis',
39
40
  });
@@ -58,6 +59,7 @@ export function analyzeResources(program, strategy) {
58
59
  severity: 'error',
59
60
  message: `Hardware Conflict: Pin '${pinName}' is required by both ${peripheralName} and ${other}.`,
60
61
  hint: `Move one of the peripherals to different pins if your board supports remapping, or avoid using both simultaneously.`,
62
+ filePath: program.fileName,
61
63
  code: 'peripheral-peripheral-conflict',
62
64
  source: 'resource-analysis',
63
65
  });
@@ -1,4 +1,4 @@
1
1
  import type { Diagnostic } from '../types.js';
2
2
  import type { BoardConstants } from './board-resolver.js';
3
3
  import type { PeripheralUsage } from './peripheral-usage.js';
4
- export declare function validateTimer0PWMTimingConflict(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
4
+ export declare function validateTimer0PWMTimingConflict(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];