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

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 (224) hide show
  1. package/README.md +10 -10
  2. package/dist/api/board-types.d.ts +1 -1
  3. package/dist/api/config.d.ts +38 -2
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/schema/types.d.ts +15 -0
  6. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  7. package/dist/api/shared/async-runtime-static.js +69 -24
  8. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  9. package/dist/api/shared/async-symbol-detector.js +140 -0
  10. package/dist/api/shared/async-types.d.ts +24 -0
  11. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  12. package/dist/api/shared/coop-scheduler.js +149 -0
  13. package/dist/api/shared/display-adapter.d.ts +4 -3
  14. package/dist/api/shared/display-adapter.js +15 -118
  15. package/dist/api/shared/display-adapters/sdl.js +38 -31
  16. package/dist/api/shared/display-profile.d.ts +47 -25
  17. package/dist/api/shared/display-profile.js +31 -164
  18. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  19. package/dist/api/shared/framework-manifest-registry.js +27 -0
  20. package/dist/api/shared/framework-manifest.d.ts +595 -0
  21. package/dist/api/shared/framework-manifest.js +193 -0
  22. package/dist/api/shared/glcdfont.d.ts +12 -0
  23. package/dist/api/shared/glcdfont.js +124 -0
  24. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  25. package/dist/api/shared/hal-op-ir.d.ts +764 -1
  26. package/dist/api/shared/hal-op-ir.js +129 -1
  27. package/dist/api/shared/index.d.ts +22 -2
  28. package/dist/api/shared/index.js +26 -1
  29. package/dist/api/shared/ir-core.d.ts +4 -0
  30. package/dist/api/shared/ir-declarations.d.ts +6 -0
  31. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  32. package/dist/api/shared/native-display-op-resolver.js +64 -0
  33. package/dist/api/shared/platform-strategy.d.ts +119 -2
  34. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  35. package/dist/api/shared/promise-runtime.d.ts +1 -1
  36. package/dist/api/shared/promise-runtime.js +95 -13
  37. package/dist/api/shared/spdx-licenses.d.ts +139 -0
  38. package/dist/api/shared/spdx-licenses.js +385 -0
  39. package/dist/api/shared/toolchain-types.d.ts +17 -0
  40. package/dist/api/shared/types.d.ts +8 -0
  41. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  42. package/dist/api/shared/validate-framework-manifest.js +494 -0
  43. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  44. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  45. package/dist/api/shared/worker-runtime.d.ts +69 -0
  46. package/dist/api/shared/worker-runtime.js +163 -0
  47. package/dist/cli-utils.d.ts +1 -0
  48. package/dist/cli-utils.js +3 -1
  49. package/dist/cli.js +249 -32
  50. package/dist/config-loader.d.ts +7 -2
  51. package/dist/config-loader.js +23 -3
  52. package/dist/config-schema.d.ts +112 -70
  53. package/dist/config-schema.js +14 -0
  54. package/dist/contract/board-generator.d.ts +17 -0
  55. package/dist/contract/board-generator.js +57 -0
  56. package/dist/contract/contract-parser.d.ts +217 -0
  57. package/dist/contract/contract-parser.js +224 -0
  58. package/dist/contract/index.d.ts +22 -0
  59. package/dist/contract/index.js +68 -0
  60. package/dist/create/board-codegen.js +4 -4
  61. package/dist/create/board-generators.js +4 -5
  62. package/dist/create/board-spec.d.ts +72 -75
  63. package/dist/create/board-spec.js +0 -1
  64. package/dist/create/framework-catalog.d.ts +64 -0
  65. package/dist/create/framework-catalog.js +136 -0
  66. package/dist/create/index.d.ts +5 -1
  67. package/dist/create/index.js +3 -1
  68. package/dist/create/init-scaffold.d.ts +12 -3
  69. package/dist/create/init-scaffold.js +49 -14
  70. package/dist/create/init-templates.d.ts +4 -0
  71. package/dist/create/init-templates.js +271 -17
  72. package/dist/create/init-wizard.js +41 -27
  73. package/dist/create/install-deps.d.ts +32 -0
  74. package/dist/create/install-deps.js +46 -0
  75. package/dist/debug/preprocessor.js +178 -39
  76. package/dist/debug/types.d.ts +23 -0
  77. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  78. package/dist/diagnostics/mermaid-builder.js +34 -24
  79. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  80. package/dist/emit/compliance/arxml-writer.js +34 -0
  81. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  82. package/dist/emit/compliance/compliance-context.js +113 -0
  83. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  84. package/dist/emit/compliance/deviation-ledger.js +47 -0
  85. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  86. package/dist/emit/compliance/deviation-writer.js +37 -0
  87. package/dist/emit/compliance/index.d.ts +7 -0
  88. package/dist/emit/compliance/index.js +6 -0
  89. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  90. package/dist/emit/compliance/rule-engine.js +101 -0
  91. package/dist/emit/compliance/rules.d.ts +16 -0
  92. package/dist/emit/compliance/rules.js +191 -0
  93. package/dist/emit/compliance/types.d.ts +59 -0
  94. package/dist/emit/compliance/types.js +8 -0
  95. package/dist/emit/cpp-emitter.js +4 -3
  96. package/dist/emit/emitters/class-emitter.js +6 -1
  97. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  98. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  99. package/dist/emit/emitters/line-appender.js +19 -0
  100. package/dist/emit/emitters/line-marker.d.ts +38 -0
  101. package/dist/emit/emitters/line-marker.js +39 -0
  102. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  103. package/dist/emit/emitters/output-finalizer.js +101 -12
  104. package/dist/emit/emitters/setup.d.ts +17 -0
  105. package/dist/emit/emitters/setup.js +372 -30
  106. package/dist/emit/emitters/top-level-prep.js +8 -0
  107. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  108. package/dist/emit/emitters/ui-emitter.js +33 -9
  109. package/dist/emit/expression-renderer.d.ts +1 -1
  110. package/dist/emit/expression-renderer.js +91 -6
  111. package/dist/emit/route-hal-op.js +18 -5
  112. package/dist/emit/snprintf-helpers.js +15 -4
  113. package/dist/emit/statement-renderer.d.ts +10 -0
  114. package/dist/emit/statement-renderer.js +34 -5
  115. package/dist/emit/utils/async-state-machine.js +221 -125
  116. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  117. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  118. package/dist/framework-package.js +2 -0
  119. package/dist/framework-registry.d.ts +17 -0
  120. package/dist/install/framework-catalog.d.ts +53 -0
  121. package/dist/install/framework-catalog.js +107 -0
  122. package/dist/install/handle-install.d.ts +35 -0
  123. package/dist/install/handle-install.js +177 -0
  124. package/dist/install/index.d.ts +4 -0
  125. package/dist/install/index.js +3 -0
  126. package/dist/ir/adc-range-validation.js +40 -26
  127. package/dist/ir/build-ir-state.d.ts +1 -0
  128. package/dist/ir/build-ir-state.js +14 -0
  129. package/dist/ir/build-ir.js +9 -5
  130. package/dist/ir/call-graph.js +16 -0
  131. package/dist/ir/expression-to-ir.js +207 -6
  132. package/dist/ir/feature-registry.js +7 -25
  133. package/dist/ir/function-builder.js +22 -0
  134. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  135. package/dist/ir/hal/hal-emitter.js +50 -18
  136. package/dist/ir/hal/hal-parser.d.ts +6 -0
  137. package/dist/ir/hal/hal-parser.js +81 -0
  138. package/dist/ir/hal/hal-plugins.js +733 -1
  139. package/dist/ir/identifier-collector.js +35 -0
  140. package/dist/ir/interrupt-analysis.d.ts +5 -1
  141. package/dist/ir/interrupt-analysis.js +16 -19
  142. package/dist/ir/memory-budget-validation.js +1 -0
  143. package/dist/ir/network-validation.d.ts +4 -0
  144. package/dist/ir/network-validation.js +184 -0
  145. package/dist/ir/ownership-analysis.js +39 -1
  146. package/dist/ir/peripheral-ownership.js +5 -0
  147. package/dist/ir/peripheral-validation.d.ts +1 -1
  148. package/dist/ir/peripheral-validation.js +6 -3
  149. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  150. package/dist/ir/pin-alias-conflict.js +2 -1
  151. package/dist/ir/pin-capability-validation.js +71 -32
  152. package/dist/ir/pin-mode-validation.d.ts +2 -2
  153. package/dist/ir/pin-mode-validation.js +54 -18
  154. package/dist/ir/pin-safety.d.ts +1 -1
  155. package/dist/ir/pin-safety.js +2 -1
  156. package/dist/ir/program-analysis.d.ts +81 -0
  157. package/dist/ir/program-analysis.js +429 -0
  158. package/dist/ir/pulldown-validation.d.ts +1 -1
  159. package/dist/ir/pulldown-validation.js +2 -1
  160. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  161. package/dist/ir/pwm-timer-sharing.js +2 -1
  162. package/dist/ir/render-expr.js +11 -0
  163. package/dist/ir/resource-analysis.js +2 -0
  164. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  165. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  166. package/dist/ir/timing-validation.d.ts +6 -1
  167. package/dist/ir/timing-validation.js +61 -13
  168. package/dist/ir/transformers/call-statement.js +105 -0
  169. package/dist/ir/transformers/expressions.js +62 -0
  170. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  171. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  172. package/dist/ir/transformers/namespace-methods.js +17 -12
  173. package/dist/ir/transformers/ui-reactive.js +2 -2
  174. package/dist/ir/transformers/variables.js +137 -19
  175. package/dist/ir/try-catch-validation.js +2 -0
  176. package/dist/ir/type-resolution.js +20 -2
  177. package/dist/ir/ui-element-auto-wire.js +7 -5
  178. package/dist/ir/unit-suspicion-validation.js +9 -7
  179. package/dist/ir/utils/map-statements.d.ts +4 -0
  180. package/dist/ir/utils/map-statements.js +79 -0
  181. package/dist/ir/validation-orchestrator.js +15 -8
  182. package/dist/ir/worker-analysis.d.ts +10 -0
  183. package/dist/ir/worker-analysis.js +261 -0
  184. package/dist/libdef/c-to-decl.d.ts +27 -0
  185. package/dist/libdef/c-to-decl.js +397 -0
  186. package/dist/libdef/component-decls.d.ts +2 -0
  187. package/dist/libdef/component-decls.js +6 -0
  188. package/dist/libdef/component-discovery.d.ts +43 -0
  189. package/dist/libdef/component-discovery.js +83 -0
  190. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  191. package/dist/libdef/cpp-to-decl.js +72 -0
  192. package/dist/libdef/registry.js +5 -2
  193. package/dist/lint-cache.d.ts +59 -0
  194. package/dist/lint-cache.js +257 -0
  195. package/dist/orchestrator/graph-builder.js +14 -9
  196. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  197. package/dist/platform/coop-scheduler-runtime.js +41 -0
  198. package/dist/platform/generic-strategy.d.ts +15 -3
  199. package/dist/platform/generic-strategy.js +49 -4
  200. package/dist/safety/safety-bridge.d.ts +11 -0
  201. package/dist/safety/safety-bridge.js +48 -0
  202. package/dist/safety/sidecar-bridge.d.ts +5 -0
  203. package/dist/safety/sidecar-bridge.js +22 -0
  204. package/dist/safety-hook.d.ts +79 -0
  205. package/dist/safety-hook.js +35 -0
  206. package/dist/testing.d.ts +8 -2
  207. package/dist/testing.js +9 -2
  208. package/dist/transpile.d.ts +3 -0
  209. package/dist/transpile.js +191 -49
  210. package/dist/types.d.ts +22 -3
  211. package/dist/ui-hook.d.ts +17 -3
  212. package/dist/utils/cli.js +123 -2
  213. package/dist/utils/fs.d.ts +13 -0
  214. package/dist/utils/fs.js +50 -0
  215. package/dist/utils/ui.js +3 -1
  216. package/package.json +23 -3
  217. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  218. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  219. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  220. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  221. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  222. package/dist/api/shared/display-adapters/st7796.js +0 -132
  223. package/dist/ir/heap-array-validation.d.ts +0 -24
  224. package/dist/ir/heap-array-validation.js +0 -29
@@ -423,6 +423,22 @@ function collectHALOpIdentifiers(op) {
423
423
  // handler is a resolved C++ function name (e.g. "myIsr" or a placeholder)
424
424
  identifiers.add(op.handler);
425
425
  break;
426
+ case "mqtt.on_message":
427
+ // handler is the user's onMessage callback name (callback() resolves it to
428
+ // a string). Without this the tree-shaker drops the function declaration —
429
+ // the mqtt.on_message op only carries the name, so the function has no
430
+ // other reference and looks unreachable. Same shape as interrupt.attach.
431
+ identifiers.add(op.handler);
432
+ break;
433
+ case "wifi.on_event":
434
+ identifiers.add(op.handler);
435
+ break;
436
+ case "ble.on_read":
437
+ case "ble.on_write":
438
+ case "ble.on_connect":
439
+ case "ble.on_disconnect":
440
+ identifiers.add(op.handler);
441
+ break;
426
442
  case "raw": {
427
443
  // Raw C++ code may reference user-defined identifiers
428
444
  const matches = op.code.match(/[A-Za-z_][A-Za-z0-9_]*/g);
@@ -435,5 +451,24 @@ function collectHALOpIdentifiers(op) {
435
451
  }
436
452
  // Other HAL ops have only numeric/literal fields — no identifier references
437
453
  }
454
+ // wifi.* / http.* / mqtt.* ops carry resolved C++ expression texts in their
455
+ // string fields (e.g. wifi.connect ssid: `WIFI_SSID` — a top-level const, or
456
+ // a quoted literal). Scan every string field for identifiers so referenced
457
+ // globals survive tree-shaking; quoted literals are skipped.
458
+ if (op.operation.startsWith("wifi.") || op.operation.startsWith("http.")
459
+ || op.operation.startsWith("mqtt.")) {
460
+ for (const value of Object.values(op)) {
461
+ if (typeof value !== "string" || value === op.operation)
462
+ continue;
463
+ if (/^".*"$/.test(value.trim()))
464
+ continue;
465
+ const matches = value.match(/[A-Za-z_][A-Za-z0-9_]*/g);
466
+ if (matches) {
467
+ for (const match of matches) {
468
+ identifiers.add(match);
469
+ }
470
+ }
471
+ }
472
+ }
438
473
  return identifiers;
439
474
  }
@@ -1,4 +1,4 @@
1
- import type { ProgramIR } from '../api/index.js';
1
+ import type { ProgramIR, StatementIR } from '../api/index.js';
2
2
  import type { PeripheralUsage } from './peripheral-usage.js';
3
3
  import type { Diagnostic } from '../types.js';
4
4
  export { walkNestedStatements as scanNestedStatements } from './utils/walk-ir.js';
@@ -24,6 +24,10 @@ export interface InterruptHandlerInfo {
24
24
  * @returns Array of diagnostics for interrupt-related issues
25
25
  */
26
26
  export declare function analyzeInterruptSafety(program: ProgramIR, usage: PeripheralUsage, isrUnsafeOps?: Map<string, IsrUnsafeOp>): Diagnostic[];
27
+ /** Collect variable names assigned (written) within a statement body. */
28
+ export declare function collectAssignedNames(stmts: readonly StatementIR[], names: Set<string>): void;
29
+ /** Collect variable names read (referenced) within a statement body. */
30
+ export declare function collectReadNames(stmts: readonly StatementIR[], names: Set<string>): void;
27
31
  /**
28
32
  * Infer `volatile` for global variables shared between ISR callbacks and main
29
33
  * code. Mutates the IR (sets isVolatile=true on qualifying var_decls) and
@@ -7,21 +7,13 @@
7
7
  import { walkNestedStatements, walkExpressionsInExpression } from './utils/walk-ir.js';
8
8
  export { walkNestedStatements as scanNestedStatements } from './utils/walk-ir.js';
9
9
  /**
10
- * Default ISR-unsafe operations used when no platform-specific map is provided.
11
- * Platforms should supply their own list via PlatformStrategy.isrUnsafeOperations().
10
+ * Fallback ISR-unsafe operations used when no platform-specific map is provided.
11
+ * Empty by design the Wiring-derived specifics (delay, Serial.*, I2C*, SPI*)
12
+ * live in ArduinoStrategy.isrUnsafeOperations(). A generic target has no known
13
+ * ISR-unsafe surface. The orchestrator passes the loaded framework's map; this
14
+ * default only applies when none is supplied.
12
15
  */
13
- const DEFAULT_ISR_UNSAFE_OPERATIONS = new Map([
14
- ['delay', { reason: 'delay() blocks the CPU and should not be used in interrupt context', severity: 'warning' }],
15
- ['delayMicroseconds', { reason: 'delayMicroseconds() blocks and should be avoided in ISRs', severity: 'warning' }],
16
- ['Serial.print', { reason: 'Serial.print() may not work correctly in interrupt context', severity: 'info' }],
17
- ['Serial.println', { reason: 'Serial.println() may not work correctly in interrupt context', severity: 'info' }],
18
- ['Serial.write', { reason: 'Serial.write() may not work correctly in interrupt context', severity: 'info' }],
19
- ['Serial.read', { reason: 'Serial.read() may not work correctly in interrupt context', severity: 'info' }],
20
- ['I2C0', { reason: 'I2C operations can cause lockups in interrupt context', severity: 'warning' }],
21
- ['I2C1', { reason: 'I2C operations can cause lockups in interrupt context', severity: 'warning' }],
22
- ['SPI0', { reason: 'SPI operations may cause issues in interrupt context', severity: 'info' }],
23
- ['SPI1', { reason: 'SPI operations may cause issues in interrupt context', severity: 'info' }],
24
- ]);
16
+ const DEFAULT_ISR_UNSAFE_OPERATIONS = new Map();
25
17
  /**
26
18
  * Analyze a program for interrupt handler issues.
27
19
  *
@@ -93,9 +85,10 @@ function isInterruptHandlerCallback(callback, parentExpr) {
93
85
  function scanStatementForUnsafeOps(stmt, diagnostics, unsafeOps) {
94
86
  if (!stmt || typeof stmt !== 'object')
95
87
  return;
88
+ const filePath = stmt.sourceSpan?.filePath;
96
89
  if (stmt.kind === 'call') {
97
90
  const call = stmt;
98
- checkCalleeForUnsafeOp(call.callee, diagnostics, unsafeOps);
91
+ checkCalleeForUnsafeOp(call.callee, diagnostics, unsafeOps, filePath);
99
92
  }
100
93
  // HAL-op statements: after HAL resolution, bare Arduino calls like delay()
101
94
  // and delayMicroseconds() become structured hal-op statements (timing.delay,
@@ -110,7 +103,7 @@ function scanStatementForUnsafeOps(stmt, diagnostics, unsafeOps) {
110
103
  };
111
104
  const key = HAL_OP_TO_UNSAFE[op.operation];
112
105
  if (key)
113
- checkCalleeForUnsafeOp(key, diagnostics, unsafeOps);
106
+ checkCalleeForUnsafeOp(key, diagnostics, unsafeOps, filePath);
114
107
  }
115
108
  }
116
109
  walkNestedStatements(stmt, (s) => scanStatementForUnsafeOps(s, diagnostics, unsafeOps));
@@ -118,7 +111,7 @@ function scanStatementForUnsafeOps(stmt, diagnostics, unsafeOps) {
118
111
  /**
119
112
  * Check if a callee is an unsafe operation and generate diagnostic.
120
113
  */
121
- function checkCalleeForUnsafeOp(callee, diagnostics, unsafeOps) {
114
+ function checkCalleeForUnsafeOp(callee, diagnostics, unsafeOps, filePath) {
122
115
  if (!callee)
123
116
  return;
124
117
  // Check direct matches
@@ -128,6 +121,7 @@ function checkCalleeForUnsafeOp(callee, diagnostics, unsafeOps) {
128
121
  severity: unsafe.severity,
129
122
  message: `${callee}() ${unsafe.reason}`,
130
123
  code: 'interrupt-unsafe-operation',
124
+ filePath,
131
125
  source: 'interrupt-analysis',
132
126
  });
133
127
  return;
@@ -139,6 +133,7 @@ function checkCalleeForUnsafeOp(callee, diagnostics, unsafeOps) {
139
133
  severity: info.severity,
140
134
  message: `${callee} - ${info.reason}`,
141
135
  code: 'interrupt-unsafe-operation',
136
+ filePath,
142
137
  source: 'interrupt-analysis',
143
138
  });
144
139
  return;
@@ -240,7 +235,7 @@ function scanExpressionForCallbacks(expr, callback, parentExpr) {
240
235
  // emission path already handles isVolatile (statement-renderer.ts).
241
236
  // ---------------------------------------------------------------------------
242
237
  /** Collect variable names assigned (written) within a statement body. */
243
- function collectAssignedNames(stmts, names) {
238
+ export function collectAssignedNames(stmts, names) {
244
239
  for (const stmt of stmts) {
245
240
  const s = stmt;
246
241
  if (stmt.kind === 'assign') {
@@ -262,7 +257,7 @@ function collectAssignedNames(stmts, names) {
262
257
  }
263
258
  }
264
259
  /** Collect variable names read (referenced) within a statement body. */
265
- function collectReadNames(stmts, names) {
260
+ export function collectReadNames(stmts, names) {
266
261
  for (const stmt of stmts) {
267
262
  walkExpressionsInStatement(stmt, (expr) => {
268
263
  if (expr.kind === 'identifier' && typeof expr.value === 'string') {
@@ -354,6 +349,7 @@ export function inferVolatileForIsrSharedVars(program, diagnostics) {
354
349
  diagnostics.push({
355
350
  severity: 'info',
356
351
  message: `'${name}' is written in an interrupt handler and read in main code — emitted as \`volatile\` to prevent the compiler from caching it in a register (the classic ISR/loop race).`,
352
+ filePath: stmt.sourceSpan?.filePath,
357
353
  line: stmt.sourceSpan?.startLine,
358
354
  column: stmt.sourceSpan?.startColumn,
359
355
  code: 'volatile-isr-shared',
@@ -460,6 +456,7 @@ export function detectReentrancyRisk(program, diagnostics) {
460
456
  severity: 'warning',
461
457
  message: `'${name}' is called from both an interrupt handler and main-thread code. An interrupt firing mid-execution can corrupt the function's local state. Wrap the main-thread call in noInterrupts()/interrupts(), or refactor to avoid sharing the function.`,
462
458
  code: 'reentrancy-risk',
459
+ filePath: program.fileName,
463
460
  source: 'interrupt-analysis',
464
461
  });
465
462
  }
@@ -78,6 +78,7 @@ export function validateMemoryBudget(program, boardConstants) {
78
78
  : `Stack and heap share the remaining space; deep call chains or heap allocation risk collision.`),
79
79
  hint: `Reduce global/static data, move large buffers to PROGMEM (flash), shorten deep call chains, ` +
80
80
  `or use a board with more SRAM. See the heap estimate in the diagnostics report for a breakdown.`,
81
+ filePath: program.fileName,
81
82
  code: 'memory-budget',
82
83
  source: 'memory-budget-validation',
83
84
  });
@@ -0,0 +1,4 @@
1
+ import type { ProgramIR } from "../api/index.js";
2
+ import type { Diagnostic } from "../types.js";
3
+ import type { BoardConstants } from "./board-resolver.js";
4
+ export declare function validateNetworkUsage(program: ProgramIR, boardConstants?: BoardConstants): Diagnostic[];
@@ -0,0 +1,184 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Network (WiFi / HTTP) Validation
3
+ //
4
+ // Board-aware, compile-time checks for the WiFi/HTTP HAL:
5
+ //
6
+ // wifi-no-radio (error) wifi.*/http.* used on a board whose
7
+ // architecture has no WiFi radio (avr).
8
+ // http-without-wifi (warning) http.* used but the program never
9
+ // brings a WiFi link up (connect /
10
+ // connectSaved / connectAsync / AP).
11
+ // wifi-blocking-in-loop (warning) blocking WiFi.connect()/scan() or
12
+ // Http send inside loop() — stalls every
13
+ // iteration; use async/await instead.
14
+ // wifi-ap-password-short (error) WiFi.startAP() with a literal WPA2
15
+ // password shorter than 8 characters —
16
+ // esp_wifi rejects it at runtime.
17
+ // http-max-body-large (warning) Http maxBody() above 64 KB — the
18
+ // response buffer is heap-allocated.
19
+ //
20
+ // Follows the ADC-validation convention: checks that need board data return
21
+ // nothing when the data is missing (no MCU-name fallbacks).
22
+ // ---------------------------------------------------------------------------
23
+ import { walkProgramIR, walkNestedStatements } from "./utils/walk-ir.js";
24
+ /** Architectures with no WiFi radio. Driven by the `architecture` board
25
+ * constant (from the MCU package); absent data emits nothing. */
26
+ const NO_RADIO_ARCHITECTURES = new Set(["avr"]);
27
+ /** Ops that bring the WiFi link (STA or AP) up. */
28
+ const LINK_UP_OPS = new Set([
29
+ "wifi.connect",
30
+ "wifi.connect_start",
31
+ "wifi.connect_saved",
32
+ "wifi.ap_start",
33
+ ]);
34
+ /** Blocking waits that stall loop() for their full duration. */
35
+ const BLOCKING_LOOP_OPS = new Set([
36
+ "wifi.connect",
37
+ "wifi.connect_saved",
38
+ "wifi.wait_connected",
39
+ "wifi.wait_disconnected",
40
+ "wifi.scan",
41
+ "http.send",
42
+ ]);
43
+ /** Strip surrounding quotes from a resolved HAL string field; returns null
44
+ * when the field is not a literal (identifier/expression — unknowable). */
45
+ function literalString(value) {
46
+ if (typeof value !== "string")
47
+ return null;
48
+ const trimmed = value.trim();
49
+ const match = trimmed.match(/^"((?:[^"\\]|\\.)*)"$/);
50
+ return match ? match[1] : null;
51
+ }
52
+ function diagAt(stmt, partial) {
53
+ const s = stmt;
54
+ return {
55
+ ...partial,
56
+ line: s.sourceSpan?.startLine,
57
+ column: s.sourceSpan?.startColumn,
58
+ filePath: s.sourceSpan?.filePath,
59
+ source: "network-validation",
60
+ };
61
+ }
62
+ /** Pull every wifi./http. op out of a statement: hal-op statements, awaited
63
+ * __WIFI_WAIT__/__HTTP_WAIT__ marker calls (hal-expr arg), and hal-expr
64
+ * expressions in call args / initializers / conditions. */
65
+ function collectNetworkOps(stmt, out) {
66
+ const s = stmt;
67
+ const record = (op) => {
68
+ const operation = op?.operation;
69
+ if (typeof operation === "string" && (operation.startsWith("wifi.") || operation.startsWith("http."))) {
70
+ out.push({ op: op, stmt });
71
+ }
72
+ };
73
+ if (stmt.kind === "hal-op")
74
+ record(s.operation);
75
+ const visitExpr = (expr) => {
76
+ if (!expr || typeof expr !== "object")
77
+ return;
78
+ const e = expr;
79
+ if (e.kind === "hal-expr")
80
+ record(e.operation);
81
+ for (const key of ["args", "elements", "parts"]) {
82
+ if (Array.isArray(e[key]))
83
+ for (const child of e[key])
84
+ visitExpr(child);
85
+ }
86
+ for (const key of ["left", "right", "operand", "condition", "whenTrue", "whenFalse", "inner", "value", "expression"]) {
87
+ if (e[key] && typeof e[key] === "object")
88
+ visitExpr(e[key]);
89
+ }
90
+ };
91
+ for (const key of ["args", "value", "condition", "initializer", "expression"]) {
92
+ if (s[key]) {
93
+ if (Array.isArray(s[key]))
94
+ for (const child of s[key])
95
+ visitExpr(child);
96
+ else
97
+ visitExpr(s[key]);
98
+ }
99
+ }
100
+ }
101
+ export function validateNetworkUsage(program, boardConstants) {
102
+ const diagnostics = [];
103
+ const constants = boardConstants ?? program.boardConstants;
104
+ // Collect every wifi./http. op in the program.
105
+ const allOps = [];
106
+ walkProgramIR(program, (stmt) => collectNetworkOps(stmt, allOps));
107
+ if (allOps.length === 0)
108
+ return diagnostics;
109
+ const wifiOps = allOps.filter(({ op }) => op.operation.startsWith("wifi."));
110
+ const httpOps = allOps.filter(({ op }) => op.operation.startsWith("http."));
111
+ // ── wifi-no-radio ────────────────────────────────────────────────────────
112
+ const architecture = constants?.get("architecture");
113
+ const archName = typeof architecture === "string" ? architecture.replace(/^["']|["']$/g, "").toLowerCase() : null;
114
+ if (archName && NO_RADIO_ARCHITECTURES.has(archName)) {
115
+ const first = allOps[0];
116
+ diagnostics.push(diagAt(first.stmt, {
117
+ severity: "error",
118
+ code: "wifi-no-radio",
119
+ message: `WiFi/HTTP APIs are used but the target board's architecture ('${archName}') has no WiFi radio.`,
120
+ hint: "Target a WiFi-capable board (e.g. @typecad/board-esp32-devkit) or remove the WiFi/HTTP calls.",
121
+ }));
122
+ // No point piling on the remaining checks for a board that can't radio.
123
+ return diagnostics;
124
+ }
125
+ // ── http-without-wifi ────────────────────────────────────────────────────
126
+ if (httpOps.length > 0 && !wifiOps.some(({ op }) => LINK_UP_OPS.has(op.operation))) {
127
+ diagnostics.push(diagAt(httpOps[0].stmt, {
128
+ severity: "warning",
129
+ code: "http-without-wifi",
130
+ message: "HTTP requests are made but the program never brings the WiFi link up — every request will fail at runtime.",
131
+ hint: "Call WiFi.connect(ssid, password) (or WiFi.connectSaved() / WiFi.startAP()) before sending HTTP requests.",
132
+ }));
133
+ }
134
+ // ── wifi-blocking-in-loop ────────────────────────────────────────────────
135
+ // Blocking hal-op statements inside loop()'s body stall every iteration.
136
+ // Awaited forms are rewritten to __WIFI_WAIT__/__HTTP_WAIT__ marker calls
137
+ // (kind "call"), so only genuine blocking hal-ops are flagged here.
138
+ const loopFn = program.functions.find((fn) => fn.originalName === "loop");
139
+ if (loopFn?.statements) {
140
+ const visit = (stmt) => {
141
+ if (stmt.kind === "hal-op" && BLOCKING_LOOP_OPS.has(stmt.operation?.operation)) {
142
+ const opName = stmt.operation.operation;
143
+ diagnostics.push(diagAt(stmt, {
144
+ severity: "warning",
145
+ code: "wifi-blocking-in-loop",
146
+ message: `Blocking network operation '${opName}' inside loop() stalls every iteration until it completes (seconds for a connect/scan/request).`,
147
+ hint: "Move the call into an async function and `await` it — the transpiler lowers awaited WiFi/HTTP calls to non-blocking start + poll states.",
148
+ }));
149
+ }
150
+ walkNestedStatements(stmt, visit);
151
+ };
152
+ for (const stmt of loopFn.statements)
153
+ visit(stmt);
154
+ }
155
+ // ── wifi-ap-password-short ───────────────────────────────────────────────
156
+ for (const { op, stmt } of wifiOps) {
157
+ if (op.operation !== "wifi.ap_start")
158
+ continue;
159
+ const password = literalString(op.password);
160
+ if (password !== null && password.length > 0 && password.length < 8) {
161
+ diagnostics.push(diagAt(stmt, {
162
+ severity: "error",
163
+ code: "wifi-ap-password-short",
164
+ message: `WiFi.startAP() password is ${password.length} characters — WPA2 requires at least 8, and esp_wifi rejects shorter ones at runtime.`,
165
+ hint: "Use a password of 8+ characters, or omit the password entirely for an open access point.",
166
+ }));
167
+ }
168
+ }
169
+ // ── http-max-body-large ──────────────────────────────────────────────────
170
+ for (const { op, stmt } of httpOps) {
171
+ if (op.operation !== "http.set_max_body")
172
+ continue;
173
+ const bytes = Number(String(op.bytes).trim());
174
+ if (Number.isFinite(bytes) && bytes > 65536) {
175
+ diagnostics.push(diagAt(stmt, {
176
+ severity: "warning",
177
+ code: "http-max-body-large",
178
+ message: `Http maxBody(${bytes}) heap-allocates a ${Math.round(bytes / 1024)} KB response buffer — large buffers risk allocation failure under fragmentation.`,
179
+ hint: "Keep the response cap at or below 64 KB, or process large payloads in chunks server-side.",
180
+ }));
181
+ }
182
+ }
183
+ return diagnostics;
184
+ }
@@ -176,6 +176,7 @@ function scanStatementsForByValueMutation(stmts, fnName, byValueParams, diagnost
176
176
  hint: `Use '${baseName}: Mutable<T>' to pass by mutable reference (T&) instead of a copy.`,
177
177
  line: stmt.sourceSpan.startLine,
178
178
  column: stmt.sourceSpan.startColumn,
179
+ filePath: stmt.sourceSpan.filePath,
179
180
  code: 'ownership-mutate-copy',
180
181
  source: 'ownership-analysis',
181
182
  });
@@ -330,6 +331,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
330
331
  hint: `const ${stmt.name}: Shared = ${initName}; // borrow by reference instead of copying`,
331
332
  line: span.startLine,
332
333
  column: span.startColumn,
334
+ filePath: span.filePath,
333
335
  code: 'ownership-owned-copy',
334
336
  source: 'ownership-analysis',
335
337
  });
@@ -346,6 +348,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
346
348
  hint: `const ${stmt.name}: Shared = ${initName}; // borrow by const reference, zero copy`,
347
349
  line: span.startLine,
348
350
  column: span.startColumn,
351
+ filePath: span.filePath,
349
352
  code: 'ownership-implicit-copy',
350
353
  source: 'ownership-analysis',
351
354
  });
@@ -363,6 +366,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
363
366
  hint: `${storageKw} _tmp: Owned = ...;\nconst ${stmt.name}: ${annotLabel} = _tmp;`,
364
367
  line: span.startLine,
365
368
  column: span.startColumn,
369
+ filePath: span.filePath,
366
370
  code: 'ownership-temp-ref-warn',
367
371
  source: 'ownership-analysis',
368
372
  });
@@ -386,6 +390,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
386
390
  hint: `change '${stmt.target}${typeAnnotation}' → '${stmt.target}: Mutable' // Mutable allows mutation`,
387
391
  line: span.startLine,
388
392
  column: span.startColumn,
393
+ filePath: span.filePath,
389
394
  code: 'ownership-assign-to-ref',
390
395
  source: 'ownership-analysis',
391
396
  });
@@ -430,6 +435,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
430
435
  hint: `Use '${baseName}: Mutable<T>' to pass by mutable reference (T&) instead of a copy.`,
431
436
  line: span.startLine,
432
437
  column: span.startColumn,
438
+ filePath: span.filePath,
433
439
  code: 'ownership-mutate-copy',
434
440
  source: 'ownership-analysis',
435
441
  });
@@ -449,6 +455,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
449
455
  hint: `change '${stmt.target}${typeAnnotation}' → '${stmt.target}: Mutable' // Mutable allows mutation`,
450
456
  line: span.startLine,
451
457
  column: span.startColumn,
458
+ filePath: span.filePath,
452
459
  code: 'ownership-assign-to-ref',
453
460
  source: 'ownership-analysis',
454
461
  });
@@ -461,6 +468,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
461
468
  hint: `const ${stmt.target}_ref: Shared = ${stmt.target}; // add this before the move`,
462
469
  line: span.startLine,
463
470
  column: span.startColumn,
471
+ filePath: span.filePath,
464
472
  code: 'ownership-use-after-move',
465
473
  source: 'ownership-analysis',
466
474
  });
@@ -477,6 +485,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
477
485
  hint: `Use '${upBaseName}: Mutable<T>' to pass by mutable reference (T&) instead of a copy.`,
478
486
  line: span.startLine,
479
487
  column: span.startColumn,
488
+ filePath: span.filePath,
480
489
  code: 'ownership-mutate-copy',
481
490
  source: 'ownership-analysis',
482
491
  });
@@ -507,6 +516,7 @@ function analyzeStatement(stmt, scope, diagnostics) {
507
516
  hint: `return ${retVar.borrowSource} directly as Owned, or change the function to accept '${retVar.borrowSource}: Shared' as a parameter`,
508
517
  line: span.startLine,
509
518
  column: span.startColumn,
519
+ filePath: span.filePath,
510
520
  code: 'ownership-return-local-ref',
511
521
  source: 'ownership-analysis',
512
522
  });
@@ -641,6 +651,7 @@ function analyzeExpression(expr, scope, diagnostics, fallbackSpan) {
641
651
  hint: `const ${name}_ref: Shared = ${name}; // add this before the move`,
642
652
  line: span.startLine,
643
653
  column: span.startColumn,
654
+ filePath: span.filePath,
644
655
  code: 'ownership-use-after-move',
645
656
  source: 'ownership-analysis',
646
657
  });
@@ -714,6 +725,7 @@ function analyzeExpression(expr, scope, diagnostics, fallbackSpan) {
714
725
  hint: `const ${match}_ref: Shared = ${match}; // add this before the move`,
715
726
  line: span.startLine,
716
727
  column: span.startColumn,
728
+ filePath: span.filePath,
717
729
  code: 'ownership-use-after-move',
718
730
  source: 'ownership-analysis',
719
731
  });
@@ -753,6 +765,12 @@ function analyzeExpression(expr, scope, diagnostics, fallbackSpan) {
753
765
  analyzeExpression(expr.inner, scope, diagnostics, span);
754
766
  break;
755
767
  }
768
+ case 'call': {
769
+ for (const arg of expr.args) {
770
+ analyzeExpression(arg, scope, diagnostics, span);
771
+ }
772
+ break;
773
+ }
756
774
  case 'number':
757
775
  case 'string':
758
776
  case 'boolean':
@@ -908,6 +926,7 @@ function validateConstSuggestions(program, diagnostics) {
908
926
  message: `'${baseName}' is declared 'const' but its contents are mutated via index assignment — demoted to non-const in C++ so the mutation compiles.`,
909
927
  line: constEntry.span.startLine,
910
928
  column: constEntry.span.startColumn,
929
+ filePath: constEntry.span.filePath,
911
930
  code: 'ownership-const-content-mutated',
912
931
  source: 'ownership-analysis',
913
932
  });
@@ -930,6 +949,7 @@ function validateConstSuggestions(program, diagnostics) {
930
949
  message: `'${baseName}' is declared 'const' but a field is mutated via member assignment — demoted to non-const in C++ so the mutation compiles.`,
931
950
  line: constEntry.span.startLine,
932
951
  column: constEntry.span.startColumn,
952
+ filePath: constEntry.span.filePath,
933
953
  code: 'ownership-const-content-mutated',
934
954
  source: 'ownership-analysis',
935
955
  });
@@ -957,6 +977,7 @@ function validateConstSuggestions(program, diagnostics) {
957
977
  message: `'${baseName}' is declared 'const' but a field is mutated via ++/-- — demoted to non-const in C++ so the mutation compiles.`,
958
978
  line: constEntry.span.startLine,
959
979
  column: constEntry.span.startColumn,
980
+ filePath: constEntry.span.filePath,
960
981
  code: 'ownership-const-content-mutated',
961
982
  source: 'ownership-analysis',
962
983
  });
@@ -968,7 +989,20 @@ function validateConstSuggestions(program, diagnostics) {
968
989
  // keep the binding non-const so push_back/splice/etc. compile. The
969
990
  // callee is the lowered C++ name, e.g. "arr.push_back".
970
991
  if (stmt.kind === 'call' && typeof stmt.callee === 'string') {
971
- const callee = stmt.callee;
992
+ // Value-arg array methods (.push/.pop/.fill/...) are lowered in
993
+ // call-statement.ts to a fully-formed raw C++ expression wrapped as
994
+ // `__RAW_STMT__<receiver>.<method>(<args>)` (e.g. `__RAW_STMT__arr.push_back(1)`).
995
+ // Strip the wrapper — and the trailing `(args)` when present — before
996
+ // splitting, otherwise the dot parse yields a receiver of
997
+ // `__RAW_STMT__arr` and a method of `push_back(1)` and the demotion
998
+ // below never fires, leaving a non-compiling `const std::vector` +
999
+ // push_back. Mirrors the __RAW_STMT__ handling in program-analysis.ts.
1000
+ const rawCallee = stmt.callee;
1001
+ const unwrapped = rawCallee.startsWith('__RAW_STMT__')
1002
+ ? rawCallee.slice('__RAW_STMT__'.length)
1003
+ : rawCallee;
1004
+ const parenIdx = unwrapped.indexOf('(');
1005
+ const callee = parenIdx > 0 ? unwrapped.slice(0, parenIdx) : unwrapped;
972
1006
  const dot = callee.lastIndexOf('.');
973
1007
  if (dot > 0) {
974
1008
  const receiver = callee.slice(0, dot);
@@ -991,6 +1025,7 @@ function validateConstSuggestions(program, diagnostics) {
991
1025
  message: `'${receiver}' is declared 'const' but its contents are mutated via .${method}() — demoted to non-const in C++ so the mutation compiles.`,
992
1026
  line: constEntry.span.startLine,
993
1027
  column: constEntry.span.startColumn,
1028
+ filePath: constEntry.span.filePath,
994
1029
  code: 'ownership-const-content-mutated',
995
1030
  source: 'ownership-analysis',
996
1031
  });
@@ -1023,6 +1058,7 @@ function validateConstSuggestions(program, diagnostics) {
1023
1058
  message: `'${entry.name}' is never reassigned — emitted as \`const\` so the C++ compiler can place it in ROM and fold it.`,
1024
1059
  line: entry.span.startLine,
1025
1060
  column: entry.span.startColumn,
1061
+ filePath: entry.span.filePath,
1026
1062
  code: 'ownership-suggest-const',
1027
1063
  source: 'ownership-analysis',
1028
1064
  });
@@ -1072,6 +1108,7 @@ function checkBorrowMismatch(program, diagnostics) {
1072
1108
  hint: `change '${arg.value}: Shared = ...' → '${arg.value}: Mutable = ...'`,
1073
1109
  line: stmt.sourceSpan.startLine,
1074
1110
  column: stmt.sourceSpan.startColumn,
1111
+ filePath: stmt.sourceSpan.filePath,
1075
1112
  code: 'ownership-borrow-mismatch',
1076
1113
  source: 'ownership-analysis',
1077
1114
  });
@@ -1127,6 +1164,7 @@ function checkDanglingBorrowsOnScopeExit(exitingScope, diagnostics, span) {
1127
1164
  hint: `move '${v.borrowSource}' to the outer scope, or ensure '${v.name}' does not outlive it`,
1128
1165
  line: span.startLine,
1129
1166
  column: span.startColumn,
1167
+ filePath: span.filePath,
1130
1168
  code: 'ownership-dangling-borrow',
1131
1169
  source: 'ownership-analysis',
1132
1170
  });
@@ -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[];