@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.
@@ -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;AA6tCD,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"}
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 && isResolvablePrimitive(info) ? info : null,
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
- out[sig.getter] = { value: resultToJsonable(value) };
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
- out[memo.name] = { value: resultToJsonable(value) };
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
- collectPropRefs(sig.initialValue, metadata.propsObjectName, referenced);
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
- collectPropRefs(memo.computation, metadata.propsObjectName, referenced);
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, false, ts22.ScriptKind.TS);
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 };
@@ -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":"AAgCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,KAAK,EAAE,OAAO,CAAA;IACd;;;;;;OAMG;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,CA8G/F"}
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,EAKL,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;AA0BD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,UAAU,GAAG,WAAW,CAwCpE"}
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.9",
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.9"
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 `isResolvablePrimitive`).
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
- test('leaves NON-primitive members (arrays/objects) as unknown', () => {
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({ kind: 'unknown', raw: 'unknown' })
441
- expect(typeOf(ctx, 'meta')).toEqual({ kind: 'unknown', raw: 'unknown' })
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
- * - Only PRIMITIVE members (string/number/boolean) resolve a type. Those are
3447
- * the types that otherwise produce an unchecked scalar assertion (`in.X.(int)`)
3448
- * that panics. Arrays/objects/functions are left as `unknown` because typed
3449
- * adapters lower them through interface{}-based helpers (`bf_flat`, spread,
3450
- * `bf_json`); giving them concrete types would break that lowering and is a
3451
- * larger, separate change.
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 && isResolvablePrimitive(info) ? info : null,
3541
+ type: info && isResolvableMemberType(info) ? info : null,
3485
3542
  optional: !!member.questionToken,
3486
3543
  })
3487
3544
  }
@@ -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,
@@ -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
- out[sig.getter] = { value: resultToJsonable(value) }
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
- out[memo.name] = { value: resultToJsonable(value) }
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
- collectPropRefs(sig.initialValue, metadata.propsObjectName, referenced)
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
- collectPropRefs(memo.computation, metadata.propsObjectName, referenced)
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
- false,
539
+ true,
375
540
  ts.ScriptKind.TS,
376
541
  )
377
542
  const stmt = sf.statements[0]
@@ -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(signal.getter, 'signal', expr, parseExpression(expr), available),
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(memo.name, 'memo', expr, memo.parsed ?? parseExpression(expr), available),
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
  }