@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
|
@@ -51,6 +51,7 @@ export function validateBlockingDelayInLoop(program) {
|
|
|
51
51
|
`or use Async.sleep(ms) / Async.yield() to let other tasks run between checks.`,
|
|
52
52
|
line: stmt.sourceSpan?.startLine,
|
|
53
53
|
column: stmt.sourceSpan?.startColumn,
|
|
54
|
+
filePath: stmt.sourceSpan?.filePath,
|
|
54
55
|
code: 'blocking-delay-in-loop',
|
|
55
56
|
source: 'timing-validation',
|
|
56
57
|
});
|
|
@@ -14,6 +14,9 @@ function replaceHalReadBufferPlaceholder(op, varName) {
|
|
|
14
14
|
if (op.operation === "i2c.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
|
|
15
15
|
return { ...op, buffer: varName };
|
|
16
16
|
}
|
|
17
|
+
if (op.operation === "spi.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
|
|
18
|
+
return { ...op, buffer: varName };
|
|
19
|
+
}
|
|
17
20
|
return op;
|
|
18
21
|
}
|
|
19
22
|
export function assignmentOperatorToString(kind) {
|
|
@@ -430,6 +433,45 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
430
433
|
const isSingletonReceiver = ts.isIdentifier(receiver) && isHALSingleton(receiver.text);
|
|
431
434
|
if (result && (!isOwnershipMethod || isSingletonReceiver)) {
|
|
432
435
|
const isHalOpReturn = result.returnValue === "__hal_op_return__";
|
|
436
|
+
// A __TYPED_ARRAY__ return comes ONLY from a HAL method body
|
|
437
|
+
// (`return new Uint8Array(count)` in e.g. I2CDevice.readBytes /
|
|
438
|
+
// SPIDevice.readRegister). The method's side-effect HAL ops (the
|
|
439
|
+
// i2c.read_buffer / spi.read_buffer fill loops) write INTO this buffer
|
|
440
|
+
// via the __HAL_READ_BUF__ placeholder (rewritten to `varName`). So the
|
|
441
|
+
// buffer var_decl MUST precede the fill ops in emitted order — at top
|
|
442
|
+
// level the var_decl is hoisted to file scope which masks this, but a
|
|
443
|
+
// function-local `const data = ...readBytes()` would otherwise emit the
|
|
444
|
+
// fill loop referencing `data` before its declaration. Detect the typed
|
|
445
|
+
// array up front so we can emit its declaration first.
|
|
446
|
+
const isTypedArrayReturn = typeof result.returnValue === "string" && result.returnValue.startsWith("__TYPED_ARRAY__:");
|
|
447
|
+
if (isTypedArrayReturn && typeof result.returnValue === "string") {
|
|
448
|
+
const parts = result.returnValue.split(":");
|
|
449
|
+
const elementType = parts[1];
|
|
450
|
+
const size = parts[2];
|
|
451
|
+
// Force non-const storage (the buffer is written by the fill op) and
|
|
452
|
+
// synthesize a zero-init array initializer so the var_decl renderer
|
|
453
|
+
// emits `T data[] = { 0, 0, ... }` — a bare `const T data[N];` that
|
|
454
|
+
// is later written would fail to compile (assignment to const).
|
|
455
|
+
// Mirrors plain `new Uint8Array(N)` (expression-to-ir.ts).
|
|
456
|
+
const count = parseInt(size, 10);
|
|
457
|
+
const initElements = !isNaN(count) && count > 0 && count <= 256
|
|
458
|
+
? Array(count).fill(0).map(() => ({ kind: "number", value: 0 }))
|
|
459
|
+
: [];
|
|
460
|
+
lowered.push({
|
|
461
|
+
kind: "var_decl",
|
|
462
|
+
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
463
|
+
leadingComments: commentsAssigned ? [] : statementComments.leadingComments,
|
|
464
|
+
trailingComments: [],
|
|
465
|
+
name: varName,
|
|
466
|
+
storage: "let",
|
|
467
|
+
cppType: `${elementType}[${size}]`,
|
|
468
|
+
initializer: initElements.length > 0
|
|
469
|
+
? { kind: "array", elements: initElements, elementType }
|
|
470
|
+
: undefined,
|
|
471
|
+
});
|
|
472
|
+
activeCArrayVars.add(varName);
|
|
473
|
+
commentsAssigned = true;
|
|
474
|
+
}
|
|
433
475
|
if (result.halOps && result.halOps.length > 0) {
|
|
434
476
|
const sideEffectOps = isHalOpReturn ? result.halOps.slice(0, -1) : result.halOps;
|
|
435
477
|
const halStmts = sideEffectOps.map(op => ({
|
|
@@ -527,24 +569,11 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
527
569
|
if (/\b\d+\.\d+\b/.test(result.returnValue)) {
|
|
528
570
|
registerFloatVariable(varName);
|
|
529
571
|
}
|
|
572
|
+
// __TYPED_ARRAY__ returns are handled above (declared BEFORE the
|
|
573
|
+
// fill ops). Everything else is a scalar/value return captured
|
|
574
|
+
// after the side-effect ops.
|
|
530
575
|
const isTypedArray = result.returnValue.startsWith("__TYPED_ARRAY__:");
|
|
531
|
-
if (isTypedArray) {
|
|
532
|
-
const parts = result.returnValue.split(":");
|
|
533
|
-
const elementType = parts[1];
|
|
534
|
-
const size = parts[2];
|
|
535
|
-
lowered.push({
|
|
536
|
-
kind: "var_decl",
|
|
537
|
-
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
538
|
-
leadingComments: commentsAssigned ? [] : statementComments.leadingComments,
|
|
539
|
-
trailingComments: [],
|
|
540
|
-
name: varName,
|
|
541
|
-
storage,
|
|
542
|
-
cppType: `${elementType}[${size}]`,
|
|
543
|
-
initializer: undefined,
|
|
544
|
-
});
|
|
545
|
-
activeCArrayVars.add(varName);
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
576
|
+
if (!isTypedArray) {
|
|
548
577
|
lowered.push({
|
|
549
578
|
kind: "var_decl",
|
|
550
579
|
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
@@ -39,6 +39,7 @@ export function validateTryCatch(program, boardConstants, strategy) {
|
|
|
39
39
|
hint: `function readSensor(): number | null {\n const data = sensor.read();\n if (!data) return null; // error path\n return data.value; // success path\n}`,
|
|
40
40
|
line: stmt.sourceSpan?.startLine,
|
|
41
41
|
column: stmt.sourceSpan?.startColumn,
|
|
42
|
+
filePath: stmt.sourceSpan?.filePath,
|
|
42
43
|
source: 'try-catch-validation',
|
|
43
44
|
});
|
|
44
45
|
// Still recurse into nested blocks to find other try/catch
|
|
@@ -57,6 +58,7 @@ export function validateTryCatch(program, boardConstants, strategy) {
|
|
|
57
58
|
hint: `return null; // or return an error code`,
|
|
58
59
|
line: stmt.sourceSpan?.startLine,
|
|
59
60
|
column: stmt.sourceSpan?.startColumn,
|
|
61
|
+
filePath: stmt.sourceSpan?.filePath,
|
|
60
62
|
source: 'try-catch-validation',
|
|
61
63
|
});
|
|
62
64
|
return;
|
|
@@ -33,9 +33,9 @@ const PIN_INTERFACE_TYPE_NAMES = new Set([
|
|
|
33
33
|
"PinMode", "InterruptMode",
|
|
34
34
|
]);
|
|
35
35
|
const BUS_INTERFACE_TYPE_NAMES = new Set([
|
|
36
|
-
"II2CBus", "ISPIBus", "ISerialPort",
|
|
36
|
+
"II2CBus", "ISPIBus", "ISerialPort",
|
|
37
37
|
"I2CConfig", "SPIConfig", "UARTConfig",
|
|
38
|
-
"I2CAddress", "
|
|
38
|
+
"I2CAddress", "SPITransferOptions",
|
|
39
39
|
]);
|
|
40
40
|
const STRATEGY_TYPE_NAMES = new Set([
|
|
41
41
|
"NativeStrategy", "ArduinoStrategy", "BoardStrategy",
|
|
@@ -130,7 +130,7 @@ function checkSPIFrequency(value) {
|
|
|
130
130
|
* baud rate used as SPI freq, etc.). Only literal numbers are checked —
|
|
131
131
|
* expressions/variables are opaque to this validator.
|
|
132
132
|
*/
|
|
133
|
-
function checkConfigValue(kind, rawValue, diagnostics) {
|
|
133
|
+
function checkConfigValue(kind, rawValue, filePath, diagnostics) {
|
|
134
134
|
// HAL ops carry resolved numeric values as `number` when the source arg was
|
|
135
135
|
// a literal, or as `string` expression text otherwise. Only check numbers.
|
|
136
136
|
const value = typeof rawValue === 'number' ? rawValue
|
|
@@ -146,6 +146,7 @@ function checkConfigValue(kind, rawValue, diagnostics) {
|
|
|
146
146
|
severity: 'warning',
|
|
147
147
|
message,
|
|
148
148
|
code: 'unit-suspicion',
|
|
149
|
+
filePath,
|
|
149
150
|
source: 'unit-suspicion-validation',
|
|
150
151
|
});
|
|
151
152
|
}
|
|
@@ -160,15 +161,16 @@ function scanStatement(stmt, diagnostics) {
|
|
|
160
161
|
if (!stmt || typeof stmt !== 'object')
|
|
161
162
|
return;
|
|
162
163
|
const s = stmt;
|
|
164
|
+
const filePath = s.sourceSpan?.filePath;
|
|
163
165
|
// HAL-op statements carry structured operations with resolved values.
|
|
164
166
|
if (stmt.kind === 'hal-op' && s.operation) {
|
|
165
167
|
const op = s.operation;
|
|
166
168
|
switch (op.operation) {
|
|
167
169
|
case 'i2c.set_clock':
|
|
168
|
-
checkConfigValue('i2c', op.hz, diagnostics);
|
|
170
|
+
checkConfigValue('i2c', op.hz, filePath, diagnostics);
|
|
169
171
|
break;
|
|
170
172
|
case 'uart.begin':
|
|
171
|
-
checkConfigValue('baud', op.baud, diagnostics);
|
|
173
|
+
checkConfigValue('baud', op.baud, filePath, diagnostics);
|
|
172
174
|
break;
|
|
173
175
|
// SPI frequency flows through SPISettings construction text, which the
|
|
174
176
|
// HAL op carries as a string — the numeric extraction in
|
|
@@ -178,7 +180,7 @@ function scanStatement(stmt, diagnostics) {
|
|
|
178
180
|
// SPISettings({freq}, ...) — try to extract the leading frequency.
|
|
179
181
|
const m = op.settings.match(/^\s*(\d+)/);
|
|
180
182
|
if (m)
|
|
181
|
-
checkConfigValue('spi', parseInt(m[1], 10), diagnostics);
|
|
183
|
+
checkConfigValue('spi', parseInt(m[1], 10), filePath, diagnostics);
|
|
182
184
|
}
|
|
183
185
|
break;
|
|
184
186
|
}
|
|
@@ -188,18 +190,18 @@ function scanStatement(stmt, diagnostics) {
|
|
|
188
190
|
if (stmt.kind === 'call' && typeof s.callee === 'string' && Array.isArray(s.args)) {
|
|
189
191
|
const method = s.callee.split('.').pop() ?? s.callee;
|
|
190
192
|
if (method === 'setClock' && s.args.length >= 2) {
|
|
191
|
-
checkConfigValue('i2c', extractNumericValue(s.args[1]), diagnostics);
|
|
193
|
+
checkConfigValue('i2c', extractNumericValue(s.args[1]), filePath, diagnostics);
|
|
192
194
|
}
|
|
193
195
|
else if (method === 'setBaudRate' || method === 'begin') {
|
|
194
196
|
// Serial.begin(baud) / setBaudRate(baud) — last numeric arg is the baud.
|
|
195
197
|
for (const arg of s.args) {
|
|
196
198
|
const v = extractNumericValue(arg);
|
|
197
199
|
if (v !== undefined)
|
|
198
|
-
checkConfigValue('baud', v, diagnostics);
|
|
200
|
+
checkConfigValue('baud', v, filePath, diagnostics);
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
else if (method === 'setFrequency' && s.args.length >= 2) {
|
|
202
|
-
checkConfigValue('spi', extractNumericValue(s.args[1]), diagnostics);
|
|
204
|
+
checkConfigValue('spi', extractNumericValue(s.args[1]), filePath, diagnostics);
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
207
|
// Recurse into nested statements
|
|
@@ -23,13 +23,13 @@ export function runProgramValidations(program, strategy) {
|
|
|
23
23
|
const diagnostics = [];
|
|
24
24
|
const peripheralUsage = program.peripheralUsage ?? createEmptyPeripheralUsage();
|
|
25
25
|
diagnostics.push(...validatePinCapabilities(program));
|
|
26
|
-
diagnostics.push(...validatePeripherals(peripheralUsage, program.boardConstants));
|
|
27
|
-
diagnostics.push(...validateUnsafePins(peripheralUsage, program.boardConstants));
|
|
26
|
+
diagnostics.push(...validatePeripherals(peripheralUsage, program.boardConstants, program.fileName));
|
|
27
|
+
diagnostics.push(...validateUnsafePins(peripheralUsage, program.boardConstants, program.fileName));
|
|
28
28
|
diagnostics.push(...analyzeResources(program, resolvedStrategy));
|
|
29
|
-
diagnostics.push(...validatePinAliasConflicts(peripheralUsage, program.boardConstants));
|
|
30
|
-
diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants));
|
|
31
|
-
diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants));
|
|
32
|
-
diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants));
|
|
29
|
+
diagnostics.push(...validatePinAliasConflicts(peripheralUsage, program.boardConstants, program.fileName));
|
|
30
|
+
diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants, program.fileName));
|
|
31
|
+
diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants, program.fileName));
|
|
32
|
+
diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants, program.fileName));
|
|
33
33
|
diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage));
|
|
34
34
|
inferVolatileForIsrSharedVars(program, diagnostics);
|
|
35
35
|
detectReentrancyRisk(program, diagnostics);
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export type CopyleftRisk = "permissive" | "weak-copyleft" | "strong-copyleft" | "unknown";
|
|
2
|
+
export type LicenseSource = "library.properties" | "license-file" | "source-header" | "none";
|
|
3
|
+
export interface LibraryLicenseEntry {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string | undefined;
|
|
6
|
+
/** install_dir as reported by arduino-cli lib list. */
|
|
7
|
+
path: string;
|
|
8
|
+
/** Normalized SPDX ID (e.g. "BSD-3-Clause"); undefined if not determined. */
|
|
9
|
+
spdx: string | undefined;
|
|
10
|
+
risk: CopyleftRisk;
|
|
11
|
+
source: LicenseSource;
|
|
12
|
+
}
|
|
13
|
+
export type ScanOutcome = {
|
|
14
|
+
ok: true;
|
|
15
|
+
libraries: LibraryLicenseEntry[];
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
reason: "arduino-cli-not-found" | "arduino-cli-unresponsive" | "no-libraries";
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Headers the project needs. `source` is "ino" when read from the generated
|
|
23
|
+
* .ino (authoritative) or "config" when derived from cuttlefish.config.ts
|
|
24
|
+
* (partial: display/touch only).
|
|
25
|
+
*/
|
|
26
|
+
export type ProjectHeaders = {
|
|
27
|
+
ok: true;
|
|
28
|
+
headers: string[];
|
|
29
|
+
source: "ino" | "config";
|
|
30
|
+
inoPath?: string;
|
|
31
|
+
} | {
|
|
32
|
+
ok: false;
|
|
33
|
+
reason: "no-config" | "no-entry" | "unreadable-ino";
|
|
34
|
+
message: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Minimal view of ResolvedCuttlefishConfig that resolveProjectHeaders needs.
|
|
38
|
+
* Kept structural so we don't import the full config type (avoids a cycle).
|
|
39
|
+
*/
|
|
40
|
+
interface ProjectConfig {
|
|
41
|
+
configPath: string;
|
|
42
|
+
entry?: string;
|
|
43
|
+
outputOutDir?: string;
|
|
44
|
+
/** FQBN, e.g. 'arduino:avr:uno'. Used to resolve the board core for core-bundled libs. */
|
|
45
|
+
buildTarget?: string;
|
|
46
|
+
display?: {
|
|
47
|
+
profile?: string;
|
|
48
|
+
driver?: string;
|
|
49
|
+
touch?: {
|
|
50
|
+
library?: string;
|
|
51
|
+
};
|
|
52
|
+
} | null;
|
|
53
|
+
}
|
|
54
|
+
/** Classify a header as a compiler-toolchain C-library header. */
|
|
55
|
+
export declare function isToolchainHeader(header: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Result of resolving the project's board core directory.
|
|
58
|
+
*/
|
|
59
|
+
export type ProjectCore = {
|
|
60
|
+
ok: true;
|
|
61
|
+
coreDir: string;
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
reason: "no-fqbn" | "no-core";
|
|
65
|
+
message: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the project's board-core directory from its FQBN, via
|
|
69
|
+
* `arduino-cli config dump` (for the packages data dir) and the on-disk
|
|
70
|
+
* `<data>/packages/<packager>/hardware/<arch>/<version>/` layout. Returns the
|
|
71
|
+
* highest-versioned core dir. Never throws.
|
|
72
|
+
*
|
|
73
|
+
* `runConfigDump` is an injected seam (returns the `config dump` stdout, or ""
|
|
74
|
+
* on failure) so the function is unit-testable without spawning.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveProjectCore(fqbn: string | undefined, runConfigDump: () => string, readdir: (d: string) => string[]): ProjectCore;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the project's library headers. Prefers the generated .ino
|
|
79
|
+
* (authoritative); falls back to display/touch headers derived from config
|
|
80
|
+
* (partial picture, no transpile required).
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveProjectHeaders(config: ProjectConfig | undefined, readFile: (p: string) => string | undefined): ProjectHeaders;
|
|
83
|
+
/**
|
|
84
|
+
* One project header, joined to its owning library or flagged not-installed.
|
|
85
|
+
*/
|
|
86
|
+
export type ProjectLibrary = {
|
|
87
|
+
kind: "resolved";
|
|
88
|
+
lib: LibraryLicenseEntry;
|
|
89
|
+
} | {
|
|
90
|
+
kind: "core";
|
|
91
|
+
header: string;
|
|
92
|
+
} | {
|
|
93
|
+
kind: "not-installed";
|
|
94
|
+
header: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Build a header-basename -> bundled-library map from a board core's bundled
|
|
98
|
+
* libraries. Scans `<coreDir>/libraries/<Lib>/src/` for .h files (e.g. Wire.h,
|
|
99
|
+
* SPI.h). Each header maps to a synthetic RawArduinoLibrary so the existing
|
|
100
|
+
* resolveLibraryLicense can read its license from the bundled lib's header
|
|
101
|
+
* notice or library.properties.
|
|
102
|
+
*/
|
|
103
|
+
export declare function buildCoreHeaderIndex(coreDir: string, readdir: (d: string) => string[]): Map<string, RawArduinoLibrary>;
|
|
104
|
+
/**
|
|
105
|
+
* Join each project header to its owning library or classify it. Pipeline:
|
|
106
|
+
* 1. user library (arduino-cli lib list) -> resolved (license)
|
|
107
|
+
* 2. core library (project's own core) -> resolved (license, e.g. LGPL-2.1)
|
|
108
|
+
* 3. toolchain header (avr/*, util/*) -> core (gray, no license)
|
|
109
|
+
* 4. else -> not-installed
|
|
110
|
+
*
|
|
111
|
+
* `coreDir` is optional; when absent, step 2 is skipped.
|
|
112
|
+
*/
|
|
113
|
+
export declare function joinHeadersToLibraries(headers: string[], libs: RawArduinoLibrary[], readdir: (d: string) => string[], readFile: (p: string) => string | undefined, coreDir?: string): ProjectLibrary[];
|
|
114
|
+
/**
|
|
115
|
+
* Resolve a raw license input (either the short `library.properties` `license=`
|
|
116
|
+
* value, the full text of a LICENSE file, or a source-file header comment) to a
|
|
117
|
+
* canonical SPDX ID.
|
|
118
|
+
*
|
|
119
|
+
* Matching priority:
|
|
120
|
+
* 1. SPDX-License-Identifier: <id> marker (authoritative when present)
|
|
121
|
+
* 2. exact alias match (suits the short properties value)
|
|
122
|
+
* 3. substring markers match, ALL markers required (suits full LICENSE text)
|
|
123
|
+
* 4. shortMarkers match, ANY one sufficient (suits sparse header comments
|
|
124
|
+
* like Adafruit's "BSD license, all text here must be included")
|
|
125
|
+
*
|
|
126
|
+
* Returns the SPDX ID string, or undefined if nothing matched.
|
|
127
|
+
*/
|
|
128
|
+
export declare function identifySpdx(input: string): string | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Classify the copyleft risk of a known SPDX ID. Returns "unknown" for
|
|
131
|
+
* unrecognized ids.
|
|
132
|
+
*/
|
|
133
|
+
export declare function classifyRisk(spdx: string): CopyleftRisk;
|
|
134
|
+
/** Raw library entry as it appears in `arduino-cli lib list --format json`. */
|
|
135
|
+
export interface RawArduinoLibrary {
|
|
136
|
+
name: string;
|
|
137
|
+
version?: string;
|
|
138
|
+
install_dir?: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Options for `scanLicenses`. Production calls omit this object entirely; tests
|
|
142
|
+
* inject `fakeLibList` and `fakeReadFile` to avoid spawning and disk I/O.
|
|
143
|
+
*/
|
|
144
|
+
export interface ScanOptions {
|
|
145
|
+
/** Override the `arduino-cli lib list` call. Return null to simulate spawn failure. */
|
|
146
|
+
fakeLibList?: () => RawArduinoLibrary[] | null;
|
|
147
|
+
/** Override disk reads of library.properties and LICENSE files. */
|
|
148
|
+
fakeReadFile?: (p: string) => string | undefined;
|
|
149
|
+
/** Override directory listings. */
|
|
150
|
+
fakeReaddir?: (d: string) => string[];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Normalize either JSON shape (wrapped or bare array) to a flat library list.
|
|
154
|
+
* Exported for direct unit testing of the dual-shape parsing.
|
|
155
|
+
*/
|
|
156
|
+
export declare function coerceLibList(parsed: unknown): RawArduinoLibrary[];
|
|
157
|
+
/**
|
|
158
|
+
* Module-level runner override used by presenter tests so `scanLicenses()`
|
|
159
|
+
* (with no args) does not spawn. Mirrors __setArduinoCliRunnerForTest.
|
|
160
|
+
*/
|
|
161
|
+
export interface LicensesRunner {
|
|
162
|
+
listLibraries: () => RawArduinoLibrary[] | null;
|
|
163
|
+
readFile: (p: string) => string | undefined;
|
|
164
|
+
readdir: (d: string) => string[];
|
|
165
|
+
}
|
|
166
|
+
/** @internal Test-only override of the default runner. */
|
|
167
|
+
export declare function __setLicensesRunnerForTest(runner: LicensesRunner | undefined): void;
|
|
168
|
+
/**
|
|
169
|
+
* Scan installed Arduino libraries and resolve each one's license. Never throws.
|
|
170
|
+
*/
|
|
171
|
+
export declare function scanLicenses(options?: ScanOptions): ScanOutcome;
|
|
172
|
+
/** @internal Test-only override of the project config (normally loaded via loadCuttlefishConfig). */
|
|
173
|
+
export declare function __setProjectConfigForTest(config: ProjectConfig | undefined): void;
|
|
174
|
+
/** @internal Test-only override of `arduino-cli config dump` stdout. */
|
|
175
|
+
export declare function __setConfigDumpForTest(fn: (() => string) | undefined): void;
|
|
176
|
+
/**
|
|
177
|
+
* `cuttlefish licenses` presenter. `all === false` (default, project scope)
|
|
178
|
+
* resolves this project's libraries from the generated .ino (or config
|
|
179
|
+
* fallback), joins each to an installed library, and reports only those.
|
|
180
|
+
* `all === true` reports every installed library (the original behavior).
|
|
181
|
+
* Warns on unknown licenses; flags NOT INSTALLED headers in project scope; sets
|
|
182
|
+
* process.exitCode under --strict. Never calls process.exit().
|
|
183
|
+
*/
|
|
184
|
+
export declare function runLicensesPresenter(strict: boolean, all: boolean): void;
|
|
185
|
+
export {};
|