@typecad/cuttlefish 1.0.0-alpha.3 → 1.0.0-alpha.7
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 +4 -4
- package/dist/api/shared/display-adapter.d.ts +2 -1
- package/dist/api/shared/display-adapter.js +8 -1
- package/dist/api/shared/display-adapters/sdl.js +9 -2
- package/dist/api/shared/display-profile.d.ts +20 -3
- package/dist/api/shared/display-profile.js +21 -6
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +25 -0
- package/dist/api/shared/framework-manifest.d.ts +462 -0
- package/dist/api/shared/framework-manifest.js +149 -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 +28 -0
- package/dist/api/shared/hal-op-ir.d.ts +427 -1
- package/dist/api/shared/hal-op-ir.js +95 -1
- package/dist/api/shared/index.d.ts +11 -1
- package/dist/api/shared/index.js +14 -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 +9 -0
- package/dist/api/shared/promise-runtime.js +78 -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 +417 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +175 -4
- package/dist/config-loader.js +20 -1
- package/dist/config-schema.d.ts +36 -36
- package/dist/create/board-spec.d.ts +4 -4
- package/dist/create/index.d.ts +1 -1
- package/dist/create/index.js +1 -1
- package/dist/create/init-scaffold.d.ts +3 -0
- package/dist/create/init-scaffold.js +74 -2
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +217 -17
- package/dist/create/init-wizard.js +20 -1
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/emitter-context.d.ts +5 -0
- package/dist/emit/emitters/function-emitter-impl.js +69 -59
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +21 -11
- package/dist/emit/emitters/setup.js +155 -0
- package/dist/emit/emitters/top-level-prep.js +2 -0
- package/dist/emit/emitters/ui-emitter.js +23 -8
- package/dist/emit/expression-renderer.js +10 -1
- package/dist/emit/snprintf-helpers.js +8 -0
- package/dist/emit/statement-renderer.js +13 -0
- package/dist/emit/utils/async-state-machine.js +185 -116
- 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/ir/adc-range-validation.js +26 -25
- package/dist/ir/build-ir.js +5 -1
- package/dist/ir/expression-to-ir.js +57 -6
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +40 -12
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +74 -0
- package/dist/ir/hal/hal-plugins.js +571 -0
- package/dist/ir/identifier-collector.js +18 -0
- package/dist/ir/interrupt-analysis.js +8 -3
- 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 +33 -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 +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 +39 -0
- package/dist/ir/program-analysis.js +192 -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.d.ts +6 -1
- package/dist/ir/timing-validation.js +51 -12
- package/dist/ir/transformers/expressions.js +58 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/variables.js +86 -19
- 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 +9 -7
- 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/idf-discovery.d.ts +7 -0
- package/dist/libdef/idf-discovery.js +59 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/licenses.d.ts +185 -0
- package/dist/licenses.js +963 -0
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +6 -2
- package/dist/stores/display-profile-store.d.ts +1 -0
- package/dist/stores/display-profile-store.js +1 -0
- package/dist/testing.d.ts +4 -2
- package/dist/testing.js +4 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +78 -32
- package/dist/types.d.ts +5 -1
- package/dist/ui-hook.d.ts +17 -1
- package/dist/utils/cli.js +71 -1
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +9 -4
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Shared display-op resolver for native (non-Arduino) display strategies.
|
|
3
|
+
//
|
|
4
|
+
// Both NativeAVRStrategy and Esp32Strategy override resolveDisplayOp to return
|
|
5
|
+
// undefined for display.* ops (the design intent: display ops are resolved
|
|
6
|
+
// through the adapter path, not per-op HAL lowering). But the HAL lowering
|
|
7
|
+
// layer still calls resolveDisplayOp for every display.* op emitted by user
|
|
8
|
+
// code (display.init via ui.mount, display.fill_rect/draw_rect/draw_text via
|
|
9
|
+
// direct HAL calls, display.flush per frame).
|
|
10
|
+
//
|
|
11
|
+
// Those ops need to lower to CALLS INTO the adapter surface — the same
|
|
12
|
+
// display_init() / display_targetFillRect() / etc. functions the Adafruit
|
|
13
|
+
// path calls. The native adapters emit those functions with identical
|
|
14
|
+
// signatures, so the lowering is identical across native strategies.
|
|
15
|
+
//
|
|
16
|
+
// This module provides that lowering. Each native strategy's resolveDisplayOp
|
|
17
|
+
// delegates here instead of returning undefined.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/** Render a color value as a C++ hex literal (e.g. 0x07e0) for readable RGB565. */
|
|
20
|
+
function hexColor(c) {
|
|
21
|
+
return `0x${c.toString(16)}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a display HAL op to a call into the native adapter surface
|
|
25
|
+
* (display_init / display_targetFillRect / etc.). Returns undefined for ops
|
|
26
|
+
* this resolver does not handle.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveNativeDisplayOp(op) {
|
|
29
|
+
switch (op.operation) {
|
|
30
|
+
case "display.init":
|
|
31
|
+
// The adapter's display_init() does bus setup, panel init sequence,
|
|
32
|
+
// rotation, and the initial fillScreen. No backlight GPIO parameter
|
|
33
|
+
// on native paths — the Adafruit-style backlight pin is handled in
|
|
34
|
+
// the mount config if needed.
|
|
35
|
+
return { code: `display_init();` };
|
|
36
|
+
case "display.fill_rect":
|
|
37
|
+
// Delegate to the polymorphic target draw so the same call works
|
|
38
|
+
// whether the active target is the panel or an offscreen canvas.
|
|
39
|
+
return {
|
|
40
|
+
code: `display_targetFillRect(display_defaultTarget(), ${op.x}, ${op.y}, ${op.w}, ${op.h}, ${hexColor(op.color)});`,
|
|
41
|
+
};
|
|
42
|
+
case "display.draw_rect":
|
|
43
|
+
return {
|
|
44
|
+
code: `display_targetDrawRect(display_defaultTarget(), ${op.x}, ${op.y}, ${op.w}, ${op.h}, ${hexColor(op.color)});`,
|
|
45
|
+
};
|
|
46
|
+
case "display.draw_text":
|
|
47
|
+
return {
|
|
48
|
+
code: [
|
|
49
|
+
`display_targetSetCursor(display_defaultTarget(), ${op.x}, ${op.y});`,
|
|
50
|
+
`display_targetSetTextColor(display_defaultTarget(), ${hexColor(op.color)});`,
|
|
51
|
+
`display_targetSetTextSize(display_defaultTarget(), 2);`,
|
|
52
|
+
`display_targetPrint(display_defaultTarget(), ${JSON.stringify(op.text)});`,
|
|
53
|
+
].join("\n"),
|
|
54
|
+
};
|
|
55
|
+
case "display.flush":
|
|
56
|
+
// Direct-mode panels (ILI9341, ST7796S) draw immediately — no flush.
|
|
57
|
+
// Buffered panels (SSD1309) flush via display_partial_refresh, which
|
|
58
|
+
// the runtime calls through display_partial_refresh directly (not via
|
|
59
|
+
// HAL op) at frame end.
|
|
60
|
+
return { code: `/* flush: ${op.rects.length} rect(s) — native display */` };
|
|
61
|
+
default:
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -279,6 +279,15 @@ export interface PlatformSafetyStrategy {
|
|
|
279
279
|
* StaticArray wrapper's methods or the std::vector/__tc_* helper form.
|
|
280
280
|
*/
|
|
281
281
|
promotesArrayLiteralsToStaticArray?(): boolean;
|
|
282
|
+
/**
|
|
283
|
+
* Whether this target runs on a preemptive RTOS where blocking calls
|
|
284
|
+
* (e.g. vTaskDelay) yield the CPU to other tasks. When true, delay() inside
|
|
285
|
+
* loop() does NOT freeze the async queue or UI rendering — the timing
|
|
286
|
+
* validator suppresses the 'blocking-delay-in-loop' warning for plain delay().
|
|
287
|
+
* Defaults to false (Arduino/AVR use cooperative scheduling where delay
|
|
288
|
+
* freezes everything). ESP-IDF (FreeRTOS) returns true.
|
|
289
|
+
*/
|
|
290
|
+
isRtosTarget?(): boolean;
|
|
282
291
|
}
|
|
283
292
|
export interface PlatformBuildStrategy {
|
|
284
293
|
/** Queue capacity for the async microtask ring buffer. */
|
|
@@ -181,6 +181,84 @@ namespace typecad_async {
|
|
|
181
181
|
std::vector<std::function<void(const std::string&)>> _onRejected;
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
+
// Promise<void> — T=_value/const T& is illegal for void. Helpers below pass
|
|
185
|
+
// resolve as std::function<void(const void*)> (pointer, not reference).
|
|
186
|
+
template <>
|
|
187
|
+
class Promise<void> {
|
|
188
|
+
public:
|
|
189
|
+
enum class State { Pending, Fulfilled, Rejected };
|
|
190
|
+
|
|
191
|
+
using ResolveFn = std::function<void(const void*)>;
|
|
192
|
+
using RejectFn = std::function<void(const std::string&)>;
|
|
193
|
+
using Executor = std::function<void(ResolveFn, RejectFn)>;
|
|
194
|
+
|
|
195
|
+
Promise() : _state(State::Pending), _error{} {}
|
|
196
|
+
|
|
197
|
+
explicit Promise(Executor executor)
|
|
198
|
+
: _state(State::Pending), _error{} {
|
|
199
|
+
executor(
|
|
200
|
+
[this](const void*) { this->resolve(nullptr); },
|
|
201
|
+
[this](const std::string& error) { this->reject(error); }
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static Promise<void> resolveValue() {
|
|
206
|
+
Promise<void> promise;
|
|
207
|
+
promise.resolve(nullptr);
|
|
208
|
+
return promise;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static Promise<void> rejectValue(const std::string& error) {
|
|
212
|
+
Promise<void> promise;
|
|
213
|
+
promise.reject(error);
|
|
214
|
+
return promise;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
void resolve(const void* = nullptr) {
|
|
218
|
+
if (_state != State::Pending) return;
|
|
219
|
+
_state = State::Fulfilled;
|
|
220
|
+
auto callbacks = _onFulfilled;
|
|
221
|
+
enqueueMicrotask([callbacks]() mutable {
|
|
222
|
+
for (auto& callback : callbacks) { callback(nullptr); }
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
void reject(const std::string& error) {
|
|
227
|
+
if (_state != State::Pending) return;
|
|
228
|
+
_state = State::Rejected;
|
|
229
|
+
_error = error;
|
|
230
|
+
auto callbacks = _onRejected;
|
|
231
|
+
enqueueMicrotask([callbacks, error]() mutable {
|
|
232
|
+
for (auto& callback : callbacks) { callback(error); }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
Promise<void>& then(std::function<void(const void*)> onFulfilled) {
|
|
237
|
+
if (_state == State::Fulfilled) {
|
|
238
|
+
enqueueMicrotask([onFulfilled]() mutable { onFulfilled(nullptr); });
|
|
239
|
+
} else if (_state == State::Pending) {
|
|
240
|
+
_onFulfilled.push_back(std::move(onFulfilled));
|
|
241
|
+
}
|
|
242
|
+
return *this;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Promise<void>& catchError(std::function<void(const std::string&)> onRejected) {
|
|
246
|
+
if (_state == State::Rejected) {
|
|
247
|
+
const std::string error = _error;
|
|
248
|
+
enqueueMicrotask([onRejected, error]() mutable { onRejected(error); });
|
|
249
|
+
} else if (_state == State::Pending) {
|
|
250
|
+
_onRejected.push_back(std::move(onRejected));
|
|
251
|
+
}
|
|
252
|
+
return *this;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private:
|
|
256
|
+
State _state;
|
|
257
|
+
std::string _error;
|
|
258
|
+
std::vector<std::function<void(const void*)>> _onFulfilled;
|
|
259
|
+
std::vector<std::function<void(const std::string&)>> _onRejected;
|
|
260
|
+
};
|
|
261
|
+
|
|
184
262
|
// ── HAL-level implementations (inside namespace so they see Promise<T> and enqueueMicrotask) ──
|
|
185
263
|
|
|
186
264
|
// Async.sleep() — cooperative delay using millis polling
|
|
@@ -26,6 +26,14 @@ export interface Diagnostic {
|
|
|
26
26
|
column?: number;
|
|
27
27
|
code?: string;
|
|
28
28
|
source?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Path (or basename) of the source file the diagnostic refers to. When set,
|
|
31
|
+
* it is rendered in the diagnostic header so the location is not just
|
|
32
|
+
* `(line,col)` but `path/to/file.ts (line,col)`. Emitters should prefer the
|
|
33
|
+
* real file path from the originating node's sourceSpan, falling back to a
|
|
34
|
+
* basename when the full path is unavailable.
|
|
35
|
+
*/
|
|
36
|
+
filePath?: string;
|
|
29
37
|
/** Text of the source line for display context. */
|
|
30
38
|
sourceLine?: string;
|
|
31
39
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PlatformStrategy } from './platform-strategy.js';
|
|
2
|
+
import type { FrameworkToolchain } from '../../framework-registry.js';
|
|
3
|
+
import type { FrameworkManifest } from './framework-manifest.js';
|
|
4
|
+
export interface ManifestValidationContext {
|
|
5
|
+
strategy: PlatformStrategy;
|
|
6
|
+
toolchain?: FrameworkToolchain;
|
|
7
|
+
moduleExports: Record<string, unknown>;
|
|
8
|
+
packageRoot: string;
|
|
9
|
+
repoTestsDir: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ManifestValidationError {
|
|
12
|
+
code: string;
|
|
13
|
+
path: string;
|
|
14
|
+
message: string;
|
|
15
|
+
subject?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ManifestValidationWarning {
|
|
18
|
+
code: string;
|
|
19
|
+
path: string;
|
|
20
|
+
message: string;
|
|
21
|
+
subject?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ManifestValidationResult {
|
|
24
|
+
valid: boolean;
|
|
25
|
+
errors: ManifestValidationError[];
|
|
26
|
+
warnings: ManifestValidationWarning[];
|
|
27
|
+
}
|
|
28
|
+
export declare function validateFrameworkManifest(manifest: FrameworkManifest, ctx: ManifestValidationContext): ManifestValidationResult;
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Framework manifest validator
|
|
3
|
+
//
|
|
4
|
+
// Cross-checks each manifest claim against the loaded PlatformStrategy,
|
|
5
|
+
// Toolchain, and module exports. Errors are plain data (not thrown) so the
|
|
6
|
+
// validator can be called from tests, a future CLI, or agents reading errors
|
|
7
|
+
// programmatically.
|
|
8
|
+
//
|
|
9
|
+
// Categories A-H are documented in docs/framework-manifest-error-codes.md.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { POLYFILL_BACKED_OPS } from './framework-manifest.js';
|
|
14
|
+
import { HAL_OPERATION_KINDS } from './hal-op-ir.js';
|
|
15
|
+
import { DISPLAY_OPERATION_KINDS } from './display-op-ir.js';
|
|
16
|
+
class Accumulator {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.errors = [];
|
|
19
|
+
this.warnings = [];
|
|
20
|
+
}
|
|
21
|
+
error(code, path, message, subject) {
|
|
22
|
+
this.errors.push({ code, path, message, subject });
|
|
23
|
+
}
|
|
24
|
+
warning(code, path, message, subject) {
|
|
25
|
+
this.warnings.push({ code, path, message, subject });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Map each HAL category to the prefix its op kinds share.
|
|
29
|
+
const CATEGORY_PREFIXES = {
|
|
30
|
+
gpio: ['gpio.'],
|
|
31
|
+
pwm: ['pwm.'],
|
|
32
|
+
adc: ['adc.'],
|
|
33
|
+
dac: ['dac.'],
|
|
34
|
+
interrupts: ['interrupt.'],
|
|
35
|
+
tone: ['tone.'],
|
|
36
|
+
timing: ['timing.'],
|
|
37
|
+
power: ['power.'],
|
|
38
|
+
i2c: ['i2c.'],
|
|
39
|
+
spi: ['spi.'],
|
|
40
|
+
uart: ['uart.'],
|
|
41
|
+
pulse: ['pulse.'],
|
|
42
|
+
shift: ['shift.'],
|
|
43
|
+
board: ['board.'],
|
|
44
|
+
wdt: ['wdt.'],
|
|
45
|
+
wifi: ['wifi.'],
|
|
46
|
+
http: ['http.'],
|
|
47
|
+
display: ['display.'],
|
|
48
|
+
preferences: ['preferences.'],
|
|
49
|
+
};
|
|
50
|
+
function opKindsForCategory(category) {
|
|
51
|
+
const prefixes = CATEGORY_PREFIXES[category] ?? [];
|
|
52
|
+
const all = [...HAL_OPERATION_KINDS, ...DISPLAY_OPERATION_KINDS];
|
|
53
|
+
return all.filter((k) => prefixes.some((p) => k.startsWith(p)));
|
|
54
|
+
}
|
|
55
|
+
// Minimal valid payloads for op kinds whose resolver requires more than just
|
|
56
|
+
// the operation discriminator. The default probe (just `{ operation: kind }`)
|
|
57
|
+
// works for most ops; this map fills in the smallest payload that lets the
|
|
58
|
+
// resolver run without throwing on missing-arg validation.
|
|
59
|
+
//
|
|
60
|
+
// Templates must be the MINIMAL payload the resolver needs — enough to prove
|
|
61
|
+
// the framework handles the op, not to produce semantically correct output.
|
|
62
|
+
// Keep entries scoped to fields the resolver actually destructures.
|
|
63
|
+
const OP_PROBE_PAYLOADS = {
|
|
64
|
+
// i2c.write_bytes requires bus + bytes (resolver iterates bytes).
|
|
65
|
+
'i2c.write_bytes': { bus: 'i2c0', address: 0x50, bytes: [0x00, 0x01] },
|
|
66
|
+
// uart.printf requires format + args (resolver joins args).
|
|
67
|
+
'uart.printf': { port: 'Serial', format: '%d', args: ['x'] },
|
|
68
|
+
// adc.read / adc.read_voltage require pin.
|
|
69
|
+
'adc.read': { pin: 0 },
|
|
70
|
+
'adc.read_voltage': { pin: 0 },
|
|
71
|
+
// dac.write requires pin + value. Use pin 25 (a valid DAC pin on ESP32;
|
|
72
|
+
// pin 0 throws "not a DAC pin" on ESP32 and Arduino maps it to a
|
|
73
|
+
// non-DAC pin too).
|
|
74
|
+
'dac.write': { pin: 25, value: 128 },
|
|
75
|
+
// display.* require coordinates/color (still inconclusive without driver
|
|
76
|
+
// context — included so future driver-aware probes can build on them).
|
|
77
|
+
'display.fill_rect': { x: 0, y: 0, w: 10, h: 10, color: 0xffff },
|
|
78
|
+
'display.draw_text': { x: 0, y: 0, text: 'x', font: '8x16', color: 0xffff },
|
|
79
|
+
'display.draw_rect': { x: 0, y: 0, w: 10, h: 10, color: 0xffff },
|
|
80
|
+
'display.flush': { rects: [{ x: 0, y: 0, w: 10, h: 10 }] },
|
|
81
|
+
};
|
|
82
|
+
// Builds a HALOpIR probe. Uses OP_PROBE_PAYLOADS when available so resolvers
|
|
83
|
+
// that destructure required fields don't throw on the probe itself.
|
|
84
|
+
function buildHalProbe(opKind) {
|
|
85
|
+
const payload = OP_PROBE_PAYLOADS[opKind];
|
|
86
|
+
return { operation: opKind, ...(payload ?? {}) };
|
|
87
|
+
}
|
|
88
|
+
function buildDisplayProbe(opKind) {
|
|
89
|
+
const payload = OP_PROBE_PAYLOADS[opKind];
|
|
90
|
+
return { operation: opKind, ...(payload ?? {}) };
|
|
91
|
+
}
|
|
92
|
+
function probeResolve(strategy, opKind) {
|
|
93
|
+
const isDisplay = opKind.startsWith('display.');
|
|
94
|
+
try {
|
|
95
|
+
const op = isDisplay ? buildDisplayProbe(opKind) : buildHalProbe(opKind);
|
|
96
|
+
const result = isDisplay
|
|
97
|
+
? strategy.resolveDisplayOp?.(op)
|
|
98
|
+
: strategy.resolveHALOperation?.(op);
|
|
99
|
+
if (!result)
|
|
100
|
+
return 'undefined';
|
|
101
|
+
if (result.code !== undefined)
|
|
102
|
+
return 'code';
|
|
103
|
+
if (result.expression !== undefined)
|
|
104
|
+
return 'expression';
|
|
105
|
+
return 'undefined';
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return 'thrown';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function statusFromResolution(res) {
|
|
112
|
+
return res === 'undefined' || res === 'thrown' ? 'no-emit' : 'lowers';
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Category A — identity
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
function validateIdentity(manifest, ctx, acc) {
|
|
118
|
+
const strategyId = ctx.strategy.id;
|
|
119
|
+
const matchesDirect = strategyId === manifest.frameworkId;
|
|
120
|
+
const matchesInherited = manifest.inheritsStrategyId !== undefined &&
|
|
121
|
+
manifest.inheritsStrategyId === strategyId;
|
|
122
|
+
if (!matchesDirect && !matchesInherited) {
|
|
123
|
+
acc.error('identity/id-mismatch', 'frameworkId', `manifest.frameworkId is "${manifest.frameworkId}" but strategy.id is "${strategyId}". ` +
|
|
124
|
+
`Set strategy.id to match, or add "inheritsStrategyId: "${strategyId}"" to the manifest ` +
|
|
125
|
+
`to document intentional id reuse.`, manifest.frameworkId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Category B — entrypoint
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
function validateEntrypoint(manifest, ctx, acc) {
|
|
132
|
+
const ep = manifest.entrypoint;
|
|
133
|
+
const strat = ctx.strategy;
|
|
134
|
+
const checks = [
|
|
135
|
+
['entrypointFunctionName', ep.entrypointFunctionName, strat.entrypointFunctionName()],
|
|
136
|
+
['requiresLoopFunction', ep.requiresLoopFunction, strat.requiresLoopFunction()],
|
|
137
|
+
['sourceExtension', ep.sourceExtension, strat.sourceExtension(true, false)],
|
|
138
|
+
['generateHeaderFile', ep.generateHeaderFile, strat.generateHeaderFile()],
|
|
139
|
+
];
|
|
140
|
+
for (const [field, declared, actual] of checks) {
|
|
141
|
+
if (declared !== actual) {
|
|
142
|
+
acc.error(`entrypoint/${field}/mismatch`, `entrypoint.${field}`, `manifest declares ${field}=${JSON.stringify(declared)} but strategy returns ${JSON.stringify(actual)}.`, field);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Category C — HAL coverage
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
function validateHalCoverage(manifest, ctx, acc) {
|
|
150
|
+
for (const [category, declarationRaw] of Object.entries(manifest.hal)) {
|
|
151
|
+
if (category === 'raw')
|
|
152
|
+
continue;
|
|
153
|
+
const declaration = declarationRaw;
|
|
154
|
+
const knownKinds = opKindsForCategory(category);
|
|
155
|
+
// Check that every known op kind for this category appears in ops.
|
|
156
|
+
for (const kind of knownKinds) {
|
|
157
|
+
if (!(kind in declaration.ops)) {
|
|
158
|
+
acc.error(`hal/${category}/op/${kind}/undeclared`, `hal.${category}.ops.${kind}`, `manifest.hal.${category}.ops is missing "${kind}". Add it with a status of supported/stub/unsupported.`, kind);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Probe each declared op and cross-check status vs resolver behavior.
|
|
162
|
+
for (const [kind, status] of Object.entries(declaration.ops)) {
|
|
163
|
+
// 'probe-inconclusive' is an explicit acknowledgment that the minimal
|
|
164
|
+
// probe (just an operation discriminator) cannot determine support —
|
|
165
|
+
// typically because the resolver needs a valid pin/config payload.
|
|
166
|
+
// The validator does not cross-check these; the renderer flags them
|
|
167
|
+
// for manual review. This keeps the matrix honest without penalizing
|
|
168
|
+
// frameworks for the probe's limitations.
|
|
169
|
+
if (status === 'probe-inconclusive')
|
|
170
|
+
continue;
|
|
171
|
+
// 'polyfill' means the op is backed by a runtime polyfill, NOT by
|
|
172
|
+
// resolveHALOperation. Verify the named polyfill exists in
|
|
173
|
+
// POLYFILL_BACKED_OPS and is declared in polyfills.emitted.
|
|
174
|
+
// No HAL probe — the resolver legitimately returns undefined for these.
|
|
175
|
+
if (status === 'polyfill') {
|
|
176
|
+
const expectedPolyfill = POLYFILL_BACKED_OPS[kind];
|
|
177
|
+
if (!expectedPolyfill) {
|
|
178
|
+
acc.error(`hal/${category}/op/${kind}/polyfill-not-recognized`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "polyfill" but is not in POLYFILL_BACKED_OPS. Either add it to packages/cuttlefish/src/api/shared/framework-manifest.ts or use a different status.`, kind);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const emitted = manifest.polyfills.emitted.map((p) => p.id);
|
|
182
|
+
if (!emitted.includes(expectedPolyfill)) {
|
|
183
|
+
acc.error(`hal/${category}/op/${kind}/polyfill-not-declared`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "polyfill" (backed by "${expectedPolyfill}") but "${expectedPolyfill}" is not in polyfills.emitted. Add it, or remove this op from the polyfill status.`, kind);
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const res = probeResolve(ctx.strategy, kind);
|
|
188
|
+
const lowers = statusFromResolution(res) === 'lowers';
|
|
189
|
+
if (status === 'supported' && !lowers) {
|
|
190
|
+
acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "supported" but resolver returned ${res}. Either implement the lowering or change the op status.`, kind);
|
|
191
|
+
}
|
|
192
|
+
else if (status === 'stub' && !lowers) {
|
|
193
|
+
acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "stub" but resolver emitted nothing (${res}). Either emit something or change to "unsupported".`, kind);
|
|
194
|
+
}
|
|
195
|
+
else if (status === 'unsupported' && lowers) {
|
|
196
|
+
acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "unsupported" but resolver actually lowers (${res}). Either mark it supported or remove the lowering.`, kind);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Category-level summary check.
|
|
200
|
+
if (declaration.supported) {
|
|
201
|
+
// If every op is probe-inconclusive, we can't verify the supported
|
|
202
|
+
// claim at all — skip rather than false-positive.
|
|
203
|
+
const hasVerifiable = knownKinds.some((k) => declaration.ops[k] !== 'probe-inconclusive');
|
|
204
|
+
if (hasVerifiable) {
|
|
205
|
+
const anyLowersOrInconclusive = knownKinds.some((k) => {
|
|
206
|
+
if (declaration.ops[k] === 'probe-inconclusive')
|
|
207
|
+
return true;
|
|
208
|
+
if (declaration.ops[k] === 'polyfill')
|
|
209
|
+
return true;
|
|
210
|
+
return statusFromResolution(probeResolve(ctx.strategy, k)) === 'lowers';
|
|
211
|
+
});
|
|
212
|
+
if (!anyLowersOrInconclusive) {
|
|
213
|
+
acc.error(`hal/${category}/declared-supported-but-undefined`, `hal.${category}`, `manifest.hal.${category}.supported is true but resolver returns undefined for every verifiable op kind. Change supported to false with unsupportedReason, or implement the lowering.`, category);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// supported: false. Ignore probe-inconclusive and polyfill ops when
|
|
219
|
+
// checking "does it actually lower?" — polyfill ops legitimately
|
|
220
|
+
// return undefined from the HAL resolver.
|
|
221
|
+
const anyLowers = knownKinds.some((k) => {
|
|
222
|
+
if (declaration.ops[k] === 'probe-inconclusive')
|
|
223
|
+
return false;
|
|
224
|
+
if (declaration.ops[k] === 'polyfill')
|
|
225
|
+
return false;
|
|
226
|
+
return statusFromResolution(probeResolve(ctx.strategy, k)) === 'lowers';
|
|
227
|
+
});
|
|
228
|
+
if (anyLowers) {
|
|
229
|
+
acc.error(`hal/${category}/declared-unsupported-but-actually-lowers`, `hal.${category}`, `manifest.hal.${category}.supported is false but resolver lowers at least one verifiable op. Either mark supported: true or override the resolver to throw/return undefined.`, category);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Category D — polyfills
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
function validatePolyfills(manifest, ctx, acc) {
|
|
238
|
+
const strat = ctx.strategy;
|
|
239
|
+
// A polyfill is "produced" if either generateNativePolyfills OR
|
|
240
|
+
// nativePolyfills mentions it. generateNativePolyfills filters by program
|
|
241
|
+
// analysis (may omit polyfills not needed for the empty probe program);
|
|
242
|
+
// nativePolyfills is the unconditional set the strategy handles natively.
|
|
243
|
+
// Unioning both gives the true picture of what the strategy claims to emit.
|
|
244
|
+
const produced = new Set();
|
|
245
|
+
try {
|
|
246
|
+
const irs = strat.generateNativePolyfills?.({ kind: 'program', modules: [], classes: [], functions: [] }, undefined) ?? [];
|
|
247
|
+
for (const ir of irs)
|
|
248
|
+
produced.add(ir.id);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// generateNativePolyfills may throw on synthetic input; that's fine.
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const ids = strat.nativePolyfills?.() ?? new Set();
|
|
255
|
+
for (const id of ids)
|
|
256
|
+
produced.add(id);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// nativePolyfills is optional.
|
|
260
|
+
}
|
|
261
|
+
for (const declared of manifest.polyfills.emitted) {
|
|
262
|
+
if (!produced.has(declared.id)) {
|
|
263
|
+
acc.error(`polyfill/${declared.id}/declared-but-not-emitted`, `polyfills.emitted.${declared.id}`, `polyfill "${declared.id}" declared emitted but generateNativePolyfills/nativePolyfills did not produce it.`, declared.id);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (const suppressed of manifest.polyfills.suppressed) {
|
|
267
|
+
if (produced.has(suppressed.id)) {
|
|
268
|
+
acc.error(`polyfill/${suppressed.id}/declared-suppressed-but-emitted`, `polyfills.suppressed.${suppressed.id}`, `polyfill "${suppressed.id}" declared suppressed but the strategy actually emits it.`, suppressed.id);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Category E — toolchain
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
function validateToolchain(manifest, ctx, acc) {
|
|
276
|
+
const ops = manifest.toolchain.operations;
|
|
277
|
+
if (!ops.compile) {
|
|
278
|
+
acc.error('toolchain/compile/required', 'toolchain.operations.compile', 'toolchain.operations.compile must be true (LoadedFramework contract requires a compile implementation).', 'compile');
|
|
279
|
+
}
|
|
280
|
+
const tc = ctx.toolchain;
|
|
281
|
+
if (tc) {
|
|
282
|
+
const checks = [
|
|
283
|
+
['prepare', ops.prepare],
|
|
284
|
+
['compile', ops.compile],
|
|
285
|
+
['upload', ops.upload],
|
|
286
|
+
['monitor', ops.monitor],
|
|
287
|
+
];
|
|
288
|
+
for (const [op, declared] of checks) {
|
|
289
|
+
if (declared && typeof tc[op] !== 'function') {
|
|
290
|
+
acc.error(`toolchain/${op}/declared-but-missing`, `toolchain.operations.${op}`, `operation "${op}" declared true but is not a function on the loaded Toolchain.`, op);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
// Category F — library resolution
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
function validateLibraryResolution(manifest, ctx, acc) {
|
|
299
|
+
if (!manifest.libraryResolution)
|
|
300
|
+
return;
|
|
301
|
+
const lr = manifest.libraryResolution;
|
|
302
|
+
const checks = [
|
|
303
|
+
['isFrameworkLibraryImport', lr.isFrameworkLibraryImport],
|
|
304
|
+
['getFrameworkLibraryHeaderName', lr.getFrameworkLibraryHeaderName],
|
|
305
|
+
['buildClassNameMap', lr.buildClassNameMap],
|
|
306
|
+
['tryGenerateLibDecl', lr.tryGenerateLibDecl],
|
|
307
|
+
];
|
|
308
|
+
for (const [name, declared] of checks) {
|
|
309
|
+
if (declared && typeof ctx.moduleExports[name] !== 'function') {
|
|
310
|
+
acc.error(`library-resolution/${name}/declared-but-not-exported`, `libraryResolution.${name}`, `"${name}" declared true but not exported from the framework package index.`, name);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
// Category G — type emission
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
function validateTypeEmission(manifest, ctx, acc) {
|
|
318
|
+
const te = manifest.typeEmission;
|
|
319
|
+
const strat = ctx.strategy;
|
|
320
|
+
const fieldChecks = [
|
|
321
|
+
['mathHeader', te.mathHeader, strat.mathHeader()],
|
|
322
|
+
['needsStdString', te.needsStdString, strat.needsStdString()],
|
|
323
|
+
['needsStdVector', te.needsStdVector, strat.needsStdVector()],
|
|
324
|
+
['needsIostream', te.needsIostream, strat.needsIostream()],
|
|
325
|
+
['needsStdFunction', te.needsStdFunction, strat.needsStdFunction()],
|
|
326
|
+
];
|
|
327
|
+
for (const [field, declared, actual] of fieldChecks) {
|
|
328
|
+
if (declared !== actual) {
|
|
329
|
+
acc.error(`type-emission/${field}/mismatch`, `typeEmission.${field}`, `manifest declares ${field}=${JSON.stringify(declared)} but strategy returns ${JSON.stringify(actual)}.`, field);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const actualStdlib = strat.getStdLibSupport();
|
|
333
|
+
const declaredStdlib = te.stdlibSupport;
|
|
334
|
+
const stdlibEqual = actualStdlib.hasVector === declaredStdlib.hasVector &&
|
|
335
|
+
actualStdlib.hasString === declaredStdlib.hasString &&
|
|
336
|
+
actualStdlib.hasIostream === declaredStdlib.hasIostream &&
|
|
337
|
+
actualStdlib.hasExceptions === declaredStdlib.hasExceptions &&
|
|
338
|
+
actualStdlib.hasRTTI === declaredStdlib.hasRTTI &&
|
|
339
|
+
actualStdlib.recommendedArrayImpl === declaredStdlib.recommendedArrayImpl &&
|
|
340
|
+
actualStdlib.recommendedStringImpl === declaredStdlib.recommendedStringImpl;
|
|
341
|
+
if (!stdlibEqual) {
|
|
342
|
+
acc.error('type-emission/stdlib-support-mismatch', 'typeEmission.stdlibSupport', `manifest stdlibSupport does not match strategy.getStdLibSupport() return.`);
|
|
343
|
+
}
|
|
344
|
+
// Ambient types
|
|
345
|
+
const emittedAmbient = new Set();
|
|
346
|
+
try {
|
|
347
|
+
const decls = strat.ambientTypeDeclarations?.() ?? [];
|
|
348
|
+
for (const d of decls) {
|
|
349
|
+
if (typeof d === 'string') {
|
|
350
|
+
// Match all ambient declarations in the string: interface/type/class NAME
|
|
351
|
+
// and const NAME: (Arduino/AVR/ESP32 emit `const Timing: {...}` etc.).
|
|
352
|
+
const matches = [...d.matchAll(/\b(?:interface|type|class)\s+([A-Za-z_$][\w$]*)/g)];
|
|
353
|
+
for (const m of matches)
|
|
354
|
+
emittedAmbient.add(m[1]);
|
|
355
|
+
const constMatches = [...d.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*:/g)];
|
|
356
|
+
for (const m of constMatches)
|
|
357
|
+
emittedAmbient.add(m[1]);
|
|
358
|
+
}
|
|
359
|
+
else if (d && typeof d === 'object' && 'name' in d) {
|
|
360
|
+
emittedAmbient.add(String(d.name));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// ambientTypeDeclarations is optional and may require a context.
|
|
366
|
+
}
|
|
367
|
+
for (const declared of manifest.ambientTypes) {
|
|
368
|
+
if (!emittedAmbient.has(declared)) {
|
|
369
|
+
acc.error(`ambient-types/${declared}/declared-but-not-emitted`, `ambientTypes.${declared}`, `ambient type "${declared}" declared but not found in ambientTypeDeclarations() output.`, declared);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
for (const emitted of emittedAmbient) {
|
|
373
|
+
if (!manifest.ambientTypes.includes(emitted)) {
|
|
374
|
+
acc.warning(`ambient-types/${emitted}/emitted-but-undeclared`, 'ambientTypes', `ambient type "${emitted}" emitted by strategy but not listed in manifest. Consider adding it.`, emitted);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
// Category H — conformance
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
function validateConformance(manifest, ctx, acc) {
|
|
382
|
+
for (const group of manifest.conformance.hardwareTestGroups) {
|
|
383
|
+
const filePath = path.join(ctx.packageRoot, 'tests', `${group}.test.ts`);
|
|
384
|
+
if (!fs.existsSync(filePath)) {
|
|
385
|
+
acc.error(`conformance/hardware/${group}/file-not-found`, `conformance.hardwareTestGroups.${group}`, `hardware test group "${group}" listed but ${filePath} does not exist.`, group);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
for (const name of manifest.conformance.halResolutionTests) {
|
|
389
|
+
// Test dir convention is tests/packages/<package-dir-name>/hal-resolution/.
|
|
390
|
+
// Package dir name is the last segment of packageName (e.g.
|
|
391
|
+
// "@typecad/framework-esp32" -> "framework-esp32"), not frameworkId.
|
|
392
|
+
const packageDirName = manifest.packageName.split('/').pop() ?? manifest.frameworkId;
|
|
393
|
+
const filePath = path.join(ctx.repoTestsDir, 'packages', packageDirName, 'hal-resolution', `${name}.test.ts`);
|
|
394
|
+
if (!fs.existsSync(filePath)) {
|
|
395
|
+
acc.error(`conformance/hal/${name}/file-not-found`, `conformance.halResolutionTests.${name}`, `HAL resolution test "${name}" listed but ${filePath} does not exist.`, name);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
// Entry point
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
export function validateFrameworkManifest(manifest, ctx) {
|
|
403
|
+
const acc = new Accumulator();
|
|
404
|
+
validateIdentity(manifest, ctx, acc);
|
|
405
|
+
validateEntrypoint(manifest, ctx, acc);
|
|
406
|
+
validateHalCoverage(manifest, ctx, acc);
|
|
407
|
+
validatePolyfills(manifest, ctx, acc);
|
|
408
|
+
validateToolchain(manifest, ctx, acc);
|
|
409
|
+
validateLibraryResolution(manifest, ctx, acc);
|
|
410
|
+
validateTypeEmission(manifest, ctx, acc);
|
|
411
|
+
validateConformance(manifest, ctx, acc);
|
|
412
|
+
return {
|
|
413
|
+
valid: acc.errors.length === 0,
|
|
414
|
+
errors: acc.errors,
|
|
415
|
+
warnings: acc.warnings,
|
|
416
|
+
};
|
|
417
|
+
}
|
package/dist/cli-utils.d.ts
CHANGED
package/dist/cli-utils.js
CHANGED
|
@@ -50,6 +50,7 @@ export function printDiagnostics(diagnostics) {
|
|
|
50
50
|
for (const diagnostic of diagnostics) {
|
|
51
51
|
const position = diagnostic.line && diagnostic.column ? chalk.gray(`(${diagnostic.line},${diagnostic.column})`) : "";
|
|
52
52
|
const code = diagnostic.code ? chalk.gray(` [${diagnostic.code}]`) : "";
|
|
53
|
+
const file = 'filePath' in diagnostic && diagnostic.filePath ? chalk.cyan(diagnostic.filePath) : "";
|
|
53
54
|
let severityLabel;
|
|
54
55
|
let hintColor;
|
|
55
56
|
if (diagnostic.severity === "error") {
|
|
@@ -65,7 +66,8 @@ export function printDiagnostics(diagnostics) {
|
|
|
65
66
|
hintColor = chalk.cyan;
|
|
66
67
|
}
|
|
67
68
|
const location = position ? ` ${position}` : "";
|
|
68
|
-
const
|
|
69
|
+
const filePrefix = file ? `${file} ` : "";
|
|
70
|
+
const header = `${filePrefix}${severityLabel}${code}${location}: ${chalk.white(diagnostic.message)}`;
|
|
69
71
|
if (diagnostic.severity === "error") {
|
|
70
72
|
console.error(header);
|
|
71
73
|
}
|