@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/api/board-types.d.ts +1 -1
- package/dist/api/shared/hal-op-ir.d.ts +10 -1
- package/dist/api/shared/platform-strategy.d.ts +6 -0
- package/dist/api/shared/types.d.ts +8 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +75 -0
- package/dist/create/index.d.ts +1 -1
- package/dist/create/index.js +1 -1
- package/dist/create/init-scaffold.js +48 -2
- package/dist/create/init-templates.d.ts +2 -0
- package/dist/create/init-templates.js +189 -9
- package/dist/emit/emitters/setup.js +122 -1
- package/dist/ir/adc-range-validation.js +26 -25
- package/dist/ir/expression-to-ir.js +43 -6
- package/dist/ir/hal/hal-plugins.js +10 -0
- package/dist/ir/interrupt-analysis.js +8 -3
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/ownership-analysis.js +19 -0
- 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 +34 -32
- package/dist/ir/pin-mode-validation.js +5 -0
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +16 -0
- package/dist/ir/program-analysis.js +113 -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/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.js +1 -0
- package/dist/ir/transformers/variables.js +46 -17
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +2 -2
- package/dist/ir/unit-suspicion-validation.js +9 -7
- package/dist/ir/validation-orchestrator.js +6 -6
- package/dist/licenses.d.ts +185 -0
- package/dist/licenses.js +963 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/transpile.js +17 -9
- package/dist/types.d.ts +7 -1
- package/dist/utils/cli.js +44 -0
- package/package.json +5 -4
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -32,6 +32,7 @@ export function validatePeripheralOwnership(program) {
|
|
|
32
32
|
severity: 'error',
|
|
33
33
|
message: `Double-take: '${bus}' is already owned.`,
|
|
34
34
|
hint: `Call '${bus}.release()' before taking it again.`,
|
|
35
|
+
filePath: span.filePath,
|
|
35
36
|
line: span.startLine,
|
|
36
37
|
column: span.startColumn,
|
|
37
38
|
code: 'hal-ownership-double-take',
|
|
@@ -79,6 +80,7 @@ export function validatePeripheralOwnership(program) {
|
|
|
79
80
|
severity: 'error',
|
|
80
81
|
message: `Double-take: '${bus}' is already owned.`,
|
|
81
82
|
hint: `Call '${bus}.release()' before taking it again.`,
|
|
83
|
+
filePath: span.filePath,
|
|
82
84
|
line: span.startLine,
|
|
83
85
|
column: span.startColumn,
|
|
84
86
|
code: 'hal-ownership-double-take',
|
|
@@ -94,6 +96,7 @@ export function validatePeripheralOwnership(program) {
|
|
|
94
96
|
severity: 'warning',
|
|
95
97
|
message: `'${bus}.release()' called but bus was not taken.`,
|
|
96
98
|
hint: `Ensure you call '${bus}.take()' before releasing.`,
|
|
99
|
+
filePath: span.filePath,
|
|
97
100
|
line: span.startLine,
|
|
98
101
|
column: span.startColumn,
|
|
99
102
|
code: 'hal-ownership-unowned-release',
|
|
@@ -108,6 +111,7 @@ export function validatePeripheralOwnership(program) {
|
|
|
108
111
|
severity: 'error',
|
|
109
112
|
message: `Unowned access: '${bus}' is used without being taken.`,
|
|
110
113
|
hint: `Call 'const bus = ${bus}.take();' and use the returned 'bus' object, or ensure '${bus}' is taken in the current scope.`,
|
|
114
|
+
filePath: span.filePath,
|
|
111
115
|
line: span.startLine,
|
|
112
116
|
column: span.startColumn,
|
|
113
117
|
code: 'hal-ownership-unowned-access',
|
|
@@ -142,6 +146,7 @@ export function validatePeripheralOwnership(program) {
|
|
|
142
146
|
severity: 'warning',
|
|
143
147
|
message: `'${bus}' was taken but never released.`,
|
|
144
148
|
hint: `Call '${bus}.release()' when finished with the bus.`,
|
|
149
|
+
filePath: busSpan.filePath,
|
|
145
150
|
line: busSpan.startLine,
|
|
146
151
|
column: busSpan.startColumn,
|
|
147
152
|
code: 'hal-ownership-leak',
|
|
@@ -5,4 +5,4 @@ import { Diagnostic } from '../types.js';
|
|
|
5
5
|
* Validate peripheral usage and return diagnostics.
|
|
6
6
|
* This is the main entry point for peripheral validation.
|
|
7
7
|
*/
|
|
8
|
-
export declare function validatePeripherals(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
|
|
8
|
+
export declare function validatePeripherals(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
|
|
@@ -26,7 +26,7 @@ function getPeripheralCapacity(boardConstants) {
|
|
|
26
26
|
* Validate peripheral usage against board capacity.
|
|
27
27
|
* Returns an array of diagnostics for any invalid peripheral usage.
|
|
28
28
|
*/
|
|
29
|
-
function validatePeripheralUsage(usage, capacity, boardName
|
|
29
|
+
function validatePeripheralUsage(usage, capacity, boardName, filePath) {
|
|
30
30
|
const diagnostics = [];
|
|
31
31
|
// Validate I2C instances
|
|
32
32
|
for (const instance of usage.i2cInstancesUsed) {
|
|
@@ -36,6 +36,7 @@ function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
|
|
|
36
36
|
severity: 'error',
|
|
37
37
|
message: `I2C${instance} is not available on ${boardName}. Available: ${availableName}`,
|
|
38
38
|
code: 'peripheral-not-available',
|
|
39
|
+
filePath,
|
|
39
40
|
source: 'peripheral-validation',
|
|
40
41
|
});
|
|
41
42
|
}
|
|
@@ -48,6 +49,7 @@ function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
|
|
|
48
49
|
severity: 'error',
|
|
49
50
|
message: `SPI${instance} is not available on ${boardName}. Available: ${availableName}`,
|
|
50
51
|
code: 'peripheral-not-available',
|
|
52
|
+
filePath,
|
|
51
53
|
source: 'peripheral-validation',
|
|
52
54
|
});
|
|
53
55
|
}
|
|
@@ -60,6 +62,7 @@ function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
|
|
|
60
62
|
severity: 'error',
|
|
61
63
|
message: `UART${instance} is not available on ${boardName}. Available: ${availableName}`,
|
|
62
64
|
code: 'peripheral-not-available',
|
|
65
|
+
filePath,
|
|
63
66
|
source: 'peripheral-validation',
|
|
64
67
|
});
|
|
65
68
|
}
|
|
@@ -70,8 +73,8 @@ function validatePeripheralUsage(usage, capacity, boardName = 'this board') {
|
|
|
70
73
|
* Validate peripheral usage and return diagnostics.
|
|
71
74
|
* This is the main entry point for peripheral validation.
|
|
72
75
|
*/
|
|
73
|
-
export function validatePeripherals(usage, boardConstants) {
|
|
76
|
+
export function validatePeripherals(usage, boardConstants, filePath) {
|
|
74
77
|
const capacity = getPeripheralCapacity(boardConstants);
|
|
75
78
|
const boardName = boardConstants?.get('name') ?? 'this board';
|
|
76
|
-
return validatePeripheralUsage(usage, capacity, boardName);
|
|
79
|
+
return validatePeripheralUsage(usage, capacity, boardName, filePath);
|
|
77
80
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Diagnostic } from '../types.js';
|
|
2
2
|
import type { BoardConstants } from './board-resolver.js';
|
|
3
3
|
import type { PeripheralUsage } from './peripheral-usage.js';
|
|
4
|
-
export declare function validatePinAliasConflicts(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
|
|
4
|
+
export declare function validatePinAliasConflicts(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { findBoardPinByName } from './board-pin-utils.js';
|
|
2
|
-
export function validatePinAliasConflicts(usage, boardConstants) {
|
|
2
|
+
export function validatePinAliasConflicts(usage, boardConstants, filePath) {
|
|
3
3
|
if (!boardConstants || usage.pinsUsed.size === 0) {
|
|
4
4
|
return [];
|
|
5
5
|
}
|
|
@@ -26,6 +26,7 @@ export function validatePinAliasConflicts(usage, boardConstants) {
|
|
|
26
26
|
diagnostics.push({
|
|
27
27
|
severity: 'warning',
|
|
28
28
|
code: 'pin-alias-conflict',
|
|
29
|
+
filePath,
|
|
29
30
|
source: 'pin-alias-conflict',
|
|
30
31
|
message: `Pin '${canonicalPinName}' is referenced through multiple names${boardLabel}: ${orderedNames.join(', ')}. These names refer to the same physical pin. Pick one name to keep pin usage and diagnostics unambiguous.`,
|
|
31
32
|
});
|
|
@@ -80,7 +80,7 @@ function findPinsWithCapability(capability, boardConstants) {
|
|
|
80
80
|
// ---------------------------------------------------------------------------
|
|
81
81
|
// Capability check for a single HAL operation
|
|
82
82
|
// ---------------------------------------------------------------------------
|
|
83
|
-
function checkCapability(operation, sourceLine, sourceCol, boardConstants, diagnostics) {
|
|
83
|
+
function checkCapability(operation, sourceLine, sourceCol, filePath, boardConstants, diagnostics) {
|
|
84
84
|
// Only operations with a pin field are capability-checked.
|
|
85
85
|
const op = operation.operation;
|
|
86
86
|
if (!('pin' in operation))
|
|
@@ -145,48 +145,49 @@ function checkCapability(operation, sourceLine, sourceCol, boardConstants, diagn
|
|
|
145
145
|
hint,
|
|
146
146
|
line: sourceLine,
|
|
147
147
|
column: sourceCol,
|
|
148
|
+
filePath,
|
|
148
149
|
source: 'pin-capability-validation',
|
|
149
150
|
});
|
|
150
151
|
}
|
|
151
152
|
// ---------------------------------------------------------------------------
|
|
152
153
|
// IR tree scanner
|
|
153
154
|
// ---------------------------------------------------------------------------
|
|
154
|
-
function scanExpression(expr, boardConstants, parentLine, parentCol, diagnostics) {
|
|
155
|
+
function scanExpression(expr, boardConstants, parentLine, parentCol, parentFilePath, diagnostics) {
|
|
155
156
|
if (!expr || typeof expr !== 'object')
|
|
156
157
|
return;
|
|
157
158
|
switch (expr.kind) {
|
|
158
159
|
case 'binary': {
|
|
159
|
-
scanExpression(expr.left, boardConstants, parentLine, parentCol, diagnostics);
|
|
160
|
-
scanExpression(expr.right, boardConstants, parentLine, parentCol, diagnostics);
|
|
160
|
+
scanExpression(expr.left, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
161
|
+
scanExpression(expr.right, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
161
162
|
break;
|
|
162
163
|
}
|
|
163
164
|
case 'ternary': {
|
|
164
|
-
scanExpression(expr.condition, boardConstants, parentLine, parentCol, diagnostics);
|
|
165
|
-
scanExpression(expr.whenTrue, boardConstants, parentLine, parentCol, diagnostics);
|
|
166
|
-
scanExpression(expr.whenFalse, boardConstants, parentLine, parentCol, diagnostics);
|
|
165
|
+
scanExpression(expr.condition, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
166
|
+
scanExpression(expr.whenTrue, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
167
|
+
scanExpression(expr.whenFalse, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
167
168
|
break;
|
|
168
169
|
}
|
|
169
170
|
case 'property-access': {
|
|
170
|
-
scanExpression(expr.object, boardConstants, parentLine, parentCol, diagnostics);
|
|
171
|
+
scanExpression(expr.object, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
171
172
|
break;
|
|
172
173
|
}
|
|
173
174
|
case 'unary': {
|
|
174
|
-
scanExpression(expr.operand, boardConstants, parentLine, parentCol, diagnostics);
|
|
175
|
+
scanExpression(expr.operand, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
175
176
|
break;
|
|
176
177
|
}
|
|
177
178
|
case 'paren': {
|
|
178
|
-
scanExpression(expr.inner, boardConstants, parentLine, parentCol, diagnostics);
|
|
179
|
+
scanExpression(expr.inner, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
179
180
|
break;
|
|
180
181
|
}
|
|
181
182
|
case 'array': {
|
|
182
183
|
for (const el of expr.elements) {
|
|
183
|
-
scanExpression(el, boardConstants, parentLine, parentCol, diagnostics);
|
|
184
|
+
scanExpression(el, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
184
185
|
}
|
|
185
186
|
break;
|
|
186
187
|
}
|
|
187
188
|
case 'object': {
|
|
188
189
|
for (const field of expr.fields) {
|
|
189
|
-
scanExpression(field.value, boardConstants, parentLine, parentCol, diagnostics);
|
|
190
|
+
scanExpression(field.value, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
190
191
|
}
|
|
191
192
|
break;
|
|
192
193
|
}
|
|
@@ -204,43 +205,43 @@ function scanExpression(expr, boardConstants, parentLine, parentCol, diagnostics
|
|
|
204
205
|
}
|
|
205
206
|
case 'method-call': {
|
|
206
207
|
for (const arg of expr.args) {
|
|
207
|
-
scanExpression(arg, boardConstants, parentLine, parentCol, diagnostics);
|
|
208
|
+
scanExpression(arg, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
208
209
|
}
|
|
209
210
|
break;
|
|
210
211
|
}
|
|
211
212
|
case 'element-access': {
|
|
212
|
-
scanExpression(expr.object, boardConstants, parentLine, parentCol, diagnostics);
|
|
213
|
-
scanExpression(expr.index, boardConstants, parentLine, parentCol, diagnostics);
|
|
213
|
+
scanExpression(expr.object, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
214
|
+
scanExpression(expr.index, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
214
215
|
break;
|
|
215
216
|
}
|
|
216
217
|
case 'string_concat': {
|
|
217
218
|
for (const part of expr.parts) {
|
|
218
|
-
scanExpression(part, boardConstants, parentLine, parentCol, diagnostics);
|
|
219
|
+
scanExpression(part, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
219
220
|
}
|
|
220
221
|
break;
|
|
221
222
|
}
|
|
222
223
|
case 'template_string': {
|
|
223
|
-
scanExpression(expr.expression, boardConstants, parentLine, parentCol, diagnostics);
|
|
224
|
+
scanExpression(expr.expression, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
224
225
|
break;
|
|
225
226
|
}
|
|
226
227
|
case 'spread_array': {
|
|
227
|
-
scanExpression(expr.spreadExpr, boardConstants, parentLine, parentCol, diagnostics);
|
|
228
|
+
scanExpression(expr.spreadExpr, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
228
229
|
for (const el of expr.additionalElements) {
|
|
229
|
-
scanExpression(el, boardConstants, parentLine, parentCol, diagnostics);
|
|
230
|
+
scanExpression(el, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
230
231
|
}
|
|
231
232
|
break;
|
|
232
233
|
}
|
|
233
234
|
case 'instanceof': {
|
|
234
|
-
scanExpression(expr.object, boardConstants, parentLine, parentCol, diagnostics);
|
|
235
|
+
scanExpression(expr.object, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
235
236
|
break;
|
|
236
237
|
}
|
|
237
238
|
case 'await': {
|
|
238
|
-
scanExpression(expr.value, boardConstants, parentLine, parentCol, diagnostics);
|
|
239
|
+
scanExpression(expr.value, boardConstants, parentLine, parentCol, parentFilePath, diagnostics);
|
|
239
240
|
break;
|
|
240
241
|
}
|
|
241
242
|
case 'hal-expr': {
|
|
242
243
|
// Expression-form HAL operation (e.g. adc.read used as a value).
|
|
243
|
-
checkCapability(expr.operation, parentLine, parentCol, boardConstants, diagnostics);
|
|
244
|
+
checkCapability(expr.operation, parentLine, parentCol, parentFilePath, boardConstants, diagnostics);
|
|
244
245
|
break;
|
|
245
246
|
}
|
|
246
247
|
case 'number':
|
|
@@ -259,20 +260,21 @@ function scanStatement(stmt, boardConstants, diagnostics) {
|
|
|
259
260
|
return;
|
|
260
261
|
const line = stmt.sourceSpan?.startLine;
|
|
261
262
|
const col = stmt.sourceSpan?.startColumn;
|
|
263
|
+
const filePath = stmt.sourceSpan?.filePath;
|
|
262
264
|
switch (stmt.kind) {
|
|
263
265
|
case 'var_decl': {
|
|
264
266
|
if (stmt.initializer)
|
|
265
|
-
scanExpression(stmt.initializer, boardConstants, line, col, diagnostics);
|
|
267
|
+
scanExpression(stmt.initializer, boardConstants, line, col, filePath, diagnostics);
|
|
266
268
|
break;
|
|
267
269
|
}
|
|
268
270
|
case 'assign': {
|
|
269
271
|
if (stmt.value)
|
|
270
|
-
scanExpression(stmt.value, boardConstants, line, col, diagnostics);
|
|
272
|
+
scanExpression(stmt.value, boardConstants, line, col, filePath, diagnostics);
|
|
271
273
|
break;
|
|
272
274
|
}
|
|
273
275
|
case 'if': {
|
|
274
276
|
if (stmt.condition)
|
|
275
|
-
scanExpression(stmt.condition, boardConstants, line, col, diagnostics);
|
|
277
|
+
scanExpression(stmt.condition, boardConstants, line, col, filePath, diagnostics);
|
|
276
278
|
for (const s of stmt.thenBranch)
|
|
277
279
|
scanStatement(s, boardConstants, diagnostics);
|
|
278
280
|
if (stmt.elseBranch)
|
|
@@ -283,14 +285,14 @@ function scanStatement(stmt, boardConstants, diagnostics) {
|
|
|
283
285
|
case 'while':
|
|
284
286
|
case 'do_while': {
|
|
285
287
|
if (stmt.condition)
|
|
286
|
-
scanExpression(stmt.condition, boardConstants, line, col, diagnostics);
|
|
288
|
+
scanExpression(stmt.condition, boardConstants, line, col, filePath, diagnostics);
|
|
287
289
|
for (const s of stmt.body)
|
|
288
290
|
scanStatement(s, boardConstants, diagnostics);
|
|
289
291
|
break;
|
|
290
292
|
}
|
|
291
293
|
case 'for': {
|
|
292
294
|
if (stmt.condition)
|
|
293
|
-
scanExpression(stmt.condition, boardConstants, line, col, diagnostics);
|
|
295
|
+
scanExpression(stmt.condition, boardConstants, line, col, filePath, diagnostics);
|
|
294
296
|
if (stmt.initializer)
|
|
295
297
|
scanStatement(stmt.initializer, boardConstants, diagnostics);
|
|
296
298
|
if (stmt.increment)
|
|
@@ -309,23 +311,23 @@ function scanStatement(stmt, boardConstants, diagnostics) {
|
|
|
309
311
|
}
|
|
310
312
|
case 'return': {
|
|
311
313
|
if (stmt.value)
|
|
312
|
-
scanExpression(stmt.value, boardConstants, line, col, diagnostics);
|
|
314
|
+
scanExpression(stmt.value, boardConstants, line, col, filePath, diagnostics);
|
|
313
315
|
break;
|
|
314
316
|
}
|
|
315
317
|
case 'call': {
|
|
316
318
|
for (const arg of stmt.args) {
|
|
317
|
-
scanExpression(arg, boardConstants, line, col, diagnostics);
|
|
319
|
+
scanExpression(arg, boardConstants, line, col, filePath, diagnostics);
|
|
318
320
|
}
|
|
319
321
|
break;
|
|
320
322
|
}
|
|
321
323
|
case 'hal-op': {
|
|
322
324
|
// Statement-form HAL operation — the primary capability check target.
|
|
323
|
-
checkCapability(stmt.operation, line, col, boardConstants, diagnostics);
|
|
325
|
+
checkCapability(stmt.operation, line, col, filePath, boardConstants, diagnostics);
|
|
324
326
|
break;
|
|
325
327
|
}
|
|
326
328
|
case 'switch': {
|
|
327
329
|
if (stmt.expression)
|
|
328
|
-
scanExpression(stmt.expression, boardConstants, line, col, diagnostics);
|
|
330
|
+
scanExpression(stmt.expression, boardConstants, line, col, filePath, diagnostics);
|
|
329
331
|
for (const c of stmt.cases) {
|
|
330
332
|
for (const s of c.body)
|
|
331
333
|
scanStatement(s, boardConstants, diagnostics);
|
|
@@ -355,7 +357,7 @@ function scanStatement(stmt, boardConstants, diagnostics) {
|
|
|
355
357
|
}
|
|
356
358
|
case 'throw': {
|
|
357
359
|
if (stmt.value)
|
|
358
|
-
scanExpression(stmt.value, boardConstants, line, col, diagnostics);
|
|
360
|
+
scanExpression(stmt.value, boardConstants, line, col, filePath, diagnostics);
|
|
359
361
|
break;
|
|
360
362
|
}
|
|
361
363
|
case 'update':
|
|
@@ -43,6 +43,7 @@ export function validatePinModeConfig(program) {
|
|
|
43
43
|
message: `Analog pin '${receiver}' used as digital output. ` +
|
|
44
44
|
`Analog input capability (ADC) is lost while pin is in OUTPUT mode. ` +
|
|
45
45
|
`Call ${receiver}.asInput() to restore analog reading.`,
|
|
46
|
+
filePath: program.fileName,
|
|
46
47
|
code: 'analog-pin-as-output',
|
|
47
48
|
source: 'pin-mode-validation',
|
|
48
49
|
});
|
|
@@ -55,6 +56,7 @@ export function validatePinModeConfig(program) {
|
|
|
55
56
|
severity: 'warning',
|
|
56
57
|
message: `Pin '${receiver}' read via '${method}()' without prior mode configuration. ` +
|
|
57
58
|
`Call ${receiver}.asInput() or ${receiver}.inputPullUp() first.`,
|
|
59
|
+
filePath: program.fileName,
|
|
58
60
|
code: 'pin-mode-not-set',
|
|
59
61
|
source: 'pin-mode-validation',
|
|
60
62
|
});
|
|
@@ -64,6 +66,7 @@ export function validatePinModeConfig(program) {
|
|
|
64
66
|
severity: 'info',
|
|
65
67
|
message: `Pin '${receiver}' written via '${method}()' without explicit mode configuration. ` +
|
|
66
68
|
`Arduino implicitly sets OUTPUT, but explicit ${receiver}.asOutput() is recommended.`,
|
|
69
|
+
filePath: program.fileName,
|
|
67
70
|
code: 'pin-mode-not-set',
|
|
68
71
|
source: 'pin-mode-validation',
|
|
69
72
|
});
|
|
@@ -100,6 +103,7 @@ export function validatePinModeConfig(program) {
|
|
|
100
103
|
severity: 'warning',
|
|
101
104
|
message: `Pin ${op.pin} read without prior mode configuration. ` +
|
|
102
105
|
`Call asInput() or inputPullUp() first — reading a floating pin is undefined behavior.`,
|
|
106
|
+
filePath: program.fileName,
|
|
103
107
|
code: 'pin-mode-not-set',
|
|
104
108
|
source: 'pin-mode-validation',
|
|
105
109
|
});
|
|
@@ -109,6 +113,7 @@ export function validatePinModeConfig(program) {
|
|
|
109
113
|
severity: 'info',
|
|
110
114
|
message: `Pin ${op.pin} written without explicit mode configuration. ` +
|
|
111
115
|
`Arduino implicitly sets OUTPUT, but explicit asOutput() is recommended.`,
|
|
116
|
+
filePath: program.fileName,
|
|
112
117
|
code: 'pin-mode-not-set',
|
|
113
118
|
source: 'pin-mode-validation',
|
|
114
119
|
});
|
package/dist/ir/pin-safety.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ import type { Diagnostic } from '../types.js';
|
|
|
8
8
|
* @param boardConstants - Board constants containing pins.unsafe array
|
|
9
9
|
* @returns Array of diagnostic warnings for unsafe pin usage
|
|
10
10
|
*/
|
|
11
|
-
export declare function validateUnsafePins(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
|
|
11
|
+
export declare function validateUnsafePins(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
|
package/dist/ir/pin-safety.js
CHANGED
|
@@ -30,7 +30,7 @@ function buildUnsafePinMessage(pinName, boardConstants) {
|
|
|
30
30
|
* @param boardConstants - Board constants containing pins.unsafe array
|
|
31
31
|
* @returns Array of diagnostic warnings for unsafe pin usage
|
|
32
32
|
*/
|
|
33
|
-
export function validateUnsafePins(usage, boardConstants) {
|
|
33
|
+
export function validateUnsafePins(usage, boardConstants, filePath) {
|
|
34
34
|
const diagnostics = [];
|
|
35
35
|
if (!boardConstants)
|
|
36
36
|
return diagnostics;
|
|
@@ -50,6 +50,7 @@ export function validateUnsafePins(usage, boardConstants) {
|
|
|
50
50
|
severity: 'warning',
|
|
51
51
|
message: buildUnsafePinMessage(pinName, boardConstants),
|
|
52
52
|
code: 'unsafe-pin-usage',
|
|
53
|
+
filePath,
|
|
53
54
|
source: 'pin-safety',
|
|
54
55
|
});
|
|
55
56
|
}
|
|
@@ -29,6 +29,22 @@ export interface ProgramAnalysisResult {
|
|
|
29
29
|
usesTiming: boolean;
|
|
30
30
|
usesWDT: boolean;
|
|
31
31
|
usesStrPtr: boolean;
|
|
32
|
+
usesUart: boolean;
|
|
33
|
+
usesSPI: boolean;
|
|
34
|
+
usesI2C: boolean;
|
|
35
|
+
usesEEPROM: boolean;
|
|
36
|
+
usesTone: boolean;
|
|
37
|
+
/** map()/constrain() Arduino-API calls. framework-avr gates its native
|
|
38
|
+
* _native_map/_native_constrain helpers on these (they're dead code
|
|
39
|
+
* otherwise — no other internal caller references them). */
|
|
40
|
+
usesMap: boolean;
|
|
41
|
+
usesConstrain: boolean;
|
|
42
|
+
/** Comprehensive timing gate for the native millis() Timer0 ISR on AVR.
|
|
43
|
+
* True when the program directly uses millis/delay/micros, OR has hidden
|
|
44
|
+
* consumers of the soft clock: setInterval/setTimeout (timerCallCount),
|
|
45
|
+
* async functions (the runtime polls millis), or a mounted UI (per-frame
|
|
46
|
+
* tick injected by the emitter, not present in user source). */
|
|
47
|
+
usesNativeTiming: boolean;
|
|
32
48
|
hasSerialBegin: boolean;
|
|
33
49
|
hasGenerators: boolean;
|
|
34
50
|
usesStdMap: boolean;
|
|
@@ -102,6 +102,30 @@ function analyzeExpression(expr, result, strategy) {
|
|
|
102
102
|
if (expr.callee.startsWith("WDT.") || expr.callee === "WDT") {
|
|
103
103
|
result.usesWDT = true;
|
|
104
104
|
}
|
|
105
|
+
// Native AVR peripheral usage from namespace-prefixed method calls
|
|
106
|
+
// (Serial.* / SPI.* / Wire.* / EEPROM.*). The HAL resolver lowers these
|
|
107
|
+
// to structured hal-ops (detected in analyzeStatement) OR to bare
|
|
108
|
+
// lowered calls; these checks cover the pre-lowering and direct forms.
|
|
109
|
+
if (expr.callee.startsWith("Serial.") || expr.callee === "Serial") {
|
|
110
|
+
result.usesUart = true;
|
|
111
|
+
}
|
|
112
|
+
if (expr.callee.startsWith("SPI.") || expr.callee === "SPI") {
|
|
113
|
+
result.usesSPI = true;
|
|
114
|
+
}
|
|
115
|
+
if (expr.callee.startsWith("Wire.") || expr.callee === "Wire") {
|
|
116
|
+
result.usesI2C = true;
|
|
117
|
+
}
|
|
118
|
+
if (expr.callee.startsWith("EEPROM.") || expr.callee === "EEPROM") {
|
|
119
|
+
result.usesEEPROM = true;
|
|
120
|
+
}
|
|
121
|
+
// map()/constrain() Arduino-API calls appear as method-call exprs
|
|
122
|
+
// (callee "map"/"constrain"). framework-avr lowers these to _native_map/
|
|
123
|
+
// _native_constrain via symbol aliases, so the helpers are dead code
|
|
124
|
+
// unless the program actually calls them.
|
|
125
|
+
if (expr.callee === "map")
|
|
126
|
+
result.usesMap = true;
|
|
127
|
+
if (expr.callee === "constrain")
|
|
128
|
+
result.usesConstrain = true;
|
|
105
129
|
// Count setInterval/setTimeout call sites (post-rename callee names) so
|
|
106
130
|
// __tc_TimerRuntime::MAX_TIMERS can be sized to the observed count.
|
|
107
131
|
if (expr.callee === "__tc_setInterval" || expr.callee === "__tc_setTimeout"
|
|
@@ -234,6 +258,32 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
234
258
|
if (statement.callee.startsWith("WDT.") || statement.callee === "WDT") {
|
|
235
259
|
result.usesWDT = true;
|
|
236
260
|
}
|
|
261
|
+
// Native AVR peripheral usage from statement-form calls. tone()/noTone()
|
|
262
|
+
// are bare Arduino-API calls; console.* / Serial.* drive UART; the
|
|
263
|
+
// namespace prefixes mirror the method-call checks above.
|
|
264
|
+
if (statement.callee === "tone" || statement.callee === "noTone") {
|
|
265
|
+
result.usesTone = true;
|
|
266
|
+
}
|
|
267
|
+
if (statement.callee.startsWith("console.") || statement.callee.startsWith("_uart_")) {
|
|
268
|
+
result.usesUart = true;
|
|
269
|
+
}
|
|
270
|
+
if (statement.callee.startsWith("Serial.") || statement.callee === "Serial") {
|
|
271
|
+
result.usesUart = true;
|
|
272
|
+
}
|
|
273
|
+
if (statement.callee.startsWith("SPI.") || statement.callee === "SPI") {
|
|
274
|
+
result.usesSPI = true;
|
|
275
|
+
}
|
|
276
|
+
if (statement.callee.startsWith("Wire.") || statement.callee === "Wire") {
|
|
277
|
+
result.usesI2C = true;
|
|
278
|
+
}
|
|
279
|
+
if (statement.callee.startsWith("EEPROM.") || statement.callee === "EEPROM") {
|
|
280
|
+
result.usesEEPROM = true;
|
|
281
|
+
}
|
|
282
|
+
// Statement-form map()/constrain() mirror the method-call checks above.
|
|
283
|
+
if (statement.callee === "map")
|
|
284
|
+
result.usesMap = true;
|
|
285
|
+
if (statement.callee === "constrain")
|
|
286
|
+
result.usesConstrain = true;
|
|
237
287
|
// The HAL resolver lowers WDT.*/Timing.* namespace calls to bare AVR
|
|
238
288
|
// library functions (WDT.reset() → wdt_reset(), Timing.delay() → delay(),
|
|
239
289
|
// Timing.millis() → millis()). When that happens the `WDT.`/`Timing.`
|
|
@@ -357,6 +407,33 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
357
407
|
}
|
|
358
408
|
break;
|
|
359
409
|
case "hal-op":
|
|
410
|
+
// Structured HAL ops carry a typed operation name (e.g. "spi.begin",
|
|
411
|
+
// "i2c.read_byte", "tone.play", "uart.write") rather than raw code.
|
|
412
|
+
// Detect peripheral usage here so framework-avr's driver shims can be
|
|
413
|
+
// gated on actual use. Without this, SPI0.begin() → spi.begin hal-op
|
|
414
|
+
// would be invisible (no raw code to scan) and the SPI driver would
|
|
415
|
+
// always be emitted. Same blind spot the wdt.* fix below the raw-code
|
|
416
|
+
// block addresses for the watchdog.
|
|
417
|
+
if (statement.operation && typeof statement.operation.operation === "string") {
|
|
418
|
+
const opName = statement.operation.operation;
|
|
419
|
+
if (opName.startsWith("spi."))
|
|
420
|
+
result.usesSPI = true;
|
|
421
|
+
if (opName.startsWith("i2c."))
|
|
422
|
+
result.usesI2C = true;
|
|
423
|
+
if (opName.startsWith("tone."))
|
|
424
|
+
result.usesTone = true;
|
|
425
|
+
if (opName.startsWith("uart."))
|
|
426
|
+
result.usesUart = true;
|
|
427
|
+
// Timing HAL ops (timing.delay/millis/micros) carry a typed operation
|
|
428
|
+
// name, not raw code, so the regex scans below miss them. Mirror the
|
|
429
|
+
// raw-code timing detection here so usesMillis/usesTiming (and thus
|
|
430
|
+
// usesNativeTiming) fire for `delay()`/`millis()` on AVR.
|
|
431
|
+
if (opName === "timing.delay" || opName === "timing.delay_microseconds"
|
|
432
|
+
|| opName === "timing.millis" || opName === "timing.micros") {
|
|
433
|
+
result.usesTiming = true;
|
|
434
|
+
result.usesMillis = true;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
360
437
|
// Scan raw C++ code in HAL ops for polyfill helper usage
|
|
361
438
|
if (statement.operation && statement.operation.operation === "raw" && typeof statement.operation.code === "string") {
|
|
362
439
|
const code = statement.operation.code;
|
|
@@ -384,6 +461,21 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
384
461
|
result.usesTiming = true;
|
|
385
462
|
result.usesMillis = true;
|
|
386
463
|
}
|
|
464
|
+
// Native AVR peripheral usage inside raw hal-op code (e.g. the
|
|
465
|
+
// EEPROM namespace lowers to `EEPROM.write(...)` in a raw hal-op;
|
|
466
|
+
// Serial/SPI/Wire may appear as lowered library calls too).
|
|
467
|
+
if (/\bEEPROM\b/.test(code) || /\beeprom_(read|write|update)_byte\b/.test(code)) {
|
|
468
|
+
result.usesEEPROM = true;
|
|
469
|
+
}
|
|
470
|
+
if (/\b(Serial|console)\b/.test(code)) {
|
|
471
|
+
result.usesUart = true;
|
|
472
|
+
}
|
|
473
|
+
if (/\bSPI\b/.test(code)) {
|
|
474
|
+
result.usesSPI = true;
|
|
475
|
+
}
|
|
476
|
+
if (/\bWire\b/.test(code)) {
|
|
477
|
+
result.usesI2C = true;
|
|
478
|
+
}
|
|
387
479
|
}
|
|
388
480
|
break;
|
|
389
481
|
case "update":
|
|
@@ -431,6 +523,14 @@ export function analyzeProgram(program, strategy) {
|
|
|
431
523
|
usesTiming: false,
|
|
432
524
|
usesWDT: false,
|
|
433
525
|
usesStrPtr: false,
|
|
526
|
+
usesUart: false,
|
|
527
|
+
usesSPI: false,
|
|
528
|
+
usesI2C: false,
|
|
529
|
+
usesEEPROM: false,
|
|
530
|
+
usesTone: false,
|
|
531
|
+
usesMap: false,
|
|
532
|
+
usesConstrain: false,
|
|
533
|
+
usesNativeTiming: false,
|
|
434
534
|
hasSerialBegin: false,
|
|
435
535
|
hasGenerators: false,
|
|
436
536
|
usesStdMap: false,
|
|
@@ -585,5 +685,18 @@ export function analyzeProgram(program, strategy) {
|
|
|
585
685
|
if (loweredConsoleInCallback()) {
|
|
586
686
|
result.hasConsoleCalls = true;
|
|
587
687
|
}
|
|
688
|
+
// Derive the comprehensive native-timing gate for the AVR millis() Timer0
|
|
689
|
+
// ISR. The ISR is needed whenever the program touches the soft clock
|
|
690
|
+
// directly (millis/delay/micros) OR has a hidden consumer: setInterval/
|
|
691
|
+
// setTimeout (the scheduler polls millis), or async functions (the runtime
|
|
692
|
+
// polls millis). The per-frame UI tick is injected by the emitter, not
|
|
693
|
+
// present in user source — the setup emitter ORs entryHasUI() in at the
|
|
694
|
+
// consume site. Without this gate, every AVR program pulled in the Timer0
|
|
695
|
+
// ISR even when it never uses timing.
|
|
696
|
+
const hasAsync = program.functions.some(fn => fn.isAsync);
|
|
697
|
+
result.usesNativeTiming = result.usesMillis
|
|
698
|
+
|| result.usesTiming
|
|
699
|
+
|| result.timerCallCount > 0
|
|
700
|
+
|| hasAsync;
|
|
588
701
|
return result;
|
|
589
702
|
}
|
|
@@ -4,4 +4,4 @@ import type { BoardConstants } from './board-resolver.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Validate that pins used with inputPullDown() support pulldown on this board.
|
|
6
6
|
*/
|
|
7
|
-
export declare function validatePulldownSupport(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
|
|
7
|
+
export declare function validatePulldownSupport(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* Validate that pins used with inputPullDown() support pulldown on this board.
|
|
9
9
|
*/
|
|
10
|
-
export function validatePulldownSupport(usage, boardConstants) {
|
|
10
|
+
export function validatePulldownSupport(usage, boardConstants, filePath) {
|
|
11
11
|
const diagnostics = [];
|
|
12
12
|
if (usage.inputPulldownPins.size === 0) {
|
|
13
13
|
return diagnostics;
|
|
@@ -22,6 +22,7 @@ export function validatePulldownSupport(usage, boardConstants) {
|
|
|
22
22
|
diagnostics.push({
|
|
23
23
|
code: 'pulldown-not-supported',
|
|
24
24
|
message: `${pinName ?? `pin ${pinNumber}`} does not support hardware pulldown${arch ? ` on ${arch.toUpperCase()} boards` : ''}. Use ${pinName ?? 'pin'}.asInput() or ${pinName ?? 'pin'}.inputPullUp() instead.`,
|
|
25
|
+
filePath,
|
|
25
26
|
source: 'pulldown-validation',
|
|
26
27
|
severity: 'error',
|
|
27
28
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Diagnostic } from '../types.js';
|
|
2
2
|
import type { BoardConstants } from './board-resolver.js';
|
|
3
3
|
import type { PeripheralUsage } from './peripheral-usage.js';
|
|
4
|
-
export declare function validatePWMTimerSharing(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
|
|
4
|
+
export declare function validatePWMTimerSharing(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
|
|
@@ -52,7 +52,7 @@ function getPwmTimerPins(boardConstants) {
|
|
|
52
52
|
}
|
|
53
53
|
return Array.from(pinsByIndex.values()).filter(pin => pin.timerId.length > 0);
|
|
54
54
|
}
|
|
55
|
-
export function validatePWMTimerSharing(usage, boardConstants) {
|
|
55
|
+
export function validatePWMTimerSharing(usage, boardConstants, filePath) {
|
|
56
56
|
if (usage.pwmPinsUsed.size < 2 || !boardConstants) {
|
|
57
57
|
return [];
|
|
58
58
|
}
|
|
@@ -85,6 +85,7 @@ export function validatePWMTimerSharing(usage, boardConstants) {
|
|
|
85
85
|
diagnostics.push({
|
|
86
86
|
severity: 'info',
|
|
87
87
|
code: 'pwm-timer-sharing',
|
|
88
|
+
filePath,
|
|
88
89
|
source: 'pwm-timer-sharing',
|
|
89
90
|
message: `${references.join(', ')} share ${timerId}${boardLabel}. Duty cycle can differ per pin, but timer-wide PWM settings are shared across that group. Prefer pins on different timer groups if you need independent PWM timing behavior.`,
|
|
90
91
|
});
|
|
@@ -34,6 +34,7 @@ export function analyzeResources(program, strategy) {
|
|
|
34
34
|
diagnostics.push({
|
|
35
35
|
severity: 'warning',
|
|
36
36
|
message: `${pinRef} is configured as ${peripheralName} ${func.role.toUpperCase()}. Using it as GPIO may interfere with communication.\n → ${suggestion}`,
|
|
37
|
+
filePath: program.fileName,
|
|
37
38
|
code: 'peripheral-pin-conflict',
|
|
38
39
|
source: 'resource-analysis',
|
|
39
40
|
});
|
|
@@ -58,6 +59,7 @@ export function analyzeResources(program, strategy) {
|
|
|
58
59
|
severity: 'error',
|
|
59
60
|
message: `Hardware Conflict: Pin '${pinName}' is required by both ${peripheralName} and ${other}.`,
|
|
60
61
|
hint: `Move one of the peripherals to different pins if your board supports remapping, or avoid using both simultaneously.`,
|
|
62
|
+
filePath: program.fileName,
|
|
61
63
|
code: 'peripheral-peripheral-conflict',
|
|
62
64
|
source: 'resource-analysis',
|
|
63
65
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Diagnostic } from '../types.js';
|
|
2
2
|
import type { BoardConstants } from './board-resolver.js';
|
|
3
3
|
import type { PeripheralUsage } from './peripheral-usage.js';
|
|
4
|
-
export declare function validateTimer0PWMTimingConflict(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
|
|
4
|
+
export declare function validateTimer0PWMTimingConflict(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
|
|
@@ -43,7 +43,7 @@ function getTimer0PwmPins(boardConstants) {
|
|
|
43
43
|
}
|
|
44
44
|
return timer0Pins;
|
|
45
45
|
}
|
|
46
|
-
export function validateTimer0PWMTimingConflict(usage, boardConstants) {
|
|
46
|
+
export function validateTimer0PWMTimingConflict(usage, boardConstants, filePath) {
|
|
47
47
|
if (!usage.timer0 || usage.pwmPinsUsed.size === 0) {
|
|
48
48
|
return [];
|
|
49
49
|
}
|
|
@@ -65,6 +65,7 @@ export function validateTimer0PWMTimingConflict(usage, boardConstants) {
|
|
|
65
65
|
return [{
|
|
66
66
|
severity: 'info',
|
|
67
67
|
code: 'timer0-pwm-timing-conflict',
|
|
68
|
+
filePath,
|
|
68
69
|
source: 'timer0-pwm-timing-conflict',
|
|
69
70
|
message: `${pinReferences.join(', ')} use Timer0 PWM${boardLabel}, and your program also relies on Timer0-backed timing APIs such as delay(), millis(), or micros(). This coupling is common on AVR boards, so prefer non-Timer0 PWM pins when you want PWM behavior isolated from core timing.`,
|
|
70
71
|
}];
|