@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.6

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 (54) hide show
  1. package/README.md +10 -10
  2. package/dist/api/board-types.d.ts +1 -1
  3. package/dist/api/shared/hal-op-ir.d.ts +10 -1
  4. package/dist/api/shared/platform-strategy.d.ts +6 -0
  5. package/dist/api/shared/types.d.ts +8 -0
  6. package/dist/cli-utils.d.ts +1 -0
  7. package/dist/cli-utils.js +3 -1
  8. package/dist/cli.js +75 -0
  9. package/dist/create/index.d.ts +1 -1
  10. package/dist/create/index.js +1 -1
  11. package/dist/create/init-scaffold.js +48 -2
  12. package/dist/create/init-templates.d.ts +2 -0
  13. package/dist/create/init-templates.js +189 -9
  14. package/dist/emit/emitters/setup.js +122 -1
  15. package/dist/ir/adc-range-validation.js +26 -25
  16. package/dist/ir/expression-to-ir.js +43 -6
  17. package/dist/ir/hal/hal-plugins.js +10 -0
  18. package/dist/ir/interrupt-analysis.js +8 -3
  19. package/dist/ir/memory-budget-validation.js +1 -0
  20. package/dist/ir/ownership-analysis.js +19 -0
  21. package/dist/ir/peripheral-ownership.js +5 -0
  22. package/dist/ir/peripheral-validation.d.ts +1 -1
  23. package/dist/ir/peripheral-validation.js +6 -3
  24. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  25. package/dist/ir/pin-alias-conflict.js +2 -1
  26. package/dist/ir/pin-capability-validation.js +34 -32
  27. package/dist/ir/pin-mode-validation.js +5 -0
  28. package/dist/ir/pin-safety.d.ts +1 -1
  29. package/dist/ir/pin-safety.js +2 -1
  30. package/dist/ir/program-analysis.d.ts +16 -0
  31. package/dist/ir/program-analysis.js +113 -0
  32. package/dist/ir/pulldown-validation.d.ts +1 -1
  33. package/dist/ir/pulldown-validation.js +2 -1
  34. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  35. package/dist/ir/pwm-timer-sharing.js +2 -1
  36. package/dist/ir/resource-analysis.js +2 -0
  37. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  38. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  39. package/dist/ir/timing-validation.js +1 -0
  40. package/dist/ir/transformers/variables.js +46 -17
  41. package/dist/ir/try-catch-validation.js +2 -0
  42. package/dist/ir/type-resolution.js +2 -2
  43. package/dist/ir/unit-suspicion-validation.js +9 -7
  44. package/dist/ir/validation-orchestrator.js +6 -6
  45. package/dist/licenses.d.ts +185 -0
  46. package/dist/licenses.js +963 -0
  47. package/dist/testing.d.ts +1 -1
  48. package/dist/testing.js +1 -1
  49. package/dist/transpile.js +17 -9
  50. package/dist/types.d.ts +7 -1
  51. package/dist/utils/cli.js +44 -0
  52. package/package.json +5 -4
  53. package/dist/ir/heap-array-validation.d.ts +0 -24
  54. package/dist/ir/heap-array-validation.js +0 -29
@@ -32,6 +32,7 @@ export function validatePeripheralOwnership(program) {
32
32
  severity: 'error',
33
33
  message: `Double-take: '${bus}' is already owned.`,
34
34
  hint: `Call '${bus}.release()' before taking it again.`,
35
+ filePath: span.filePath,
35
36
  line: span.startLine,
36
37
  column: span.startColumn,
37
38
  code: 'hal-ownership-double-take',
@@ -79,6 +80,7 @@ export function validatePeripheralOwnership(program) {
79
80
  severity: 'error',
80
81
  message: `Double-take: '${bus}' is already owned.`,
81
82
  hint: `Call '${bus}.release()' before taking it again.`,
83
+ filePath: span.filePath,
82
84
  line: span.startLine,
83
85
  column: span.startColumn,
84
86
  code: 'hal-ownership-double-take',
@@ -94,6 +96,7 @@ export function validatePeripheralOwnership(program) {
94
96
  severity: 'warning',
95
97
  message: `'${bus}.release()' called but bus was not taken.`,
96
98
  hint: `Ensure you call '${bus}.take()' before releasing.`,
99
+ filePath: span.filePath,
97
100
  line: span.startLine,
98
101
  column: span.startColumn,
99
102
  code: 'hal-ownership-unowned-release',
@@ -108,6 +111,7 @@ export function validatePeripheralOwnership(program) {
108
111
  severity: 'error',
109
112
  message: `Unowned access: '${bus}' is used without being taken.`,
110
113
  hint: `Call 'const bus = ${bus}.take();' and use the returned 'bus' object, or ensure '${bus}' is taken in the current scope.`,
114
+ filePath: span.filePath,
111
115
  line: span.startLine,
112
116
  column: span.startColumn,
113
117
  code: 'hal-ownership-unowned-access',
@@ -142,6 +146,7 @@ export function validatePeripheralOwnership(program) {
142
146
  severity: 'warning',
143
147
  message: `'${bus}' was taken but never released.`,
144
148
  hint: `Call '${bus}.release()' when finished with the bus.`,
149
+ filePath: busSpan.filePath,
145
150
  line: busSpan.startLine,
146
151
  column: busSpan.startColumn,
147
152
  code: 'hal-ownership-leak',
@@ -5,4 +5,4 @@ import { Diagnostic } from '../types.js';
5
5
  * Validate peripheral usage and return diagnostics.
6
6
  * This is the main entry point for peripheral validation.
7
7
  */
8
- export declare function validatePeripherals(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
8
+ export declare function validatePeripherals(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
@@ -26,7 +26,7 @@ function getPeripheralCapacity(boardConstants) {
26
26
  * Validate peripheral usage against board capacity.
27
27
  * Returns an array of diagnostics for any invalid peripheral usage.
28
28
  */
29
- function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
29
+ function validatePeripheralUsage(usage, capacity, boardName, filePath) {
30
30
  const diagnostics = [];
31
31
  // Validate I2C instances
32
32
  for (const instance of usage.i2cInstancesUsed) {
@@ -36,6 +36,7 @@ function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
36
36
  severity: 'error',
37
37
  message: `I2C${instance} is not available on ${boardName}. Available: ${availableName}`,
38
38
  code: 'peripheral-not-available',
39
+ filePath,
39
40
  source: 'peripheral-validation',
40
41
  });
41
42
  }
@@ -48,6 +49,7 @@ function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
48
49
  severity: 'error',
49
50
  message: `SPI${instance} is not available on ${boardName}. Available: ${availableName}`,
50
51
  code: 'peripheral-not-available',
52
+ filePath,
51
53
  source: 'peripheral-validation',
52
54
  });
53
55
  }
@@ -60,6 +62,7 @@ function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
60
62
  severity: 'error',
61
63
  message: `UART${instance} is not available on ${boardName}. Available: ${availableName}`,
62
64
  code: 'peripheral-not-available',
65
+ filePath,
63
66
  source: 'peripheral-validation',
64
67
  });
65
68
  }
@@ -70,8 +73,8 @@ function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
70
73
  * Validate peripheral usage and return diagnostics.
71
74
  * This is the main entry point for peripheral validation.
72
75
  */
73
- export function validatePeripherals(usage, boardConstants) {
76
+ export function validatePeripherals(usage, boardConstants, filePath) {
74
77
  const capacity = getPeripheralCapacity(boardConstants);
75
78
  const boardName = boardConstants?.get('name') ?? 'this board';
76
- return validatePeripheralUsage(usage, capacity, boardName);
79
+ return validatePeripheralUsage(usage, capacity, boardName, filePath);
77
80
  }
@@ -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 validatePinAliasConflicts(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
4
+ export declare function validatePinAliasConflicts(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
@@ -1,5 +1,5 @@
1
1
  import { findBoardPinByName } from './board-pin-utils.js';
2
- export function validatePinAliasConflicts(usage, boardConstants) {
2
+ export function validatePinAliasConflicts(usage, boardConstants, filePath) {
3
3
  if (!boardConstants || usage.pinsUsed.size === 0) {
4
4
  return [];
5
5
  }
@@ -26,6 +26,7 @@ export function validatePinAliasConflicts(usage, boardConstants) {
26
26
  diagnostics.push({
27
27
  severity: 'warning',
28
28
  code: 'pin-alias-conflict',
29
+ filePath,
29
30
  source: 'pin-alias-conflict',
30
31
  message: `Pin '${canonicalPinName}' is referenced through multiple names${boardLabel}: ${orderedNames.join(', ')}. These names refer to the same physical pin. Pick one name to keep pin usage and diagnostics unambiguous.`,
31
32
  });
@@ -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;
@@ -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"
@@ -234,6 +258,32 @@ function analyzeStatement(statement, result, strategy) {
234
258
  if (statement.callee.startsWith("WDT.") || statement.callee === "WDT") {
235
259
  result.usesWDT = true;
236
260
  }
261
+ // Native AVR peripheral usage from statement-form calls. tone()/noTone()
262
+ // are bare Arduino-API calls; console.* / Serial.* drive UART; the
263
+ // namespace prefixes mirror the method-call checks above.
264
+ if (statement.callee === "tone" || statement.callee === "noTone") {
265
+ result.usesTone = true;
266
+ }
267
+ if (statement.callee.startsWith("console.") || statement.callee.startsWith("_uart_")) {
268
+ result.usesUart = true;
269
+ }
270
+ if (statement.callee.startsWith("Serial.") || statement.callee === "Serial") {
271
+ result.usesUart = true;
272
+ }
273
+ if (statement.callee.startsWith("SPI.") || statement.callee === "SPI") {
274
+ result.usesSPI = true;
275
+ }
276
+ if (statement.callee.startsWith("Wire.") || statement.callee === "Wire") {
277
+ result.usesI2C = true;
278
+ }
279
+ if (statement.callee.startsWith("EEPROM.") || statement.callee === "EEPROM") {
280
+ result.usesEEPROM = true;
281
+ }
282
+ // Statement-form map()/constrain() mirror the method-call checks above.
283
+ if (statement.callee === "map")
284
+ result.usesMap = true;
285
+ if (statement.callee === "constrain")
286
+ result.usesConstrain = true;
237
287
  // The HAL resolver lowers WDT.*/Timing.* namespace calls to bare AVR
238
288
  // library functions (WDT.reset() → wdt_reset(), Timing.delay() → delay(),
239
289
  // Timing.millis() → millis()). When that happens the `WDT.`/`Timing.`
@@ -357,6 +407,33 @@ function analyzeStatement(statement, result, strategy) {
357
407
  }
358
408
  break;
359
409
  case "hal-op":
410
+ // Structured HAL ops carry a typed operation name (e.g. "spi.begin",
411
+ // "i2c.read_byte", "tone.play", "uart.write") rather than raw code.
412
+ // Detect peripheral usage here so framework-avr's driver shims can be
413
+ // gated on actual use. Without this, SPI0.begin() → spi.begin hal-op
414
+ // would be invisible (no raw code to scan) and the SPI driver would
415
+ // always be emitted. Same blind spot the wdt.* fix below the raw-code
416
+ // block addresses for the watchdog.
417
+ if (statement.operation && typeof statement.operation.operation === "string") {
418
+ const opName = statement.operation.operation;
419
+ if (opName.startsWith("spi."))
420
+ result.usesSPI = true;
421
+ if (opName.startsWith("i2c."))
422
+ result.usesI2C = true;
423
+ if (opName.startsWith("tone."))
424
+ result.usesTone = true;
425
+ if (opName.startsWith("uart."))
426
+ result.usesUart = true;
427
+ // Timing HAL ops (timing.delay/millis/micros) carry a typed operation
428
+ // name, not raw code, so the regex scans below miss them. Mirror the
429
+ // raw-code timing detection here so usesMillis/usesTiming (and thus
430
+ // usesNativeTiming) fire for `delay()`/`millis()` on AVR.
431
+ if (opName === "timing.delay" || opName === "timing.delay_microseconds"
432
+ || opName === "timing.millis" || opName === "timing.micros") {
433
+ result.usesTiming = true;
434
+ result.usesMillis = true;
435
+ }
436
+ }
360
437
  // Scan raw C++ code in HAL ops for polyfill helper usage
361
438
  if (statement.operation && statement.operation.operation === "raw" && typeof statement.operation.code === "string") {
362
439
  const code = statement.operation.code;
@@ -384,6 +461,21 @@ function analyzeStatement(statement, result, strategy) {
384
461
  result.usesTiming = true;
385
462
  result.usesMillis = true;
386
463
  }
464
+ // Native AVR peripheral usage inside raw hal-op code (e.g. the
465
+ // EEPROM namespace lowers to `EEPROM.write(...)` in a raw hal-op;
466
+ // Serial/SPI/Wire may appear as lowered library calls too).
467
+ if (/\bEEPROM\b/.test(code) || /\beeprom_(read|write|update)_byte\b/.test(code)) {
468
+ result.usesEEPROM = true;
469
+ }
470
+ if (/\b(Serial|console)\b/.test(code)) {
471
+ result.usesUart = true;
472
+ }
473
+ if (/\bSPI\b/.test(code)) {
474
+ result.usesSPI = true;
475
+ }
476
+ if (/\bWire\b/.test(code)) {
477
+ result.usesI2C = true;
478
+ }
387
479
  }
388
480
  break;
389
481
  case "update":
@@ -431,6 +523,14 @@ export function analyzeProgram(program, strategy) {
431
523
  usesTiming: false,
432
524
  usesWDT: false,
433
525
  usesStrPtr: false,
526
+ usesUart: false,
527
+ usesSPI: false,
528
+ usesI2C: false,
529
+ usesEEPROM: false,
530
+ usesTone: false,
531
+ usesMap: false,
532
+ usesConstrain: false,
533
+ usesNativeTiming: false,
434
534
  hasSerialBegin: false,
435
535
  hasGenerators: false,
436
536
  usesStdMap: false,
@@ -585,5 +685,18 @@ export function analyzeProgram(program, strategy) {
585
685
  if (loweredConsoleInCallback()) {
586
686
  result.hasConsoleCalls = true;
587
687
  }
688
+ // Derive the comprehensive native-timing gate for the AVR millis() Timer0
689
+ // ISR. The ISR is needed whenever the program touches the soft clock
690
+ // directly (millis/delay/micros) OR has a hidden consumer: setInterval/
691
+ // setTimeout (the scheduler polls millis), or async functions (the runtime
692
+ // polls millis). The per-frame UI tick is injected by the emitter, not
693
+ // present in user source — the setup emitter ORs entryHasUI() in at the
694
+ // consume site. Without this gate, every AVR program pulled in the Timer0
695
+ // ISR even when it never uses timing.
696
+ const hasAsync = program.functions.some(fn => fn.isAsync);
697
+ result.usesNativeTiming = result.usesMillis
698
+ || result.usesTiming
699
+ || result.timerCallCount > 0
700
+ || hasAsync;
588
701
  return result;
589
702
  }
@@ -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[];
@@ -43,7 +43,7 @@ function getTimer0PwmPins(boardConstants) {
43
43
  }
44
44
  return timer0Pins;
45
45
  }
46
- export function validateTimer0PWMTimingConflict(usage, boardConstants) {
46
+ export function validateTimer0PWMTimingConflict(usage, boardConstants, filePath) {
47
47
  if (!usage.timer0 || usage.pwmPinsUsed.size === 0) {
48
48
  return [];
49
49
  }
@@ -65,6 +65,7 @@ export function validateTimer0PWMTimingConflict(usage, boardConstants) {
65
65
  return [{
66
66
  severity: 'info',
67
67
  code: 'timer0-pwm-timing-conflict',
68
+ filePath,
68
69
  source: 'timer0-pwm-timing-conflict',
69
70
  message: `${pinReferences.join(', ')} use Timer0 PWM${boardLabel}, and your program also relies on Timer0-backed timing APIs such as delay(), millis(), or micros(). This coupling is common on AVR boards, so prefer non-Timer0 PWM pins when you want PWM behavior isolated from core timing.`,
70
71
  }];