@barefootjs/erb 0.31.4 → 0.31.5

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.
@@ -128,17 +128,23 @@ export declare class ErbAdapter extends BaseAdapter implements IRNodeEmitter<Erb
128
128
  */
129
129
  private localConstants;
130
130
  /**
131
- * Names currently bound by an enclosing loop body — the block-param
132
- * locals `renderLoop` introduces (item, index, per-binding destructure
133
- * fields) ref-counted so nested loops compose. This is load-bearing
134
- * for TWO things in the ERB adapter (more than the Mojo original, which
135
- * only used it to guard const-inlining): it also decides the
136
- * fundamental `v[:name]` vs bare-Ruby-local rendering choice in
137
- * `ErbTopLevelEmitter.identifier` — see `emit-context.ts`'s
138
- * `isLoopBoundName` docstring for why ERB's two-locals model needs this
139
- * where Perl's uniform `$name` sigil does not.
131
+ * The one canonical, position-accurate "names bound by an enclosing loop
132
+ * callback" service (#2482 Stage 2) — replaces the ref-counted
133
+ * `Map<string, number>` this adapter used to push/pop around
134
+ * `renderLoop`'s body. Load-bearing for TWO things in the ERB adapter
135
+ * (more than the Mojo original, which only used it to guard
136
+ * const-inlining): it also decides the fundamental `v[:name]` vs
137
+ * bare-Ruby-local rendering choice in `ErbTopLevelEmitter.identifier` —
138
+ * see `emit-context.ts`'s `isLoopBoundName` docstring for why ERB's
139
+ * two-locals model needs this where Perl's uniform `$name` sigil does
140
+ * not. Threaded via save/restore-by-reference around
141
+ * `renderChildren(loop.children)` in `renderLoop` (immutable — no
142
+ * ref-count bookkeeping), mirroring the Stage 1a/1b `ctx.scope`
143
+ * precedent in `jsx-to-ir.ts`. `IRLoop` already structurally satisfies
144
+ * `LoopBindingSource` (`param`/`index`/`paramBindings`/`preamble`), so
145
+ * `renderLoop` passes the loop node straight to `enterLoopRow`.
140
146
  */
141
- private loopBoundNames;
147
+ private scope;
142
148
  /**
143
149
  * Prop names whose value is `nil` in the template body when the caller
144
150
  * omits them — so a bare-reference attribute should be dropped rather
@@ -1 +1 @@
1
- {"version":3,"file":"erb-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/erb-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"erb-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/erb-adapter.ts"],"names":[],"mappings":"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"}
@@ -187314,7 +187314,8 @@ import {
187314
187314
  dangerousInnerHtmlMetacharViolation,
187315
187315
  dangerousInnerHtmlDiagnostic,
187316
187316
  resolveStaticLoopSource,
187317
- derivesScopeFromSlot
187317
+ derivesScopeFromSlot,
187318
+ BindingScope
187318
187319
  } from "@barefootjs/jsx";
187319
187320
 
187320
187321
  // src/adapter/boolean-result.ts
@@ -188372,7 +188373,7 @@ class ErbAdapter extends BaseAdapter {
188372
188373
  _loweringMatchers = [];
188373
188374
  moduleStringConsts = new Map;
188374
188375
  localConstants = [];
188375
- loopBoundNames = new Map;
188376
+ scope = BindingScope.EMPTY;
188376
188377
  nullableOptionalProps = new Set;
188377
188378
  constructor(options = {}) {
188378
188379
  super();
@@ -188394,7 +188395,7 @@ class ErbAdapter extends BaseAdapter {
188394
188395
  this._searchParamsLocals = searchParamsLocalNames(ir.metadata);
188395
188396
  this._loweringMatchers = prepareLoweringMatchers(ir.metadata);
188396
188397
  this.localConstants = ir.metadata.localConstants ?? [];
188397
- this.loopBoundNames.clear();
188398
+ this.scope = BindingScope.EMPTY;
188398
188399
  this.errors = [];
188399
188400
  this.childrenCaptureCounter = 0;
188400
188401
  if (!options?.siblingTemplatesRegistered) {
@@ -188452,7 +188453,7 @@ class ErbAdapter extends BaseAdapter {
188452
188453
  };
188453
188454
  }
188454
188455
  resolveLiteralConst(name) {
188455
- if (this.loopBoundNames?.has?.(name))
188456
+ if (this.scope.isBound(name))
188456
188457
  return null;
188457
188458
  const c = (this.localConstants ?? []).find((lc) => lc.name === name);
188458
188459
  if (c?.value === undefined)
@@ -188466,15 +188467,13 @@ class ErbAdapter extends BaseAdapter {
188466
188467
  return null;
188467
188468
  }
188468
188469
  resolveStaticRecordLiteral(objectName, key) {
188469
- if (this.loopBoundNames?.has?.(objectName))
188470
- return null;
188471
- const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants);
188470
+ const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants, (name) => this.scope.isBound(name));
188472
188471
  if (!hit)
188473
188472
  return null;
188474
188473
  return hit.kind === "number" ? hit.text : rubyStringLiteral(hit.text);
188475
188474
  }
188476
188475
  resolveModuleStringConst(name) {
188477
- if (this.loopBoundNames.has(name))
188476
+ if (this.scope.isBound(name))
188478
188477
  return null;
188479
188478
  const value = this.moduleStringConsts.get(name);
188480
188479
  if (value === undefined)
@@ -188482,7 +188481,7 @@ class ErbAdapter extends BaseAdapter {
188482
188481
  return rubyStringLiteral(value);
188483
188482
  }
188484
188483
  isLoopBoundName(name) {
188485
- return this.loopBoundNames.has(name);
188484
+ return this.scope.isBound(name);
188486
188485
  }
188487
188486
  generateScriptRegistrations(ir, scriptBaseName, scriptAssets, preloadAssets) {
188488
188487
  if (scriptAssets) {
@@ -188759,12 +188758,12 @@ ${whenTrue}
188759
188758
  });
188760
188759
  }
188761
188760
  const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
188762
- isNameShadowed: (name) => this.loopBoundNames.has(name)
188761
+ isNameShadowed: this.scope.asShadowPredicate()
188763
188762
  });
188764
188763
  const staticArray = staticItems !== null ? staticValueToRuby(staticItems) : null;
188765
188764
  if (staticArray === null && loop.arrayParsed?.kind === "identifier") {
188766
188765
  const arrayName = loop.arrayParsed.name;
188767
- const isUnresolvableLocalConst = !this.loopBoundNames.has(arrayName) && this.resolveModuleStringConst(arrayName) === null && this.resolveLiteralConst(arrayName) === null && this.localConstants.some((c) => c.name === arrayName && !c.isModule);
188766
+ const isUnresolvableLocalConst = !this.scope.isBound(arrayName) && this.resolveModuleStringConst(arrayName) === null && this.resolveLiteralConst(arrayName) === null && this.localConstants.some((c) => c.name === arrayName && !c.isModule);
188768
188767
  if (isUnresolvableLocalConst) {
188769
188768
  this._recordExprBF101(`Loop array \`${arrayName}\` is a component-scope const computed from a runtime expression the ERB adapter cannot evaluate at SSR render time.`, `Options:
188770
188769
  1. Inline the array expression directly in the .map() call instead of a preceding const.
@@ -188781,13 +188780,9 @@ ${whenTrue}
188781
188780
  }
188782
188781
  const param = loop.param;
188783
188782
  const indexVar = loop.iterationShape === "keys" ? rubyLocal(param) : rubyLocal(loop.index ?? "_i");
188784
- const loopBound = loop.objectIteration === "entries" ? [param, loop.index ?? "_k"] : loop.objectIteration === "keys" || loop.objectIteration === "values" || loop.iterationShape === "keys" ? [param] : supportableDestructure ? ["__bf_item", ...(loop.paramBindings ?? []).map((b) => b.name), loop.index ?? "_i"] : [param, loop.index ?? "_i"];
188785
188783
  const preambleDecls = loop.preamble?.declarations ?? [];
188786
- for (const d of preambleDecls)
188787
- loopBound.push(d.name);
188788
- for (const n of loopBound) {
188789
- this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
188790
- }
188784
+ const prevScope = this.scope;
188785
+ this.scope = prevScope.enterLoopRow(loop);
188791
188786
  const prevLoopKeyDepth = this.currentLoopKeyDepth;
188792
188787
  this.currentLoopKeyDepth = loop.depth;
188793
188788
  const renderedChildren = this.renderChildren(loop.children);
@@ -188797,13 +188792,7 @@ ${renderedChildren}` : renderedChildren;
188797
188792
  const lines = [];
188798
188793
  lines.push(`<%= bf.comment("loop:${loop.markerId}") %>`);
188799
188794
  if (sortedHoist && loop.sortComparator) {
188800
- for (const n of loopBound) {
188801
- const c = (this.loopBoundNames.get(n) ?? 1) - 1;
188802
- if (c <= 0)
188803
- this.loopBoundNames.delete(n);
188804
- else
188805
- this.loopBoundNames.set(n, c);
188806
- }
188795
+ this.scope = prevScope;
188807
188796
  const sortEmit = (e) => this.convertExpressionToRuby("", e);
188808
188797
  const sortArrow = loop.sortComparator.arrow;
188809
188798
  let sorted = null;
@@ -188819,9 +188808,7 @@ ${renderedChildren}` : renderedChildren;
188819
188808
  this._recordExprBF101(`.sort(...) loop comparator is not lowerable to a template sort`, `Pre-sort the array in the route handler, or mark the loop @client-only.`);
188820
188809
  sorted = rawArray;
188821
188810
  }
188822
- for (const n of loopBound) {
188823
- this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
188824
- }
188811
+ this.scope = prevScope.enterLoopRow(loop);
188825
188812
  lines.push(`<%- ${sortedHoist} = ${sorted} -%>`);
188826
188813
  }
188827
188814
  if (loop.objectIteration) {
@@ -188867,13 +188854,7 @@ ${renderedChildren}` : renderedChildren;
188867
188854
  lines.push(...preambleLines);
188868
188855
  lines.push(children);
188869
188856
  }
188870
- for (const n of loopBound) {
188871
- const c = (this.loopBoundNames.get(n) ?? 1) - 1;
188872
- if (c <= 0)
188873
- this.loopBoundNames.delete(n);
188874
- else
188875
- this.loopBoundNames.set(n, c);
188876
- }
188857
+ this.scope = prevScope;
188877
188858
  lines.push(`<%- end -%>`);
188878
188859
  lines.push(`<%= bf.comment("/loop:${loop.markerId}") %>`);
188879
188860
  return lines.join(`
@@ -189035,7 +189016,7 @@ ${children}`;
189035
189016
  if (ternaryHash !== null) {
189036
189017
  return `<%= bf.spread_attrs(${ternaryHash}) %>`;
189037
189018
  }
189038
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.loopBoundNames.has(trimmed)) {
189019
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.scope.isBound(trimmed)) {
189039
189020
  const localConst = this.localConstants.find((c) => c.name === trimmed && !c.isModule);
189040
189021
  if (localConst?.value !== undefined) {
189041
189022
  const initTrimmed = localConst.value.trim();
package/dist/index.js CHANGED
@@ -187314,7 +187314,8 @@ import {
187314
187314
  dangerousInnerHtmlMetacharViolation,
187315
187315
  dangerousInnerHtmlDiagnostic,
187316
187316
  resolveStaticLoopSource,
187317
- derivesScopeFromSlot
187317
+ derivesScopeFromSlot,
187318
+ BindingScope
187318
187319
  } from "@barefootjs/jsx";
187319
187320
 
187320
187321
  // src/adapter/boolean-result.ts
@@ -188372,7 +188373,7 @@ class ErbAdapter extends BaseAdapter {
188372
188373
  _loweringMatchers = [];
188373
188374
  moduleStringConsts = new Map;
188374
188375
  localConstants = [];
188375
- loopBoundNames = new Map;
188376
+ scope = BindingScope.EMPTY;
188376
188377
  nullableOptionalProps = new Set;
188377
188378
  constructor(options = {}) {
188378
188379
  super();
@@ -188394,7 +188395,7 @@ class ErbAdapter extends BaseAdapter {
188394
188395
  this._searchParamsLocals = searchParamsLocalNames(ir.metadata);
188395
188396
  this._loweringMatchers = prepareLoweringMatchers(ir.metadata);
188396
188397
  this.localConstants = ir.metadata.localConstants ?? [];
188397
- this.loopBoundNames.clear();
188398
+ this.scope = BindingScope.EMPTY;
188398
188399
  this.errors = [];
188399
188400
  this.childrenCaptureCounter = 0;
188400
188401
  if (!options?.siblingTemplatesRegistered) {
@@ -188452,7 +188453,7 @@ class ErbAdapter extends BaseAdapter {
188452
188453
  };
188453
188454
  }
188454
188455
  resolveLiteralConst(name) {
188455
- if (this.loopBoundNames?.has?.(name))
188456
+ if (this.scope.isBound(name))
188456
188457
  return null;
188457
188458
  const c = (this.localConstants ?? []).find((lc) => lc.name === name);
188458
188459
  if (c?.value === undefined)
@@ -188466,15 +188467,13 @@ class ErbAdapter extends BaseAdapter {
188466
188467
  return null;
188467
188468
  }
188468
188469
  resolveStaticRecordLiteral(objectName, key) {
188469
- if (this.loopBoundNames?.has?.(objectName))
188470
- return null;
188471
- const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants);
188470
+ const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants, (name) => this.scope.isBound(name));
188472
188471
  if (!hit)
188473
188472
  return null;
188474
188473
  return hit.kind === "number" ? hit.text : rubyStringLiteral(hit.text);
188475
188474
  }
188476
188475
  resolveModuleStringConst(name) {
188477
- if (this.loopBoundNames.has(name))
188476
+ if (this.scope.isBound(name))
188478
188477
  return null;
188479
188478
  const value = this.moduleStringConsts.get(name);
188480
188479
  if (value === undefined)
@@ -188482,7 +188481,7 @@ class ErbAdapter extends BaseAdapter {
188482
188481
  return rubyStringLiteral(value);
188483
188482
  }
188484
188483
  isLoopBoundName(name) {
188485
- return this.loopBoundNames.has(name);
188484
+ return this.scope.isBound(name);
188486
188485
  }
188487
188486
  generateScriptRegistrations(ir, scriptBaseName, scriptAssets, preloadAssets) {
188488
188487
  if (scriptAssets) {
@@ -188759,12 +188758,12 @@ ${whenTrue}
188759
188758
  });
188760
188759
  }
188761
188760
  const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
188762
- isNameShadowed: (name) => this.loopBoundNames.has(name)
188761
+ isNameShadowed: this.scope.asShadowPredicate()
188763
188762
  });
188764
188763
  const staticArray = staticItems !== null ? staticValueToRuby(staticItems) : null;
188765
188764
  if (staticArray === null && loop.arrayParsed?.kind === "identifier") {
188766
188765
  const arrayName = loop.arrayParsed.name;
188767
- const isUnresolvableLocalConst = !this.loopBoundNames.has(arrayName) && this.resolveModuleStringConst(arrayName) === null && this.resolveLiteralConst(arrayName) === null && this.localConstants.some((c) => c.name === arrayName && !c.isModule);
188766
+ const isUnresolvableLocalConst = !this.scope.isBound(arrayName) && this.resolveModuleStringConst(arrayName) === null && this.resolveLiteralConst(arrayName) === null && this.localConstants.some((c) => c.name === arrayName && !c.isModule);
188768
188767
  if (isUnresolvableLocalConst) {
188769
188768
  this._recordExprBF101(`Loop array \`${arrayName}\` is a component-scope const computed from a runtime expression the ERB adapter cannot evaluate at SSR render time.`, `Options:
188770
188769
  1. Inline the array expression directly in the .map() call instead of a preceding const.
@@ -188781,13 +188780,9 @@ ${whenTrue}
188781
188780
  }
188782
188781
  const param = loop.param;
188783
188782
  const indexVar = loop.iterationShape === "keys" ? rubyLocal(param) : rubyLocal(loop.index ?? "_i");
188784
- const loopBound = loop.objectIteration === "entries" ? [param, loop.index ?? "_k"] : loop.objectIteration === "keys" || loop.objectIteration === "values" || loop.iterationShape === "keys" ? [param] : supportableDestructure ? ["__bf_item", ...(loop.paramBindings ?? []).map((b) => b.name), loop.index ?? "_i"] : [param, loop.index ?? "_i"];
188785
188783
  const preambleDecls = loop.preamble?.declarations ?? [];
188786
- for (const d of preambleDecls)
188787
- loopBound.push(d.name);
188788
- for (const n of loopBound) {
188789
- this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
188790
- }
188784
+ const prevScope = this.scope;
188785
+ this.scope = prevScope.enterLoopRow(loop);
188791
188786
  const prevLoopKeyDepth = this.currentLoopKeyDepth;
188792
188787
  this.currentLoopKeyDepth = loop.depth;
188793
188788
  const renderedChildren = this.renderChildren(loop.children);
@@ -188797,13 +188792,7 @@ ${renderedChildren}` : renderedChildren;
188797
188792
  const lines = [];
188798
188793
  lines.push(`<%= bf.comment("loop:${loop.markerId}") %>`);
188799
188794
  if (sortedHoist && loop.sortComparator) {
188800
- for (const n of loopBound) {
188801
- const c = (this.loopBoundNames.get(n) ?? 1) - 1;
188802
- if (c <= 0)
188803
- this.loopBoundNames.delete(n);
188804
- else
188805
- this.loopBoundNames.set(n, c);
188806
- }
188795
+ this.scope = prevScope;
188807
188796
  const sortEmit = (e) => this.convertExpressionToRuby("", e);
188808
188797
  const sortArrow = loop.sortComparator.arrow;
188809
188798
  let sorted = null;
@@ -188819,9 +188808,7 @@ ${renderedChildren}` : renderedChildren;
188819
188808
  this._recordExprBF101(`.sort(...) loop comparator is not lowerable to a template sort`, `Pre-sort the array in the route handler, or mark the loop @client-only.`);
188820
188809
  sorted = rawArray;
188821
188810
  }
188822
- for (const n of loopBound) {
188823
- this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
188824
- }
188811
+ this.scope = prevScope.enterLoopRow(loop);
188825
188812
  lines.push(`<%- ${sortedHoist} = ${sorted} -%>`);
188826
188813
  }
188827
188814
  if (loop.objectIteration) {
@@ -188867,13 +188854,7 @@ ${renderedChildren}` : renderedChildren;
188867
188854
  lines.push(...preambleLines);
188868
188855
  lines.push(children);
188869
188856
  }
188870
- for (const n of loopBound) {
188871
- const c = (this.loopBoundNames.get(n) ?? 1) - 1;
188872
- if (c <= 0)
188873
- this.loopBoundNames.delete(n);
188874
- else
188875
- this.loopBoundNames.set(n, c);
188876
- }
188857
+ this.scope = prevScope;
188877
188858
  lines.push(`<%- end -%>`);
188878
188859
  lines.push(`<%= bf.comment("/loop:${loop.markerId}") %>`);
188879
188860
  return lines.join(`
@@ -189035,7 +189016,7 @@ ${children}`;
189035
189016
  if (ternaryHash !== null) {
189036
189017
  return `<%= bf.spread_attrs(${ternaryHash}) %>`;
189037
189018
  }
189038
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.loopBoundNames.has(trimmed)) {
189019
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.scope.isBound(trimmed)) {
189039
189020
  const localConst = this.localConstants.find((c) => c.name === trimmed && !c.isModule);
189040
189021
  if (localConst?.value !== undefined) {
189041
189022
  const initTrimmed = localConst.value.trim();
package/dist/vite.js CHANGED
@@ -5,7 +5,7 @@ import { barefoot as coreBarefoot } from "@barefootjs/vite";
5
5
  import { devModuleUrl, loadManifest, resolveDevOrigin, resolveScriptAssets, toPosixRelative } from "@barefootjs/vite";
6
6
 
7
7
  // ../jsx/src/compiler.ts
8
- import ts23 from "typescript";
8
+ import ts24 from "typescript";
9
9
 
10
10
  // ../jsx/src/analyzer.ts
11
11
  import ts9 from "typescript";
@@ -2527,7 +2527,7 @@ var REACTIVE_PRIMITIVES = new Set([
2527
2527
  ]);
2528
2528
 
2529
2529
  // ../jsx/src/jsx-to-ir.ts
2530
- import ts12 from "typescript";
2530
+ import ts13 from "typescript";
2531
2531
 
2532
2532
  // ../jsx/src/types.ts
2533
2533
  var SCOPE_FORBIDDEN = {
@@ -2577,6 +2577,7 @@ function preambleAnalysisTemplateText(p) {
2577
2577
  }
2578
2578
 
2579
2579
  // ../jsx/src/module-exports.ts
2580
+ import ts10 from "typescript";
2580
2581
  function formatParamWithType(p) {
2581
2582
  const rest = p.isRest ? "..." : "";
2582
2583
  const optional = p.optional ? "?" : "";
@@ -2607,12 +2608,55 @@ function findReachableNames(primaryRefs, declarations) {
2607
2608
  }
2608
2609
  return reachable;
2609
2610
  }
2611
+ function findAssignedNames(bodyText, candidates) {
2612
+ const assigned = new Set;
2613
+ if (candidates.size === 0)
2614
+ return assigned;
2615
+ const sf = ts10.createSourceFile("bf-assignment-scan.tsx", bodyText, ts10.ScriptTarget.Latest, false, ts10.ScriptKind.TSX);
2616
+ const record = (target) => {
2617
+ if (ts10.isIdentifier(target) && candidates.has(target.text)) {
2618
+ assigned.add(target.text);
2619
+ }
2620
+ };
2621
+ const visit = (node) => {
2622
+ if (ts10.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) {
2623
+ record(node.left);
2624
+ } else if ((ts10.isPrefixUnaryExpression(node) || ts10.isPostfixUnaryExpression(node)) && (node.operator === ts10.SyntaxKind.PlusPlusToken || node.operator === ts10.SyntaxKind.MinusMinusToken)) {
2625
+ record(node.operand);
2626
+ }
2627
+ ts10.forEachChild(node, visit);
2628
+ };
2629
+ ts10.forEachChild(sf, visit);
2630
+ return assigned;
2631
+ }
2632
+ function closeOverWritersOfMutableBindings(primaryRefs, declarations, mutableNames) {
2633
+ let reachable = findReachableNames(primaryRefs, declarations);
2634
+ if (mutableNames.size === 0)
2635
+ return reachable;
2636
+ let seedText = primaryRefs;
2637
+ for (let round = 0;round <= declarations.length; round++) {
2638
+ const survivingMutables = new Set([...reachable].filter((name) => mutableNames.has(name)));
2639
+ if (survivingMutables.size === 0)
2640
+ return reachable;
2641
+ const added = declarations.filter((d) => !reachable.has(d.name)).filter((d) => findAssignedNames(d.body, survivingMutables).size > 0).map((d) => d.name);
2642
+ if (added.length === 0)
2643
+ return reachable;
2644
+ seedText += `
2645
+ ` + added.join(`
2646
+ `);
2647
+ reachable = findReachableNames(seedText, declarations);
2648
+ }
2649
+ return reachable;
2650
+ }
2651
+ function isAssignmentOperator(kind) {
2652
+ return kind >= ts10.SyntaxKind.FirstAssignment && kind <= ts10.SyntaxKind.LastAssignment;
2653
+ }
2610
2654
 
2611
2655
  // ../jsx/src/reactivity-checker.ts
2612
- import ts10 from "typescript";
2656
+ import ts11 from "typescript";
2613
2657
 
2614
2658
  // ../jsx/src/free-refs.ts
2615
- import ts11 from "typescript";
2659
+ import ts12 from "typescript";
2616
2660
  var _bindingMapCache = new WeakMap;
2617
2661
 
2618
2662
  // ../jsx/src/to-locale-date-lowering.ts
@@ -3026,13 +3070,13 @@ var KEYWORDS_AND_GLOBALS = new Set([
3026
3070
  ]);
3027
3071
 
3028
3072
  // ../jsx/src/ir-to-client-js/walk-prop-accesses.ts
3029
- import ts13 from "typescript";
3073
+ import ts14 from "typescript";
3030
3074
 
3031
3075
  // ../jsx/src/value-references.ts
3032
- import ts14 from "typescript";
3076
+ import ts15 from "typescript";
3033
3077
 
3034
3078
  // ../jsx/src/relocate.ts
3035
- import ts15 from "typescript";
3079
+ import ts16 from "typescript";
3036
3080
 
3037
3081
  // ../jsx/src/lowering-registry.ts
3038
3082
  var plugins = [];
@@ -3249,10 +3293,10 @@ function matchSearchParamsMethodCall(callee, args, localNames) {
3249
3293
  }
3250
3294
 
3251
3295
  // ../jsx/src/ir-to-client-js/prune-unused-prop-extractions.ts
3252
- import ts16 from "typescript";
3296
+ import ts17 from "typescript";
3253
3297
 
3254
3298
  // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts
3255
- import ts17 from "typescript";
3299
+ import ts18 from "typescript";
3256
3300
  var NO_PREAMBLE = {
3257
3301
  lazySafe: true,
3258
3302
  facts: { declaredNames: new Set, freeNames: new Set }
@@ -3302,7 +3346,7 @@ var INERT_BINDING_GLOBALS = new Set([
3302
3346
  ]);
3303
3347
 
3304
3348
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
3305
- import ts18 from "typescript";
3349
+ import ts19 from "typescript";
3306
3350
 
3307
3351
  // ../jsx/src/ir-to-client-js/control-flow/stringify/event-delegation.ts
3308
3352
  var NON_BUBBLING_EVENTS = new Set([
@@ -3317,7 +3361,7 @@ var NON_BUBBLING_EVENTS = new Set([
3317
3361
  ]);
3318
3362
 
3319
3363
  // ../jsx/src/ir-to-client-js/rewrite-props-object.ts
3320
- import ts19 from "typescript";
3364
+ import ts20 from "typescript";
3321
3365
 
3322
3366
  // ../jsx/src/ir-to-client-js/source-map.ts
3323
3367
  var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -3408,15 +3452,15 @@ class SourceMapGenerator {
3408
3452
  }
3409
3453
 
3410
3454
  // ../jsx/src/preprocess-inline-jsx-callbacks.ts
3411
- import ts20 from "typescript";
3455
+ import ts21 from "typescript";
3412
3456
 
3413
3457
  // ../jsx/src/ssr-defaults.ts
3414
- import ts21 from "typescript";
3458
+ import ts22 from "typescript";
3415
3459
  var UNRESOLVED = Symbol("unresolved");
3416
3460
  var NO_RETURN = Symbol("no-return");
3417
3461
 
3418
3462
  // ../jsx/src/augment-inherited-props.ts
3419
- import ts22 from "typescript";
3463
+ import ts23 from "typescript";
3420
3464
  function collectContextConsumers(metadata) {
3421
3465
  const constants = metadata.localConstants ?? [];
3422
3466
  const contextDefaults = new Map;
@@ -3448,47 +3492,47 @@ function collectContextConsumers(metadata) {
3448
3492
  }
3449
3493
  function parseUseContextArg(source) {
3450
3494
  const expr = parseSingleExpression(source);
3451
- if (!expr || !ts22.isCallExpression(expr))
3495
+ if (!expr || !ts23.isCallExpression(expr))
3452
3496
  return null;
3453
- if (!ts22.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
3497
+ if (!ts23.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
3454
3498
  return null;
3455
3499
  if (expr.arguments.length !== 1)
3456
3500
  return null;
3457
3501
  const arg = expr.arguments[0];
3458
- return ts22.isIdentifier(arg) ? arg.text : null;
3502
+ return ts23.isIdentifier(arg) ? arg.text : null;
3459
3503
  }
3460
3504
  function parseCreateContextDefault(source) {
3461
3505
  const expr = parseSingleExpression(source);
3462
- if (!expr || !ts22.isCallExpression(expr))
3506
+ if (!expr || !ts23.isCallExpression(expr))
3463
3507
  return null;
3464
3508
  if (expr.arguments.length === 0)
3465
3509
  return null;
3466
3510
  const arg = expr.arguments[0];
3467
- if (ts22.isStringLiteral(arg) || ts22.isNoSubstitutionTemplateLiteral(arg))
3511
+ if (ts23.isStringLiteral(arg) || ts23.isNoSubstitutionTemplateLiteral(arg))
3468
3512
  return arg.text;
3469
- if (ts22.isNumericLiteral(arg))
3513
+ if (ts23.isNumericLiteral(arg))
3470
3514
  return Number(arg.text);
3471
- if (arg.kind === ts22.SyntaxKind.TrueKeyword)
3515
+ if (arg.kind === ts23.SyntaxKind.TrueKeyword)
3472
3516
  return true;
3473
- if (arg.kind === ts22.SyntaxKind.FalseKeyword)
3517
+ if (arg.kind === ts23.SyntaxKind.FalseKeyword)
3474
3518
  return false;
3475
3519
  return null;
3476
3520
  }
3477
3521
  function isObjectLiteralCreateContextDefault(source) {
3478
3522
  const expr = parseSingleExpression(source);
3479
- if (!expr || !ts22.isCallExpression(expr))
3523
+ if (!expr || !ts23.isCallExpression(expr))
3480
3524
  return false;
3481
3525
  if (expr.arguments.length === 0)
3482
3526
  return false;
3483
- return ts22.isObjectLiteralExpression(expr.arguments[0]);
3527
+ return ts23.isObjectLiteralExpression(expr.arguments[0]);
3484
3528
  }
3485
3529
  function parseSingleExpression(source) {
3486
- const sf = ts22.createSourceFile("__ctx.ts", `(${source})`, ts22.ScriptTarget.Latest, false);
3530
+ const sf = ts23.createSourceFile("__ctx.ts", `(${source})`, ts23.ScriptTarget.Latest, false);
3487
3531
  const stmt = sf.statements[0];
3488
- if (!stmt || !ts22.isExpressionStatement(stmt))
3532
+ if (!stmt || !ts23.isExpressionStatement(stmt))
3489
3533
  return null;
3490
3534
  let e = stmt.expression;
3491
- while (ts22.isParenthesizedExpression(e))
3535
+ while (ts23.isParenthesizedExpression(e))
3492
3536
  e = e.expression;
3493
3537
  return e;
3494
3538
  }
@@ -3513,25 +3557,25 @@ function augmentInheritedPropAccesses(ir) {
3513
3557
  const pinCoalesceLiterals = (s) => {
3514
3558
  if (!s || !s.includes(propsObj))
3515
3559
  return;
3516
- const sf = ts22.createSourceFile("__aug.ts", `(${s})`, ts22.ScriptTarget.Latest, false);
3560
+ const sf = ts23.createSourceFile("__aug.ts", `(${s})`, ts23.ScriptTarget.Latest, false);
3517
3561
  const visit = (n) => {
3518
- if (ts22.isBinaryExpression(n) && (n.operatorToken.kind === ts22.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts22.SyntaxKind.BarBarToken)) {
3562
+ if (ts23.isBinaryExpression(n) && (n.operatorToken.kind === ts23.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts23.SyntaxKind.BarBarToken)) {
3519
3563
  let left = n.left;
3520
- while (ts22.isParenthesizedExpression(left))
3564
+ while (ts23.isParenthesizedExpression(left))
3521
3565
  left = left.expression;
3522
- if (ts22.isPropertyAccessExpression(left) && ts22.isIdentifier(left.expression) && left.expression.text === propsObj) {
3566
+ if (ts23.isPropertyAccessExpression(left) && ts23.isIdentifier(left.expression) && left.expression.text === propsObj) {
3523
3567
  const name = left.name.text;
3524
3568
  let right = n.right;
3525
- while (ts22.isParenthesizedExpression(right))
3569
+ while (ts23.isParenthesizedExpression(right))
3526
3570
  right = right.expression;
3527
- if (ts22.isPrefixUnaryExpression(right))
3571
+ if (ts23.isPrefixUnaryExpression(right))
3528
3572
  right = right.operand;
3529
- const kind = ts22.isNumericLiteral(right) ? "number" : right.kind === ts22.SyntaxKind.TrueKeyword || right.kind === ts22.SyntaxKind.FalseKeyword ? "boolean" : ts22.isStringLiteralLike(right) ? "string" : null;
3573
+ const kind = ts23.isNumericLiteral(right) ? "number" : right.kind === ts23.SyntaxKind.TrueKeyword || right.kind === ts23.SyntaxKind.FalseKeyword ? "boolean" : ts23.isStringLiteralLike(right) ? "string" : null;
3530
3574
  if (kind && !coalesceLiteralTypes.has(name))
3531
3575
  coalesceLiteralTypes.set(name, kind);
3532
3576
  }
3533
3577
  }
3534
- ts22.forEachChild(n, visit);
3578
+ ts23.forEachChild(n, visit);
3535
3579
  };
3536
3580
  visit(sf);
3537
3581
  };
@@ -3642,33 +3686,33 @@ function augmentInheritedPropAccesses(ir) {
3642
3686
  }
3643
3687
  }
3644
3688
  function parseStaticStringConst(source) {
3645
- const sf = ts22.createSourceFile("__const.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3689
+ const sf = ts23.createSourceFile("__const.ts", `const __x = (${source});`, ts23.ScriptTarget.Latest, false);
3646
3690
  const stmt = sf.statements[0];
3647
- if (!stmt || !ts22.isVariableStatement(stmt))
3691
+ if (!stmt || !ts23.isVariableStatement(stmt))
3648
3692
  return null;
3649
3693
  let init = stmt.declarationList.declarations[0]?.initializer;
3650
- while (init && ts22.isParenthesizedExpression(init))
3694
+ while (init && ts23.isParenthesizedExpression(init))
3651
3695
  init = init.expression;
3652
3696
  if (!init)
3653
3697
  return null;
3654
- if (ts22.isStringLiteral(init) || ts22.isNoSubstitutionTemplateLiteral(init)) {
3698
+ if (ts23.isStringLiteral(init) || ts23.isNoSubstitutionTemplateLiteral(init)) {
3655
3699
  return init.text;
3656
3700
  }
3657
3701
  return evalStringArrayJoin(source);
3658
3702
  }
3659
3703
  function evalTemplateOfStringConsts(source, resolved) {
3660
- const sf = ts22.createSourceFile("__const.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3704
+ const sf = ts23.createSourceFile("__const.ts", `const __x = (${source});`, ts23.ScriptTarget.Latest, false);
3661
3705
  const stmt = sf.statements[0];
3662
- if (!stmt || !ts22.isVariableStatement(stmt))
3706
+ if (!stmt || !ts23.isVariableStatement(stmt))
3663
3707
  return null;
3664
3708
  let init = stmt.declarationList.declarations[0]?.initializer;
3665
- while (init && ts22.isParenthesizedExpression(init))
3709
+ while (init && ts23.isParenthesizedExpression(init))
3666
3710
  init = init.expression;
3667
- if (!init || !ts22.isTemplateExpression(init))
3711
+ if (!init || !ts23.isTemplateExpression(init))
3668
3712
  return null;
3669
3713
  let out = init.head.text;
3670
3714
  for (const span of init.templateSpans) {
3671
- if (!ts22.isIdentifier(span.expression))
3715
+ if (!ts23.isIdentifier(span.expression))
3672
3716
  return null;
3673
3717
  const value = resolved.get(span.expression.text);
3674
3718
  if (value === undefined)
@@ -3695,34 +3739,36 @@ function collectModuleStringConsts(constants) {
3695
3739
  }
3696
3740
  return map;
3697
3741
  }
3698
- function lookupStaticRecordLiteral(objectName, key, constants) {
3742
+ function lookupStaticRecordLiteral(objectName, key, constants, isShadowed) {
3743
+ if (isShadowed(objectName))
3744
+ return null;
3699
3745
  const constInfo = (constants ?? []).find((c) => c.name === objectName && c.isModule);
3700
3746
  if (constInfo?.value === undefined)
3701
3747
  return null;
3702
- const sf = ts22.createSourceFile("__rec.ts", `(${constInfo.value})`, ts22.ScriptTarget.Latest, true);
3748
+ const sf = ts23.createSourceFile("__rec.ts", `(${constInfo.value})`, ts23.ScriptTarget.Latest, true);
3703
3749
  if (sf.statements.length !== 1)
3704
3750
  return null;
3705
3751
  const stmt = sf.statements[0];
3706
- if (!ts22.isExpressionStatement(stmt))
3752
+ if (!ts23.isExpressionStatement(stmt))
3707
3753
  return null;
3708
3754
  let parsed = stmt.expression;
3709
- while (ts22.isParenthesizedExpression(parsed))
3755
+ while (ts23.isParenthesizedExpression(parsed))
3710
3756
  parsed = parsed.expression;
3711
- if (!ts22.isObjectLiteralExpression(parsed))
3757
+ if (!ts23.isObjectLiteralExpression(parsed))
3712
3758
  return null;
3713
3759
  for (const prop of parsed.properties) {
3714
- if (!ts22.isPropertyAssignment(prop))
3760
+ if (!ts23.isPropertyAssignment(prop))
3715
3761
  continue;
3716
3762
  const name = prop.name;
3717
- const propKey = ts22.isIdentifier(name) || ts22.isStringLiteral(name) || ts22.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
3763
+ const propKey = ts23.isIdentifier(name) || ts23.isStringLiteral(name) || ts23.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
3718
3764
  if (propKey !== key)
3719
3765
  continue;
3720
3766
  let v = prop.initializer;
3721
- while (ts22.isParenthesizedExpression(v))
3767
+ while (ts23.isParenthesizedExpression(v))
3722
3768
  v = v.expression;
3723
- if (ts22.isNumericLiteral(v))
3769
+ if (ts23.isNumericLiteral(v))
3724
3770
  return { kind: "number", text: v.text };
3725
- if (ts22.isStringLiteral(v) || ts22.isNoSubstitutionTemplateLiteral(v)) {
3771
+ if (ts23.isStringLiteral(v) || ts23.isNoSubstitutionTemplateLiteral(v)) {
3726
3772
  return { kind: "string", text: v.text };
3727
3773
  }
3728
3774
  return null;
@@ -3730,28 +3776,28 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
3730
3776
  return null;
3731
3777
  }
3732
3778
  function evalStringArrayJoin(source) {
3733
- const sf = ts22.createSourceFile("__join.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3779
+ const sf = ts23.createSourceFile("__join.ts", `const __x = (${source});`, ts23.ScriptTarget.Latest, false);
3734
3780
  const stmt = sf.statements[0];
3735
- if (!stmt || !ts22.isVariableStatement(stmt))
3781
+ if (!stmt || !ts23.isVariableStatement(stmt))
3736
3782
  return null;
3737
3783
  let node = stmt.declarationList.declarations[0]?.initializer;
3738
- while (node && ts22.isParenthesizedExpression(node))
3784
+ while (node && ts23.isParenthesizedExpression(node))
3739
3785
  node = node.expression;
3740
- if (!node || !ts22.isCallExpression(node))
3786
+ if (!node || !ts23.isCallExpression(node))
3741
3787
  return null;
3742
3788
  const callee = node.expression;
3743
- if (!ts22.isPropertyAccessExpression(callee))
3789
+ if (!ts23.isPropertyAccessExpression(callee))
3744
3790
  return null;
3745
3791
  if (callee.name.text !== "join")
3746
3792
  return null;
3747
3793
  let recv = callee.expression;
3748
- while (ts22.isParenthesizedExpression(recv))
3794
+ while (ts23.isParenthesizedExpression(recv))
3749
3795
  recv = recv.expression;
3750
- if (!ts22.isArrayLiteralExpression(recv))
3796
+ if (!ts23.isArrayLiteralExpression(recv))
3751
3797
  return null;
3752
3798
  const parts = [];
3753
3799
  for (const el of recv.elements) {
3754
- if (ts22.isStringLiteral(el) || ts22.isNoSubstitutionTemplateLiteral(el)) {
3800
+ if (ts23.isStringLiteral(el) || ts23.isNoSubstitutionTemplateLiteral(el)) {
3755
3801
  parts.push(el.text);
3756
3802
  } else {
3757
3803
  return null;
@@ -3760,7 +3806,7 @@ function evalStringArrayJoin(source) {
3760
3806
  let sep = ",";
3761
3807
  if (node.arguments.length >= 1) {
3762
3808
  const arg = node.arguments[0];
3763
- if (ts22.isStringLiteral(arg) || ts22.isNoSubstitutionTemplateLiteral(arg))
3809
+ if (ts23.isStringLiteral(arg) || ts23.isNoSubstitutionTemplateLiteral(arg))
3764
3810
  sep = arg.text;
3765
3811
  else
3766
3812
  return null;
@@ -3768,11 +3814,11 @@ function evalStringArrayJoin(source) {
3768
3814
  return parts.join(sep);
3769
3815
  }
3770
3816
  function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
3771
- if (!ts22.isElementAccessExpression(val))
3817
+ if (!ts23.isElementAccessExpression(val))
3772
3818
  return null;
3773
3819
  const obj = val.expression;
3774
3820
  const arg = val.argumentExpression;
3775
- if (!ts22.isIdentifier(obj) || !ts22.isIdentifier(arg))
3821
+ if (!ts23.isIdentifier(obj) || !ts23.isIdentifier(arg))
3776
3822
  return null;
3777
3823
  let indexPropName;
3778
3824
  let defaultKey;
@@ -3788,35 +3834,35 @@ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
3788
3834
  const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
3789
3835
  if (constInfo?.value === undefined)
3790
3836
  return null;
3791
- const sf = ts22.createSourceFile("__rec.ts", `(${constInfo.value})`, ts22.ScriptTarget.Latest, true);
3837
+ const sf = ts23.createSourceFile("__rec.ts", `(${constInfo.value})`, ts23.ScriptTarget.Latest, true);
3792
3838
  if (sf.statements.length !== 1)
3793
3839
  return null;
3794
3840
  const stmt = sf.statements[0];
3795
- if (!ts22.isExpressionStatement(stmt))
3841
+ if (!ts23.isExpressionStatement(stmt))
3796
3842
  return null;
3797
3843
  let parsed = stmt.expression;
3798
- while (ts22.isParenthesizedExpression(parsed))
3844
+ while (ts23.isParenthesizedExpression(parsed))
3799
3845
  parsed = parsed.expression;
3800
- if (!ts22.isObjectLiteralExpression(parsed))
3846
+ if (!ts23.isObjectLiteralExpression(parsed))
3801
3847
  return null;
3802
3848
  const entries = [];
3803
3849
  for (const prop of parsed.properties) {
3804
- if (!ts22.isPropertyAssignment(prop))
3850
+ if (!ts23.isPropertyAssignment(prop))
3805
3851
  return null;
3806
3852
  let key;
3807
- if (ts22.isIdentifier(prop.name)) {
3853
+ if (ts23.isIdentifier(prop.name)) {
3808
3854
  key = prop.name.text;
3809
- } else if (ts22.isStringLiteral(prop.name) || ts22.isNoSubstitutionTemplateLiteral(prop.name)) {
3855
+ } else if (ts23.isStringLiteral(prop.name) || ts23.isNoSubstitutionTemplateLiteral(prop.name)) {
3810
3856
  key = prop.name.text;
3811
3857
  } else {
3812
3858
  return null;
3813
3859
  }
3814
3860
  let v = prop.initializer;
3815
- while (ts22.isParenthesizedExpression(v))
3861
+ while (ts23.isParenthesizedExpression(v))
3816
3862
  v = v.expression;
3817
- if (ts22.isNumericLiteral(v)) {
3863
+ if (ts23.isNumericLiteral(v)) {
3818
3864
  entries.push({ key, value: { kind: "number", text: v.text } });
3819
- } else if (ts22.isStringLiteral(v) || ts22.isNoSubstitutionTemplateLiteral(v)) {
3865
+ } else if (ts23.isStringLiteral(v) || ts23.isNoSubstitutionTemplateLiteral(v)) {
3820
3866
  entries.push({ key, value: { kind: "string", text: v.text } });
3821
3867
  } else {
3822
3868
  return null;
@@ -3872,7 +3918,7 @@ function computeSsrSeedPlan(metadata) {
3872
3918
  // ../jsx/src/rich-type-refusal.ts
3873
3919
  var EMPTY_BINDINGS2 = new Map;
3874
3920
  // ../jsx/src/shared-program.ts
3875
- import ts24 from "typescript";
3921
+ import ts25 from "typescript";
3876
3922
  // ../jsx/src/adapters/interface.ts
3877
3923
  class BaseAdapter {
3878
3924
  renderChildren(children) {
@@ -3925,7 +3971,7 @@ class JsxAdapter extends BaseAdapter {
3925
3971
  ...localFunctions.map((f) => ({ name: f.name, body: f.body })),
3926
3972
  ...localConstants.map((c) => ({ name: c.name, body: c.value }))
3927
3973
  ];
3928
- const reachable = findReachableNames(primaryRefText, declarations);
3974
+ const reachable = closeOverWritersOfMutableBindings(primaryRefText, declarations, new Set(ir.metadata.localConstants.filter((c) => (c.declarationKind ?? "const") !== "const").map((c) => c.name)));
3929
3975
  const reachableBodies = [...reachable].map((name) => {
3930
3976
  const func = localFunctions.find((f) => f.name === name);
3931
3977
  if (func)
@@ -3957,7 +4003,8 @@ class JsxAdapter extends BaseAdapter {
3957
4003
  if (signal.setter) {
3958
4004
  const setterUsed = identifierPattern(signal.setter).test(setterRefText);
3959
4005
  if (setterUsed) {
3960
- lines.push(` const ${signal.setter} = (..._args: any[]) => {}`);
4006
+ const setterType = preserveTypes && signal.type.kind !== "unknown" ? `(valueOrFn: ${signal.type.raw} | ((prev: ${signal.type.raw}) => ${signal.type.raw})) => void` : null;
4007
+ lines.push(setterType ? ` const ${signal.setter}: ${setterType} = () => {}` : ` const ${signal.setter} = (..._args: any[]) => {}`);
3961
4008
  }
3962
4009
  }
3963
4010
  }
@@ -4153,7 +4200,7 @@ class JsxAdapter extends BaseAdapter {
4153
4200
  }
4154
4201
 
4155
4202
  // ../jsx/src/adapters/template-imports.ts
4156
- import ts25 from "typescript";
4203
+ import ts26 from "typescript";
4157
4204
  var CLIENT_PACKAGE_SOURCES = new Set([
4158
4205
  "@barefootjs/client",
4159
4206
  "@barefootjs/client/runtime"
@@ -4292,7 +4339,8 @@ export default ${this.componentName}` : "";
4292
4339
  const noArgDefault = hasRequiredProps ? "" : ` = {} as ${propsTypeExpr}`;
4293
4340
  const lines = [];
4294
4341
  const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
4295
- lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
4342
+ const typeParameters = ir.metadata.typeParameters ?? "";
4343
+ lines.push(`${exportPrefix}function ${name}${typeParameters}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
4296
4344
  if (hasClientInteractivity) {
4297
4345
  lines.push(` const __scopeId = (/_s\\d/.test(__bfScope || '') ? __bfScope : null) || __instanceId || \`${name}_\${Math.random().toString(36).slice(2, 8)}\``);
4298
4346
  } else {
@@ -4827,7 +4875,7 @@ function dangerousInnerHtmlDiagnostic(expr, loc, reason) {
4827
4875
  };
4828
4876
  }
4829
4877
  // ../jsx/src/combine-client-js.ts
4830
- import ts26 from "typescript";
4878
+ import ts27 from "typescript";
4831
4879
  // ../jsx/src/loop-destructure.ts
4832
4880
  function isLowerableLoopDestructure(loop) {
4833
4881
  const bindings = loop.paramBindings;
@@ -4967,9 +5015,9 @@ function escapeRe(s) {
4967
5015
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4968
5016
  }
4969
5017
  // ../jsx/src/debug.ts
4970
- import ts27 from "typescript";
4971
- // ../jsx/src/profiler.ts
4972
5018
  import ts28 from "typescript";
5019
+ // ../jsx/src/profiler.ts
5020
+ import ts29 from "typescript";
4973
5021
 
4974
5022
  // ../jsx/src/index.ts
4975
5023
  registerBuiltinLoweringPlugins();
@@ -5840,7 +5888,7 @@ function collectImportedLoopChildComponentErrors(ir, componentName) {
5840
5888
  }
5841
5889
 
5842
5890
  // src/adapter/spread/spread-codegen.ts
5843
- import ts29 from "typescript";
5891
+ import ts30 from "typescript";
5844
5892
  function conditionalSpreadToRuby(ctx, expr) {
5845
5893
  if (!expr || expr.kind !== "conditional")
5846
5894
  return null;
@@ -5895,7 +5943,7 @@ function recordIndexAccessToRuby(ctx, val) {
5895
5943
  if (val.kind !== "index-access" || val.object.kind !== "identifier" || val.index.kind !== "identifier") {
5896
5944
  return null;
5897
5945
  }
5898
- const tsVal = ts29.factory.createElementAccessExpression(ts29.factory.createIdentifier(val.object.name), ts29.factory.createIdentifier(val.index.name));
5946
+ const tsVal = ts30.factory.createElementAccessExpression(ts30.factory.createIdentifier(val.object.name), ts30.factory.createIdentifier(val.index.name));
5899
5947
  const parsed = parseRecordIndexAccess(tsVal, ctx.localConstants, ctx.propsParams);
5900
5948
  if (!parsed)
5901
5949
  return null;
@@ -6008,7 +6056,7 @@ class ErbAdapter extends BaseAdapter {
6008
6056
  _loweringMatchers = [];
6009
6057
  moduleStringConsts = new Map;
6010
6058
  localConstants = [];
6011
- loopBoundNames = new Map;
6059
+ scope = BindingScope.EMPTY;
6012
6060
  nullableOptionalProps = new Set;
6013
6061
  constructor(options = {}) {
6014
6062
  super();
@@ -6030,7 +6078,7 @@ class ErbAdapter extends BaseAdapter {
6030
6078
  this._searchParamsLocals = searchParamsLocalNames(ir.metadata);
6031
6079
  this._loweringMatchers = prepareLoweringMatchers(ir.metadata);
6032
6080
  this.localConstants = ir.metadata.localConstants ?? [];
6033
- this.loopBoundNames.clear();
6081
+ this.scope = BindingScope.EMPTY;
6034
6082
  this.errors = [];
6035
6083
  this.childrenCaptureCounter = 0;
6036
6084
  if (!options?.siblingTemplatesRegistered) {
@@ -6088,7 +6136,7 @@ class ErbAdapter extends BaseAdapter {
6088
6136
  };
6089
6137
  }
6090
6138
  resolveLiteralConst(name) {
6091
- if (this.loopBoundNames?.has?.(name))
6139
+ if (this.scope.isBound(name))
6092
6140
  return null;
6093
6141
  const c = (this.localConstants ?? []).find((lc) => lc.name === name);
6094
6142
  if (c?.value === undefined)
@@ -6102,15 +6150,13 @@ class ErbAdapter extends BaseAdapter {
6102
6150
  return null;
6103
6151
  }
6104
6152
  resolveStaticRecordLiteral(objectName, key) {
6105
- if (this.loopBoundNames?.has?.(objectName))
6106
- return null;
6107
- const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants);
6153
+ const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants, (name) => this.scope.isBound(name));
6108
6154
  if (!hit)
6109
6155
  return null;
6110
6156
  return hit.kind === "number" ? hit.text : rubyStringLiteral(hit.text);
6111
6157
  }
6112
6158
  resolveModuleStringConst(name) {
6113
- if (this.loopBoundNames.has(name))
6159
+ if (this.scope.isBound(name))
6114
6160
  return null;
6115
6161
  const value = this.moduleStringConsts.get(name);
6116
6162
  if (value === undefined)
@@ -6118,7 +6164,7 @@ class ErbAdapter extends BaseAdapter {
6118
6164
  return rubyStringLiteral(value);
6119
6165
  }
6120
6166
  isLoopBoundName(name) {
6121
- return this.loopBoundNames.has(name);
6167
+ return this.scope.isBound(name);
6122
6168
  }
6123
6169
  generateScriptRegistrations(ir, scriptBaseName, scriptAssets, preloadAssets) {
6124
6170
  if (scriptAssets) {
@@ -6395,12 +6441,12 @@ ${whenTrue}
6395
6441
  });
6396
6442
  }
6397
6443
  const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
6398
- isNameShadowed: (name) => this.loopBoundNames.has(name)
6444
+ isNameShadowed: this.scope.asShadowPredicate()
6399
6445
  });
6400
6446
  const staticArray = staticItems !== null ? staticValueToRuby(staticItems) : null;
6401
6447
  if (staticArray === null && loop.arrayParsed?.kind === "identifier") {
6402
6448
  const arrayName = loop.arrayParsed.name;
6403
- const isUnresolvableLocalConst = !this.loopBoundNames.has(arrayName) && this.resolveModuleStringConst(arrayName) === null && this.resolveLiteralConst(arrayName) === null && this.localConstants.some((c) => c.name === arrayName && !c.isModule);
6449
+ const isUnresolvableLocalConst = !this.scope.isBound(arrayName) && this.resolveModuleStringConst(arrayName) === null && this.resolveLiteralConst(arrayName) === null && this.localConstants.some((c) => c.name === arrayName && !c.isModule);
6404
6450
  if (isUnresolvableLocalConst) {
6405
6451
  this._recordExprBF101(`Loop array \`${arrayName}\` is a component-scope const computed from a runtime expression the ERB adapter cannot evaluate at SSR render time.`, `Options:
6406
6452
  1. Inline the array expression directly in the .map() call instead of a preceding const.
@@ -6417,13 +6463,9 @@ ${whenTrue}
6417
6463
  }
6418
6464
  const param = loop.param;
6419
6465
  const indexVar = loop.iterationShape === "keys" ? rubyLocal(param) : rubyLocal(loop.index ?? "_i");
6420
- const loopBound = loop.objectIteration === "entries" ? [param, loop.index ?? "_k"] : loop.objectIteration === "keys" || loop.objectIteration === "values" || loop.iterationShape === "keys" ? [param] : supportableDestructure ? ["__bf_item", ...(loop.paramBindings ?? []).map((b) => b.name), loop.index ?? "_i"] : [param, loop.index ?? "_i"];
6421
6466
  const preambleDecls = loop.preamble?.declarations ?? [];
6422
- for (const d of preambleDecls)
6423
- loopBound.push(d.name);
6424
- for (const n of loopBound) {
6425
- this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
6426
- }
6467
+ const prevScope = this.scope;
6468
+ this.scope = prevScope.enterLoopRow(loop);
6427
6469
  const prevLoopKeyDepth = this.currentLoopKeyDepth;
6428
6470
  this.currentLoopKeyDepth = loop.depth;
6429
6471
  const renderedChildren = this.renderChildren(loop.children);
@@ -6433,13 +6475,7 @@ ${renderedChildren}` : renderedChildren;
6433
6475
  const lines = [];
6434
6476
  lines.push(`<%= bf.comment("loop:${loop.markerId}") %>`);
6435
6477
  if (sortedHoist && loop.sortComparator) {
6436
- for (const n of loopBound) {
6437
- const c = (this.loopBoundNames.get(n) ?? 1) - 1;
6438
- if (c <= 0)
6439
- this.loopBoundNames.delete(n);
6440
- else
6441
- this.loopBoundNames.set(n, c);
6442
- }
6478
+ this.scope = prevScope;
6443
6479
  const sortEmit = (e) => this.convertExpressionToRuby("", e);
6444
6480
  const sortArrow = loop.sortComparator.arrow;
6445
6481
  let sorted = null;
@@ -6455,9 +6491,7 @@ ${renderedChildren}` : renderedChildren;
6455
6491
  this._recordExprBF101(`.sort(...) loop comparator is not lowerable to a template sort`, `Pre-sort the array in the route handler, or mark the loop @client-only.`);
6456
6492
  sorted = rawArray;
6457
6493
  }
6458
- for (const n of loopBound) {
6459
- this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
6460
- }
6494
+ this.scope = prevScope.enterLoopRow(loop);
6461
6495
  lines.push(`<%- ${sortedHoist} = ${sorted} -%>`);
6462
6496
  }
6463
6497
  if (loop.objectIteration) {
@@ -6503,13 +6537,7 @@ ${renderedChildren}` : renderedChildren;
6503
6537
  lines.push(...preambleLines);
6504
6538
  lines.push(children);
6505
6539
  }
6506
- for (const n of loopBound) {
6507
- const c = (this.loopBoundNames.get(n) ?? 1) - 1;
6508
- if (c <= 0)
6509
- this.loopBoundNames.delete(n);
6510
- else
6511
- this.loopBoundNames.set(n, c);
6512
- }
6540
+ this.scope = prevScope;
6513
6541
  lines.push(`<%- end -%>`);
6514
6542
  lines.push(`<%= bf.comment("/loop:${loop.markerId}") %>`);
6515
6543
  return lines.join(`
@@ -6671,7 +6699,7 @@ ${children}`;
6671
6699
  if (ternaryHash !== null) {
6672
6700
  return `<%= bf.spread_attrs(${ternaryHash}) %>`;
6673
6701
  }
6674
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.loopBoundNames.has(trimmed)) {
6702
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.scope.isBound(trimmed)) {
6675
6703
  const localConst = this.localConstants.find((c) => c.name === trimmed && !c.isModule);
6676
6704
  if (localConst?.value !== undefined) {
6677
6705
  const initTrimmed = localConst.value.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/erb",
3
- "version": "0.31.4",
3
+ "version": "0.31.5",
4
4
  "description": "ERB (Embedded Ruby) adapter for BarefootJS — compiles IR to .erb templates and ships the Ruby rendering backend; runs under any Rack app (Sinatra, Rails)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,7 +54,7 @@
54
54
  "directory": "packages/adapter-erb"
55
55
  },
56
56
  "dependencies": {
57
- "@barefootjs/shared": "0.31.4"
57
+ "@barefootjs/shared": "0.31.5"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@barefootjs/jsx": ">=0.2.0",
@@ -71,9 +71,9 @@
71
71
  },
72
72
  "devDependencies": {
73
73
  "@barefootjs/adapter-tests": "0.1.0",
74
- "@barefootjs/jsx": "0.31.4",
75
- "@barefootjs/vite": "0.31.4",
76
- "@barefootjs/client": "0.31.4",
74
+ "@barefootjs/jsx": "0.31.5",
75
+ "@barefootjs/vite": "0.31.5",
76
+ "@barefootjs/client": "0.31.5",
77
77
  "typescript": "^5.0.0",
78
78
  "vite": "^6.0.0"
79
79
  }
@@ -94,6 +94,7 @@ import {
94
94
  dangerousInnerHtmlDiagnostic,
95
95
  resolveStaticLoopSource,
96
96
  derivesScopeFromSlot,
97
+ BindingScope,
97
98
  } from '@barefootjs/jsx'
98
99
  import { isAriaBooleanAttr, isBooleanResultExpr, isExplicitStringCall } from './boolean-result.ts'
99
100
  import type { ParsedExpr, LoweringMatcher, LoopBindingPathSegment } from '@barefootjs/jsx'
@@ -248,17 +249,23 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
248
249
  */
249
250
  private localConstants: IRMetadata['localConstants'] = []
250
251
  /**
251
- * Names currently bound by an enclosing loop body — the block-param
252
- * locals `renderLoop` introduces (item, index, per-binding destructure
253
- * fields) ref-counted so nested loops compose. This is load-bearing
254
- * for TWO things in the ERB adapter (more than the Mojo original, which
255
- * only used it to guard const-inlining): it also decides the
256
- * fundamental `v[:name]` vs bare-Ruby-local rendering choice in
257
- * `ErbTopLevelEmitter.identifier` — see `emit-context.ts`'s
258
- * `isLoopBoundName` docstring for why ERB's two-locals model needs this
259
- * where Perl's uniform `$name` sigil does not.
252
+ * The one canonical, position-accurate "names bound by an enclosing loop
253
+ * callback" service (#2482 Stage 2) — replaces the ref-counted
254
+ * `Map<string, number>` this adapter used to push/pop around
255
+ * `renderLoop`'s body. Load-bearing for TWO things in the ERB adapter
256
+ * (more than the Mojo original, which only used it to guard
257
+ * const-inlining): it also decides the fundamental `v[:name]` vs
258
+ * bare-Ruby-local rendering choice in `ErbTopLevelEmitter.identifier` —
259
+ * see `emit-context.ts`'s `isLoopBoundName` docstring for why ERB's
260
+ * two-locals model needs this where Perl's uniform `$name` sigil does
261
+ * not. Threaded via save/restore-by-reference around
262
+ * `renderChildren(loop.children)` in `renderLoop` (immutable — no
263
+ * ref-count bookkeeping), mirroring the Stage 1a/1b `ctx.scope`
264
+ * precedent in `jsx-to-ir.ts`. `IRLoop` already structurally satisfies
265
+ * `LoopBindingSource` (`param`/`index`/`paramBindings`/`preamble`), so
266
+ * `renderLoop` passes the loop node straight to `enterLoopRow`.
260
267
  */
261
- private loopBoundNames: Map<string, number> = new Map()
268
+ private scope: BindingScope = BindingScope.EMPTY
262
269
  /**
263
270
  * Prop names whose value is `nil` in the template body when the caller
264
271
  * omits them — so a bare-reference attribute should be dropped rather
@@ -298,7 +305,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
298
305
  this._searchParamsLocals = searchParamsLocalNames(ir.metadata)
299
306
  this._loweringMatchers = prepareLoweringMatchers(ir.metadata)
300
307
  this.localConstants = ir.metadata.localConstants ?? []
301
- this.loopBoundNames.clear()
308
+ this.scope = BindingScope.EMPTY
302
309
  this.errors = []
303
310
  this.childrenCaptureCounter = 0
304
311
 
@@ -440,7 +447,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
440
447
  * would read nil.
441
448
  */
442
449
  private resolveLiteralConst(name: string): string | null {
443
- if (this.loopBoundNames?.has?.(name)) return null
450
+ if (this.scope.isBound(name)) return null
444
451
  const c = (this.localConstants ?? []).find(lc => lc.name === name)
445
452
  if (c?.value === undefined) return null
446
453
  const v = c.value.trim()
@@ -451,8 +458,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
451
458
  }
452
459
 
453
460
  private resolveStaticRecordLiteral(objectName: string, key: string): string | null {
454
- if (this.loopBoundNames?.has?.(objectName)) return null
455
- const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants)
461
+ const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants, name => this.scope.isBound(name))
456
462
  if (!hit) return null
457
463
  return hit.kind === 'number' ? hit.text : rubyStringLiteral(hit.text)
458
464
  }
@@ -460,7 +466,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
460
466
  private resolveModuleStringConst(name: string): string | null {
461
467
  // A loop body introduces block-param bindings that shadow a module
462
468
  // const of the same name — never inline inside one.
463
- if (this.loopBoundNames.has(name)) return null
469
+ if (this.scope.isBound(name)) return null
464
470
  const value = this.moduleStringConsts.get(name)
465
471
  if (value === undefined) return null
466
472
  return rubyStringLiteral(value)
@@ -469,7 +475,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
469
475
  /** Whether `name` currently names a loop-bound Ruby local. See
470
476
  * `ErbEmitContext.isLoopBoundName`'s docstring. */
471
477
  private isLoopBoundName(name: string): boolean {
472
- return this.loopBoundNames.has(name)
478
+ return this.scope.isBound(name)
473
479
  }
474
480
 
475
481
  // ===========================================================================
@@ -957,15 +963,17 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
957
963
  // `unsupported` object-literal path (BF101, "Expression not
958
964
  // supported"), which is what this loop-specific check now also does
959
965
  // deliberately, up front, for both shapes.
966
+ // Canonical, position-accurate predicate (#2482 Stage 2) — the
967
+ // enclosing loop scope's own membership.
960
968
  const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
961
- isNameShadowed: name => this.loopBoundNames.has(name),
969
+ isNameShadowed: this.scope.asShadowPredicate(),
962
970
  })
963
971
  const staticArray = staticItems !== null ? staticValueToRuby(staticItems) : null
964
972
 
965
973
  if (staticArray === null && loop.arrayParsed?.kind === 'identifier') {
966
974
  const arrayName = loop.arrayParsed.name
967
975
  const isUnresolvableLocalConst =
968
- !this.loopBoundNames.has(arrayName) &&
976
+ !this.scope.isBound(arrayName) &&
969
977
  this.resolveModuleStringConst(arrayName) === null &&
970
978
  this.resolveLiteralConst(arrayName) === null &&
971
979
  this.localConstants.some(c => c.name === arrayName && !c.isModule)
@@ -994,29 +1002,22 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
994
1002
  const indexVar = loop.iterationShape === 'keys'
995
1003
  ? rubyLocal(param)
996
1004
  : rubyLocal(loop.index ?? '_i')
997
- // Names this loop binds in body scope. Guard module-const inlining (and,
998
- // in ERB, the fundamental v[:name]-vs-local rendering choice) for the
999
- // whole body (children + key + filter) so a same-named loop variable
1000
- // isn't replaced by the const literal / a vars-Hash read. Ref-counted
1001
- // for nested loops; released after the body lines are assembled below.
1002
- const loopBound = loop.objectIteration === 'entries'
1003
- ? [param, loop.index ?? '_k']
1004
- : loop.objectIteration === 'keys' || loop.objectIteration === 'values' || loop.iterationShape === 'keys'
1005
- ? [param]
1006
- : supportableDestructure
1007
- ? ['__bf_item', ...(loop.paramBindings ?? []).map(b => b.name), loop.index ?? '_i']
1008
- : [param, loop.index ?? '_i']
1009
- // A `.map()` callback preamble lowers to one per-row Ruby local per
1010
- // declaration (#2447). The names must be loop-bound for the whole body,
1011
- // or `ErbTopLevelEmitter.identifier` renders each read as `v[:cls]` —
1012
- // a vars-Hash key nothing ever seeds, i.e. the empty attribute this
1013
- // fixes. Phase 1 guarantees `declarations` is present or the loop was
1014
- // already refused, so there is no partial-lowering case here.
1005
+ // This loop's row scope. Guards module-const inlining (and, in ERB, the
1006
+ // fundamental v[:name]-vs-local rendering choice) for the whole body
1007
+ // (children + key + filter) so a same-named loop variable isn't
1008
+ // replaced by the const literal / a vars-Hash read. `IRLoop` already
1009
+ // structurally satisfies `LoopBindingSource`
1010
+ // (`param`/`index`/`paramBindings`/`preamble`) `enterLoopRow(loop)`
1011
+ // binds exactly what this renderLoop's header + preamble locals
1012
+ // introduce (#2482 Stage 2 replaces the ref-counted `Map` this
1013
+ // adapter used to carry). Restored by reference (immutable — no
1014
+ // ref-count bookkeeping) at the matching pop below, once the WHOLE
1015
+ // body (including the filter predicate) has been rendered except for
1016
+ // one temporary drop-to-outer window around the hoisted sort-comparator
1017
+ // emission below, since that runs OUTSIDE this loop.
1015
1018
  const preambleDecls = loop.preamble?.declarations ?? []
1016
- for (const d of preambleDecls) loopBound.push(d.name)
1017
- for (const n of loopBound) {
1018
- this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1)
1019
- }
1019
+ const prevScope = this.scope
1020
+ this.scope = prevScope.enterLoopRow(loop)
1020
1021
  const prevLoopKeyDepth = this.currentLoopKeyDepth
1021
1022
  this.currentLoopKeyDepth = loop.depth
1022
1023
  const renderedChildren = this.renderChildren(loop.children)
@@ -1042,18 +1043,16 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1042
1043
  // fall back to the structured `bf.sort` for a comparator the
1043
1044
  // evaluator can't model (e.g. `localeCompare`).
1044
1045
  //
1045
- // The hoisted sort runs OUTSIDE this loop, so this loop's bound names
1046
+ // The hoisted sort runs OUTSIDE this loop, so this loop's row scope
1046
1047
  // must not shadow the comparator's captured free vars while emitting
1047
1048
  // the env — otherwise a captured var that happens to share a
1048
1049
  // loop-param name is blocked from inlining its module const / from
1049
1050
  // reading `v[:name]` and instead resolves to the (out-of-scope) loop
1050
- // local. Drop this loop's bound names for the sort emit, then
1051
- // restore (a nested loop's outer bindings, ref-counted, stay in effect).
1052
- for (const n of loopBound) {
1053
- const c = (this.loopBoundNames.get(n) ?? 1) - 1
1054
- if (c <= 0) this.loopBoundNames.delete(n)
1055
- else this.loopBoundNames.set(n, c)
1056
- }
1051
+ // local. Drop back to the outer (pre-row) scope for the sort emit,
1052
+ // then restore the row scope below (a nested loop's own outer
1053
+ // bindings stay in effect throughout, since `prevScope` already
1054
+ // carries them).
1055
+ this.scope = prevScope
1057
1056
  const sortEmit = (e: ParsedExpr) => this.convertExpressionToRuby('', e)
1058
1057
  const sortArrow = loop.sortComparator.arrow
1059
1058
  let sorted: string | null = null
@@ -1074,9 +1073,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1074
1073
  )
1075
1074
  sorted = rawArray
1076
1075
  }
1077
- for (const n of loopBound) {
1078
- this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1)
1079
- }
1076
+ this.scope = prevScope.enterLoopRow(loop)
1080
1077
  lines.push(`<%- ${sortedHoist} = ${sorted} -%>`)
1081
1078
  }
1082
1079
  if (loop.objectIteration) {
@@ -1184,12 +1181,8 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1184
1181
  lines.push(children)
1185
1182
  }
1186
1183
 
1187
- // Body fully rendered — release the loop-bound names.
1188
- for (const n of loopBound) {
1189
- const c = (this.loopBoundNames.get(n) ?? 1) - 1
1190
- if (c <= 0) this.loopBoundNames.delete(n)
1191
- else this.loopBoundNames.set(n, c)
1192
- }
1184
+ // Body fully rendered — restore the outer (pre-row) scope.
1185
+ this.scope = prevScope
1193
1186
 
1194
1187
  lines.push(`<%- end -%>`)
1195
1188
  lines.push(`<%= bf.comment("/loop:${loop.markerId}") %>`)
@@ -1558,11 +1551,11 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1558
1551
  // function-scope (`!isModule`) consts whose value is NOT itself a
1559
1552
  // bare identifier (loop guard) are considered.
1560
1553
  //
1561
- // `loopBoundNames` guard (#2489): an enclosing `.map()` callback's own
1562
- // param can shadow this outer const's name (`.map((attrs) => <p
1554
+ // `this.scope` shadow guard (#2489): an enclosing `.map()` callback's
1555
+ // own param can shadow this outer const's name (`.map((attrs) => <p
1563
1556
  // {...attrs} />)`) — without the guard this forwarded the OUTER
1564
1557
  // const's value at every iteration instead of the per-item value.
1565
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.loopBoundNames.has(trimmed)) {
1558
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.scope.isBound(trimmed)) {
1566
1559
  const localConst = this.localConstants.find(
1567
1560
  c => c.name === trimmed && !c.isModule,
1568
1561
  )
@@ -352,11 +352,11 @@ export class ErbTopLevelEmitter implements ParsedExprEmitter {
352
352
  if (name === 'undefined' || name === 'null') return 'nil'
353
353
  // A loop-bound name (this identifier resolves to a bare Ruby local
354
354
  // introduced by an enclosing loop, not a vars-Hash entry) takes
355
- // priority over const inlining — mirrors the Mojo adapter's
356
- // `loopBoundNames` shadow guard, but here it ALSO decides the
357
- // fundamental v[:name]-vs-bare-local rendering, not just the const
358
- // fast path (ERB's two-locals variable model needs this distinction;
359
- // Perl's uniform `$name` sigil does not — see `ErbEmitContext`).
355
+ // priority over const inlining — mirrors the Mojo adapter's threaded
356
+ // scope shadow guard, but here it ALSO decides the fundamental
357
+ // v[:name]-vs-bare-local rendering, not just the const fast path
358
+ // (ERB's two-locals variable model needs this distinction; Perl's
359
+ // uniform `$name` sigil does not — see `ErbEmitContext`).
360
360
  if (this.ctx.isLoopBoundName(name)) return rubyLocal(name)
361
361
  // Module pure-string const (e.g. `const baseClasses = '...'` used in a
362
362
  // className template literal): inline the literal value rather than