@barefootjs/jsx 0.35.1 → 0.35.3

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":"build-static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/build-static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAA4B,MAAM,aAAa,CAAA;AAoB5E,OAAO,KAAK,EAQV,yBAAyB,EAC1B,MAAM,8BAA8B,CAAA;AAErC,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,eAAe,GACnB,yBAAyB,CAsC3B"}
1
+ {"version":3,"file":"build-static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/build-static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAmD,MAAM,aAAa,CAAA;AAoBnG,OAAO,KAAK,EAQV,yBAAyB,EAC1B,MAAM,8BAA8B,CAAA;AAErC,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,eAAe,GACnB,yBAAyB,CA4D3B"}
@@ -7,7 +7,9 @@
7
7
  * - `outer-nested` — depth 0 の `nestedComponents`。outer forEach で
8
8
  * `__iterEl.querySelector(...)` 経由で initChild。
9
9
  * - `inner-loop-nested` — depth > 0 の `nestedComponents`。outer + inner
10
- * forEach の二重ループで initChild
10
+ * forEach の二重ループで initChild。同じ二重ループで
11
+ * プレーン要素の ref / reactive text / reactive attr
12
+ * も配線する (#2798、depth 1 のみ)。
11
13
  * - `component-rooted-inner-loop`
12
14
  * — outer の loop item root が **child component**
13
15
  * (`loop.childComponent`) で、その JSX children に
@@ -23,6 +25,8 @@
23
25
  * shapes were determined by inline branching on the IR.
24
26
  */
25
27
  import type { PreludeStatements } from '../control-flow/plan/inner-loop.ts';
28
+ import type { LoopChildRefBinding } from '../control-flow/plan/loop.ts';
29
+ import type { LoopChildReactiveAttr, LoopChildReactiveText } from '../types.ts';
26
30
  /** Pre-built `{ name: value, ... }` props object expression. */
27
31
  export type PropsExpr = string;
28
32
  /** A single child-component initialiser inside an inner-loop body. */
@@ -137,6 +141,20 @@ export interface InnerLoopNestedInitPlan {
137
141
  depth: number;
138
142
  /** Per-component initialisers emitted inside the inner forEach body. */
139
143
  comps: readonly InnerLoopComp[];
144
+ /**
145
+ * Reactive attrs on plain elements inside this inner loop's body,
146
+ * grouped by child slot id (#2798 — a static outer array's nested
147
+ * `.map()` over plain elements used to wire up NOTHING: no refs, no
148
+ * reactive text/attr effects, since the whole `elem.innerLoops` pass
149
+ * only ever ran for depth-N CHILD COMPONENTS). Only populated for a
150
+ * depth-1 inner loop under a plain-element-rooted outer item — see
151
+ * `buildStaticArrayChildInitsPlan`'s docstring.
152
+ */
153
+ attrsBySlot: ReadonlyArray<readonly [string, readonly LoopChildReactiveAttr[]]>;
154
+ /** Reactive text interpolations inside this inner loop's body (#2798). */
155
+ texts: readonly LoopChildReactiveText[];
156
+ /** Imperative ref callbacks on elements inside this inner loop's body (#2798). */
157
+ refs: readonly LoopChildRefBinding[];
140
158
  }
141
159
  /**
142
160
  * Plan for an inner `.map()` of components living inside a **component-rooted**
@@ -1 +1 @@
1
- {"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/static-array-child-init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/static-array-child-init.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/stringify/static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAMV,yBAAyB,EAC1B,MAAM,oCAAoC,CAAA;AAG3C,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,yBAAyB,GAC/B,IAAI,CAIN"}
1
+ {"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/stringify/static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAMV,yBAAyB,EAC1B,MAAM,oCAAoC,CAAA;AAO3C,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,yBAAyB,GAC/B,IAAI,CAIN"}
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-to-ir.d.ts","sourceRoot":"","sources":["../src/jsx-to-ir.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,KAAK,MAAM,EAgCZ,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,eAAe,EAA0E,MAAM,uBAAuB,CAAA;AAkhCpI,wBAAgB,OAAO,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAOhE"}
1
+ {"version":3,"file":"jsx-to-ir.d.ts","sourceRoot":"","sources":["../src/jsx-to-ir.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,KAAK,MAAM,EAgCZ,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,eAAe,EAA0E,MAAM,uBAAuB,CAAA;AAmhCpI,wBAAgB,OAAO,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAOhE"}
@@ -33,6 +33,32 @@ import ts from 'typescript';
33
33
  * #1425).
34
34
  */
35
35
  export declare function collectAstPropRefs(node: ts.Node, propNames: Set<string>, out: Set<string>): void;
36
+ /**
37
+ * Scope-aware rewrite: parse `text` as an expression, walk it with the
38
+ * binding stack, and splice `replacementFor(name)` onto exactly the
39
+ * identifier references that are (a) in `names` and (b) not shadowed by
40
+ * a binding inside the text. Shorthand properties expand
41
+ * (`{ org }` → `{ org: <replacement> }`) so the result stays
42
+ * syntactically valid — this is the one place in the compiler that
43
+ * correctly distinguishes a value reference from an object-literal key
44
+ * / property-access name / shorthand property / binding name, so every
45
+ * caller doing this class of substitution (prop refs, #1425 branch-local
46
+ * refs, …) should route through this rather than growing its own
47
+ * regex-based text splice (#2856 — a second, regex-based substitution
48
+ * mechanism doesn't know shorthand is simultaneously a key and a value,
49
+ * and drops the key).
50
+ *
51
+ * With `allowStatements`, a `text` that fails to parse as a bare
52
+ * expression is retried as a standalone statement list (no wrapping
53
+ * parens) — for callers whose raw-captured text isn't guaranteed to be
54
+ * expression-shaped (e.g. a `.map()`-callback preamble statement).
55
+ *
56
+ * Returns null when `text` doesn't parse cleanly under either attempt —
57
+ * the caller falls back to its own legacy mechanism.
58
+ */
59
+ export declare function rewriteScopedValueRefs(text: string, names: ReadonlySet<string>, replacementFor: (name: string) => string, opts?: {
60
+ allowStatements?: boolean;
61
+ }): string | null;
36
62
  /**
37
63
  * Apply the targeted regex rewrite for one or more prop names on a
38
64
  * type-stripped expression text. Idempotent under `_p.X` (negative
@@ -1 +1 @@
1
- {"version":3,"file":"prop-rewrite.d.ts","sourceRoot":"","sources":["../src/prop-rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;AAoF3B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GACf,IAAI,CAMN;AA8DD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC1B,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAChE,MAAM,CAqBR;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAChE,MAAM,GAAG,SAAS,CAcpB"}
1
+ {"version":3,"file":"prop-rewrite.d.ts","sourceRoot":"","sources":["../src/prop-rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;AA6F3B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GACf,IAAI,CAMN;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,EAC1B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACxC,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACnC,MAAM,GAAG,IAAI,CAmCf;AA6BD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC1B,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAChE,MAAM,CAqBR;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAChE,MAAM,GAAG,SAAS,CAcpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/jsx",
3
- "version": "0.35.1",
3
+ "version": "0.35.3",
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.35.1"
52
+ "@barefootjs/shared": "0.35.3"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@barefootjs/client": ">=0.2.0",
@@ -124,7 +124,7 @@ const ALLOWLIST: Record<string, Partial<Record<Pattern, number>>> = {
124
124
  // passthrough, called only from `generateNewPropsFunction`'s
125
125
  // `emitStaticChildInstances`), `computeDerivedConstFields` and
126
126
  // `isStringExpr` (same `generateNewPropsFunction` constructor-context
127
- // bucket), and `resolveModuleNumericConst` (guarded in place by
127
+ // bucket), and `resolveModuleConstAsGo` (guarded in place by
128
128
  // `isCurrentLoopItem`/`isOuterLoopParam`, both already `this.scope.lookup`-
129
129
  // backed) — 3 are shape-1 (no live scope in the ctor-generation prepass)
130
130
  // and 1 is already scope-guarded. The 5th, `renderLoop`'s loop-array
@@ -305,19 +305,28 @@ describe('branch-local at element-attribute position (#1414)', () => {
305
305
  expect(hydrate).toContain('foo bar')
306
306
  })
307
307
 
308
- test('branch-local substitution skips string literals and `$`-prefixed identifiers', () => {
308
+ test('branch-local substitution skips string-literal text and correctly resolves a `$`-prefixed identifier', () => {
309
309
  // The substitution machinery used to scan with `text.replace(/\b
310
310
  // (name1|name2)\b/g, ...)`, which would (a) rewrite occurrences
311
311
  // of the name inside string literals into invalid JS and
312
312
  // (b) mis-match `\b` between `$` and a word char, splitting an
313
313
  // identifier like `$kind` and rewriting just its `kind` tail.
314
- // The fix routes both substitution passes through
315
- // `replaceInExprContexts` (skips opaque tokens strings, regex,
316
- // template bodies, comments) and replaces `\b` with `[\w$]`
317
- // lookarounds so the boundary check treats `$` as part of an
318
- // identifier. This test pins both invariants by declaring a
319
- // branch-local whose name collides with a token inside a string
320
- // literal *and* with the tail of a `$`-prefixed identifier.
314
+ // The regex-scanner fallback (`replaceInExprContexts`, still used
315
+ // when text doesn't parse as an expression/statement list) fixes
316
+ // both by skipping opaque tokens strings, regex, template
317
+ // bodies, comments and replacing `\b` with `[\w$]` lookarounds.
318
+ // But it treats an entire template literal as one opaque token,
319
+ // so it can't reach a branch-local reference INSIDE a `${...}`
320
+ // hole either `$kind` used to survive as a literal, undeclared
321
+ // identifier in the emitted output (a latent ReferenceError, the
322
+ // same failure class as #2856). The AST-based
323
+ // `rewriteScopedValueRefs` (#2856) parses the template literal
324
+ // properly and resolves the hole like any other expression
325
+ // position, while still leaving the cooked text (`label:kind=`)
326
+ // alone since it's not an identifier. This test pins a branch-local
327
+ // whose name collides with a token inside a string literal *and*
328
+ // with the tail of a `$`-prefixed identifier used inside a
329
+ // template-literal hole.
321
330
  const source = `
322
331
  'use client'
323
332
  import { createSignal } from '@barefootjs/client'
@@ -345,9 +354,12 @@ describe('branch-local at element-attribute position (#1414)', () => {
345
354
  // verbatim — pre-fix, the `kind` inside the template-literal head
346
355
  // would have been rewritten into `('plain')`, mangling the string.
347
356
  expect(clientJs).toContain('label:kind=')
348
- // The `$kind` identifier must stay intact — pre-fix, `\\bkind\\b`
349
- // would have split `$kind` into `$` + `kind` and rewritten just
350
- // the `kind` tail.
351
- expect(clientJs).toContain('$kind')
357
+ // The `$kind` identifier (distinct from the branch-local `kind`)
358
+ // must resolve to its own branch-local value, not leak through
359
+ // unresolved (which would be a ReferenceError at hydrate — no
360
+ // `$kind` binding exists in the emitted module scope) and not have
361
+ // its `kind` tail mistakenly matched by the `kind` substitution.
362
+ expect(clientJs).not.toMatch(/[^(]\$kind\b/)
363
+ expect(clientJs).toContain("label:kind=${('dollar')}")
352
364
  })
353
365
  })
@@ -22,10 +22,38 @@
22
22
  * about would fire BF021 and never reach the reactive-emission sites this
23
23
  * suite is about.
24
24
  */
25
- import { describe, test, expect } from 'bun:test'
25
+ import { describe, test, expect, beforeAll } from 'bun:test'
26
26
  import { compileJSX } from '../index'
27
27
  import { TestAdapter } from '../adapters/test-adapter'
28
28
  import { ErrorCodes } from '../errors'
29
+ import { getLoweringPlugins } from '../lowering-registry'
30
+ import { BUILTIN_LOWERING_PLUGINS } from '../builtin-lowering-plugins'
31
+
32
+ // #2831 — the persistent, CI-only "test (jsx-2)" flake on this suite's own
33
+ // assertions traced to `date-lowering.test.ts`'s per-test `afterEach`
34
+ // filtering the built-in `'date'` plugin out of the SHARED, module-level
35
+ // `lowering-registry.ts` registry (fixed: dbaba32, "date-lowering.test.ts
36
+ // leaked the built-in 'date' plugin out of the shared registry"). `bun
37
+ // test` runs every file in one process with no guaranteed file order, so a
38
+ // leak in one file's registry manipulation silently starves every
39
+ // LATER-running file that depends on a built-in plugin — this suite's own
40
+ // `datePlugin`/`toLocaleDatePlugin` dependency was the victim. A future
41
+ // leak of the same shape would again fail here as a confusing "lowering
42
+ // wasn't applied" assertion with no clue why. This guard converts that
43
+ // into a named, up-front failure instead.
44
+ beforeAll(() => {
45
+ const registered = new Set(getLoweringPlugins().map((p) => p.name))
46
+ const missing = BUILTIN_LOWERING_PLUGINS.map((p) => p.name).filter((name) => !registered.has(name))
47
+ if (missing.length > 0) {
48
+ throw new Error(
49
+ `Built-in lowering plugin(s) missing from the shared registry before this suite ran: ${missing.join(', ')}. ` +
50
+ `This suite's assertions depend on them being registered — a SIBLING test file most likely reset or ` +
51
+ `filtered the shared \`lowering-registry.ts\` registry without restoring it (the #2831 bug class: use ` +
52
+ `\`beforeAll\`/\`afterAll\` snapshot-and-restore around a describe block, never a per-test \`afterEach\` ` +
53
+ `filter, when touching this shared, module-level registry).`,
54
+ )
55
+ }
56
+ })
29
57
 
30
58
  function compile(src: string) {
31
59
  const result = compileJSX(src.trimStart(), 'T.tsx', { adapter: new TestAdapter() })
@@ -0,0 +1,127 @@
1
+ /**
2
+ * #2798: a static (non-signal) array's `.map()` faithfully invokes a
3
+ * `ref` callback, and reactively updates a signal-derived text, on the
4
+ * TOP-LEVEL row (`buildStaticLoopPlan`'s `childRefs`/`texts`). But a
5
+ * nested `.map()` INSIDE a static outer row's item — a plain element,
6
+ * not a child component — reached `elem.innerLoops` processing only
7
+ * when it had matching depth-N CHILD COMPONENTS
8
+ * (`buildStaticArrayChildInitsPlan`'s `innerComps.length === 0`
9
+ * skip). A plain-element inner loop under a static outer array had
10
+ * NOTHING wired: no ref invocation, no reactive-text/attr effect — the
11
+ * whole row was silently frozen at its SSR-baked value.
12
+ *
13
+ * (The issue's own repro — a TOP-LEVEL static loop with a `ref` — was
14
+ * already fixed by the time this was investigated; that shape is
15
+ * pinned here too as regression armor against the issue's own
16
+ * misdiagnosis being reintroduced.)
17
+ */
18
+ import { describe, test, expect } from 'bun:test'
19
+ import { compileJSX } from '../index'
20
+ import { HonoAdapter } from '../../../adapter-hono/src/adapter/index'
21
+
22
+ function compileClientJs(source: string): string {
23
+ const result = compileJSX(source, 'test.tsx', { adapter: new HonoAdapter() })
24
+ expect(result.errors.filter(e => e.severity === 'error')).toHaveLength(0)
25
+ const clientJs = result.files.find(f => f.type === 'clientJs')
26
+ if (!clientJs) throw new Error('No client JS emitted')
27
+ return clientJs.content
28
+ }
29
+
30
+ describe('static array ref/reactive bindings (#2798)', () => {
31
+ test('top-level static loop still invokes a ref on each row (regression armor)', () => {
32
+ const source = `
33
+ 'use client'
34
+ export function StaticRefCase() {
35
+ const items = [{ id: 1 }, { id: 2 }]
36
+ const trackMount = (el: Element) => { el.setAttribute('data-tracked', '1') }
37
+ return (
38
+ <ul>
39
+ {items.map(item => (
40
+ <li key={item.id} ref={trackMount}>{item.id}</li>
41
+ ))}
42
+ </ul>
43
+ )
44
+ }
45
+ `
46
+ const js = compileClientJs(source)
47
+ expect(js).toContain('(trackMount)(')
48
+ })
49
+
50
+ test('a static outer loop with a nested .map() invokes the inner ref on each row', () => {
51
+ const source = `
52
+ 'use client'
53
+ export function StaticNestedRefCase() {
54
+ const items = [{ id: 1, children: [{ id: 11 }, { id: 12 }] }]
55
+ const trackMount = (el: Element) => { el.setAttribute('data-tracked', '1') }
56
+ return (
57
+ <ul>
58
+ {items.map(item => (
59
+ <li key={item.id}>
60
+ {item.children.map(child => (
61
+ <span key={child.id} ref={trackMount}>{child.id}</span>
62
+ ))}
63
+ </li>
64
+ ))}
65
+ </ul>
66
+ )
67
+ }
68
+ `
69
+ const js = compileClientJs(source)
70
+ // Double forEach (outer item, inner child), with the ref invoked
71
+ // inside the inner one.
72
+ expect(js).toContain('items.forEach((item, __idx) => {')
73
+ expect(js).toContain('item.children.forEach((child, __innerIdx) => {')
74
+ expect(js).toContain('(trackMount)(')
75
+ })
76
+
77
+ test('a static outer loop with a nested .map() reactively updates a signal-derived inner text', () => {
78
+ const source = `
79
+ 'use client'
80
+ import { createSignal } from '@barefootjs/client'
81
+ export function StaticNestedSignalCase() {
82
+ const items = [{ id: 1, children: [{ id: 11 }, { id: 12 }] }]
83
+ const [count] = createSignal(0)
84
+ return (
85
+ <ul>
86
+ {items.map(item => (
87
+ <li key={item.id}>
88
+ {item.children.map(child => (
89
+ <span key={child.id}>{child.id}:{count()}</span>
90
+ ))}
91
+ </li>
92
+ ))}
93
+ </ul>
94
+ )
95
+ }
96
+ `
97
+ const js = compileClientJs(source)
98
+ // Pre-fix, this text was frozen at its SSR-baked value — no
99
+ // `createEffect` at all inside the inner forEach body.
100
+ expect(js).toMatch(/createEffect\(\(\) => \{ __bfw_\w+\('s\d+', String\(count\(\)\)\) \}\)/)
101
+ })
102
+
103
+ test('a static outer loop with a nested .map() wires a reactive attr on the inner element', () => {
104
+ const source = `
105
+ 'use client'
106
+ import { createSignal } from '@barefootjs/client'
107
+ export function StaticNestedAttrCase() {
108
+ const items = [{ id: 1, children: [{ id: 11 }, { id: 12 }] }]
109
+ const [sel] = createSignal(11)
110
+ return (
111
+ <ul>
112
+ {items.map(item => (
113
+ <li key={item.id}>
114
+ {item.children.map(child => (
115
+ <span key={child.id} data-active={child.id === sel()}>{child.id}</span>
116
+ ))}
117
+ </li>
118
+ ))}
119
+ </ul>
120
+ )
121
+ }
122
+ `
123
+ const js = compileClientJs(source)
124
+ expect(js).toContain('data-active')
125
+ expect(js).toMatch(/createEffect\(\(\) => \{/)
126
+ })
127
+ })
@@ -0,0 +1,86 @@
1
+ /**
2
+ * #2856: `replaceBranchLocalRefs` (jsx-to-ir.ts) used to substitute a
3
+ * branch-local's occurrences via a plain regex token scanner
4
+ * (`replaceInExprContexts`), which has no notion of AST position. An
5
+ * object-literal shorthand property (`{ local }`, simultaneously the
6
+ * key and the value) got its substitution wrapped in parens with the
7
+ * key silently dropped — `{ (_p.tag) }` — invalid JS that broke the
8
+ * whole client bundle's parse at runtime with no compile-time
9
+ * diagnostic.
10
+ *
11
+ * The fix routes both call sites through `rewriteScopedValueRefs`
12
+ * (`prop-rewrite.ts`), the same AST-based, scope-aware walk that
13
+ * already handled this exact key/value duality for destructured props
14
+ * (#2828/#2853). These tests pin the general mechanism directly
15
+ * (`rewriteScopedValueRefs`) — shorthand expansion, and the three
16
+ * sibling positions a naive scanner conflates with a value reference:
17
+ * a member-access name, an explicit object-literal key, and a name
18
+ * shadowed by an inner binding. An end-to-end compiler regression for
19
+ * the issue's own repro lives in
20
+ * `rewrite-destructured-props.test.ts` ("branch-local transitive
21
+ * prop-dep via shorthand reference").
22
+ */
23
+ import { describe, test, expect } from 'bun:test'
24
+ import { rewriteScopedValueRefs } from '../prop-rewrite.ts'
25
+
26
+ describe('rewriteScopedValueRefs (#2856)', () => {
27
+ test('expands an object-literal shorthand property to `name: value`', () => {
28
+ const out = rewriteScopedValueRefs('{ local }', new Set(['local']), () => '(_p.tag)')
29
+ expect(out).toBe('{ local: (_p.tag) }')
30
+ })
31
+
32
+ test('does not touch a member-access name that merely shares the branch-local name', () => {
33
+ const out = rewriteScopedValueRefs('obj.local', new Set(['local']), () => '(_p.tag)')
34
+ expect(out).toBe('obj.local')
35
+ })
36
+
37
+ test('does not touch an explicit object-literal key', () => {
38
+ const out = rewriteScopedValueRefs('{ local: 1 }', new Set(['local']), () => '(_p.tag)')
39
+ expect(out).toBe('{ local: 1 }')
40
+ })
41
+
42
+ test('does not substitute a name shadowed by an inner arrow parameter', () => {
43
+ const out = rewriteScopedValueRefs('[1, 2, 3].map((local) => local)', new Set(['local']), () => '(_p.tag)')
44
+ expect(out).toBe('[1, 2, 3].map((local) => local)')
45
+ })
46
+
47
+ test('substitutes a real value reference alongside an unrelated shadowed one', () => {
48
+ const out = rewriteScopedValueRefs(
49
+ 'local + [1, 2, 3].map((local) => local).join(",")',
50
+ new Set(['local']),
51
+ () => '(_p.tag)',
52
+ )
53
+ expect(out).toBe('(_p.tag) + [1, 2, 3].map((local) => local).join(",")')
54
+ })
55
+
56
+ test('does not touch a destructuring declaration\'s source key (pullfrog review, #2889)', () => {
57
+ // `{ local: renamed } = obj` -- `local` here is the SOURCE key of a
58
+ // renamed destructuring binding, not a value reference. Distinct from
59
+ // the shorthand case above: a renamed destructuring pattern has BOTH
60
+ // `propertyName` (the source key) and `name` (the local binding), and
61
+ // only the latter was excluded pre-fix.
62
+ const out = rewriteScopedValueRefs(
63
+ '(el) => { const { local: renamed } = obj; use(renamed) }',
64
+ new Set(['local']),
65
+ () => '(_p.tag)',
66
+ )
67
+ expect(out).toBe('(el) => { const { local: renamed } = obj; use(renamed) }')
68
+ })
69
+
70
+ test('resolves inside a template-literal interpolation hole, leaving the cooked text alone', () => {
71
+ const out = rewriteScopedValueRefs('`label:local=${local}`', new Set(['local']), () => '(_p.tag)')
72
+ expect(out).toBe('`label:local=${(_p.tag)}`')
73
+ })
74
+
75
+ test('returns null for text that does not parse as an expression when allowStatements is not set', () => {
76
+ const out = rewriteScopedValueRefs('const x = local', new Set(['local']), () => '(_p.tag)')
77
+ expect(out).toBeNull()
78
+ })
79
+
80
+ test('with allowStatements, resolves a value reference in statement-shaped text', () => {
81
+ const out = rewriteScopedValueRefs('const x = local', new Set(['local']), () => '(_p.tag)', {
82
+ allowStatements: true,
83
+ })
84
+ expect(out).toBe('const x = (_p.tag)')
85
+ })
86
+ })
@@ -1,4 +1,5 @@
1
1
  import { describe, test, expect } from 'bun:test'
2
+ import ts from 'typescript'
2
3
  import { compileJSX } from '../index'
3
4
  import { HonoAdapter } from '../../../adapter-hono/src/adapter/index'
4
5
 
@@ -153,11 +154,15 @@ export function Comp({ tag }: { tag: string }) {
153
154
  // Pre-fix: the hydrate template's module-scope lambda kept a bare
154
155
  // `tag` reference (no binding at that scope -- ReferenceError at pure
155
156
  // CSR mount). Post-fix: it resolves through `_p.tag` like every other
156
- // prop reference. (The surrounding `{ (_p.tag) }` shape -- the branch-
157
- // local substitution dropping the shorthand's own key -- is a
158
- // separate, pre-existing defect tracked independently; this test pins
159
- // only the prop-dependency question #2828's follow-up fixed.)
157
+ // prop reference, and the shorthand key survives the substitution
158
+ // (#2856 -- the branch-local substitution used to drop it, emitting
159
+ // the invalid `{ (_p.tag) }`).
160
160
  expect(js).not.toContain('{ (tag) }')
161
- expect(js).toContain('{ (_p.tag) }')
161
+ expect(js).not.toContain('{ (_p.tag) }')
162
+ expect(js).toContain('{ local: (_p.tag) }')
163
+ // The emitted client JS must actually parse.
164
+ const sf = ts.createSourceFile('__test.ts', js, ts.ScriptTarget.Latest, true)
165
+ const diagnostics = (sf as unknown as { parseDiagnostics?: unknown[] }).parseDiagnostics
166
+ expect(diagnostics ?? []).toHaveLength(0)
162
167
  })
163
168
  })
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import type { IRLoopChildComponent, MapCallbackPreamble } from '../../types.ts'
21
- import type { ClientJsContext, NestedLoop, TopLevelLoop } from '../types.ts'
21
+ import type { ClientJsContext, LoopChildReactiveAttr, NestedLoop, TopLevelLoop } from '../types.ts'
22
22
  import { quotePropName, varSlotId, attrValueToString, buildLoopChildIndexExpr } from '../utils.ts'
23
23
  import { jsxChildrenPropGetterExpr, renderPreamble, irToHtmlTemplate } from '../html-template.ts'
24
24
 
@@ -34,7 +34,7 @@ function staticPreludeStatements(preamble: MapCallbackPreamble | undefined): str
34
34
  })]
35
35
  : []
36
36
  }
37
- import { buildCompSelector } from '../control-flow/shared.ts'
37
+ import { buildCompSelector, buildStaticChildRefBindings } from '../control-flow/shared.ts'
38
38
 
39
39
  /** The inline prop shape carried on `IRLoopChildComponent.props`. */
40
40
  type LoopChildCompProp = IRLoopChildComponent['props'][number]
@@ -66,25 +66,47 @@ export function buildStaticArrayChildInitsPlan(
66
66
  if (comp.loopDepth) continue // handled in inner-loop pass
67
67
  plans.push(buildOuterNestedPlan(elem, comp))
68
68
  }
69
+ }
70
+
71
+ if (!elem.innerLoops) continue
69
72
 
70
- if (elem.innerLoops) {
71
- for (const innerLoop of elem.innerLoops) {
72
- const innerComps = elem.nestedComponents.filter(c =>
73
- (c.loopDepth ?? 0) === innerLoop.depth && c.innerLoopArray === innerLoop.array,
74
- )
75
- if (innerComps.length === 0) continue
76
- // Component-rooted outer item (#1725): the inner `.map()` lives
77
- // inside the child component's JSX children. The element-offset
78
- // addressing of `inner-loop-nested` can't reach a fragment-rooted
79
- // passthrough's flattened items, so use the document-order zip
80
- // shape instead.
81
- plans.push(
82
- elem.childComponent
83
- ? buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps)
84
- : buildInnerLoopNestedPlan(elem, innerLoop, innerComps),
85
- )
86
- }
73
+ for (const innerLoop of elem.innerLoops) {
74
+ const innerComps = (elem.nestedComponents ?? []).filter(c =>
75
+ (c.loopDepth ?? 0) === innerLoop.depth && c.innerLoopArray === innerLoop.array,
76
+ )
77
+
78
+ if (elem.childComponent) {
79
+ // Component-rooted outer item (#1725): the inner `.map()` lives
80
+ // inside the child component's JSX children. The element-offset
81
+ // addressing of `inner-loop-nested` can't reach a fragment-rooted
82
+ // passthrough's flattened items, so use the document-order zip
83
+ // shape instead. (Plain-element bindings on a component-rooted
84
+ // inner loop aren't addressed by this shape either — out of scope
85
+ // here, same as this shape's existing filter/sort limitation.)
86
+ if (innerComps.length === 0) continue
87
+ plans.push(buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps))
88
+ continue
87
89
  }
90
+
91
+ // Plain-element-rooted outer item: `inner-loop-nested`'s element-offset
92
+ // addressing also wires up a plain element's own reactive attrs/texts/
93
+ // refs inside the inner loop (#2798) — previously this whole pass only
94
+ // ever ran for depth-N CHILD COMPONENTS (gated behind
95
+ // `elem.nestedComponents.length > 0` with an `innerComps.length === 0`
96
+ // skip), so a static outer array's nested `.map()` over plain elements
97
+ // wired up NOTHING: no ref invocation, no reactive text/attr effect,
98
+ // even though the outer row's OWN bindings at this same depth-0 level
99
+ // are faithfully wired (`buildStaticLoopPlan`'s `childRefs`/`texts`/
100
+ // `attrsBySlot`). Scoped to depth 1 — `NestedLoop` has no parent link
101
+ // to thread a second offset through for depth ≥ 2, and the inner
102
+ // array expression there reads the depth-1 param, so a flat emission
103
+ // would `ReferenceError`; deeper nesting stays a known limitation.
104
+ if (innerLoop.depth !== 1) continue
105
+ const hasPlainBindings = innerLoop.bindings.refs.length > 0
106
+ || innerLoop.bindings.reactiveTexts.length > 0
107
+ || innerLoop.bindings.reactiveAttrs.length > 0
108
+ if (innerComps.length === 0 && !hasPlainBindings) continue
109
+ plans.push(buildInnerLoopNestedPlan(elem, innerLoop, innerComps))
88
110
  }
89
111
  }
90
112
 
@@ -147,6 +169,21 @@ function buildInnerLoopNestedPlan(
147
169
  propsExpr: buildStaticPropsExpr(comp.props),
148
170
  }))
149
171
 
172
+ // Plain-element bindings (#2798) — grouped/passed the same way
173
+ // `buildStaticLoopPlan` handles the OUTER row's own bindings: `forEach`
174
+ // binds the param as the raw item value, so texts/attrs stay unwrapped
175
+ // (no signal-accessor rewrite) and refs go through
176
+ // `buildStaticChildRefBindings`, not `buildChildRefBindings`.
177
+ const attrsBySlotMap = new Map<string, LoopChildReactiveAttr[]>()
178
+ for (const attr of innerLoop.bindings.reactiveAttrs) {
179
+ let bucket = attrsBySlotMap.get(attr.childSlotId)
180
+ if (!bucket) {
181
+ bucket = []
182
+ attrsBySlotMap.set(attr.childSlotId, bucket)
183
+ }
184
+ bucket.push(attr)
185
+ }
186
+
150
187
  return {
151
188
  kind: 'inner-loop-nested',
152
189
  containerVar: `_${varSlotId(elem.slotId)}`,
@@ -163,6 +200,9 @@ function buildInnerLoopNestedPlan(
163
200
  innerPreludeStatements: staticPreludeStatements(innerLoop.preamble),
164
201
  depth: innerLoop.depth,
165
202
  comps,
203
+ attrsBySlot: [...attrsBySlotMap].map(([slotId, attrs]) => [slotId, attrs] as const),
204
+ texts: innerLoop.bindings.reactiveTexts,
205
+ refs: buildStaticChildRefBindings(innerLoop.bindings.refs),
166
206
  }
167
207
  }
168
208
 
@@ -7,7 +7,9 @@
7
7
  * - `outer-nested` — depth 0 の `nestedComponents`。outer forEach で
8
8
  * `__iterEl.querySelector(...)` 経由で initChild。
9
9
  * - `inner-loop-nested` — depth > 0 の `nestedComponents`。outer + inner
10
- * forEach の二重ループで initChild
10
+ * forEach の二重ループで initChild。同じ二重ループで
11
+ * プレーン要素の ref / reactive text / reactive attr
12
+ * も配線する (#2798、depth 1 のみ)。
11
13
  * - `component-rooted-inner-loop`
12
14
  * — outer の loop item root が **child component**
13
15
  * (`loop.childComponent`) で、その JSX children に
@@ -24,6 +26,8 @@
24
26
  */
25
27
 
26
28
  import type { PreludeStatements } from '../control-flow/plan/inner-loop.ts'
29
+ import type { LoopChildRefBinding } from '../control-flow/plan/loop.ts'
30
+ import type { LoopChildReactiveAttr, LoopChildReactiveText } from '../types.ts'
27
31
 
28
32
  /** Pre-built `{ name: value, ... }` props object expression. */
29
33
  export type PropsExpr = string
@@ -143,6 +147,20 @@ export interface InnerLoopNestedInitPlan {
143
147
  depth: number
144
148
  /** Per-component initialisers emitted inside the inner forEach body. */
145
149
  comps: readonly InnerLoopComp[]
150
+ /**
151
+ * Reactive attrs on plain elements inside this inner loop's body,
152
+ * grouped by child slot id (#2798 — a static outer array's nested
153
+ * `.map()` over plain elements used to wire up NOTHING: no refs, no
154
+ * reactive text/attr effects, since the whole `elem.innerLoops` pass
155
+ * only ever ran for depth-N CHILD COMPONENTS). Only populated for a
156
+ * depth-1 inner loop under a plain-element-rooted outer item — see
157
+ * `buildStaticArrayChildInitsPlan`'s docstring.
158
+ */
159
+ attrsBySlot: ReadonlyArray<readonly [string, readonly LoopChildReactiveAttr[]]>
160
+ /** Reactive text interpolations inside this inner loop's body (#2798). */
161
+ texts: readonly LoopChildReactiveText[]
162
+ /** Imperative ref callbacks on elements inside this inner loop's body (#2798). */
163
+ refs: readonly LoopChildRefBinding[]
146
164
  }
147
165
 
148
166
  /**