@barefootjs/jsx 0.31.9 → 0.31.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +20 -0
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.js +99 -9
- package/dist/ssr-defaults.d.ts +10 -0
- package/dist/ssr-defaults.d.ts.map +1 -1
- package/dist/ssr-seed-plan.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/date-lowering.test.ts +1 -1
- package/src/__tests__/props-destructuring.test.ts +40 -3
- package/src/__tests__/ssr-defaults.test.ts +230 -0
- package/src/__tests__/ssr-seed-plan.test.ts +72 -0
- package/src/analyzer.ts +83 -26
- package/src/expression-parser.ts +10 -1
- package/src/ssr-defaults.ts +171 -6
- package/src/ssr-seed-plan.ts +82 -2
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;AAG3B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAInE,OAAO,EACL,KAAK,eAAe,EAUrB,MAAM,uBAAuB,CAAA;AAqB9B;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAU/D;AA8BD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf;IAAE,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC;IAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAA;CAAE,GAAG,IAAI,CA8CpF;AAMD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,EACpB,mBAAmB,CAAC,EAAE,oBAAoB,EAC1C,eAAe,CAAC,EAAE,OAAO,GACxB,eAAe,CAkKjB;AAuhBD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAavE;AAuuCD,MAAM,MAAM,aAAa,GAAG,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,qBAAqB,GAAG,EAAE,CAAC,WAAW,CAAA;AAErF;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC;QACd,SAAS,EAAE,EAAE,CAAC,UAAU,CAAA;QACxB,SAAS,EAAE,aAAa,GAAG,IAAI,CAAA;QAC/B;;;;;WAKG;QACH,mBAAmB,CAAC,EAAE,EAAE,CAAC,UAAU,EAAE,CAAA;KACtC,CAAC,CAAA;IACF,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;IAC9B,kBAAkB,CAAC,EAAE,EAAE,CAAC,UAAU,CAAA;IAClC;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,EAAE,CAAC,iBAAiB,EAAE,CAAA;CAClC;AAuBD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,EAAE,CAAC,KAAK,EACd,aAAa,UAAQ,GACpB,sBAAsB,GAAG,IAAI,CA+J/B;AAsGD;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,GAAG,OAAO,CA6BpE;AA2LD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAuCzE;AAuDD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAelE;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;AAG3B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAInE,OAAO,EACL,KAAK,eAAe,EAUrB,MAAM,uBAAuB,CAAA;AAqB9B;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAU/D;AA8BD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf;IAAE,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC;IAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAA;CAAE,GAAG,IAAI,CA8CpF;AAMD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,EACpB,mBAAmB,CAAC,EAAE,oBAAoB,EAC1C,eAAe,CAAC,EAAE,OAAO,GACxB,eAAe,CAkKjB;AAuhBD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAavE;AAuuCD,MAAM,MAAM,aAAa,GAAG,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,qBAAqB,GAAG,EAAE,CAAC,WAAW,CAAA;AAErF;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC;QACd,SAAS,EAAE,EAAE,CAAC,UAAU,CAAA;QACxB,SAAS,EAAE,aAAa,GAAG,IAAI,CAAA;QAC/B;;;;;WAKG;QACH,mBAAmB,CAAC,EAAE,EAAE,CAAC,UAAU,EAAE,CAAA;KACtC,CAAC,CAAA;IACF,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;IAC9B,kBAAkB,CAAC,EAAE,EAAE,CAAC,UAAU,CAAA;IAClC;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,EAAE,CAAC,iBAAiB,EAAE,CAAA;CAClC;AAuBD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,EAAE,CAAC,KAAK,EACd,aAAa,UAAQ,GACpB,sBAAsB,GAAG,IAAI,CA+J/B;AAsGD;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,GAAG,OAAO,CA6BpE;AA2LD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAuCzE;AAuDD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAelE;AAsxCD,eAAO,MAAM,wBAAwB,aAOnC,CAAA;AAqBF;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,MAAM,EAAE,CA0DV;AAcD,eAAO,MAAM,mBAAmB,aAG9B,CAAA;AA25CF;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CA4EvE;AAiID,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA"}
|
|
@@ -426,6 +426,26 @@ export interface FoldBlockOptions {
|
|
|
426
426
|
*/
|
|
427
427
|
pureCallNames?: ReadonlySet<string>;
|
|
428
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Inline `name → value` everywhere it appears free in `expr` (the let-inline
|
|
431
|
+
* step). Returns `null` if the substitution would capture a free variable of
|
|
432
|
+
* `value` under a nested callback parameter of the same name — that shape is
|
|
433
|
+
* unsound to inline non-hygienically, so the caller refuses with
|
|
434
|
+
* {@link CAPTURE_BLOCK_REASON}. A nested arrow parameter that shadows `name`
|
|
435
|
+
* leaves that inner reference untouched (it is the parameter, not the binding).
|
|
436
|
+
* Mirrors the structural walk of `substituteDestructuredFields`; every
|
|
437
|
+
* `ParsedExpr` kind is handled so a new kind surfaces as a compile error here.
|
|
438
|
+
*
|
|
439
|
+
* Exported for `computeSsrSeedPlan` (`ssr-seed-plan.ts`), which reuses this
|
|
440
|
+
* SAME let-inline step to resolve a signal/memo initializer through
|
|
441
|
+
* component-scope `const` locals sitting between it and the prop it reads
|
|
442
|
+
* (`const mid = props.label; createSignal(mid ?? 'Default')`) — the
|
|
443
|
+
* structural (never string-splicing) analogue of what `foldBlockToExpr`
|
|
444
|
+
* already does for a block-bodied memo's own internal `const`s, and of what
|
|
445
|
+
* the CSR client-JS emitter's `csrSubstitute` does over TS source text for
|
|
446
|
+
* the compiled JS domain (#2685 review).
|
|
447
|
+
*/
|
|
448
|
+
export declare function inlineBinding(expr: ParsedExpr, name: string, value: ParsedExpr): ParsedExpr | null;
|
|
429
449
|
/**
|
|
430
450
|
* Fold a value-producing block body — a {@link ParsedStatement} sequence of
|
|
431
451
|
* `const` bindings, value-producing `if` / early `return`, and a terminal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expression-parser.d.ts","sourceRoot":"","sources":["../src/expression-parser.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"expression-parser.d.ts","sourceRoot":"","sources":["../src/expression-parser.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7849,15 +7849,28 @@ function collectKeysFromMembers(members, ctx) {
|
|
|
7849
7849
|
}
|
|
7850
7850
|
return keys;
|
|
7851
7851
|
}
|
|
7852
|
+
function isResolvableMemberType(info) {
|
|
7853
|
+
switch (info.kind) {
|
|
7854
|
+
case "primitive":
|
|
7855
|
+
return info.primitive === "string" || info.primitive === "number" || info.primitive === "boolean";
|
|
7856
|
+
case "interface":
|
|
7857
|
+
return CATALOGUED_RICH_TYPE_NAMES.has(baseTypeName(info.raw));
|
|
7858
|
+
case "array":
|
|
7859
|
+
return !!info.elementType && isResolvableMemberType(info.elementType);
|
|
7860
|
+
case "object":
|
|
7861
|
+
return info.properties !== undefined && info.properties.every((prop) => isResolvableMemberType(prop.type));
|
|
7862
|
+
default:
|
|
7863
|
+
return false;
|
|
7864
|
+
}
|
|
7865
|
+
}
|
|
7852
7866
|
function collectMemberTypes(typeNode, ctx) {
|
|
7853
|
-
const isResolvablePrimitive = (info) => info.kind === "primitive" && (info.primitive === "string" || info.primitive === "number" || info.primitive === "boolean") || info.kind === "interface" && CATALOGUED_RICH_TYPE_NAMES.has(baseTypeName(info.raw));
|
|
7854
7867
|
const fromMembers = (members) => {
|
|
7855
7868
|
const map = new Map;
|
|
7856
7869
|
for (const member of members) {
|
|
7857
7870
|
if (ts9.isPropertySignature(member) && member.name) {
|
|
7858
7871
|
const info = member.type ? typeNodeToTypeInfo(member.type, ctx.sourceFile) : null;
|
|
7859
7872
|
map.set(member.name.getText(ctx.sourceFile), {
|
|
7860
|
-
type: info &&
|
|
7873
|
+
type: info && isResolvableMemberType(info) ? info : null,
|
|
7861
7874
|
optional: !!member.questionToken
|
|
7862
7875
|
});
|
|
7863
7876
|
}
|
|
@@ -23208,6 +23221,7 @@ function extractSsrDefaults(metadata) {
|
|
|
23208
23221
|
out[metadata.restPropsName] = { isRestProps: true, value: {} };
|
|
23209
23222
|
}
|
|
23210
23223
|
const bindings = {};
|
|
23224
|
+
const localConsts = localConstValuesByName(metadata);
|
|
23211
23225
|
for (const c of metadata.localConstants ?? []) {
|
|
23212
23226
|
if (!c.isModule || c.value === undefined)
|
|
23213
23227
|
continue;
|
|
@@ -23223,14 +23237,26 @@ function extractSsrDefaults(metadata) {
|
|
|
23223
23237
|
if (sig.envReader)
|
|
23224
23238
|
continue;
|
|
23225
23239
|
const value = tryStaticEval(sig.initialValue, { bindings, propsLike });
|
|
23226
|
-
|
|
23240
|
+
if (metadata.propsObjectName !== null && referencesOwnProp(sig.initialValue, metadata.propsObjectName, sig.getter, localConsts)) {
|
|
23241
|
+
if (!(sig.getter in out)) {
|
|
23242
|
+
out[sig.getter] = { propName: sig.getter, value: null };
|
|
23243
|
+
}
|
|
23244
|
+
} else {
|
|
23245
|
+
out[sig.getter] = { value: resultToJsonable(value) };
|
|
23246
|
+
}
|
|
23227
23247
|
bindings[sig.getter] = value;
|
|
23228
23248
|
}
|
|
23229
23249
|
for (const memo of metadata.memos) {
|
|
23230
23250
|
if (memo.isModule)
|
|
23231
23251
|
continue;
|
|
23232
23252
|
const value = tryStaticEval(memo.computation, { bindings, propsLike });
|
|
23233
|
-
|
|
23253
|
+
if (metadata.propsObjectName !== null && referencesOwnProp(memo.computation, metadata.propsObjectName, memo.name, localConsts)) {
|
|
23254
|
+
if (!(memo.name in out)) {
|
|
23255
|
+
out[memo.name] = { propName: memo.name, value: null };
|
|
23256
|
+
}
|
|
23257
|
+
} else {
|
|
23258
|
+
out[memo.name] = { value: resultToJsonable(value) };
|
|
23259
|
+
}
|
|
23234
23260
|
bindings[memo.name] = value;
|
|
23235
23261
|
}
|
|
23236
23262
|
if (metadata.propsObjectName !== null) {
|
|
@@ -23238,12 +23264,12 @@ function extractSsrDefaults(metadata) {
|
|
|
23238
23264
|
for (const sig of metadata.signals) {
|
|
23239
23265
|
if (!sig.getter || sig.isModule || sig.envReader)
|
|
23240
23266
|
continue;
|
|
23241
|
-
|
|
23267
|
+
collectPropRefsTransitive(sig.initialValue, metadata.propsObjectName, localConsts, referenced);
|
|
23242
23268
|
}
|
|
23243
23269
|
for (const memo of metadata.memos) {
|
|
23244
23270
|
if (memo.isModule)
|
|
23245
23271
|
continue;
|
|
23246
|
-
|
|
23272
|
+
collectPropRefsTransitive(memo.computation, metadata.propsObjectName, localConsts, referenced);
|
|
23247
23273
|
}
|
|
23248
23274
|
for (const name of referenced) {
|
|
23249
23275
|
if (name in out)
|
|
@@ -23253,6 +23279,20 @@ function extractSsrDefaults(metadata) {
|
|
|
23253
23279
|
}
|
|
23254
23280
|
return Object.keys(out).length === 0 ? undefined : out;
|
|
23255
23281
|
}
|
|
23282
|
+
function localConstValuesByName(metadata) {
|
|
23283
|
+
const out = new Map;
|
|
23284
|
+
for (const c of metadata.localConstants ?? []) {
|
|
23285
|
+
if (c.isModule || c.value === undefined)
|
|
23286
|
+
continue;
|
|
23287
|
+
out.set(c.name, c.value);
|
|
23288
|
+
}
|
|
23289
|
+
return out;
|
|
23290
|
+
}
|
|
23291
|
+
function referencesOwnProp(expr, propsObjectName, name, localConsts) {
|
|
23292
|
+
const referenced = new Set;
|
|
23293
|
+
collectPropRefsTransitive(expr, propsObjectName, localConsts, referenced);
|
|
23294
|
+
return referenced.has(name);
|
|
23295
|
+
}
|
|
23256
23296
|
function collectPropRefs(expr, propsObjectName, out) {
|
|
23257
23297
|
if (!expr || !expr.trim())
|
|
23258
23298
|
return;
|
|
@@ -23268,6 +23308,23 @@ function collectPropRefs(expr, propsObjectName, out) {
|
|
|
23268
23308
|
};
|
|
23269
23309
|
visit3(node);
|
|
23270
23310
|
}
|
|
23311
|
+
function collectPropRefsTransitive(expr, propsObjectName, localConsts, out, visited = new Set) {
|
|
23312
|
+
if (!expr || !expr.trim())
|
|
23313
|
+
return;
|
|
23314
|
+
collectPropRefs(expr, propsObjectName, out);
|
|
23315
|
+
const node = parseExpression2(expr);
|
|
23316
|
+
if (!node)
|
|
23317
|
+
return;
|
|
23318
|
+
for (const id of extractFreeIdentifiersFromNode(node)) {
|
|
23319
|
+
if (visited.has(id))
|
|
23320
|
+
continue;
|
|
23321
|
+
const constValue = localConsts.get(id);
|
|
23322
|
+
if (constValue === undefined)
|
|
23323
|
+
continue;
|
|
23324
|
+
visited.add(id);
|
|
23325
|
+
collectPropRefsTransitive(constValue, propsObjectName, localConsts, out, visited);
|
|
23326
|
+
}
|
|
23327
|
+
}
|
|
23271
23328
|
function resultToJsonable(v) {
|
|
23272
23329
|
if (v === UNRESOLVED)
|
|
23273
23330
|
return null;
|
|
@@ -23312,7 +23369,7 @@ function evalStatementsForReturn(statements, ctx) {
|
|
|
23312
23369
|
return NO_RETURN;
|
|
23313
23370
|
}
|
|
23314
23371
|
function parseExpression2(expr) {
|
|
23315
|
-
const sf = ts22.createSourceFile("__ssr_default__.ts", `(${expr})`, ts22.ScriptTarget.Latest,
|
|
23372
|
+
const sf = ts22.createSourceFile("__ssr_default__.ts", `(${expr})`, ts22.ScriptTarget.Latest, true, ts22.ScriptKind.TS);
|
|
23316
23373
|
const stmt = sf.statements[0];
|
|
23317
23374
|
if (!stmt || !ts22.isExpressionStatement(stmt))
|
|
23318
23375
|
return null;
|
|
@@ -23943,6 +24000,38 @@ function classify2(name, origin, expr, parsed, available) {
|
|
|
23943
24000
|
}
|
|
23944
24001
|
return { kind: "derived", name, origin, expr, parsed, frees: [...frees] };
|
|
23945
24002
|
}
|
|
24003
|
+
function localConstExprsByName(metadata) {
|
|
24004
|
+
const out = new Map;
|
|
24005
|
+
for (const c of metadata.localConstants ?? []) {
|
|
24006
|
+
if (c.isModule || c.declarationKind !== "const" || c.value === undefined)
|
|
24007
|
+
continue;
|
|
24008
|
+
out.set(c.name, c.parsed ?? parseExpression(c.value.trim()));
|
|
24009
|
+
}
|
|
24010
|
+
return out;
|
|
24011
|
+
}
|
|
24012
|
+
function resolveThroughLocalConsts(parsed, localConsts) {
|
|
24013
|
+
let current = parsed;
|
|
24014
|
+
const maxIter = localConsts.size + 1;
|
|
24015
|
+
for (let i = 0;i < maxIter; i++) {
|
|
24016
|
+
const frees = freeIdentifiers(current);
|
|
24017
|
+
if (frees === null)
|
|
24018
|
+
break;
|
|
24019
|
+
let changed = false;
|
|
24020
|
+
for (const name of frees) {
|
|
24021
|
+
const value = localConsts.get(name);
|
|
24022
|
+
if (!value)
|
|
24023
|
+
continue;
|
|
24024
|
+
const inlined = inlineBinding(current, name, value);
|
|
24025
|
+
if (inlined === null)
|
|
24026
|
+
continue;
|
|
24027
|
+
current = inlined;
|
|
24028
|
+
changed = true;
|
|
24029
|
+
}
|
|
24030
|
+
if (!changed)
|
|
24031
|
+
break;
|
|
24032
|
+
}
|
|
24033
|
+
return current;
|
|
24034
|
+
}
|
|
23946
24035
|
function computeSsrSeedPlan(metadata) {
|
|
23947
24036
|
const baseScope = metadata.propsParams.map((p) => p.name);
|
|
23948
24037
|
if (metadata.propsObjectName)
|
|
@@ -23951,6 +24040,7 @@ function computeSsrSeedPlan(metadata) {
|
|
|
23951
24040
|
baseScope.push(name);
|
|
23952
24041
|
}
|
|
23953
24042
|
const available = new Set(baseScope);
|
|
24043
|
+
const localConsts = localConstExprsByName(metadata);
|
|
23954
24044
|
const steps = [];
|
|
23955
24045
|
for (const signal of metadata.signals) {
|
|
23956
24046
|
if (signal.envReader) {
|
|
@@ -23962,13 +24052,13 @@ function computeSsrSeedPlan(metadata) {
|
|
|
23962
24052
|
}
|
|
23963
24053
|
}
|
|
23964
24054
|
const expr = signal.initialValue.trim();
|
|
23965
|
-
steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify2(signal.getter, "signal", expr, parseExpression(expr), available));
|
|
24055
|
+
steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify2(signal.getter, "signal", expr, resolveThroughLocalConsts(parseExpression(expr), localConsts), available));
|
|
23966
24056
|
available.add(signal.getter);
|
|
23967
24057
|
}
|
|
23968
24058
|
for (const memo of metadata.memos) {
|
|
23969
24059
|
const body = extractArrowBodyExpression(memo.computation);
|
|
23970
24060
|
const expr = body?.trim() ?? "";
|
|
23971
|
-
steps.push(expr === "" ? { kind: "opaque", name: memo.name, origin: "memo" } : classify2(memo.name, "memo", expr, memo.parsed ?? parseExpression(expr), available));
|
|
24061
|
+
steps.push(expr === "" ? { kind: "opaque", name: memo.name, origin: "memo" } : classify2(memo.name, "memo", expr, resolveThroughLocalConsts(memo.parsed ?? parseExpression(expr), localConsts), available));
|
|
23972
24062
|
available.add(memo.name);
|
|
23973
24063
|
}
|
|
23974
24064
|
return { baseScope, steps };
|
package/dist/ssr-defaults.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ export interface SsrDefault {
|
|
|
19
19
|
* (`{ variant = 'default' }`) where the template variable maps 1:1
|
|
20
20
|
* to a caller-supplied prop. Omitted for signal / memo entries that
|
|
21
21
|
* are internal to the component.
|
|
22
|
+
*
|
|
23
|
+
* Invariant (#2669): a signal / memo entry carries `propName` IF AND
|
|
24
|
+
* ONLY IF it's a self-derivation collision — the signal/memo's own
|
|
25
|
+
* template variable shares a name with the prop its initializer
|
|
26
|
+
* derives from (`const [label] = createSignal(props.label ?? 'x')`).
|
|
27
|
+
* Every OTHER signal/memo entry is `propName`-less. Consumers (e.g.
|
|
28
|
+
* every template-stash adapter's conformance harness, `test-render.ts`)
|
|
29
|
+
* rely on this to know a signal/memo's stash seed must defer to the
|
|
30
|
+
* already-derived prop value rather than re-seeding from its own
|
|
31
|
+
* evaluated initial — see `extractSsrDefaults`'s signal/memo loops.
|
|
22
32
|
*/
|
|
23
33
|
propName?: string;
|
|
24
34
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-defaults.d.ts","sourceRoot":"","sources":["../src/ssr-defaults.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ssr-defaults.d.ts","sourceRoot":"","sources":["../src/ssr-defaults.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,KAAK,EAAE,OAAO,CAAA;IACd;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB;AAkBD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,SAAS,CA8K/F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-seed-plan.d.ts","sourceRoot":"","sources":["../src/ssr-seed-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACnF,OAAO,
|
|
1
|
+
{"version":3,"file":"ssr-seed-plan.d.ts","sourceRoot":"","sources":["../src/ssr-seed-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACnF,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/G;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAA;CAAE,CAAA;AAE/D,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,KAAK,EAAE,WAAW,EAAE,CAAA;CACrB;AA4FD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,UAAU,GAAG,WAAW,CAqDpE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jsx",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.10",
|
|
4
4
|
"description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"directory": "packages/jsx"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@barefootjs/shared": "0.31.
|
|
52
|
+
"@barefootjs/shared": "0.31.10"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@barefootjs/client": ">=0.2.0",
|
|
@@ -101,7 +101,7 @@ describe('datePlugin matcher recognition (#2274)', () => {
|
|
|
101
101
|
// destructured path reads the type from propsType, not propsParams, so
|
|
102
102
|
// the widening isn't load-bearing for the matcher above, but propsParams
|
|
103
103
|
// is itself observable IR metadata this plugin's existence is meant to
|
|
104
|
-
// unlock (see analyzer.ts's docstring on `
|
|
104
|
+
// unlock (see analyzer.ts's docstring on `isResolvableMemberType`).
|
|
105
105
|
const md = metadata(`
|
|
106
106
|
export function Foo({ createdAt }: { createdAt: Date }) {
|
|
107
107
|
return <div>{createdAt.toISOString()}</div>
|
|
@@ -426,7 +426,13 @@ describe('Destructured props keep their declared types (BF043 fixture #2150)', (
|
|
|
426
426
|
})
|
|
427
427
|
})
|
|
428
428
|
|
|
429
|
-
|
|
429
|
+
// #2677: a STRUCTURAL member (array/object) built entirely out of
|
|
430
|
+
// primitives resolves fully now — go-template's `emitSynthPropStructs`
|
|
431
|
+
// (#2674/#2676) can synthesize a real struct for either shape, so the
|
|
432
|
+
// #2150 "unchecked assertion" concern doesn't apply to them any more.
|
|
433
|
+
// `rows: number[][]` is a nested array of primitives; `meta: { id:
|
|
434
|
+
// string }` is an inline object literal with a primitive property.
|
|
435
|
+
test('resolves STRUCTURAL members (nested arrays / inline objects) built entirely out of primitives (#2677)', () => {
|
|
430
436
|
const source = `
|
|
431
437
|
type Props = { rows: number[][]; meta: { id: string } }
|
|
432
438
|
|
|
@@ -437,7 +443,38 @@ describe('Destructured props keep their declared types (BF043 fixture #2150)', (
|
|
|
437
443
|
|
|
438
444
|
const ctx = analyzeComponent(source, 'Component.tsx')
|
|
439
445
|
|
|
440
|
-
expect(typeOf(ctx, 'rows')).toEqual({
|
|
441
|
-
|
|
446
|
+
expect(typeOf(ctx, 'rows')).toEqual({
|
|
447
|
+
kind: 'array',
|
|
448
|
+
raw: 'number[][]',
|
|
449
|
+
elementType: { kind: 'array', raw: 'number[]', elementType: { kind: 'primitive', raw: 'number', primitive: 'number' } },
|
|
450
|
+
})
|
|
451
|
+
expect(typeOf(ctx, 'meta')).toEqual({
|
|
452
|
+
kind: 'object',
|
|
453
|
+
raw: '{ id: string }',
|
|
454
|
+
properties: [{ name: 'id', type: { kind: 'primitive', raw: 'string', primitive: 'string' }, optional: false, readonly: false }],
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
// A structural member declines WHOLLY (not per-leaf) when ANY reachable
|
|
459
|
+
// leaf is a union, a function, or an un-catalogued named type — the same
|
|
460
|
+
// three shapes `isResolvableMemberType` declines at the top level.
|
|
461
|
+
test('declines a structural member when a nested leaf is a union/function/un-catalogued named type (#2677)', () => {
|
|
462
|
+
const source = `
|
|
463
|
+
type Props = {
|
|
464
|
+
variants: { id: string; kind: 'a' | 'b' }[]
|
|
465
|
+
handlers: { onClick: () => void }
|
|
466
|
+
configs: { data: Map<string, string> }
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function Component({ variants, handlers, configs }: Props) {
|
|
470
|
+
return <div>{variants.length}</div>
|
|
471
|
+
}
|
|
472
|
+
`
|
|
473
|
+
|
|
474
|
+
const ctx = analyzeComponent(source, 'Component.tsx')
|
|
475
|
+
|
|
476
|
+
expect(typeOf(ctx, 'variants')).toEqual({ kind: 'unknown', raw: 'unknown' })
|
|
477
|
+
expect(typeOf(ctx, 'handlers')).toEqual({ kind: 'unknown', raw: 'unknown' })
|
|
478
|
+
expect(typeOf(ctx, 'configs')).toEqual({ kind: 'unknown', raw: 'unknown' })
|
|
442
479
|
})
|
|
443
480
|
})
|
|
@@ -103,6 +103,211 @@ describe('extractSsrDefaults', () => {
|
|
|
103
103
|
expect(defaults?.n).toEqual({ value: 0 })
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
+
test('self-derived signal collision (#2669, shape A): entry stays a PROP entry, not the evaluated signal value', () => {
|
|
107
|
+
// `props.label` and the signal getter `label` share a name — the
|
|
108
|
+
// bare-props-arg form is the only shape where this collides (a
|
|
109
|
+
// destructured-arg `function C({ label })` + `const [label] = ...`
|
|
110
|
+
// is a JS redeclaration error, so it can't happen there). The
|
|
111
|
+
// emitted template reads the stash var `label` as the RAW caller
|
|
112
|
+
// prop and OVERWRITES it with the derived signal value under that
|
|
113
|
+
// SAME name (`{% set label = (label if label is defined else
|
|
114
|
+
// 'Default') %}`). Seeding the stash with the pre-fix `{ value:
|
|
115
|
+
// 'Default' }` (the EVALUATED signal value, discarding `propName`)
|
|
116
|
+
// means a caller-passed `label` can never win — production seeds
|
|
117
|
+
// 'Default', the template sees a non-none value, and keeps it. The
|
|
118
|
+
// fix: this entry must stay a PROP entry (`propName` set, `value:
|
|
119
|
+
// null`) so the template's own `?? 'Default'` guard supplies the
|
|
120
|
+
// real fallback, and a caller-supplied prop still wins via
|
|
121
|
+
// `propName`.
|
|
122
|
+
const metadata = metadataFor(`
|
|
123
|
+
'use client'
|
|
124
|
+
import { createSignal } from '@barefootjs/client'
|
|
125
|
+
function C(props: { label?: string }) {
|
|
126
|
+
const [label, setLabel] = createSignal(props.label ?? 'Default')
|
|
127
|
+
return <span>{label()}</span>
|
|
128
|
+
}
|
|
129
|
+
`)
|
|
130
|
+
|
|
131
|
+
const defaults = extractSsrDefaults(metadata)
|
|
132
|
+
expect(defaults?.label).toEqual({ propName: 'label', value: null })
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('self-derived signal collision (#2669, shape B): non-idempotent derivation must not seed the pre-fix double-applied value', () => {
|
|
136
|
+
// `(props.count ?? 1) * 2` is NOT idempotent: seeding the stash with
|
|
137
|
+
// the pre-fix EVALUATED value (2, for no caller prop) makes the
|
|
138
|
+
// template's own recompute apply the `* 2` a second time (`2 * 2 =
|
|
139
|
+
// 4`) — wrong even with no caller props at all. Pin that the entry
|
|
140
|
+
// is the RAW-prop shape (`value: null`), not `{ value: 2 }`.
|
|
141
|
+
const metadata = metadataFor(`
|
|
142
|
+
'use client'
|
|
143
|
+
import { createSignal } from '@barefootjs/client'
|
|
144
|
+
function C(props: { count?: number }) {
|
|
145
|
+
const [count, setCount] = createSignal((props.count ?? 1) * 2)
|
|
146
|
+
return <span>{count()}</span>
|
|
147
|
+
}
|
|
148
|
+
`)
|
|
149
|
+
|
|
150
|
+
const defaults = extractSsrDefaults(metadata)
|
|
151
|
+
expect(defaults?.count).toEqual({ propName: 'count', value: null })
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('self-derived memo collision (#2669): the same rule applies to a memo whose computation derives from a same-named prop', () => {
|
|
155
|
+
// Same collision, memo flavor: `createMemo(() => (props.label ??
|
|
156
|
+
// 'Default') + n())` — the memo's OWN name (`label`) is the
|
|
157
|
+
// template variable the emitted template both reads (raw prop) and
|
|
158
|
+
// overwrites (derived memo value). `n` is an unrelated ordinary
|
|
159
|
+
// signal and must be unaffected (plain `{ value }` entry, no
|
|
160
|
+
// `propName`).
|
|
161
|
+
const metadata = metadataFor(`
|
|
162
|
+
'use client'
|
|
163
|
+
import { createSignal, createMemo } from '@barefootjs/client'
|
|
164
|
+
function C(props: { label?: string }) {
|
|
165
|
+
const [n, setN] = createSignal(0)
|
|
166
|
+
const label = createMemo(() => (props.label ?? 'Default') + n())
|
|
167
|
+
return <span>{label()}</span>
|
|
168
|
+
}
|
|
169
|
+
`)
|
|
170
|
+
|
|
171
|
+
const defaults = extractSsrDefaults(metadata)
|
|
172
|
+
expect(defaults?.label).toEqual({ propName: 'label', value: null })
|
|
173
|
+
expect(defaults?.n).toEqual({ value: 0 })
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('NON-self-derived collision (#2669, shape C — out of scope, must be unchanged): signal value still wins, no `propName`', () => {
|
|
177
|
+
// Same-named `label` getter and `label` prop, but the signal's OWN
|
|
178
|
+
// initializer does NOT derive from `props.label` (it's a plain
|
|
179
|
+
// string literal) — the JSX body separately reads `label()` AND
|
|
180
|
+
// `props.label`. That's a template-variable-ALIASING defect (both
|
|
181
|
+
// expressions lower to the same template variable, so no seeding
|
|
182
|
+
// choice can be right for both readers) — a different bug, tracked
|
|
183
|
+
// separately, and this fix must leave it byte-identical: the entry
|
|
184
|
+
// stays the plain evaluated-signal-value shape with no `propName`.
|
|
185
|
+
const metadata = metadataFor(`
|
|
186
|
+
'use client'
|
|
187
|
+
import { createSignal } from '@barefootjs/client'
|
|
188
|
+
function C(props: { label?: string }) {
|
|
189
|
+
const [label, setLabel] = createSignal('sig')
|
|
190
|
+
return <span>{label()}{props.label}</span>
|
|
191
|
+
}
|
|
192
|
+
`)
|
|
193
|
+
|
|
194
|
+
const defaults = extractSsrDefaults(metadata)
|
|
195
|
+
expect(defaults?.label).toEqual({ value: 'sig' })
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('self-derived signal collision THROUGH a component-scope const (#2685 review): entry stays a PROP entry', () => {
|
|
199
|
+
// One hop of pure indirection past #2669's shape A: `referencesOwnProp`
|
|
200
|
+
// (built on `collectPropRefs`) only saw a DIRECT `props.<name>` access
|
|
201
|
+
// in the initializer — a `const mid = props.label` sitting between the
|
|
202
|
+
// prop read and `createSignal(mid ?? 'Default')` defeated the
|
|
203
|
+
// detection, so this entry regressed to the pre-#2669 `{ value:
|
|
204
|
+
// 'Default' }` shape (no `propName`) even after the #2669 fix landed.
|
|
205
|
+
// Same fix, widened: `collectPropRefsTransitive` now looks through
|
|
206
|
+
// component-scope const locals.
|
|
207
|
+
const metadata = metadataFor(`
|
|
208
|
+
'use client'
|
|
209
|
+
import { createSignal } from '@barefootjs/client'
|
|
210
|
+
function C(props: { label?: string }) {
|
|
211
|
+
const mid = props.label
|
|
212
|
+
const [label, setLabel] = createSignal(mid ?? 'Default')
|
|
213
|
+
return <span>{label()}</span>
|
|
214
|
+
}
|
|
215
|
+
`)
|
|
216
|
+
|
|
217
|
+
const defaults = extractSsrDefaults(metadata)
|
|
218
|
+
expect(defaults?.label).toEqual({ propName: 'label', value: null })
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('self-derived memo collision THROUGH a component-scope const (#2685 review)', () => {
|
|
222
|
+
const metadata = metadataFor(`
|
|
223
|
+
'use client'
|
|
224
|
+
import { createMemo } from '@barefootjs/client'
|
|
225
|
+
function C(props: { label?: string }) {
|
|
226
|
+
const mid = props.label
|
|
227
|
+
const label = createMemo(() => mid ?? 'Default')
|
|
228
|
+
return <span>{label()}</span>
|
|
229
|
+
}
|
|
230
|
+
`)
|
|
231
|
+
|
|
232
|
+
const defaults = extractSsrDefaults(metadata)
|
|
233
|
+
expect(defaults?.label).toEqual({ propName: 'label', value: null })
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('multi-hop const chain resolves transitively (#2685 review): `const a = props.x; const b = a`', () => {
|
|
237
|
+
// Two hops of indirection (`b` reads `a`, `a` reads `props.x`) — the
|
|
238
|
+
// signal here is named `count`, a DIFFERENT name from the prop `x`, so
|
|
239
|
+
// this exercises the bare-props safety net's transitive resolution
|
|
240
|
+
// (not the self-derivation `propName` collision path above): `x` must
|
|
241
|
+
// still be seeded, or the template-stash adapters' bare-scalar
|
|
242
|
+
// recompute for `x` aborts at render time (#1297/#2126).
|
|
243
|
+
const metadata = metadataFor(`
|
|
244
|
+
'use client'
|
|
245
|
+
import { createSignal } from '@barefootjs/client'
|
|
246
|
+
function C(props: { x?: number }) {
|
|
247
|
+
const a = props.x
|
|
248
|
+
const b = a
|
|
249
|
+
const [count, setCount] = createSignal(b ?? 1)
|
|
250
|
+
return <span>{count()}</span>
|
|
251
|
+
}
|
|
252
|
+
`)
|
|
253
|
+
|
|
254
|
+
const defaults = extractSsrDefaults(metadata)
|
|
255
|
+
expect(defaults?.count).toEqual({ value: 1 })
|
|
256
|
+
expect(defaults?.x).toEqual({ propName: 'x', value: null })
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test('safety-net seeding sees a prop through a component-scope const (#2685 review)', () => {
|
|
260
|
+
// The #1297/#2126 bare-props safety net (`collectPropRefs` at the
|
|
261
|
+
// bottom of `extractSsrDefaults`) only saw a DIRECT `props.X` access
|
|
262
|
+
// too — this is that same gap, single hop: `const mid = props.initial;
|
|
263
|
+
// createSignal(mid ?? 0)` misses seeding `initial`, which is a
|
|
264
|
+
// `Global symbol "$initial" requires explicit package name` render
|
|
265
|
+
// abort on Perl-strict backends. `count` (the signal's own name) is
|
|
266
|
+
// unrelated to `initial`, so this is NOT the self-collision path —
|
|
267
|
+
// purely the safety net.
|
|
268
|
+
const metadata = metadataFor(`
|
|
269
|
+
'use client'
|
|
270
|
+
import { createSignal } from '@barefootjs/client'
|
|
271
|
+
function Counter(props: { initial?: number }) {
|
|
272
|
+
const mid = props.initial
|
|
273
|
+
const [count, setCount] = createSignal(mid ?? 0)
|
|
274
|
+
return <p>{count()}</p>
|
|
275
|
+
}
|
|
276
|
+
`)
|
|
277
|
+
|
|
278
|
+
const defaults = extractSsrDefaults(metadata)
|
|
279
|
+
expect(defaults?.count).toEqual({ value: 0 })
|
|
280
|
+
expect(defaults?.initial).toEqual({ propName: 'initial', value: null })
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
test('NEGATIVE (#2685 review): a const NOT derived from props keeps the signal-wins behavior', () => {
|
|
284
|
+
// Same-named `label` getter and `label` prop, wrapped in a const — but
|
|
285
|
+
// the const's OWN value doesn't read `props` at all, so
|
|
286
|
+
// `collectPropRefsTransitive` must NOT report a collision here. Must
|
|
287
|
+
// stay the plain evaluated-signal-value shape with no `propName` — the
|
|
288
|
+
// const-chain widening only ever ADDS prop references it finds by
|
|
289
|
+
// actually walking through `props.X`, never invents one.
|
|
290
|
+
//
|
|
291
|
+
// (The value resolves to `null`, not `'sig'`: `tryStaticEval` doesn't
|
|
292
|
+
// bind component-scope const locals into its evaluation `bindings` —
|
|
293
|
+
// see the signal loop's `bindings` comment above — so `mid` itself is
|
|
294
|
+
// unresolved for VALUE purposes independent of this fix; only the
|
|
295
|
+
// separate `propName`-detection walk this test is pinning follows the
|
|
296
|
+
// const chain.)
|
|
297
|
+
const metadata = metadataFor(`
|
|
298
|
+
'use client'
|
|
299
|
+
import { createSignal } from '@barefootjs/client'
|
|
300
|
+
function C(props: { label?: string }) {
|
|
301
|
+
const mid = 'sig'
|
|
302
|
+
const [label, setLabel] = createSignal(mid)
|
|
303
|
+
return <span>{label()}{props.label}</span>
|
|
304
|
+
}
|
|
305
|
+
`)
|
|
306
|
+
|
|
307
|
+
const defaults = extractSsrDefaults(metadata)
|
|
308
|
+
expect(defaults?.label).toEqual({ value: null })
|
|
309
|
+
})
|
|
310
|
+
|
|
106
311
|
test('aliased destructured prop (#2460): `propName` is the CALLER-facing key, not the local binding', () => {
|
|
107
312
|
// `{ n: count }` — the caller passes `n`, the template variable (and
|
|
108
313
|
// stash entry key) is the local binding `count`. The manifest
|
|
@@ -302,6 +507,31 @@ describe('deriveStashFromDefaults', () => {
|
|
|
302
507
|
expect(deriveStashFromDefaults(defaults, { n: 0 })).toEqual({ count: 0 })
|
|
303
508
|
})
|
|
304
509
|
|
|
510
|
+
test('self-derived signal collision (#2669): the caller-supplied prop now wins, and an absent prop falls back to null', () => {
|
|
511
|
+
// End-to-end proof of the fix's effect at the `deriveStashFromDefaults`
|
|
512
|
+
// layer: run the real `extractSsrDefaults` output for shape A through
|
|
513
|
+
// the seeding function. Pre-fix, this entry was `{ value: 'Default' }`
|
|
514
|
+
// (propName-less) so a caller's `label: 'Hello'` was IGNORED —
|
|
515
|
+
// `deriveStashFromDefaults` had no `propName` to resolve against and
|
|
516
|
+
// always used the static value. Post-fix the entry is a PROP entry, so
|
|
517
|
+
// the caller's value wins, and an absent prop resolves to the RAW
|
|
518
|
+
// fallback `null` (not the old evaluated `'Default'`) — the emitted
|
|
519
|
+
// template's own `?? 'Default'` guard supplies the real default from
|
|
520
|
+
// there.
|
|
521
|
+
const metadata = metadataFor(`
|
|
522
|
+
'use client'
|
|
523
|
+
import { createSignal } from '@barefootjs/client'
|
|
524
|
+
function C(props: { label?: string }) {
|
|
525
|
+
const [label, setLabel] = createSignal(props.label ?? 'Default')
|
|
526
|
+
return <span>{label()}</span>
|
|
527
|
+
}
|
|
528
|
+
`)
|
|
529
|
+
const defaults = extractSsrDefaults(metadata)!
|
|
530
|
+
|
|
531
|
+
expect(deriveStashFromDefaults(defaults, { label: 'Hello' })).toEqual({ label: 'Hello' })
|
|
532
|
+
expect(deriveStashFromDefaults(defaults, {})).toEqual({ label: null })
|
|
533
|
+
})
|
|
534
|
+
|
|
305
535
|
test('propName-less entry (signal / memo local): always uses the static value', () => {
|
|
306
536
|
// A caller cannot override an internal signal/memo by construction —
|
|
307
537
|
// even a same-named caller prop must not leak in.
|
|
@@ -209,4 +209,76 @@ describe('computeSsrSeedPlan', () => {
|
|
|
209
209
|
expect(on.frees).toEqual(['props'])
|
|
210
210
|
}
|
|
211
211
|
})
|
|
212
|
+
|
|
213
|
+
test('prop-derived signal init THROUGH a component-scope const (#2685 review): still derived, resolves to the prop free', () => {
|
|
214
|
+
// Pre-fix, `mid` (a component-scope const, not part of `baseScope`)
|
|
215
|
+
// was a free identifier `classify` couldn't find in `available`, so
|
|
216
|
+
// this step fell to `opaque` — no adapter emits an in-template
|
|
217
|
+
// recompute for it at all (a DIFFERENT, worse-than-#2669 defect: even
|
|
218
|
+
// with `propName` restored on the manifest side, an absent caller prop
|
|
219
|
+
// would render permanently empty instead of the signal's own
|
|
220
|
+
// `'Default'`). `resolveThroughLocalConsts` inlines `mid` to
|
|
221
|
+
// `props.defaultOn` before classification, so this now derives exactly
|
|
222
|
+
// like the direct-access form above.
|
|
223
|
+
const plan = planFor(`
|
|
224
|
+
'use client'
|
|
225
|
+
import { createSignal } from '@barefootjs/client'
|
|
226
|
+
function Toggle(props: { defaultOn?: boolean }) {
|
|
227
|
+
const mid = props.defaultOn
|
|
228
|
+
const [on, setOn] = createSignal(mid ?? false)
|
|
229
|
+
return <button aria-pressed={on()}>t</button>
|
|
230
|
+
}
|
|
231
|
+
`)
|
|
232
|
+
|
|
233
|
+
const on = step(plan, 'on')
|
|
234
|
+
expect(on.kind).toBe('derived')
|
|
235
|
+
if (on.kind === 'derived') {
|
|
236
|
+
expect(on.origin).toBe('signal')
|
|
237
|
+
// The RESOLVED free set names `props` (what the inlined form reads),
|
|
238
|
+
// never the intermediate const `mid` — a consumer emitting `on`'s
|
|
239
|
+
// seed line never needs to know `mid` existed.
|
|
240
|
+
expect(on.frees).toEqual(['props'])
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('multi-hop const chain THROUGH TWO component-scope consts (#2685 review): still derived', () => {
|
|
245
|
+
const plan = planFor(`
|
|
246
|
+
'use client'
|
|
247
|
+
import { createSignal } from '@barefootjs/client'
|
|
248
|
+
function C(props: { x?: number }) {
|
|
249
|
+
const a = props.x
|
|
250
|
+
const b = a
|
|
251
|
+
const [count, setCount] = createSignal(b ?? 1)
|
|
252
|
+
return <p>{count()}</p>
|
|
253
|
+
}
|
|
254
|
+
`)
|
|
255
|
+
|
|
256
|
+
const count = step(plan, 'count')
|
|
257
|
+
expect(count.kind).toBe('derived')
|
|
258
|
+
if (count.kind === 'derived') {
|
|
259
|
+
expect(count.frees).toEqual(['props'])
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
test('const chain that bottoms out on an UNAVAILABLE name → opaque (fails safe, not a wrong substitution)', () => {
|
|
264
|
+
// `mid` inlines to `laterMemo`, a memo declared AFTER `on` in source —
|
|
265
|
+
// per the plan's ordering guarantee, `laterMemo` is not yet in
|
|
266
|
+
// `available` when `on` is classified, so this must stay `opaque`
|
|
267
|
+
// exactly like a direct forward-reference would (mirrors the existing
|
|
268
|
+
// "forward reference … → opaque" case above, one hop of const
|
|
269
|
+
// indirection removed).
|
|
270
|
+
const plan = planFor(`
|
|
271
|
+
'use client'
|
|
272
|
+
import { createSignal, createMemo } from '@barefootjs/client'
|
|
273
|
+
function C() {
|
|
274
|
+
const mid = laterMemo()
|
|
275
|
+
const [on, setOn] = createSignal(mid ?? false)
|
|
276
|
+
const laterMemo = createMemo(() => true)
|
|
277
|
+
return <button aria-pressed={on()}>{laterMemo()}</button>
|
|
278
|
+
}
|
|
279
|
+
`)
|
|
280
|
+
|
|
281
|
+
const on = step(plan, 'on')
|
|
282
|
+
expect(on.kind).toBe('opaque')
|
|
283
|
+
})
|
|
212
284
|
})
|
package/src/analyzer.ts
CHANGED
|
@@ -3429,6 +3429,82 @@ function collectKeysFromMembers(
|
|
|
3429
3429
|
return keys
|
|
3430
3430
|
}
|
|
3431
3431
|
|
|
3432
|
+
/**
|
|
3433
|
+
* Decide whether a member's `TypeInfo` is admissible for
|
|
3434
|
+
* `collectMemberTypes`'s destructured-parameter resolution (#2677 —
|
|
3435
|
+
* widened from the original string/number/boolean-only gate).
|
|
3436
|
+
*
|
|
3437
|
+
* #2150 originally restricted the gate to string/number/boolean, because a
|
|
3438
|
+
* non-primitive `TypeInfo` there used to mean "the typed adapters will emit
|
|
3439
|
+
* an unchecked scalar assertion (`in.X.(int)`) that panics" for a shape the
|
|
3440
|
+
* template layer had no representation for. That reasoning does NOT extend
|
|
3441
|
+
* to a rich type with a CATALOGUED lowering (#2274: `Date` → the `date`
|
|
3442
|
+
* helper): its propsParams `TypeInfo` is consumed only as call-site
|
|
3443
|
+
* evidence for `resolveReceiverType` (rich-type-evidence.ts) — never
|
|
3444
|
+
* emitted as a concrete field type (`typeInfoToGo`'s `interface` case falls
|
|
3445
|
+
* through to `interface{}` for an unbacked host name exactly as `unknown`
|
|
3446
|
+
* already did) — so there is no assertion to panic. Gating on
|
|
3447
|
+
* `CATALOGUED_RICH_TYPE_NAMES` specifically (not `HOST_RICH_TYPE_NAMES`
|
|
3448
|
+
* wholesale) keeps an un-catalogued rich type (`Map`, `Set`, …) at
|
|
3449
|
+
* `unknown`, i.e. avoids resurrecting the #2150 mistake for a shape no
|
|
3450
|
+
* lowering plugin exists for yet.
|
|
3451
|
+
*
|
|
3452
|
+
* Nor does it extend to a STRUCTURAL shape (`kind: 'array'` /
|
|
3453
|
+
* `kind: 'object'`) any more. `typeNodeToTypeInfo` already builds these
|
|
3454
|
+
* fully recursively (`elementType`, `properties`) — the same walk the
|
|
3455
|
+
* `props`-object form (`extractPropsFromTypeMembers`) always got — and
|
|
3456
|
+
* go-template's `emitSynthPropStructs` (#2674/#2676) synthesizes a real,
|
|
3457
|
+
* json-tagged Go struct for any anonymous object type it reaches through
|
|
3458
|
+
* `ir.metadata.propsParams[].type`, array-element positions included. A
|
|
3459
|
+
* structural `TypeInfo` is representable today, so admitting it here no
|
|
3460
|
+
* longer resurrects #2150 either — it lets `propsParams[].type` carry the
|
|
3461
|
+
* SAME shape the props-object form already resolved, closing the
|
|
3462
|
+
* asymmetry #2677 reports (`{ items }: { items: {...}[] }` degrading to
|
|
3463
|
+
* `unknown` while `(props: { items: {...}[] })` resolved fully).
|
|
3464
|
+
*
|
|
3465
|
+
* `array`/`object` admit only when EVERY reachable leaf resolves — an
|
|
3466
|
+
* array's element type and an object's every property type must pass this
|
|
3467
|
+
* same check, recursively. A union, a function, or an un-catalogued named
|
|
3468
|
+
* type reachable ANYWHERE inside the shape declines the WHOLE member (not
|
|
3469
|
+
* just that one leaf): `collectMemberTypes` is an all-or-nothing gate per
|
|
3470
|
+
* member, so a partially-resolvable structural type must still decline —
|
|
3471
|
+
* per-field graceful degradation is `typeInfoToGo`'s job downstream, not
|
|
3472
|
+
* this gate's.
|
|
3473
|
+
*
|
|
3474
|
+
* The `object` arm requires `properties` to be PRESENT, not merely falsy-
|
|
3475
|
+
* coalesced to `[]` — an absent-members claim and a zero-members claim are
|
|
3476
|
+
* different things, and `.every()` on a defaulted-to-`[]` array would admit
|
|
3477
|
+
* both identically (vacuously `true`), which is the #2150 failure mode
|
|
3478
|
+
* wearing a different hat: a concrete representation with no evidence
|
|
3479
|
+
* behind it. `typeNodeToTypeInfo`'s `object` arm DOES omit `properties`
|
|
3480
|
+
* entirely in its `synthetic` mode (the checker-driven `tsTypeToTypeInfo`
|
|
3481
|
+
* path, used for e.g. `createMemo` inference off a resolved `ts.Type`,
|
|
3482
|
+
* which has no source positions for `membersToProperties`' `getText()`).
|
|
3483
|
+
* This gate's only caller (`collectMemberTypes`'s `fromMembers`) always
|
|
3484
|
+
* calls the NON-synthetic 2-arg form, so `properties` is in practice always
|
|
3485
|
+
* at least `[]` here today — but `isResolvableMemberType` is a module-level
|
|
3486
|
+
* function, not a closure scoped to that one caller, so it guards the
|
|
3487
|
+
* general case rather than trust a caller-specific invariant. A genuinely
|
|
3488
|
+
* EMPTY object literal (`{}`, real zero members) DOES resolve — an empty
|
|
3489
|
+
* synthesized struct is exactly as faithful a representation as the type
|
|
3490
|
+
* itself claims, nothing is being guessed.
|
|
3491
|
+
*/
|
|
3492
|
+
function isResolvableMemberType(info: TypeInfo): boolean {
|
|
3493
|
+
switch (info.kind) {
|
|
3494
|
+
case 'primitive':
|
|
3495
|
+
return info.primitive === 'string' || info.primitive === 'number' || info.primitive === 'boolean'
|
|
3496
|
+
case 'interface':
|
|
3497
|
+
return CATALOGUED_RICH_TYPE_NAMES.has(baseTypeName(info.raw))
|
|
3498
|
+
case 'array':
|
|
3499
|
+
return !!info.elementType && isResolvableMemberType(info.elementType)
|
|
3500
|
+
case 'object':
|
|
3501
|
+
return info.properties !== undefined && info.properties.every(prop => isResolvableMemberType(prop.type))
|
|
3502
|
+
// Unions, functions, and `unknown` all decline — see docstring.
|
|
3503
|
+
default:
|
|
3504
|
+
return false
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3432
3508
|
/**
|
|
3433
3509
|
* Build a property-name -> { type, optional } map from a props type
|
|
3434
3510
|
* annotation, for destructured params (`{ value }: Props`) so they carry the
|
|
@@ -3443,36 +3519,17 @@ function collectKeysFromMembers(
|
|
|
3443
3519
|
* degraded to `unknown` -> `interface{}` for attribute omission, because
|
|
3444
3520
|
* #2252's nullish-flip machinery supplies the nillable representation
|
|
3445
3521
|
* exactly where absence is semantically observable (#2259).
|
|
3446
|
-
* -
|
|
3447
|
-
*
|
|
3448
|
-
*
|
|
3449
|
-
*
|
|
3450
|
-
* `
|
|
3451
|
-
*
|
|
3522
|
+
* - A member resolves a type when it is a PRIMITIVE (string/number/boolean),
|
|
3523
|
+
* a CATALOGUED rich type (`Date`), or a STRUCTURAL shape (array/object)
|
|
3524
|
+
* built entirely out of those, recursively (#2677). Unions, functions, and
|
|
3525
|
+
* un-catalogued named types (`Map`, `Set`, …) are left as `unknown` — see
|
|
3526
|
+
* `isResolvableMemberType`'s own docstring for why the structural cases
|
|
3527
|
+
* are safe to admit and why those three still are not.
|
|
3452
3528
|
*/
|
|
3453
3529
|
function collectMemberTypes(
|
|
3454
3530
|
typeNode: ts.TypeNode,
|
|
3455
3531
|
ctx: AnalyzerContext
|
|
3456
3532
|
): Map<string, { type: TypeInfo | null; optional: boolean }> | null {
|
|
3457
|
-
// #2150 originally restricted this gate to string/number/boolean only,
|
|
3458
|
-
// because a non-primitive TypeInfo here used to mean "the typed adapters
|
|
3459
|
-
// will emit an unchecked scalar assertion (`in.X.(int)`) that panics" for
|
|
3460
|
-
// a shape the template layer has no representation for. That reasoning
|
|
3461
|
-
// does NOT extend to a rich type with a CATALOGUED lowering (#2274: `Date`
|
|
3462
|
-
// → the `date` helper): its propsParams TypeInfo is consumed only as
|
|
3463
|
-
// call-site evidence for `resolveReceiverType` (rich-type-evidence.ts) —
|
|
3464
|
-
// never emitted as a concrete field type (`typeInfoToGo`'s `interface`
|
|
3465
|
-
// case falls through to `interface{}` for an unbacked host name exactly
|
|
3466
|
-
// as `unknown` already did) — so there is no assertion to panic. Gating on
|
|
3467
|
-
// `CATALOGUED_RICH_TYPE_NAMES` specifically (not `HOST_RICH_TYPE_NAMES`
|
|
3468
|
-
// wholesale) keeps an un-catalogued rich type (`Map`, `Set`, …) at
|
|
3469
|
-
// `unknown`, i.e. avoids resurrecting the #2150 mistake for a shape no
|
|
3470
|
-
// lowering plugin exists for yet.
|
|
3471
|
-
const isResolvablePrimitive = (info: TypeInfo): boolean =>
|
|
3472
|
-
(info.kind === 'primitive' &&
|
|
3473
|
-
(info.primitive === 'string' || info.primitive === 'number' || info.primitive === 'boolean')) ||
|
|
3474
|
-
(info.kind === 'interface' && CATALOGUED_RICH_TYPE_NAMES.has(baseTypeName(info.raw)))
|
|
3475
|
-
|
|
3476
3533
|
const fromMembers = (
|
|
3477
3534
|
members: ts.NodeArray<ts.TypeElement>
|
|
3478
3535
|
): Map<string, { type: TypeInfo | null; optional: boolean }> => {
|
|
@@ -3481,7 +3538,7 @@ function collectMemberTypes(
|
|
|
3481
3538
|
if (ts.isPropertySignature(member) && member.name) {
|
|
3482
3539
|
const info = member.type ? typeNodeToTypeInfo(member.type, ctx.sourceFile) : null
|
|
3483
3540
|
map.set(member.name.getText(ctx.sourceFile), {
|
|
3484
|
-
type: info &&
|
|
3541
|
+
type: info && isResolvableMemberType(info) ? info : null,
|
|
3485
3542
|
optional: !!member.questionToken,
|
|
3486
3543
|
})
|
|
3487
3544
|
}
|
package/src/expression-parser.ts
CHANGED
|
@@ -3037,8 +3037,17 @@ function usesPerPath(name: string, expr: ParsedExpr): { min: number; max: number
|
|
|
3037
3037
|
* leaves that inner reference untouched (it is the parameter, not the binding).
|
|
3038
3038
|
* Mirrors the structural walk of `substituteDestructuredFields`; every
|
|
3039
3039
|
* `ParsedExpr` kind is handled so a new kind surfaces as a compile error here.
|
|
3040
|
+
*
|
|
3041
|
+
* Exported for `computeSsrSeedPlan` (`ssr-seed-plan.ts`), which reuses this
|
|
3042
|
+
* SAME let-inline step to resolve a signal/memo initializer through
|
|
3043
|
+
* component-scope `const` locals sitting between it and the prop it reads
|
|
3044
|
+
* (`const mid = props.label; createSignal(mid ?? 'Default')`) — the
|
|
3045
|
+
* structural (never string-splicing) analogue of what `foldBlockToExpr`
|
|
3046
|
+
* already does for a block-bodied memo's own internal `const`s, and of what
|
|
3047
|
+
* the CSR client-JS emitter's `csrSubstitute` does over TS source text for
|
|
3048
|
+
* the compiled JS domain (#2685 review).
|
|
3040
3049
|
*/
|
|
3041
|
-
function inlineBinding(
|
|
3050
|
+
export function inlineBinding(
|
|
3042
3051
|
expr: ParsedExpr,
|
|
3043
3052
|
name: string,
|
|
3044
3053
|
value: ParsedExpr,
|
package/src/ssr-defaults.ts
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
// `undef`, which Mojo renders as empty string).
|
|
31
31
|
|
|
32
32
|
import ts from 'typescript'
|
|
33
|
+
import { extractFreeIdentifiersFromNode } from './analyzer.ts'
|
|
33
34
|
import type { IRMetadata } from './types.ts'
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -52,6 +53,16 @@ export interface SsrDefault {
|
|
|
52
53
|
* (`{ variant = 'default' }`) where the template variable maps 1:1
|
|
53
54
|
* to a caller-supplied prop. Omitted for signal / memo entries that
|
|
54
55
|
* are internal to the component.
|
|
56
|
+
*
|
|
57
|
+
* Invariant (#2669): a signal / memo entry carries `propName` IF AND
|
|
58
|
+
* ONLY IF it's a self-derivation collision — the signal/memo's own
|
|
59
|
+
* template variable shares a name with the prop its initializer
|
|
60
|
+
* derives from (`const [label] = createSignal(props.label ?? 'x')`).
|
|
61
|
+
* Every OTHER signal/memo entry is `propName`-less. Consumers (e.g.
|
|
62
|
+
* every template-stash adapter's conformance harness, `test-render.ts`)
|
|
63
|
+
* rely on this to know a signal/memo's stash seed must defer to the
|
|
64
|
+
* already-derived prop value rather than re-seeding from its own
|
|
65
|
+
* evaluated initial — see `extractSsrDefaults`'s signal/memo loops.
|
|
55
66
|
*/
|
|
56
67
|
propName?: string
|
|
57
68
|
/**
|
|
@@ -204,6 +215,11 @@ export function extractSsrDefaults(metadata: IRMetadata): Record<string, SsrDefa
|
|
|
204
215
|
// fed into the bindings map so subsequent memos can reference earlier
|
|
205
216
|
// signals (Counter's `doubled = createMemo(() => count() * 2)`).
|
|
206
217
|
const bindings: Record<string, EvalResult> = {}
|
|
218
|
+
// Component-scope const locals, for the transitive prop-reference
|
|
219
|
+
// resolution both `referencesOwnProp` calls below and the bare-props
|
|
220
|
+
// safety net (further down) use — computed once so every caller shares
|
|
221
|
+
// the same table.
|
|
222
|
+
const localConsts = localConstValuesByName(metadata)
|
|
207
223
|
|
|
208
224
|
// (#checkbox) Seed module-scope constants so a memo template-literal that
|
|
209
225
|
// references them resolves to a concrete string. Checkbox's `classes` memo
|
|
@@ -225,14 +241,73 @@ export function extractSsrDefaults(metadata: IRMetadata): Record<string, SsrDefa
|
|
|
225
241
|
// baked initial value.
|
|
226
242
|
if (sig.envReader) continue
|
|
227
243
|
const value = tryStaticEval(sig.initialValue, { bindings, propsLike })
|
|
228
|
-
|
|
244
|
+
// Self-derivation collision (#2669): a signal whose getter shares its
|
|
245
|
+
// name with the prop its OWN initializer derives from
|
|
246
|
+
// (`const [label] = createSignal(props.label ?? 'Default')`) gets a
|
|
247
|
+
// template-stash variable that the emitted template both READS (as the
|
|
248
|
+
// raw caller prop) and OVERWRITES (with the derived signal value) under
|
|
249
|
+
// that SAME name — see the Mojo/Jinja/etc. emitter's `{% set label =
|
|
250
|
+
// (label if label is defined else 'Default') %}`. Seeding the stash
|
|
251
|
+
// with the DERIVED value here would either permanently lock out a
|
|
252
|
+
// caller-supplied prop (idempotent derivations like `?? 'Default'` hide
|
|
253
|
+
// this — a caller's real value can never win) or double-apply a
|
|
254
|
+
// non-idempotent derivation (`(props.count ?? 1) * 2` seeded with the
|
|
255
|
+
// evaluated `2` re-derives to `2 * 2 = 4`). The correct seed is the RAW
|
|
256
|
+
// prop, so the entry must be a PROP entry (`propName` set, `value:
|
|
257
|
+
// null`) instead of a plain signal-value entry — the template's own
|
|
258
|
+
// `?? <default>` / `is not none` guard performs the real derivation,
|
|
259
|
+
// exactly like the bare-props safety net below.
|
|
260
|
+
//
|
|
261
|
+
// NOT self-derived (the initializer references a DIFFERENT prop, or no
|
|
262
|
+
// prop at all) keeps today's behavior: the signal's evaluated value
|
|
263
|
+
// wins the entry outright. This also correctly leaves alone the
|
|
264
|
+
// separate (out-of-scope) case where the signal's OWN initializer does
|
|
265
|
+
// NOT reference the same-named prop but the JSX body separately reads
|
|
266
|
+
// both `label()` and `props.label` — that's a template-variable-
|
|
267
|
+
// aliasing defect, not this one, and must render byte-identically.
|
|
268
|
+
//
|
|
269
|
+
// Invariant this establishes (relied on by every template-stash
|
|
270
|
+
// conformance harness's seeding loops — see `test-render.ts`): a
|
|
271
|
+
// signal/memo entry carries `propName` IF AND ONLY IF it went through
|
|
272
|
+
// this collision path. An ordinary signal/memo entry never has
|
|
273
|
+
// `propName` — that's exclusively how a harness (or a production
|
|
274
|
+
// manifest consumer) can tell "this local's stash seed must come from
|
|
275
|
+
// the caller-facing prop, not the local's own evaluated value" apart
|
|
276
|
+
// from "this is an internal signal/memo the caller cannot override".
|
|
277
|
+
if (metadata.propsObjectName !== null && referencesOwnProp(sig.initialValue, metadata.propsObjectName, sig.getter, localConsts)) {
|
|
278
|
+
// Pass 1 above already wrote the prop entry when the prop is
|
|
279
|
+
// *declared* on the props type — leave it as-is. An undeclared
|
|
280
|
+
// (untyped / inline-typed) prop has no pass-1 entry yet; create it
|
|
281
|
+
// here so the collision is still resolved for that shape.
|
|
282
|
+
if (!(sig.getter in out)) {
|
|
283
|
+
out[sig.getter] = { propName: sig.getter, value: null }
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
out[sig.getter] = { value: resultToJsonable(value) }
|
|
287
|
+
}
|
|
288
|
+
// `bindings` always gets the EVALUATED value regardless of which stash
|
|
289
|
+
// seed the entry above ended up with — a later memo referencing this
|
|
290
|
+
// getter (`createMemo(() => label() + '!')`) must keep resolving
|
|
291
|
+
// through the chain exactly as before; only the manifest's own seed
|
|
292
|
+
// choice changes, not the static-eval semantics.
|
|
229
293
|
bindings[sig.getter] = value
|
|
230
294
|
}
|
|
231
295
|
|
|
232
296
|
for (const memo of metadata.memos) {
|
|
233
297
|
if (memo.isModule) continue
|
|
234
298
|
const value = tryStaticEval(memo.computation, { bindings, propsLike })
|
|
235
|
-
|
|
299
|
+
// Same self-derivation collision as signals above, for a memo whose
|
|
300
|
+
// computation derives from a same-named prop
|
|
301
|
+
// (`createMemo(() => (props.label ?? 'Default') + n())`). See the
|
|
302
|
+
// signal loop's comment for the full mechanism and the `propName`
|
|
303
|
+
// invariant this relies on.
|
|
304
|
+
if (metadata.propsObjectName !== null && referencesOwnProp(memo.computation, metadata.propsObjectName, memo.name, localConsts)) {
|
|
305
|
+
if (!(memo.name in out)) {
|
|
306
|
+
out[memo.name] = { propName: memo.name, value: null }
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
out[memo.name] = { value: resultToJsonable(value) }
|
|
310
|
+
}
|
|
236
311
|
bindings[memo.name] = value
|
|
237
312
|
}
|
|
238
313
|
|
|
@@ -251,11 +326,11 @@ export function extractSsrDefaults(metadata: IRMetadata): Record<string, SsrDefa
|
|
|
251
326
|
const referenced = new Set<string>()
|
|
252
327
|
for (const sig of metadata.signals) {
|
|
253
328
|
if (!sig.getter || sig.isModule || sig.envReader) continue
|
|
254
|
-
|
|
329
|
+
collectPropRefsTransitive(sig.initialValue, metadata.propsObjectName, localConsts, referenced)
|
|
255
330
|
}
|
|
256
331
|
for (const memo of metadata.memos) {
|
|
257
332
|
if (memo.isModule) continue
|
|
258
|
-
|
|
333
|
+
collectPropRefsTransitive(memo.computation, metadata.propsObjectName, localConsts, referenced)
|
|
259
334
|
}
|
|
260
335
|
for (const name of referenced) {
|
|
261
336
|
// Don't clobber a signal / memo (or already-seeded prop) of the same
|
|
@@ -268,6 +343,47 @@ export function extractSsrDefaults(metadata: IRMetadata): Record<string, SsrDefa
|
|
|
268
343
|
return Object.keys(out).length === 0 ? undefined : out
|
|
269
344
|
}
|
|
270
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Component-scope (non-module) `const` locals, by name → value source text.
|
|
348
|
+
* `referencesOwnProp` and `collectPropRefsTransitive` (below) use this to
|
|
349
|
+
* see PAST one hop of pure indirection between a prop read and the
|
|
350
|
+
* signal/memo that derives from it — `const mid = props.label;
|
|
351
|
+
* createSignal(mid ?? 'Default')` (#2685 review, one hop past #2669's
|
|
352
|
+
* direct-access-only detection). Module-scope consts are excluded: a
|
|
353
|
+
* MODULE const's value is fixed at compile time and isn't a `props.X` read
|
|
354
|
+
* (a module const referencing `props` isn't legal JS — `props` is a
|
|
355
|
+
* function parameter), so it can never contribute a prop reference here.
|
|
356
|
+
*/
|
|
357
|
+
function localConstValuesByName(metadata: IRMetadata): ReadonlyMap<string, string> {
|
|
358
|
+
const out = new Map<string, string>()
|
|
359
|
+
for (const c of metadata.localConstants ?? []) {
|
|
360
|
+
if (c.isModule || c.value === undefined) continue
|
|
361
|
+
out.set(c.name, c.value)
|
|
362
|
+
}
|
|
363
|
+
return out
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Does `expr` (a signal initializer or memo computation) read
|
|
368
|
+
* `propsObjectName.<name>` where `name` is that SAME signal's getter / that
|
|
369
|
+
* SAME memo's name — directly, or through a chain of component-scope
|
|
370
|
+
* `const` locals? This is the #2669 self-derivation test (widened by
|
|
371
|
+
* #2685 review to see through indirection): reuses `collectPropRefsTransitive`
|
|
372
|
+
* (never a bespoke walker — see the CLAUDE.md rule this file already
|
|
373
|
+
* follows for `props.X` collection) and just checks membership of the one
|
|
374
|
+
* name we care about.
|
|
375
|
+
*/
|
|
376
|
+
function referencesOwnProp(
|
|
377
|
+
expr: string | undefined,
|
|
378
|
+
propsObjectName: string,
|
|
379
|
+
name: string,
|
|
380
|
+
localConsts: ReadonlyMap<string, string>,
|
|
381
|
+
): boolean {
|
|
382
|
+
const referenced = new Set<string>()
|
|
383
|
+
collectPropRefsTransitive(expr, propsObjectName, localConsts, referenced)
|
|
384
|
+
return referenced.has(name)
|
|
385
|
+
}
|
|
386
|
+
|
|
271
387
|
/**
|
|
272
388
|
* Collect the first-level property name of every `propsObjectName.X` access
|
|
273
389
|
* within `expr` (e.g. `props.initial ?? 0` → `initial`). Used to seed the
|
|
@@ -276,6 +392,11 @@ export function extractSsrDefaults(metadata: IRMetadata): Record<string, SsrDefa
|
|
|
276
392
|
* prop `a`: adapters lower that to `$a->{b}`, so the bare `$a` needs
|
|
277
393
|
* seeding just the same — the walk stops at the first
|
|
278
394
|
* `propsObjectName.<name>` match and collects `a` (not `b`).
|
|
395
|
+
*
|
|
396
|
+
* Direct-access only — does NOT look through a component-scope `const`
|
|
397
|
+
* that itself reads `propsObjectName.X`. Callers that need to see past
|
|
398
|
+
* that indirection use `collectPropRefsTransitive` below, which wraps this
|
|
399
|
+
* function rather than duplicating its walk.
|
|
279
400
|
*/
|
|
280
401
|
function collectPropRefs(
|
|
281
402
|
expr: string | undefined,
|
|
@@ -304,6 +425,46 @@ function collectPropRefs(
|
|
|
304
425
|
visit(node)
|
|
305
426
|
}
|
|
306
427
|
|
|
428
|
+
/**
|
|
429
|
+
* `collectPropRefs`, widened to look THROUGH component-scope `const`
|
|
430
|
+
* locals (#2685 review): a signal/memo initializer often reads a prop one
|
|
431
|
+
* hop removed — `const mid = props.label; createSignal(mid ?? 'Default')`
|
|
432
|
+
* — rather than `propsObjectName.X` directly. Runs the existing
|
|
433
|
+
* direct-access walk over `expr` first (unchanged behavior for the common
|
|
434
|
+
* case), then finds every VALUE-POSITION identifier `expr` references
|
|
435
|
+
* (via the analyzer's `extractFreeIdentifiersFromNode` — never a bespoke
|
|
436
|
+
* identifier scan, so a property-access tail like `foo.mid` or an
|
|
437
|
+
* object-literal key like `{ mid: 1 }` is correctly NOT mistaken for a
|
|
438
|
+
* read of a local named `mid`) and, for each one that names a local
|
|
439
|
+
* const, recurses into THAT const's own value expression the same way.
|
|
440
|
+
*
|
|
441
|
+
* `visited` guards re-entering the same const twice — both for the
|
|
442
|
+
* (source-impossible, since JS TDZ forbids a const referencing itself)
|
|
443
|
+
* pathological-cycle case, and for the ordinary diamond case (two
|
|
444
|
+
* branches of `expr` both reading the same local) so it isn't walked
|
|
445
|
+
* twice. Fresh per top-level call via the default parameter, so unrelated
|
|
446
|
+
* calls for different signals/memos don't share state.
|
|
447
|
+
*/
|
|
448
|
+
function collectPropRefsTransitive(
|
|
449
|
+
expr: string | undefined,
|
|
450
|
+
propsObjectName: string,
|
|
451
|
+
localConsts: ReadonlyMap<string, string>,
|
|
452
|
+
out: Set<string>,
|
|
453
|
+
visited: Set<string> = new Set(),
|
|
454
|
+
): void {
|
|
455
|
+
if (!expr || !expr.trim()) return
|
|
456
|
+
collectPropRefs(expr, propsObjectName, out)
|
|
457
|
+
const node = parseExpression(expr)
|
|
458
|
+
if (!node) return
|
|
459
|
+
for (const id of extractFreeIdentifiersFromNode(node)) {
|
|
460
|
+
if (visited.has(id)) continue
|
|
461
|
+
const constValue = localConsts.get(id)
|
|
462
|
+
if (constValue === undefined) continue
|
|
463
|
+
visited.add(id)
|
|
464
|
+
collectPropRefsTransitive(constValue, propsObjectName, localConsts, out, visited)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
307
468
|
function resultToJsonable(v: EvalResult): unknown {
|
|
308
469
|
if (v === UNRESOLVED) return null
|
|
309
470
|
if (v === undefined) return null
|
|
@@ -366,12 +527,16 @@ function evalStatementsForReturn(
|
|
|
366
527
|
|
|
367
528
|
function parseExpression(expr: string): ts.Expression | null {
|
|
368
529
|
// Wrap in parens so a leading `{}` parses as an object literal rather
|
|
369
|
-
// than an empty block statement.
|
|
530
|
+
// than an empty block statement. Parent nodes ARE set (unlike a bare
|
|
531
|
+
// parse) so `collectPropRefsTransitive` can call the analyzer's
|
|
532
|
+
// `extractFreeIdentifiersFromNode` — which needs `.parent` to tell a
|
|
533
|
+
// value-position identifier from a property-access tail / object-literal
|
|
534
|
+
// key / parameter name — on the result.
|
|
370
535
|
const sf = ts.createSourceFile(
|
|
371
536
|
'__ssr_default__.ts',
|
|
372
537
|
`(${expr})`,
|
|
373
538
|
ts.ScriptTarget.Latest,
|
|
374
|
-
|
|
539
|
+
true,
|
|
375
540
|
ts.ScriptKind.TS,
|
|
376
541
|
)
|
|
377
542
|
const stmt = sf.statements[0]
|
package/src/ssr-seed-plan.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { envSignalReaderFor, type EnvSignalReader } from './adapters/env-signal.
|
|
|
35
35
|
import {
|
|
36
36
|
extractArrowBodyExpression,
|
|
37
37
|
freeIdentifiers,
|
|
38
|
+
inlineBinding,
|
|
38
39
|
isSupported,
|
|
39
40
|
parseExpression,
|
|
40
41
|
type ParsedExpr,
|
|
@@ -97,6 +98,72 @@ function classify(
|
|
|
97
98
|
return { kind: 'derived', name, origin, expr, parsed, frees: [...frees] }
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Component-scope (non-module) `const` locals, by name, parsed to their
|
|
103
|
+
* `ParsedExpr` value — the substitution table `resolveThroughLocalConsts`
|
|
104
|
+
* inlines through. Module-scope consts are excluded: they're already part
|
|
105
|
+
* of `baseScope` (every adapter compile-time-inlines them to their literal
|
|
106
|
+
* value, so a reference to one is never a template-variable read — see the
|
|
107
|
+
* module doc), and a `let` has no stable value to substitute.
|
|
108
|
+
*/
|
|
109
|
+
function localConstExprsByName(metadata: IRMetadata): ReadonlyMap<string, ParsedExpr> {
|
|
110
|
+
const out = new Map<string, ParsedExpr>()
|
|
111
|
+
for (const c of metadata.localConstants ?? []) {
|
|
112
|
+
if (c.isModule || c.declarationKind !== 'const' || c.value === undefined) continue
|
|
113
|
+
out.set(c.name, c.parsed ?? parseExpression(c.value.trim()))
|
|
114
|
+
}
|
|
115
|
+
return out
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolve a signal/memo's parsed expression through any component-scope
|
|
120
|
+
* `const` locals it references, so a hop of pure indirection between a
|
|
121
|
+
* prop read and its derived signal/memo — `const mid = props.label;
|
|
122
|
+
* createSignal(mid ?? 'Default')` — classifies from the SAME scope its
|
|
123
|
+
* fully-inlined form (`props.label ?? 'Default'`) would (#2685 review, the
|
|
124
|
+
* jsx-side twin of `ssr-defaults.ts`'s own `collectPropRefs` closure).
|
|
125
|
+
*
|
|
126
|
+
* Structural substitution only (never string splicing, per CLAUDE.md):
|
|
127
|
+
* reuses `inlineBinding`, the exact "let-inline" step `foldBlockToExpr`
|
|
128
|
+
* already performs for a block-bodied memo's own internal `const`s. A
|
|
129
|
+
* chained const (`const a = props.x; const b = a; createSignal(b ?? 1)`)
|
|
130
|
+
* closes the same way — each pass substitutes whatever local consts are
|
|
131
|
+
* still free in the current form, so `b` resolves to `a` first and `a`
|
|
132
|
+
* resolves to `props.x` on the next pass. Bounded to `localConsts.size + 1`
|
|
133
|
+
* iterations (the longest possible chain) purely as a cycle guard; a real
|
|
134
|
+
* cycle is impossible in valid source (JS TDZ forbids a const referencing
|
|
135
|
+
* itself, directly or transitively).
|
|
136
|
+
*
|
|
137
|
+
* `inlineBinding` returns `null` only on a capture hazard (the const's own
|
|
138
|
+
* free variable would be shadowed by a nested callback parameter of the
|
|
139
|
+
* same name inside the current form) — that occurrence is left un-inlined,
|
|
140
|
+
* so its name simply stays free and later fails `classify`'s availability
|
|
141
|
+
* check like any other out-of-scope reference (fails safe to `opaque`,
|
|
142
|
+
* never a wrong substitution).
|
|
143
|
+
*/
|
|
144
|
+
function resolveThroughLocalConsts(
|
|
145
|
+
parsed: ParsedExpr,
|
|
146
|
+
localConsts: ReadonlyMap<string, ParsedExpr>,
|
|
147
|
+
): ParsedExpr {
|
|
148
|
+
let current = parsed
|
|
149
|
+
const maxIter = localConsts.size + 1
|
|
150
|
+
for (let i = 0; i < maxIter; i++) {
|
|
151
|
+
const frees = freeIdentifiers(current)
|
|
152
|
+
if (frees === null) break
|
|
153
|
+
let changed = false
|
|
154
|
+
for (const name of frees) {
|
|
155
|
+
const value = localConsts.get(name)
|
|
156
|
+
if (!value) continue
|
|
157
|
+
const inlined = inlineBinding(current, name, value)
|
|
158
|
+
if (inlined === null) continue
|
|
159
|
+
current = inlined
|
|
160
|
+
changed = true
|
|
161
|
+
}
|
|
162
|
+
if (!changed) break
|
|
163
|
+
}
|
|
164
|
+
return current
|
|
165
|
+
}
|
|
166
|
+
|
|
100
167
|
/**
|
|
101
168
|
* Compute the component's SSR seed plan from its metadata. See the module
|
|
102
169
|
* doc for the contract. Memo steps are gated to EXPRESSION-BODIED memos
|
|
@@ -111,6 +178,7 @@ export function computeSsrSeedPlan(metadata: IRMetadata): SsrSeedPlan {
|
|
|
111
178
|
}
|
|
112
179
|
|
|
113
180
|
const available = new Set<string>(baseScope)
|
|
181
|
+
const localConsts = localConstExprsByName(metadata)
|
|
114
182
|
const steps: SsrSeedStep[] = []
|
|
115
183
|
|
|
116
184
|
for (const signal of metadata.signals) {
|
|
@@ -126,7 +194,13 @@ export function computeSsrSeedPlan(metadata: IRMetadata): SsrSeedPlan {
|
|
|
126
194
|
steps.push(
|
|
127
195
|
expr === ''
|
|
128
196
|
? { kind: 'opaque', name: signal.getter, origin: 'signal' }
|
|
129
|
-
: classify(
|
|
197
|
+
: classify(
|
|
198
|
+
signal.getter,
|
|
199
|
+
'signal',
|
|
200
|
+
expr,
|
|
201
|
+
resolveThroughLocalConsts(parseExpression(expr), localConsts),
|
|
202
|
+
available,
|
|
203
|
+
),
|
|
130
204
|
)
|
|
131
205
|
available.add(signal.getter)
|
|
132
206
|
}
|
|
@@ -137,7 +211,13 @@ export function computeSsrSeedPlan(metadata: IRMetadata): SsrSeedPlan {
|
|
|
137
211
|
steps.push(
|
|
138
212
|
expr === ''
|
|
139
213
|
? { kind: 'opaque', name: memo.name, origin: 'memo' }
|
|
140
|
-
: classify(
|
|
214
|
+
: classify(
|
|
215
|
+
memo.name,
|
|
216
|
+
'memo',
|
|
217
|
+
expr,
|
|
218
|
+
resolveThroughLocalConsts(memo.parsed ?? parseExpression(expr), localConsts),
|
|
219
|
+
available,
|
|
220
|
+
),
|
|
141
221
|
)
|
|
142
222
|
available.add(memo.name)
|
|
143
223
|
}
|