@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
@@ -40,6 +40,12 @@ export function preprocess(options) {
40
40
  // Collect variables in scope at each breakpoint
41
41
  const scopeAnalyzer = new ScopeAnalyzer(sf);
42
42
  const debugStrategy = getDebugStrategy();
43
+ // Per-file breakpoint ID counter. Each HALTING breakpoint gets a stable
44
+ // integer ID so the codegen can emit a per-breakpoint disable flag (skipped
45
+ // via the 's' key at runtime). IDs are scoped to this file and reset per
46
+ // preprocess() call, so they stay small and deterministic across builds.
47
+ // Logpoints never halt and don't need an ID.
48
+ let nextBreakpointId = 0;
43
49
  // Find the first non-import, non-comment line to insert debug init
44
50
  let insertIndex = 0;
45
51
  for (let i = 0; i < lines.length; i++) {
@@ -67,8 +73,12 @@ export function preprocess(options) {
67
73
  const displayLine = lineText.trim();
68
74
  // Collect variables in scope at this line
69
75
  const vars = scopeAnalyzer.getVariablesInScope(lineNum);
76
+ // Halting breakpoints get a per-file ID (used by the codegen to emit a
77
+ // disable flag the user can toggle at runtime with the 's' key). Logpoints
78
+ // never halt, so they pass no ID.
79
+ const breakpointId = bp.logMessage ? undefined : nextBreakpointId++;
70
80
  // Inject debug code BEFORE the breakpoint line
71
- outputLines.push(...generateBreakpointCode(relativeFileName, lineNum + 1, displayLine, vars, bp, debugStrategy));
81
+ outputLines.push(...generateBreakpointCode(relativeFileName, lineNum + 1, displayLine, vars, bp, debugStrategy, breakpointId));
72
82
  }
73
83
  // Always include the original line
74
84
  outputLines.push(lineText);
@@ -87,14 +97,14 @@ export function preprocess(options) {
87
97
  * Generate debug code for a breakpoint.
88
98
  * Delegates to the loaded strategy when available.
89
99
  */
90
- function generateBreakpointCode(fileName, lineNum, originalLine, variables, bp, debugStrategy) {
100
+ function generateBreakpointCode(fileName, lineNum, originalLine, variables, bp, debugStrategy, breakpointId) {
91
101
  // If this is a logpoint (has logMessage), generate log-only code
92
102
  if (bp.logMessage) {
93
103
  const parts = parseLogMessage(bp.logMessage);
94
104
  if (debugStrategy.generateDebugLogpointCode) {
95
105
  return debugStrategy.generateDebugLogpointCode({
96
106
  fileName, lineNum, parts,
97
- variables: variables.map(v => ({ name: v.name, isFunction: v.isFunction })),
107
+ variables: variables.map(v => ({ name: v.name, isFunction: v.isFunction, cppType: v.cppType })),
98
108
  });
99
109
  }
100
110
  return [];
@@ -104,8 +114,9 @@ function generateBreakpointCode(fileName, lineNum, originalLine, variables, bp,
104
114
  if (debugStrategy.generateDebugBreakpointCode) {
105
115
  return debugStrategy.generateDebugBreakpointCode({
106
116
  fileName, lineNum, originalLine,
107
- variables: variables.map(v => ({ name: v.name, isFunction: v.isFunction })),
117
+ variables: variables.map(v => ({ name: v.name, isFunction: v.isFunction, cppType: v.cppType })),
108
118
  normalizedCondition,
119
+ breakpointId,
109
120
  });
110
121
  }
111
122
  return [];
@@ -162,52 +173,180 @@ class ScopeAnalyzer {
162
173
  if (this.functionVars.has(line)) {
163
174
  return this.functionVars.get(line);
164
175
  }
165
- return this.functionVars.get(-1) || [];
176
+ // Module-scope (top-level): filter by declaration order so breakpoints
177
+ // before a variable's declaration don't reference it (use-before-declare).
178
+ const topVars = this.functionVars.get(-1) || [];
179
+ return topVars.filter(v => v.declLine === undefined || v.declLine < line);
166
180
  }
167
181
  analyze() {
182
+ // Top-level (module-scope) variables. Iterating sf.statements is reliable
183
+ // across TS statement-classification variants (a bare `let x = 0;` can be
184
+ // parsed as either VariableStatement or FirstStatement; both expose
185
+ // .declarationList, so read it uniformly).
168
186
  const topLevelVars = [];
169
- this.collectVariables(this.sf, topLevelVars);
187
+ for (const stmt of this.sf.statements) {
188
+ this.collectVariablesFromStatement(stmt, topLevelVars);
189
+ }
170
190
  this.functionVars.set(-1, topLevelVars);
171
- const visit = (node, currentVars) => {
172
- if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)) {
173
- const startLine = this.sf.getLineAndCharacterOfPosition(node.getStart()).line;
174
- const endLine = node.body
175
- ? this.sf.getLineAndCharacterOfPosition(node.body.getEnd()).line
176
- : startLine;
177
- const localVars = [...currentVars];
178
- for (const param of node.parameters) {
179
- if (ts.isIdentifier(param.name)) {
180
- localVars.push({ name: param.name.text });
181
- }
182
- }
183
- if (node.body) {
184
- this.collectVariables(node.body, localVars);
185
- }
186
- for (let line = startLine; line <= endLine; line++) {
187
- this.functionVars.set(line, localVars);
191
+ // Record a scope entry for every line inside each function body, capturing
192
+ // enclosing-scope vars + parameters + body locals. Nested functions get
193
+ // their own entries that shadow the parent on their line range.
194
+ //
195
+ // Per-line scope filtering: body locals are only in scope on lines STRICTLY
196
+ // AFTER their declaration line. The breakpoint dump is injected at the
197
+ // START of the breakpoint line (before that line's own declaration runs),
198
+ // so a local declared on line N is not yet in scope at line N. Params and
199
+ // enclosing-scope vars are in scope from the function's first line.
200
+ const recordFunction = (node, currentVars) => {
201
+ const startLine = this.sf.getLineAndCharacterOfPosition(node.getStart()).line;
202
+ const endLine = node.body
203
+ ? this.sf.getLineAndCharacterOfPosition(node.body.getEnd()).line
204
+ : startLine;
205
+ // Always-in-scope from the function's first line: enclosing vars + params.
206
+ const alwaysInScope = [...currentVars];
207
+ for (const param of node.parameters) {
208
+ if (ts.isIdentifier(param.name)) {
209
+ alwaysInScope.push({
210
+ name: param.name.text,
211
+ // Params have no initializer; infer from the annotation only.
212
+ cppType: this.inferCppTypeFromAnnotation(param.type),
213
+ });
188
214
  }
189
- return localVars;
190
215
  }
191
- ts.forEachChild(node, child => visit(child, currentVars));
192
- return currentVars;
193
- };
194
- visit(this.sf, topLevelVars);
195
- }
196
- collectVariables(node, vars) {
197
- if (ts.isVariableStatement(node)) {
198
- for (const decl of node.declarationList.declarations) {
199
- if (ts.isIdentifier(decl.name)) {
200
- const isFunction = decl.initializer !== undefined && this.isFunctionExpression(decl.initializer);
201
- vars.push({ name: decl.name.text, isFunction });
202
- }
216
+ // Body locals carry their own declaration line for per-line filtering.
217
+ const bodyLocals = [];
218
+ if (node.body) {
219
+ this.collectVariablesInBody(node.body, bodyLocals, recordFunction);
220
+ }
221
+ for (let line = startLine; line <= endLine; line++) {
222
+ // A body local is in scope at `line` only if declared on a strictly
223
+ // earlier line (declLine < line). This prevents both compile errors
224
+ // (use-before-declaration on the declaring line) and runtime reads of
225
+ // uninitialized locals at breakpoints above the declaration.
226
+ const localsInScope = bodyLocals.filter(v => v.declLine === undefined || v.declLine < line);
227
+ this.functionVars.set(line, [...alwaysInScope, ...localsInScope]);
203
228
  }
229
+ };
230
+ // Find top-level functions, then recurse into their bodies for nested ones.
231
+ for (const stmt of this.sf.statements) {
232
+ this.findAndRecordFunctions(stmt, topLevelVars, recordFunction);
233
+ }
234
+ }
235
+ /** Collect variables declared directly by a single statement (top-level use). */
236
+ collectVariablesFromStatement(stmt, vars) {
237
+ if (ts.isVariableStatement(stmt) && stmt.declarationList) {
238
+ this.collectFromDeclarationList(stmt.declarationList, vars);
204
239
  }
205
- ts.forEachChild(node, child => {
206
- if (!ts.isFunctionDeclaration(child) && !ts.isArrowFunction(child) && !ts.isMethodDeclaration(child)) {
207
- this.collectVariables(child, vars);
240
+ else if (this.isFirstStatementVariableDeclaration(stmt)) {
241
+ this.collectFromDeclarationList(stmt.declarationList, vars);
242
+ }
243
+ }
244
+ /**
245
+ * Walk a function body collecting locals, recursing into nested functions so
246
+ * they get their own scope entry (without leaking their locals to the parent).
247
+ */
248
+ collectVariablesInBody(body, vars, recordFunction) {
249
+ ts.forEachChild(body, (child) => {
250
+ if (ts.isFunctionDeclaration(child) || ts.isArrowFunction(child) || ts.isMethodDeclaration(child)) {
251
+ // Nested function: record its scope, but do not add its name to this
252
+ // scope's printable vars (it's a function value).
253
+ recordFunction(child, vars);
254
+ return;
208
255
  }
256
+ this.collectVariablesFromStatement(child, vars);
257
+ this.collectVariablesInBody(child, vars, recordFunction);
209
258
  });
210
259
  }
260
+ /** Recurse looking for function declarations to record. */
261
+ findAndRecordFunctions(node, currentVars, recordFunction) {
262
+ if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)) {
263
+ recordFunction(node, currentVars);
264
+ return; // recordFunction already walks the body for nested functions.
265
+ }
266
+ ts.forEachChild(node, (child) => this.findAndRecordFunctions(child, currentVars, recordFunction));
267
+ }
268
+ collectFromDeclarationList(list, vars) {
269
+ for (const decl of list.declarations) {
270
+ if (ts.isIdentifier(decl.name)) {
271
+ const isFunction = decl.initializer !== undefined && this.isFunctionExpression(decl.initializer);
272
+ // declLine is the 0-indexed source line of the declaration. Used by
273
+ // recordFunction to filter body locals per line (declLine < line).
274
+ const declLine = this.sf.getLineAndCharacterOfPosition(decl.getStart()).line;
275
+ vars.push({
276
+ name: decl.name.text,
277
+ isFunction,
278
+ declLine,
279
+ cppType: isFunction ? undefined : this.inferCppType(decl),
280
+ });
281
+ }
282
+ }
283
+ }
284
+ /**
285
+ * Infer a coarse C++ type category from an explicit `: T` type annotation
286
+ * node. Returns `unknown` when there is no annotation or it isn't a primitive
287
+ * keyword we recognize.
288
+ */
289
+ inferCppTypeFromAnnotation(typeNode) {
290
+ if (!typeNode)
291
+ return 'unknown';
292
+ if (typeNode.kind === ts.SyntaxKind.BooleanKeyword)
293
+ return 'bool';
294
+ if (typeNode.kind === ts.SyntaxKind.StringKeyword)
295
+ return 'string';
296
+ if (typeNode.kind === ts.SyntaxKind.NumberKeyword)
297
+ return 'float'; // TS number → %g is the safe choice
298
+ if (typeNode.kind === ts.SyntaxKind.BigIntKeyword)
299
+ return 'long';
300
+ return 'unknown';
301
+ }
302
+ /**
303
+ * Infer a coarse C++ type category from a variable declaration, WITHOUT a
304
+ * TypeChecker (the debug path is parse-only). Priority:
305
+ * 1. Explicit `: T` annotation (authoritative).
306
+ * 2. Initializer shape (literal kind, or a small allowlist of known HAL
307
+ * calls like millis()/micros() → long).
308
+ * 3. `unknown` — printf codegens cast to double + %g so it always compiles.
309
+ *
310
+ * Function-valued vars are handled by the caller (isFunction); this returns
311
+ * unknown for them but the caller skips emission of function-typed vars.
312
+ */
313
+ inferCppType(decl) {
314
+ // 1. Explicit type annotation wins.
315
+ const fromAnnotation = this.inferCppTypeFromAnnotation(decl.type);
316
+ if (fromAnnotation !== 'unknown')
317
+ return fromAnnotation;
318
+ // 2. Initializer shape.
319
+ const init = decl.initializer;
320
+ if (init) {
321
+ if (init.kind === ts.SyntaxKind.TrueKeyword || init.kind === ts.SyntaxKind.FalseKeyword)
322
+ return 'bool';
323
+ if (ts.isStringLiteral(init))
324
+ return 'string';
325
+ if (ts.isNoSubstitutionTemplateLiteral(init) || ts.isTemplateExpression(init))
326
+ return 'string';
327
+ if (ts.isNumericLiteral(init)) {
328
+ // Integer literal unless it contains '.' or an exponent marker.
329
+ return /[.eE]/.test(init.text) ? 'float' : 'int';
330
+ }
331
+ if (ts.isCallExpression(init)) {
332
+ // A small allowlist of HAL calls with known C++ return types.
333
+ const callee = ts.isIdentifier(init.expression) ? init.expression.text : '';
334
+ if (callee === 'millis' || callee === 'micros')
335
+ return 'long';
336
+ }
337
+ }
338
+ // 3. Anything else (references, binary expressions, unknown calls, …).
339
+ return 'unknown';
340
+ }
341
+ /**
342
+ * TS sometimes parses a bare `let x = 0;` as a FirstStatement node rather
343
+ * than a VariableStatement. Detect that case by shape (has declarationList).
344
+ */
345
+ isFirstStatementVariableDeclaration(node) {
346
+ if (node.kind !== ts.SyntaxKind.FirstStatement)
347
+ return false;
348
+ return Object.prototype.hasOwnProperty.call(node, 'declarationList');
349
+ }
211
350
  isFunctionExpression(expr) {
212
351
  return ts.isArrowFunction(expr) || ts.isFunctionExpression(expr);
213
352
  }
@@ -6,7 +6,30 @@ export interface CapturedVariable {
6
6
  name: string;
7
7
  /** Whether this is a function (cannot print value) */
8
8
  isFunction?: boolean;
9
+ /**
10
+ * Coarse C++ type category, inferred from the declaration's annotation or
11
+ * initializer shape (no TypeChecker is available in the debug path). Used by
12
+ * printf-based codegens (ESP-IDF) to pick the right format specifier; ignored
13
+ * by type-agnostic codegens (Arduino's Serial.println).
14
+ */
15
+ cppType?: DebugCppType;
16
+ /**
17
+ * 0-indexed source line on which this variable is declared. Used by the
18
+ * scope analyzer to exclude not-yet-declared locals from a breakpoint's
19
+ * variable dump (the dump is injected at the START of the breakpoint line,
20
+ * before that line's own declaration executes).
21
+ */
22
+ declLine?: number;
9
23
  }
24
+ /**
25
+ * Coarse C++ type category for debug variable formatting.
26
+ *
27
+ * The debug preprocessor has no TypeChecker (it uses parse-only
28
+ * createSourceFile), so this is inferred from explicit `: T` annotations and
29
+ * initializer shape. `unknown` is the fallback; printf-based codegens cast to
30
+ * `double` and use `%g` so the code always compiles.
31
+ */
32
+ export type DebugCppType = 'bool' | 'int' | 'long' | 'float' | 'string' | 'unknown';
10
33
  /**
11
34
  * A rich breakpoint with optional condition and log message.
12
35
  */
@@ -2,7 +2,7 @@ import type { CallGraph } from "../ir/call-graph.js";
2
2
  import type { GpioPinEntry, PeripheralAllocation, AsyncTaskEntry, ExecutionFlow, HeapEstimate, PeripheralConflictEntry, ModuleGraph, TreeShakingReport, BuildTiming } from "./json-schema.js";
3
3
  /**
4
4
  * Build a categorized architecture diagram showing how the sketch
5
- * is organized: entry points, HAL objects, Arduino APIs (grouped by
5
+ * is organized: entry points, HAL objects, HAL APIs (grouped by
6
6
  * subsystem), state variables, and custom functions.
7
7
  *
8
8
  * Noise symbols (true/false/HIGH/board chain) are filtered out.
@@ -41,30 +41,40 @@ const BOARD_CHAIN = new Set([
41
41
  "TypeCAD", "Arduino", "Uno", "Nano", "Mega", "Mega2560",
42
42
  "Demo", "Board", "ATmega328P", "Features", "Esp32", "DevKit",
43
43
  ]);
44
- /** Known Arduino / HAL runtime API calls. */
45
- function isArduinoAPI(name) {
46
- const apis = [
47
- // GPIO
48
- "digitalWrite", "digitalRead", "analogWrite", "analogRead", "pinMode",
49
- // Serial
50
- "Serial", "println", "print", "read", "write",
51
- // Audio
52
- "tone", "noTone",
53
- // Timing
54
- "millis", "micros", "delay", "delayMicroseconds",
55
- // Interrupts
56
- "attachInterrupt", "detachInterrupt",
57
- // SPI / Shift
58
- "shiftOut", "shiftIn", "pulseIn",
59
- // HAL event tokens (from Button, etc.)
60
- "pressed", "released", "input",
61
- ];
62
- return apis.includes(name);
44
+ /**
45
+ * Known HAL runtime API call names used for diagram coloring/grouping.
46
+ *
47
+ * This is the cross-framework Wiring-derived HAL surface (the call names that
48
+ * appear in emitted C++ across Arduino-core, ESP32 Arduino, RP2040 Arduino,
49
+ * etc.). It is a diagnostic heuristic for grouping nodes in the mermaid
50
+ * execution-flow and interrupt-map diagrams — not emitted code. The set is
51
+ * generic: it identifies "this is a HAL/runtime call" for coloring purposes,
52
+ * regardless of which Wiring-derived framework produced it.
53
+ */
54
+ const HAL_API_NAMES = new Set([
55
+ // GPIO
56
+ "digitalWrite", "digitalRead", "analogWrite", "analogRead", "pinMode",
57
+ // Serial
58
+ "Serial", "println", "print", "read", "write",
59
+ // Audio
60
+ "tone", "noTone",
61
+ // Timing
62
+ "millis", "micros", "delay", "delayMicroseconds",
63
+ // Interrupts
64
+ "attachInterrupt", "detachInterrupt",
65
+ // SPI / Shift
66
+ "shiftOut", "shiftIn", "pulseIn",
67
+ // HAL event tokens (from Button, etc.)
68
+ "pressed", "released", "input",
69
+ ]);
70
+ /** Recognize a HAL/runtime API call name for diagram coloring. */
71
+ function isHalApi(name) {
72
+ return HAL_API_NAMES.has(name);
63
73
  }
64
74
  // ── Execution Flow Diagram ──────────────────────────────────────────────────
65
75
  /**
66
76
  * Build a categorized architecture diagram showing how the sketch
67
- * is organized: entry points, HAL objects, Arduino APIs (grouped by
77
+ * is organized: entry points, HAL objects, HAL APIs (grouped by
68
78
  * subsystem), state variables, and custom functions.
69
79
  *
70
80
  * Noise symbols (true/false/HIGH/board chain) are filtered out.
@@ -133,7 +143,7 @@ export function buildExecutionFlowDiagram(flow, callGraph) {
133
143
  if (entry.has(name) || isr.has(name) || async.has(name))
134
144
  continue;
135
145
  const node = callGraph.nodes.get(name);
136
- if (isArduinoAPI(name)) {
146
+ if (isHalApi(name)) {
137
147
  apis.push(name);
138
148
  }
139
149
  else if (node?.kind === "function") {
@@ -187,9 +197,9 @@ export function buildExecutionFlowDiagram(flow, callGraph) {
187
197
  lines.push(" end");
188
198
  lines.push("");
189
199
  }
190
- // --- Arduino APIs (grouped by subsystem) ─────────────────────────
200
+ // --- HAL APIs (grouped by subsystem) ─────────────────────────────
191
201
  if (apis.length > 0) {
192
- lines.push(" subgraph APIs[\"Arduino APIs\"]");
202
+ lines.push(" subgraph APIs[\"HAL APIs\"]");
193
203
  lines.push(" direction LR");
194
204
  const groups = {
195
205
  "GPIO": ["digitalWrite", "digitalRead", "analogWrite", "analogRead", "pinMode"],
@@ -333,7 +343,7 @@ export function buildInterruptMap(flow, callGraph) {
333
343
  return;
334
344
  visited.add(name);
335
345
  const node = callGraph.nodes.get(name);
336
- const style = isRoot ? ":::isr" : (isArduinoAPI(name) ? ":::api" : ":::logic");
346
+ const style = isRoot ? ":::isr" : (isHalApi(name) ? ":::api" : ":::logic");
337
347
  const shape = isRoot ? `[[${esc(name)}]]` : `(${esc(name)})`;
338
348
  lines.push(` ${name}${shape}${style}`);
339
349
  if (node) {
@@ -0,0 +1,11 @@
1
+ import type { ComplianceContext } from "./compliance-context.js";
2
+ /**
3
+ * Render the deviation ledger as an AUTOSAR ARXML XML projection.
4
+ *
5
+ * ARXML is the AUTOSAR-standard interchange format consumed by tooling
6
+ * like Artop and DaVinci. This is a *projection* of the same ledger that
7
+ * `renderRegistryJson` produces — single source of truth, two renderings.
8
+ * Many teams only need the JSON; ARXML generation is gated behind
9
+ * `--autosar-arxml` so projects that don't need it pay no cost.
10
+ */
11
+ export declare function renderArxml(ctx: ComplianceContext, emittedArtifact: string): string;
@@ -0,0 +1,34 @@
1
+ /** Escape XML special characters in text/attribute content. */
2
+ function escapeXml(s) {
3
+ return s.replace(/[<>&'"]/g, (c) => c === "<" ? "&lt;" :
4
+ c === ">" ? "&gt;" :
5
+ c === "&" ? "&amp;" :
6
+ c === "'" ? "&apos;" :
7
+ "&quot;");
8
+ }
9
+ /**
10
+ * Render the deviation ledger as an AUTOSAR ARXML XML projection.
11
+ *
12
+ * ARXML is the AUTOSAR-standard interchange format consumed by tooling
13
+ * like Artop and DaVinci. This is a *projection* of the same ledger that
14
+ * `renderRegistryJson` produces — single source of truth, two renderings.
15
+ * Many teams only need the JSON; ARXML generation is gated behind
16
+ * `--autosar-arxml` so projects that don't need it pay no cost.
17
+ */
18
+ export function renderArxml(ctx, emittedArtifact) {
19
+ const deviations = ctx.ledger().all();
20
+ const body = deviations.map((d) => {
21
+ const source = d.source
22
+ ? `\n <SOURCE TS-FILE="${escapeXml(d.source.tsFile)}" TS-LINE="${d.source.tsLine}" KIND="${d.source.kind}"/>`
23
+ : "";
24
+ return ` <DEVIATION RULE="${d.ruleId}" LINE="${d.line}" END-LINE="${d.endLine}" STATUS="${d.reviewStatus}">
25
+ <JUSTIFICATION>${escapeXml(d.justification)}</JUSTIFICATION>
26
+ <SNIPPET>${escapeXml(d.snippet)}</SNIPPET>${source}
27
+ </DEVIATION>`;
28
+ }).join("\n");
29
+ return `<?xml version="1.0" encoding="UTF-8"?>
30
+ <AUTOSAR xmlns="http://autosar.org/schema/r4.0" ARTIFACT="${escapeXml(emittedArtifact)}">
31
+ ${body}
32
+ </AUTOSAR>
33
+ `;
34
+ }
@@ -0,0 +1,57 @@
1
+ import { DeviationLedger } from "./deviation-ledger.js";
2
+ import type { ComplianceMode, DeviationKind } from "./types.js";
3
+ /**
4
+ * Per-emit compliance bag, threaded through EmitterContext. Owns the
5
+ * deviation ledger and exposes the renderer-facing API.
6
+ *
7
+ * When mode === "off" (the default), every method is a no-op: helpers
8
+ * return their input unchanged, nothing is recorded. This is what lets
9
+ * the feature land without changing any existing emitted output.
10
+ */
11
+ export declare class ComplianceContext {
12
+ private readonly complianceMode;
13
+ private readonly ledgerInstance;
14
+ private readonly bannedKinds;
15
+ constructor(complianceMode: ComplianceMode);
16
+ mode(): ComplianceMode;
17
+ isEnabled(): boolean;
18
+ ledger(): Readonly<DeviationLedger>;
19
+ /**
20
+ * Record a deviation for a known unavoidable pattern (used by the
21
+ * self-check's knownPatterns mechanism). Unlike emitWithDeviation, this
22
+ * does NOT append an inline comment — the deviation is recorded in the
23
+ * ledger only, so the sidecar lists it. The emitted C++ text is unchanged.
24
+ */
25
+ recordKnownDeviation(ruleId: string, justification: string, line: number, file: "source" | "header", snippet: string, source?: {
26
+ tsFile: string;
27
+ tsLine: number;
28
+ kind: DeviationKind;
29
+ }): void;
30
+ /** Renderer gate: should it avoid the banned spelling for this rule? */
31
+ isBanned(ruleId: string): boolean;
32
+ /**
33
+ * Returns `line` with an appended inline deviation comment, and records
34
+ * the deviation(s) to the ledger atomically. The two cannot drift apart.
35
+ *
36
+ * `line` is the C++ text the renderer was already going to emit (without
37
+ * a trailing newline). `currentLine` is the 1-based line number it will
38
+ * occupy in the emitted file (used for the ledger).
39
+ */
40
+ emitWithDeviation(line: string, ruleId: string | string[], justification: string, currentLine?: number, file?: "source" | "header", source?: {
41
+ tsFile: string;
42
+ tsLine: number;
43
+ kind: DeviationKind;
44
+ }): string;
45
+ /**
46
+ * Opens a deviation region. Returns the opening line with the inline
47
+ * comment appended (the renderer emits this as the first line of the
48
+ * block). The matching closeDeviationRegion records the endLine.
49
+ */
50
+ openDeviationRegion(ruleId: string, justification: string, startLine: number, openingSnippet: string, file?: "source" | "header"): string;
51
+ closeDeviationRegion(endLine: number): void;
52
+ /**
53
+ * Optional positive assertion: "I deliberately satisfied ruleId here."
54
+ * No-op today; recorded for future coverage tooling.
55
+ */
56
+ noteCompliant(_ruleId: string): void;
57
+ }
@@ -0,0 +1,113 @@
1
+ import { DeviationLedger } from "./deviation-ledger.js";
2
+ import { getRule, rulesByCategory } from "./rules.js";
3
+ /**
4
+ * Per-emit compliance bag, threaded through EmitterContext. Owns the
5
+ * deviation ledger and exposes the renderer-facing API.
6
+ *
7
+ * When mode === "off" (the default), every method is a no-op: helpers
8
+ * return their input unchanged, nothing is recorded. This is what lets
9
+ * the feature land without changing any existing emitted output.
10
+ */
11
+ export class ComplianceContext {
12
+ constructor(complianceMode) {
13
+ this.complianceMode = complianceMode;
14
+ this.ledgerInstance = new DeviationLedger();
15
+ // Precompute the set of [C]-category rule ids once.
16
+ this.bannedKinds = new Set(rulesByCategory("C").map((r) => r.id));
17
+ }
18
+ mode() {
19
+ return this.complianceMode;
20
+ }
21
+ isEnabled() {
22
+ return this.complianceMode !== "off";
23
+ }
24
+ ledger() {
25
+ return this.ledgerInstance;
26
+ }
27
+ /**
28
+ * Record a deviation for a known unavoidable pattern (used by the
29
+ * self-check's knownPatterns mechanism). Unlike emitWithDeviation, this
30
+ * does NOT append an inline comment — the deviation is recorded in the
31
+ * ledger only, so the sidecar lists it. The emitted C++ text is unchanged.
32
+ */
33
+ recordKnownDeviation(ruleId, justification, line, file, snippet, source) {
34
+ if (!this.isEnabled())
35
+ return;
36
+ this.ledgerInstance.record({
37
+ ruleId,
38
+ file,
39
+ line,
40
+ snippet,
41
+ justification,
42
+ source,
43
+ });
44
+ }
45
+ /** Renderer gate: should it avoid the banned spelling for this rule? */
46
+ isBanned(ruleId) {
47
+ if (!this.isEnabled())
48
+ return false;
49
+ return this.bannedKinds.has(ruleId);
50
+ }
51
+ /**
52
+ * Returns `line` with an appended inline deviation comment, and records
53
+ * the deviation(s) to the ledger atomically. The two cannot drift apart.
54
+ *
55
+ * `line` is the C++ text the renderer was already going to emit (without
56
+ * a trailing newline). `currentLine` is the 1-based line number it will
57
+ * occupy in the emitted file (used for the ledger).
58
+ */
59
+ emitWithDeviation(line, ruleId, justification, currentLine = 0, file = "source", source) {
60
+ if (!this.isEnabled())
61
+ return line;
62
+ const ids = Array.isArray(ruleId) ? ruleId : [ruleId];
63
+ for (const id of ids) {
64
+ if (!getRule(id)) {
65
+ throw new Error(`Unknown AUTOSAR rule id: ${id}`);
66
+ }
67
+ }
68
+ const tag = ids.join(", ");
69
+ for (const id of ids) {
70
+ this.ledgerInstance.record({
71
+ ruleId: id,
72
+ file,
73
+ line: currentLine,
74
+ snippet: line,
75
+ justification,
76
+ source,
77
+ });
78
+ }
79
+ return `${line} // AUTOSAR Deviation ${tag}: ${justification}`;
80
+ }
81
+ /**
82
+ * Opens a deviation region. Returns the opening line with the inline
83
+ * comment appended (the renderer emits this as the first line of the
84
+ * block). The matching closeDeviationRegion records the endLine.
85
+ */
86
+ openDeviationRegion(ruleId, justification, startLine, openingSnippet, file = "source") {
87
+ if (!this.isEnabled())
88
+ return openingSnippet;
89
+ if (!getRule(ruleId)) {
90
+ throw new Error(`Unknown AUTOSAR rule id: ${ruleId}`);
91
+ }
92
+ this.ledgerInstance.openRegion({
93
+ ruleId,
94
+ file,
95
+ line: startLine,
96
+ snippet: openingSnippet,
97
+ justification,
98
+ });
99
+ return `${openingSnippet} // AUTOSAR Deviation ${ruleId}: ${justification}`;
100
+ }
101
+ closeDeviationRegion(endLine) {
102
+ if (!this.isEnabled())
103
+ return;
104
+ this.ledgerInstance.closeRegion(endLine);
105
+ }
106
+ /**
107
+ * Optional positive assertion: "I deliberately satisfied ruleId here."
108
+ * No-op today; recorded for future coverage tooling.
109
+ */
110
+ noteCompliant(_ruleId) {
111
+ // intentionally no-op; reserved for future use.
112
+ }
113
+ }
@@ -0,0 +1,27 @@
1
+ import type { Deviation } from "./types.js";
2
+ /** Input shape for recording a deviation (line + endLine derived from context). */
3
+ export interface DeviationInput {
4
+ ruleId: string;
5
+ file: "source" | "header";
6
+ line: number;
7
+ snippet: string;
8
+ justification: string;
9
+ source?: Deviation["source"];
10
+ }
11
+ /**
12
+ * Append-only store of deviations recorded during emit. Dedupes identical
13
+ * (ruleId, file, line) entries; supports region deviations via openRegion /
14
+ * closeRegion. The ledger is the single source of truth that the sidecar
15
+ * registry is rendered from.
16
+ */
17
+ export declare class DeviationLedger {
18
+ private readonly entries;
19
+ private openRegionEntry;
20
+ record(input: DeviationInput): void;
21
+ openRegion(input: DeviationInput): void;
22
+ closeRegion(endLine: number): void;
23
+ all(): readonly Deviation[];
24
+ /** Used by the self-check to verify every ledger entry has a matching inline comment. */
25
+ hasEntry(file: "source" | "header", line: number, ruleId: string): boolean;
26
+ private append;
27
+ }