@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.10
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.
- package/README.md +10 -10
- package/dist/api/board-types.d.ts +1 -1
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +595 -0
- package/dist/api/shared/framework-manifest.js +193 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +764 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +20 -2
- package/dist/api/shared/index.js +25 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +119 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/types.d.ts +8 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +249 -32
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/framework-catalog.d.ts +64 -0
- package/dist/create/framework-catalog.js +136 -0
- package/dist/create/index.d.ts +5 -1
- package/dist/create/index.js +3 -1
- package/dist/create/init-scaffold.d.ts +12 -3
- package/dist/create/init-scaffold.js +49 -14
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +263 -17
- package/dist/create/init-wizard.js +41 -27
- package/dist/create/install-deps.d.ts +32 -0
- package/dist/create/install-deps.js +46 -0
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +372 -30
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +40 -26
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +207 -6
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +733 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +16 -19
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +39 -1
- package/dist/ir/peripheral-ownership.js +5 -0
- package/dist/ir/peripheral-validation.d.ts +1 -1
- package/dist/ir/peripheral-validation.js +6 -3
- package/dist/ir/pin-alias-conflict.d.ts +1 -1
- package/dist/ir/pin-alias-conflict.js +2 -1
- package/dist/ir/pin-capability-validation.js +71 -32
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +54 -18
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +81 -0
- package/dist/ir/program-analysis.js +429 -0
- package/dist/ir/pulldown-validation.d.ts +1 -1
- package/dist/ir/pulldown-validation.js +2 -1
- package/dist/ir/pwm-timer-sharing.d.ts +1 -1
- package/dist/ir/pwm-timer-sharing.js +2 -1
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/resource-analysis.js +2 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
- package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +61 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +137 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +20 -2
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/unit-suspicion-validation.js +9 -7
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +15 -8
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +8 -2
- package/dist/testing.js +9 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +191 -49
- package/dist/types.d.ts +22 -3
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +122 -2
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/dist/utils/ui.js +3 -1
- package/package.json +23 -3
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/ir/heap-array-validation.d.ts +0 -24
- 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
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
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
|
|
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[];
|