@typecad/cuttlefish 1.0.0-alpha.6 → 1.0.0-alpha.8

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 (187) hide show
  1. package/dist/api/config.d.ts +32 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/schema/types.d.ts +15 -0
  4. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  5. package/dist/api/shared/async-runtime-static.js +69 -24
  6. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  7. package/dist/api/shared/async-symbol-detector.js +140 -0
  8. package/dist/api/shared/async-types.d.ts +24 -0
  9. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  10. package/dist/api/shared/coop-scheduler.js +149 -0
  11. package/dist/api/shared/display-adapter.d.ts +4 -3
  12. package/dist/api/shared/display-adapter.js +15 -118
  13. package/dist/api/shared/display-adapters/sdl.js +38 -31
  14. package/dist/api/shared/display-profile.d.ts +47 -25
  15. package/dist/api/shared/display-profile.js +31 -164
  16. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  17. package/dist/api/shared/framework-manifest-registry.js +27 -0
  18. package/dist/api/shared/framework-manifest.d.ts +581 -0
  19. package/dist/api/shared/framework-manifest.js +182 -0
  20. package/dist/api/shared/glcdfont.d.ts +12 -0
  21. package/dist/api/shared/glcdfont.js +124 -0
  22. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  23. package/dist/api/shared/hal-op-ir.d.ts +755 -1
  24. package/dist/api/shared/hal-op-ir.js +129 -1
  25. package/dist/api/shared/index.d.ts +20 -2
  26. package/dist/api/shared/index.js +25 -1
  27. package/dist/api/shared/ir-core.d.ts +4 -0
  28. package/dist/api/shared/ir-declarations.d.ts +6 -0
  29. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  30. package/dist/api/shared/native-display-op-resolver.js +64 -0
  31. package/dist/api/shared/platform-strategy.d.ts +113 -2
  32. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  33. package/dist/api/shared/promise-runtime.d.ts +1 -1
  34. package/dist/api/shared/promise-runtime.js +95 -13
  35. package/dist/api/shared/toolchain-types.d.ts +17 -0
  36. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  37. package/dist/api/shared/validate-framework-manifest.js +494 -0
  38. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  39. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  40. package/dist/api/shared/worker-runtime.d.ts +69 -0
  41. package/dist/api/shared/worker-runtime.js +163 -0
  42. package/dist/cli.js +186 -82
  43. package/dist/config-loader.d.ts +7 -2
  44. package/dist/config-loader.js +23 -3
  45. package/dist/config-schema.d.ts +112 -70
  46. package/dist/config-schema.js +14 -0
  47. package/dist/contract/board-generator.d.ts +17 -0
  48. package/dist/contract/board-generator.js +57 -0
  49. package/dist/contract/contract-parser.d.ts +217 -0
  50. package/dist/contract/contract-parser.js +224 -0
  51. package/dist/contract/index.d.ts +22 -0
  52. package/dist/contract/index.js +68 -0
  53. package/dist/create/board-codegen.js +4 -4
  54. package/dist/create/board-generators.js +4 -5
  55. package/dist/create/board-spec.d.ts +72 -75
  56. package/dist/create/board-spec.js +0 -1
  57. package/dist/create/init-scaffold.d.ts +9 -2
  58. package/dist/create/init-scaffold.js +0 -14
  59. package/dist/create/init-templates.d.ts +2 -0
  60. package/dist/create/init-templates.js +74 -8
  61. package/dist/create/init-wizard.js +31 -7
  62. package/dist/debug/preprocessor.js +178 -39
  63. package/dist/debug/types.d.ts +23 -0
  64. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  65. package/dist/diagnostics/mermaid-builder.js +34 -24
  66. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  67. package/dist/emit/compliance/arxml-writer.js +34 -0
  68. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  69. package/dist/emit/compliance/compliance-context.js +113 -0
  70. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  71. package/dist/emit/compliance/deviation-ledger.js +47 -0
  72. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  73. package/dist/emit/compliance/deviation-writer.js +37 -0
  74. package/dist/emit/compliance/index.d.ts +7 -0
  75. package/dist/emit/compliance/index.js +6 -0
  76. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  77. package/dist/emit/compliance/rule-engine.js +101 -0
  78. package/dist/emit/compliance/rules.d.ts +16 -0
  79. package/dist/emit/compliance/rules.js +191 -0
  80. package/dist/emit/compliance/types.d.ts +59 -0
  81. package/dist/emit/compliance/types.js +8 -0
  82. package/dist/emit/cpp-emitter.js +4 -3
  83. package/dist/emit/emitters/class-emitter.js +6 -1
  84. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  85. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  86. package/dist/emit/emitters/line-appender.js +19 -0
  87. package/dist/emit/emitters/line-marker.d.ts +38 -0
  88. package/dist/emit/emitters/line-marker.js +39 -0
  89. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  90. package/dist/emit/emitters/output-finalizer.js +101 -12
  91. package/dist/emit/emitters/setup.d.ts +17 -0
  92. package/dist/emit/emitters/setup.js +257 -36
  93. package/dist/emit/emitters/top-level-prep.js +8 -0
  94. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  95. package/dist/emit/emitters/ui-emitter.js +33 -9
  96. package/dist/emit/expression-renderer.d.ts +1 -1
  97. package/dist/emit/expression-renderer.js +91 -6
  98. package/dist/emit/route-hal-op.js +18 -5
  99. package/dist/emit/snprintf-helpers.js +15 -4
  100. package/dist/emit/statement-renderer.d.ts +10 -0
  101. package/dist/emit/statement-renderer.js +34 -5
  102. package/dist/emit/utils/async-state-machine.js +221 -125
  103. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  104. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  105. package/dist/framework-package.js +2 -0
  106. package/dist/framework-registry.d.ts +17 -0
  107. package/dist/ir/adc-range-validation.js +14 -1
  108. package/dist/ir/build-ir-state.d.ts +1 -0
  109. package/dist/ir/build-ir-state.js +14 -0
  110. package/dist/ir/build-ir.js +9 -5
  111. package/dist/ir/call-graph.js +16 -0
  112. package/dist/ir/expression-to-ir.js +164 -0
  113. package/dist/ir/feature-registry.js +7 -25
  114. package/dist/ir/function-builder.js +22 -0
  115. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  116. package/dist/ir/hal/hal-emitter.js +50 -18
  117. package/dist/ir/hal/hal-parser.d.ts +6 -0
  118. package/dist/ir/hal/hal-parser.js +81 -0
  119. package/dist/ir/hal/hal-plugins.js +723 -1
  120. package/dist/ir/identifier-collector.js +35 -0
  121. package/dist/ir/interrupt-analysis.d.ts +5 -1
  122. package/dist/ir/interrupt-analysis.js +8 -16
  123. package/dist/ir/network-validation.d.ts +4 -0
  124. package/dist/ir/network-validation.js +184 -0
  125. package/dist/ir/ownership-analysis.js +20 -1
  126. package/dist/ir/pin-capability-validation.js +37 -0
  127. package/dist/ir/pin-mode-validation.d.ts +2 -2
  128. package/dist/ir/pin-mode-validation.js +49 -18
  129. package/dist/ir/program-analysis.d.ts +65 -0
  130. package/dist/ir/program-analysis.js +318 -2
  131. package/dist/ir/render-expr.js +11 -0
  132. package/dist/ir/timing-validation.d.ts +6 -1
  133. package/dist/ir/timing-validation.js +60 -13
  134. package/dist/ir/transformers/call-statement.js +105 -0
  135. package/dist/ir/transformers/expressions.js +62 -0
  136. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  137. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  138. package/dist/ir/transformers/namespace-methods.js +17 -12
  139. package/dist/ir/transformers/ui-reactive.js +2 -2
  140. package/dist/ir/transformers/variables.js +92 -3
  141. package/dist/ir/type-resolution.js +18 -0
  142. package/dist/ir/ui-element-auto-wire.js +7 -5
  143. package/dist/ir/utils/map-statements.d.ts +4 -0
  144. package/dist/ir/utils/map-statements.js +79 -0
  145. package/dist/ir/validation-orchestrator.js +9 -2
  146. package/dist/ir/worker-analysis.d.ts +10 -0
  147. package/dist/ir/worker-analysis.js +261 -0
  148. package/dist/libdef/c-to-decl.d.ts +27 -0
  149. package/dist/libdef/c-to-decl.js +397 -0
  150. package/dist/libdef/component-decls.d.ts +2 -0
  151. package/dist/libdef/component-decls.js +6 -0
  152. package/dist/libdef/component-discovery.d.ts +43 -0
  153. package/dist/libdef/component-discovery.js +83 -0
  154. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  155. package/dist/libdef/cpp-to-decl.js +72 -0
  156. package/dist/libdef/registry.js +5 -2
  157. package/dist/lint-cache.d.ts +59 -0
  158. package/dist/lint-cache.js +257 -0
  159. package/dist/orchestrator/graph-builder.js +14 -9
  160. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  161. package/dist/platform/coop-scheduler-runtime.js +41 -0
  162. package/dist/platform/generic-strategy.d.ts +15 -3
  163. package/dist/platform/generic-strategy.js +49 -4
  164. package/dist/safety/safety-bridge.d.ts +11 -0
  165. package/dist/safety/safety-bridge.js +48 -0
  166. package/dist/safety/sidecar-bridge.d.ts +5 -0
  167. package/dist/safety/sidecar-bridge.js +22 -0
  168. package/dist/safety-hook.d.ts +79 -0
  169. package/dist/safety-hook.js +35 -0
  170. package/dist/testing.d.ts +7 -1
  171. package/dist/testing.js +8 -1
  172. package/dist/transpile.d.ts +3 -0
  173. package/dist/transpile.js +175 -41
  174. package/dist/types.d.ts +13 -2
  175. package/dist/ui-hook.d.ts +17 -3
  176. package/dist/utils/cli.js +78 -4
  177. package/dist/utils/fs.d.ts +13 -0
  178. package/dist/utils/fs.js +50 -0
  179. package/package.json +24 -5
  180. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  181. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  182. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  183. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  184. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  185. package/dist/api/shared/display-adapters/st7796.js +0 -132
  186. package/dist/licenses.d.ts +0 -185
  187. package/dist/licenses.js +0 -963
@@ -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
  *
@@ -243,7 +235,7 @@ function scanExpressionForCallbacks(expr, callback, parentExpr) {
243
235
  // emission path already handles isVolatile (statement-renderer.ts).
244
236
  // ---------------------------------------------------------------------------
245
237
  /** Collect variable names assigned (written) within a statement body. */
246
- function collectAssignedNames(stmts, names) {
238
+ export function collectAssignedNames(stmts, names) {
247
239
  for (const stmt of stmts) {
248
240
  const s = stmt;
249
241
  if (stmt.kind === 'assign') {
@@ -265,7 +257,7 @@ function collectAssignedNames(stmts, names) {
265
257
  }
266
258
  }
267
259
  /** Collect variable names read (referenced) within a statement body. */
268
- function collectReadNames(stmts, names) {
260
+ export function collectReadNames(stmts, names) {
269
261
  for (const stmt of stmts) {
270
262
  walkExpressionsInStatement(stmt, (expr) => {
271
263
  if (expr.kind === 'identifier' && typeof expr.value === 'string') {
@@ -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
+ }
@@ -765,6 +765,12 @@ function analyzeExpression(expr, scope, diagnostics, fallbackSpan) {
765
765
  analyzeExpression(expr.inner, scope, diagnostics, span);
766
766
  break;
767
767
  }
768
+ case 'call': {
769
+ for (const arg of expr.args) {
770
+ analyzeExpression(arg, scope, diagnostics, span);
771
+ }
772
+ break;
773
+ }
768
774
  case 'number':
769
775
  case 'string':
770
776
  case 'boolean':
@@ -983,7 +989,20 @@ function validateConstSuggestions(program, diagnostics) {
983
989
  // keep the binding non-const so push_back/splice/etc. compile. The
984
990
  // callee is the lowered C++ name, e.g. "arr.push_back".
985
991
  if (stmt.kind === 'call' && typeof stmt.callee === 'string') {
986
- 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;
987
1006
  const dot = callee.lastIndexOf('.');
988
1007
  if (dot > 0) {
989
1008
  const receiver = callee.slice(0, dot);
@@ -77,6 +77,35 @@ function findPinsWithCapability(capability, boardConstants) {
77
77
  }
78
78
  return result;
79
79
  }
80
+ /**
81
+ * Does the board declare ANY per-pin `type:'pwm'` function entry anywhere?
82
+ *
83
+ * AVR-family boards (atmega328p, etc.) hardwire PWM to specific timer-output
84
+ * pins, so their MCU descriptors list `{ type:'pwm', ... }` per pin and the
85
+ * capability check correctly rejects PWM on non-timer pins. ESP32-family
86
+ * boards route LEDC to ANY output GPIO via the GPIO matrix, so their MCU
87
+ * descriptors carry zero `type:'pwm'` entries — PWM is a board-wide property
88
+ * (the `pins.pwm` collection), not a per-pin function. On such boards the
89
+ * per-pin capability check would false-positive on every pin, so the caller
90
+ * skips it when this returns false. This distinguishes the two families
91
+ * cleanly (AVR has entries, ESP32 has none) without special-casing arch names.
92
+ */
93
+ function boardHasAnyPwmFunctionEntries(boardConstants) {
94
+ if (!boardConstants)
95
+ return false;
96
+ for (let i = 0; i < 100; i++) {
97
+ if (boardConstants.get(`pins.all.${i}.name`) === undefined)
98
+ continue;
99
+ for (let j = 0; j < 10; j++) {
100
+ const type = boardConstants.get(`pins.all.${i}.functions.${j}.type`);
101
+ if (type === undefined)
102
+ break;
103
+ if (String(type) === 'pwm')
104
+ return true;
105
+ }
106
+ }
107
+ return false;
108
+ }
80
109
  // ---------------------------------------------------------------------------
81
110
  // Capability check for a single HAL operation
82
111
  // ---------------------------------------------------------------------------
@@ -88,6 +117,13 @@ function checkCapability(operation, sourceLine, sourceCol, filePath, boardConsta
88
117
  const capability = operationToCapability(op);
89
118
  if (!capability)
90
119
  return; // GPIO/timing — always available.
120
+ // ESP32-family boards route PWM (LEDC) to any output GPIO via the GPIO
121
+ // matrix, so they carry zero per-pin `type:'pwm'` entries (unlike AVR,
122
+ // which hardwires PWM to timer-output pins). On such boards the per-pin PWM
123
+ // capability check would reject every pin; skip it and let the framework's
124
+ // LEDC driver handle routing. This does NOT weaken AVR, which has entries.
125
+ if (capability === 'pwm' && !boardHasAnyPwmFunctionEntries(boardConstants))
126
+ return;
91
127
  const pin = operation.pin;
92
128
  if (typeof pin !== 'number' || pin < 0)
93
129
  return;
@@ -250,6 +286,7 @@ function scanExpression(expr, boardConstants, parentLine, parentCol, parentFileP
250
286
  case 'identifier':
251
287
  case 'raw':
252
288
  case 'tuple-access':
289
+ case 'call':
253
290
  break;
254
291
  default:
255
292
  assertNever(expr);
@@ -4,7 +4,7 @@ import type { Diagnostic } from '../types.js';
4
4
  * Validate that GPIO I/O operations are preceded by mode configuration.
5
5
  *
6
6
  * - Read without prior mode → **warning** (undefined behavior on floating pin)
7
- * - Write without prior mode → **info** (Arduino implicitly sets OUTPUT, but
8
- * explicit configuration is recommended)
7
+ * - Write without prior mode → **info** (Wiring-derived frameworks implicitly
8
+ * set OUTPUT, but explicit configuration is recommended)
9
9
  */
10
10
  export declare function validatePinModeConfig(program: ProgramIR): Diagnostic[];
@@ -5,6 +5,7 @@
5
5
  // configuration. Generates warnings for reads and info for writes.
6
6
  // ---------------------------------------------------------------------------
7
7
  import { scanNestedStatements } from './interrupt-analysis.js';
8
+ import { hasLoadedFramework, getLoadedFramework } from '../framework-registry.js';
8
9
  /** Methods that configure pin mode. */
9
10
  const MODE_SET_METHODS = new Set([
10
11
  'inputPullUp', 'inputPullDown', 'outputOpenDrain',
@@ -14,7 +15,7 @@ const MODE_SET_METHODS = new Set([
14
15
  const READ_METHODS = new Set([
15
16
  'read', 'isHigh', 'isLow', 'readAnalog', 'readVoltage',
16
17
  ]);
17
- /** Write operations that implicitly set OUTPUT mode on Arduino. */
18
+ /** Write operations that implicitly set OUTPUT mode on Wiring-derived frameworks. */
18
19
  const WRITE_METHODS = new Set([
19
20
  'write', 'high', 'low', 'toggle', 'pulse', 'pwm', 'tone',
20
21
  ]);
@@ -26,8 +27,8 @@ const PIN_RECEIVER_KINDS = new Set([
26
27
  * Validate that GPIO I/O operations are preceded by mode configuration.
27
28
  *
28
29
  * - Read without prior mode → **warning** (undefined behavior on floating pin)
29
- * - Write without prior mode → **info** (Arduino implicitly sets OUTPUT, but
30
- * explicit configuration is recommended)
30
+ * - Write without prior mode → **info** (Wiring-derived frameworks implicitly
31
+ * set OUTPUT, but explicit configuration is recommended)
31
32
  */
32
33
  export function validatePinModeConfig(program) {
33
34
  const diagnostics = [];
@@ -65,7 +66,7 @@ export function validatePinModeConfig(program) {
65
66
  diagnostics.push({
66
67
  severity: 'info',
67
68
  message: `Pin '${receiver}' written via '${method}()' without explicit mode configuration. ` +
68
- `Arduino implicitly sets OUTPUT, but explicit ${receiver}.asOutput() is recommended.`,
69
+ `The target framework implicitly sets OUTPUT, but explicit ${receiver}.asOutput() is recommended.`,
69
70
  filePath: program.fileName,
70
71
  code: 'pin-mode-not-set',
71
72
  source: 'pin-mode-validation',
@@ -112,7 +113,7 @@ export function validatePinModeConfig(program) {
112
113
  diagnostics.push({
113
114
  severity: 'info',
114
115
  message: `Pin ${op.pin} written without explicit mode configuration. ` +
115
- `Arduino implicitly sets OUTPUT, but explicit asOutput() is recommended.`,
116
+ `The target framework implicitly sets OUTPUT, but explicit asOutput() is recommended.`,
116
117
  filePath: program.fileName,
117
118
  code: 'pin-mode-not-set',
118
119
  source: 'pin-mode-validation',
@@ -148,21 +149,51 @@ export function validatePinModeConfig(program) {
148
149
  // Raw nodes: after full HAL resolution, some pin ops land as their C++ text
149
150
  // (e.g. `digitalRead(2)`, `digitalWrite(2, 1)`) rather than structured
150
151
  // hal-expr nodes. Extract the pin number and apply the same mode check.
151
- // This mirrors how peripheral-usage.ts detects gpio via emitted text.
152
+ // Detect GPIO ops in lowered raw C++ text. Build the read/write name
153
+ // patterns from the loaded framework's HAL vocabulary (halCallNames),
154
+ // classifying names containing "Read" as reads and "Write" as writes (the
155
+ // Wiring-derived convention). Seed with digitalRead/digitalWrite as the
156
+ // canonical Wiring forms so the validator works even when no framework is
157
+ // loaded. Non-Wiring frameworks that emit other call forms register them
158
+ // via halCallNames; this loop picks them up.
152
159
  if (e.kind === 'raw' && typeof e.value === 'string') {
153
- const readMatch = e.value.match(/digitalRead\((\d+)\)/);
154
- const writeMatch = e.value.match(/digitalWrite\((\d+)/);
155
- const toggleMatch = e.value.match(/digitalWrite\((\d+),\s*digitalRead/);
156
- const pin = readMatch ? parseInt(readMatch[1], 10)
157
- : writeMatch ? parseInt(writeMatch[1], 10)
158
- : null;
159
- if (pin !== null) {
160
- const op = toggleMatch
161
- ? { operation: 'gpio.toggle', pin }
162
- : readMatch ? { operation: 'gpio.read', pin }
163
- : { operation: 'gpio.write', pin };
164
- checkGpioHalOp(op);
160
+ const frameworkHalNames = hasLoadedFramework()
161
+ ? getLoadedFramework().strategy.halCallNames?.()
162
+ : undefined;
163
+ const halNames = frameworkHalNames ?? new Set(['digitalRead', 'digitalWrite']);
164
+ const readNames = [...halNames].filter(n => /read/i.test(n));
165
+ const writeNames = [...halNames].filter(n => /write/i.test(n));
166
+ let pin = null;
167
+ let op = null;
168
+ for (const rn of readNames) {
169
+ const esc = rn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
170
+ const m = e.value.match(new RegExp(`${esc}\\((\\d+)\\)`));
171
+ if (m) {
172
+ pin = parseInt(m[1], 10);
173
+ op = { operation: 'gpio.read', pin };
174
+ break;
175
+ }
176
+ }
177
+ if (!op) {
178
+ for (const wn of writeNames) {
179
+ const esc = wn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
180
+ const m = e.value.match(new RegExp(`${esc}\\((\\d+)`));
181
+ if (m) {
182
+ pin = parseInt(m[1], 10);
183
+ op = { operation: 'gpio.write', pin };
184
+ break;
185
+ }
186
+ }
187
+ }
188
+ // toggle: a write call whose second arg is a read call on the same pin.
189
+ if (!op) {
190
+ const tm = e.value.match(/\((\d+),\s*\w*[Rr]ead/);
191
+ if (tm) {
192
+ op = { operation: 'gpio.toggle', pin: parseInt(tm[1], 10) };
193
+ }
165
194
  }
195
+ if (op)
196
+ checkGpioHalOp(op);
166
197
  }
167
198
  // Recurse into nested expressions
168
199
  if (e.args && Array.isArray(e.args)) {
@@ -52,6 +52,71 @@ export interface ProgramAnalysisResult {
52
52
  * __tc_TimerRuntime::MAX_TIMERS to the observed count (floor 1) rather than
53
53
  * a blind constant, so a one-timer program links one slot, not eight. */
54
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
+ /** Fine-grained WiFi sub-feature tracking — gates individual shim sub-blocks
74
+ * so that unused static helper functions don't trigger -Wunused-function. */
75
+ usesWifiConnect: boolean;
76
+ usesWifiConnectBlocking: boolean;
77
+ usesWifiQuery: boolean;
78
+ usesWifiScan: boolean;
79
+ usesWifiConfig: boolean;
80
+ usesHttp: boolean;
81
+ usesBle: boolean;
82
+ /** Preferences (NVS) usage — framework-esp32 gates the __tc_prefs runtime shim
83
+ * and its nvs_flash/nvs includes on this. Detected from preferences.* ops. */
84
+ usesPreferences: boolean;
85
+ /** Random usage — frameworks gate their PRNG runtime/include on this
86
+ * (e.g. framework-esp32 includes <esp_random.h>). Detected from random.* ops. */
87
+ usesRandom: boolean;
88
+ /** FS (filesystem) usage — frameworks gate their VFS/SD runtime shim on this.
89
+ * Detected from fs.* ops. */
90
+ usesFS: boolean;
91
+ /** mDNS usage — frameworks gate the esp_mdns runtime/include on this. */
92
+ usesMdns: boolean;
93
+ /** MQTT usage — frameworks gate the esp_mqtt runtime/include on this. */
94
+ usesMqtt: boolean;
95
+ /** OTA usage — frameworks gate the esp_https_ota runtime/include on this. */
96
+ usesOta: boolean;
97
+ /** Temperature (die temp) usage. Detected from temp.* ops. */
98
+ usesTemp: boolean;
99
+ /** Hardware timer (GPTimer) usage. Detected from hwtimer.* ops. */
100
+ usesHwtimer: boolean;
101
+ /** Capacitive touch pins usage. Detected from capacitive.* ops. */
102
+ usesCapacitive: boolean;
103
+ /** Worker offload usage. Detected from worker.* ops. Frameworks gate the
104
+ * worker_runtime polyfill (and its per-framework backing) on this. */
105
+ usesWorker: boolean;
106
+ /** Native/desktop: std::chrono / steady_clock usage (gates <chrono>). */
107
+ usesChrono: boolean;
108
+ /** Native/desktop: std::set usage (gates <set>). */
109
+ usesSet: boolean;
110
+ /** Native/desktop: std::algorithm usage (std::sort/find/transform etc., gates <algorithm>). */
111
+ usesAlgorithm: boolean;
112
+ /** Native/desktop: cstdio usage (printf/fprintf/snprintf, gates <cstdio>). */
113
+ usesCstdio: boolean;
114
+ /** digitalRead() in lowered raw text or gpio.read hal-op (gates the native digitalRead shim). */
115
+ usesDigitalRead: boolean;
116
+ /** display.* hal-op present, or a UI is mounted (replaces ad-hoc programUsesDisplay walks). */
117
+ usesDisplay: boolean;
118
+ /** cuttlefish_halt() in lowered raw text (gates the cuttlefish_halt macro polyfill). */
119
+ usesHalt: boolean;
55
120
  }
56
121
  /**
57
122
  * Analyze a program IR in a single pass to detect all features.