Skip to content

Commit fdcfbfd

Browse files
committed
Test coverage and dead code removal
1 parent a298225 commit fdcfbfd

11 files changed

Lines changed: 624 additions & 501 deletions

File tree

.changeset/thirty-buttons-stay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stackables/bridge-compiler": patch
3+
---
4+
5+
Republish broken package

packages/bridge-compiler/package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
"build"
1313
],
1414
"scripts": {
15-
"disabled___build": "tsc -p tsconfig.build.json",
16-
"disabled___lint:types": "tsc -p tsconfig.json",
17-
"disabled___test": "node --experimental-transform-types --test test/*.test.ts",
18-
"disabled___fuzz": "node --experimental-transform-types --test test/*.fuzz.ts",
19-
"disabled___prepack": "pnpm build"
15+
"build": "tsc -p tsconfig.build.json",
16+
"lint:types": "tsc -p tsconfig.json",
17+
"test": "node --experimental-transform-types --test test/*.test.ts",
18+
"test:coverage": "node --experimental-test-coverage --test-coverage-exclude=\"test/**\" --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=lcov.info --experimental-transform-types --test test/*.test.ts",
19+
"fuzz": "node --experimental-transform-types --test test/*.fuzz.ts",
20+
"prepack": "pnpm build"
2021
},
2122
"dependencies": {
2223
"@stackables/bridge-core": "workspace:*",

packages/bridge-compiler/src/codegen.ts

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,24 @@ function jsStr(s: string): string {
7171
return "'" + s.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'";
7272
}
7373

74+
/**
75+
* Build a tool function lookup expression.
76+
* For dotted names like "vendor.sub.api", generates both a nested optional
77+
* chain (`tools?.["vendor"]?.["sub"]?.["api"]`) and a flat-key fallback
78+
* (`tools?.["vendor.sub.api"]`) so that either tools shape is accepted.
79+
* Single-segment names produce a plain bracket access.
80+
*/
81+
function buildToolLookupExpr(fnName: string): string {
82+
const segments = fnName.split(".");
83+
if (segments.length <= 1) return `tools[${jsStr(fnName)}]`;
84+
// Use double-quoted bracket notation: tools?.["a"]?.["b"]
85+
const dqSeg = (s: string) =>
86+
`["${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`;
87+
const nested = `tools?.${segments.map(dqSeg).join("?.")}`;
88+
const flat = `tools?.${dqSeg(fnName)}`;
89+
return `(${nested} ?? ${flat})`;
90+
}
91+
7492
/** Emit a JS object literal for a SourceLocation. */
7593
function jsLoc(loc: {
7694
startLine: number;
@@ -437,6 +455,13 @@ class CodegenContext {
437455
this.emit("let __output = {};");
438456
this.emit("");
439457

458+
// If the bridge body has no output wires (recursively), emit a runtime throw.
459+
if (!this.hasAnyOutputWires(this.bridge.body)) {
460+
this.emit(
461+
`throw new Error("Bridge '${this.bridge.type}.${this.bridge.field}' has no output wires.");`,
462+
);
463+
}
464+
440465
// Compile the bridge body
441466
this.compileBody(this.bridge.body, rootScope, "__output");
442467

@@ -938,7 +963,7 @@ class CodegenContext {
938963
// Emit tool function lookup — resolve fn through ToolDef extends chain
939964
const toolDef = this.resolveToolDef(h.name);
940965
const fnName = toolDef?.fn ?? h.name;
941-
this.emit(`const ${toolFnVar} = tools[${jsStr(fnName)}];`);
966+
this.emit(`const ${toolFnVar} = ${buildToolLookupExpr(fnName)};`);
942967
break;
943968
}
944969
case "define":
@@ -1141,7 +1166,7 @@ class CodegenContext {
11411166
// Resolve fn through ToolDef extends chain
11421167
const innerToolDef = this.resolveToolDef(h.name);
11431168
const fnName = innerToolDef?.fn ?? h.name;
1144-
this.emit(`const ${toolFnVar} = tools[${jsStr(fnName)}];`);
1169+
this.emit(`const ${toolFnVar} = ${buildToolLookupExpr(fnName)};`);
11451170
break;
11461171
}
11471172
case "define":
@@ -2085,6 +2110,26 @@ class CodegenContext {
20852110
return undefined;
20862111
}
20872112

2113+
/** Recursively check if any output wire or spread exists in the body tree. */
2114+
private hasAnyOutputWires(body: Statement[]): boolean {
2115+
for (const stmt of body) {
2116+
if (stmt.kind === "wire") {
2117+
if (
2118+
stmt.target.module === SELF_MODULE &&
2119+
stmt.target.type === this.bridge.type &&
2120+
stmt.target.field === this.bridge.field
2121+
) {
2122+
return true;
2123+
}
2124+
} else if (stmt.kind === "spread") {
2125+
return true;
2126+
} else if (stmt.kind === "scope") {
2127+
if (this.hasAnyOutputWires(stmt.body)) return true;
2128+
}
2129+
}
2130+
return false;
2131+
}
2132+
20882133
/**
20892134
* Group output-targeting wire statements by their target path key.
20902135
* Only groups wires that target the bridge's own output (skips tool input wires).
@@ -2219,9 +2264,10 @@ class CodegenContext {
22192264
}
22202265

22212266
/**
2222-
* Emit a runtime-sorted overdefinition block for wires where all static
2223-
* costs are equal. Uses tool metadata (`bridge.cost`, `bridge.sync`) to
2224-
* determine cost at runtime and sort the evaluation order.
2267+
* Throw a compile-time error for overdefined wires where all static costs
2268+
* are equal and greater than zero (i.e. all sources are tool-backed with
2269+
* the same priority). The AOT compiler cannot statically determine which
2270+
* tool should win, so this configuration is rejected as incompatible.
22252271
*/
22262272
private compileRuntimeSortedOverdef(
22272273
ranked: { wire: WireStatement; index: number; cost: number }[],
@@ -2294,7 +2340,7 @@ class CodegenContext {
22942340
}
22952341

22962342
/**
2297-
* Compute a JS expression that evaluates to the runtime cost of an expression.
2343+
* Compute a runtime cost expression for a source expression.
22982344
* For tool refs, checks `tools[name].bridge?.cost ?? (tools[name].bridge?.sync ? 1 : 2)`.
22992345
* For non-tool expressions, returns a literal number.
23002346
*/
@@ -2498,6 +2544,13 @@ class CodegenContext {
24982544

24992545
// ── Source chain compilation ──────────────────────────────────────────
25002546

2547+
/**
2548+
* Throw BridgeCompilerIncompatibleError if any source in the chain uses a
2549+
* falsy gate (||) with a tool-backed ref as the fallback. Tool-backed
2550+
* falsy fallbacks are unsupported because they may trigger the secondary
2551+
* tool call even for valid falsy values (0, "", false). Use ?? (nullish)
2552+
* or split into overdefined wires instead.
2553+
*/
25012554
private compileSourceChain(
25022555
sources: WireSourceEntry[],
25032556
wireCatch: WireCatch | undefined,

0 commit comments

Comments
 (0)