@barefootjs/jsx 0.31.4 → 0.31.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/jsx-adapter.d.ts.map +1 -1
- package/dist/adapters/loop-bound-names.d.ts +18 -0
- package/dist/adapters/loop-bound-names.d.ts.map +1 -1
- package/dist/adapters/test-adapter.d.ts.map +1 -1
- package/dist/augment-inherited-props.d.ts +12 -2
- package/dist/augment-inherited-props.d.ts.map +1 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/free-refs.d.ts +11 -2
- package/dist/free-refs.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +773 -649
- package/dist/ir-to-client-js/client-only-elision.d.ts +11 -5
- package/dist/ir-to-client-js/client-only-elision.d.ts.map +1 -1
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts +7 -0
- package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +16 -0
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +16 -0
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/ir-to-client-js/utils.d.ts +15 -0
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/module-exports.d.ts +64 -0
- package/dist/module-exports.d.ts.map +1 -1
- package/dist/scope/binding-scope.d.ts +1 -1
- package/dist/types.d.ts +112 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +117 -17
- package/src/__tests__/binding-scope-ratchet.test.ts +146 -21
- package/src/__tests__/component-type-parameters.test.ts +70 -0
- package/src/__tests__/csr-materialize-loop-preamble-shadow.test.ts +10 -1
- package/src/__tests__/doc-examples.test.ts +1 -0
- package/src/__tests__/free-refs.test.ts +1 -1
- package/src/__tests__/mutable-binding-writers.test.ts +133 -0
- package/src/__tests__/preamble-conditional-reactivity.test.ts +191 -0
- package/src/__tests__/signal-setter-updater-type.test.ts +81 -0
- package/src/adapters/jsx-adapter.ts +29 -3
- package/src/adapters/loop-bound-names.ts +18 -0
- package/src/adapters/test-adapter.ts +4 -1
- package/src/augment-inherited-props.ts +13 -1
- package/src/compiler.ts +18 -0
- package/src/debug.ts +34 -21
- package/src/free-refs.ts +14 -5
- package/src/index.ts +6 -0
- package/src/ir-to-client-js/client-only-elision.ts +11 -5
- package/src/ir-to-client-js/collect-elements.ts +17 -2
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +1 -0
- package/src/ir-to-client-js/control-flow/plan/reactive-effects.ts +7 -0
- package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +12 -2
- package/src/ir-to-client-js/html-template.ts +51 -0
- package/src/ir-to-client-js/reactivity.ts +4 -2
- package/src/ir-to-client-js/types.ts +16 -0
- package/src/ir-to-client-js/utils.ts +15 -0
- package/src/jsx-to-ir.ts +117 -1
- package/src/module-exports.ts +137 -0
- package/src/scope/binding-scope.ts +1 -1
- package/src/types.ts +118 -0
|
@@ -757,7 +757,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div><button bf="s0"
|
|
|
757
757
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
758
758
|
`;
|
|
759
759
|
|
|
760
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
760
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L143 — ✅ Nested \`.filter()\` / \`.map()\` now compiles everywhere 1`] = `
|
|
761
761
|
"import { createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
|
|
762
762
|
|
|
763
763
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -815,7 +815,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
|
|
|
815
815
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
816
816
|
`;
|
|
817
817
|
|
|
818
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
818
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L150 — ❌ BF101 on Go/Mojo; works on Hono 1`] = `
|
|
819
819
|
"import { createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
|
|
820
820
|
|
|
821
821
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -873,7 +873,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
|
|
|
873
873
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
874
874
|
`;
|
|
875
875
|
|
|
876
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
876
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L153 — ✅ Add /* @client */ to evaluate on the client 1`] = `
|
|
877
877
|
"import { createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
|
|
878
878
|
|
|
879
879
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -927,11 +927,11 @@ export function initExample(__scope, _p = {}) {
|
|
|
927
927
|
|
|
928
928
|
}
|
|
929
929
|
|
|
930
|
-
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"
|
|
930
|
+
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"></div>\` })
|
|
931
931
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
932
932
|
`;
|
|
933
933
|
|
|
934
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
934
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L160 — ❌ BF101 on Go/Mojo; works on Hono 1`] = `
|
|
935
935
|
"import { createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
|
|
936
936
|
|
|
937
937
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -989,7 +989,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
|
|
|
989
989
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
990
990
|
`;
|
|
991
991
|
|
|
992
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
992
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L163 — ✅ Use /* @client */ 1`] = `
|
|
993
993
|
"import { createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
|
|
994
994
|
|
|
995
995
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -1043,11 +1043,11 @@ export function initExample(__scope, _p = {}) {
|
|
|
1043
1043
|
|
|
1044
1044
|
}
|
|
1045
1045
|
|
|
1046
|
-
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"
|
|
1046
|
+
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"></div>\` })
|
|
1047
1047
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1048
1048
|
`;
|
|
1049
1049
|
|
|
1050
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
1050
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L180 — ❌ BF101 on Go/Mojo; works on Hono 1`] = `
|
|
1051
1051
|
"import { createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
|
|
1052
1052
|
|
|
1053
1053
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -1105,7 +1105,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
|
|
|
1105
1105
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1106
1106
|
`;
|
|
1107
1107
|
|
|
1108
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
1108
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L183 — ✅ Use arrow functions for adapter portability 1`] = `
|
|
1109
1109
|
"import { createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
|
|
1110
1110
|
|
|
1111
1111
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -1163,7 +1163,107 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
|
|
|
1163
1163
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1164
1164
|
`;
|
|
1165
1165
|
|
|
1166
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
1166
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L192 — ❌ BF101 on Go/Mojo/Xslate/Twig/ERB/Blade/Jinja/MiniJinja; works on Hono 1`] = `
|
|
1167
|
+
"import { $, createComponent, createEffect, escapeAttr, escapeText, hydrate, lazySlots } from '@barefootjs/client/runtime'
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
export function initReactionBar(__scope, _p = {}) {
|
|
1171
|
+
if (!__scope) return
|
|
1172
|
+
const __scopeId = __scope.getAttribute('bf-s')
|
|
1173
|
+
|
|
1174
|
+
const entries = Object.entries(_p.reactions).filter(([, users]) => users.length > 0)
|
|
1175
|
+
|
|
1176
|
+
const [_s2] = $(__scope, 's2')
|
|
1177
|
+
|
|
1178
|
+
// Reactive texts / CSR materialize in static array children
|
|
1179
|
+
if (_s2) {
|
|
1180
|
+
entries.forEach(([emoji, users], __idx) => {
|
|
1181
|
+
let __iterEl = _s2.children[__idx]
|
|
1182
|
+
if (!__iterEl) {
|
|
1183
|
+
const __tpl = document.createElement('template')
|
|
1184
|
+
__tpl.innerHTML = \`<span data-key="\${escapeAttr(emoji)}"><!--bf:s0-->\${escapeText(emoji)}<!--/-->: <!--bf:s1-->\${escapeText(String(users.length))}<!--/--></span>\`
|
|
1185
|
+
const __cloned = __tpl.content.firstElementChild
|
|
1186
|
+
if (__cloned) {
|
|
1187
|
+
const __anchor = _s2.children[__idx] ?? null
|
|
1188
|
+
_s2.insertBefore(__cloned, __anchor)
|
|
1189
|
+
__iterEl = __cloned
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (__iterEl) {
|
|
1193
|
+
const __bfw_s0 = lazySlots(__iterEl, [{ id: 's0', kind: 'text', path: [] }, { id: 's1', kind: 'text', path: [] }])
|
|
1194
|
+
createEffect(() => { __bfw_s0('s0', String(emoji)) })
|
|
1195
|
+
createEffect(() => { __bfw_s0('s1', String(String(users.length))) })
|
|
1196
|
+
}
|
|
1197
|
+
})
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
hydrate('ReactionBar__b3c36eee', { init: initReactionBar, template: (_p) => \`<div bf="s2"><!--bf-loop:l0-->\${[].map(([emoji, users]) => \`<span data-key="\${escapeAttr(emoji)}"><!--bf:s0-->\${escapeText(emoji)}<!--/-->: <!--bf:s1-->\${escapeText(String(users.length))}<!--/--></span>\`).join('')}<!--bf-/loop:l0--></div>\`, name: 'ReactionBar' })
|
|
1203
|
+
export function ReactionBar(_p, __bfKey) { return createComponent('ReactionBar__b3c36eee', _p, __bfKey) }"
|
|
1204
|
+
`;
|
|
1205
|
+
|
|
1206
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L201 — ✅ Best: precompute the array in the parent/route handler and pass it as a prop 1`] = `
|
|
1207
|
+
"import { $, createComponent, escapeAttr, escapeText, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
export function initReactionBarByProp(__scope, _p = {}) {
|
|
1211
|
+
if (!__scope) return
|
|
1212
|
+
const __scopeId = __scope.getAttribute('bf-s')
|
|
1213
|
+
|
|
1214
|
+
const entries = _p.entries ?? []
|
|
1215
|
+
|
|
1216
|
+
const [_s2] = $(__scope, 's2')
|
|
1217
|
+
|
|
1218
|
+
const __tpl_l0 = document.createElement('template')
|
|
1219
|
+
__tpl_l0.innerHTML = \`<span data-key=""><!--bf:s0--><!--/-->: <!--bf:s1--><!--/--></span>\`
|
|
1220
|
+
const __lzp_l0 = [[0], [3]]
|
|
1221
|
+
const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }, { id: 's1', kind: 'text', path: [] }]
|
|
1222
|
+
const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }, { id: 's1', kind: 'text', path: __lzp_l0[1] }]
|
|
1223
|
+
mapArrayLazy(() => entries, _s2, ([emoji, users]) => String(emoji), {
|
|
1224
|
+
createRow: (__e, __idx) => {
|
|
1225
|
+
const __bfItem = () => __e.item
|
|
1226
|
+
const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
|
|
1227
|
+
const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
|
|
1228
|
+
const __l = __e.last = []
|
|
1229
|
+
{ const __x = __bfItem()[0]
|
|
1230
|
+
__r[0]('s0', textOrNode(__x))
|
|
1231
|
+
__l[0] = __x }
|
|
1232
|
+
{ const __x = String(__bfItem()[1].length)
|
|
1233
|
+
__r[0]('s1', textOrNode(__x))
|
|
1234
|
+
__l[1] = __x }
|
|
1235
|
+
return __el
|
|
1236
|
+
},
|
|
1237
|
+
applyItem: (__e) => {
|
|
1238
|
+
const __bfItem = () => __e.item
|
|
1239
|
+
const __r = __e.refs ?? (__e.refs = [])
|
|
1240
|
+
const __l = __e.last ?? (__e.last = [])
|
|
1241
|
+
const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
|
|
1242
|
+
{ const __x = __bfItem()[0]
|
|
1243
|
+
if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
|
|
1244
|
+
__l[0] = __x }
|
|
1245
|
+
{ const __x = String(__bfItem()[1].length)
|
|
1246
|
+
if (!(1 in __l) || !Object.is(__l[1], __x)) __d('s1', textOrNode(__x))
|
|
1247
|
+
__l[1] = __x }
|
|
1248
|
+
},
|
|
1249
|
+
}, 'l0')
|
|
1250
|
+
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
hydrate('ReactionBarByProp__b3c36eee', { init: initReactionBarByProp, template: (_p) => \`<div bf="s2"><!--bf-loop:l0-->\${_p.entries.map(([emoji, users]) => \`<span data-key="\${escapeAttr(emoji)}"><!--bf:s0-->\${escapeText(emoji)}<!--/-->: <!--bf:s1-->\${escapeText(String(users.length))}<!--/--></span>\`).join('')}<!--bf-/loop:l0--></div>\`, name: 'ReactionBarByProp' })
|
|
1254
|
+
export function ReactionBarByProp(_p, __bfKey) { return createComponent('ReactionBarByProp__b3c36eee', _p, __bfKey) }"
|
|
1255
|
+
`;
|
|
1256
|
+
|
|
1257
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L209 — ✅ Or defer to the client with /* @client */ 1`] = `
|
|
1258
|
+
"import { createComponent, hydrate } from '@barefootjs/client/runtime'
|
|
1259
|
+
|
|
1260
|
+
function initStatementExample() {}
|
|
1261
|
+
|
|
1262
|
+
hydrate('StatementExample', { init: initStatementExample, template: (_p) => \`<div></div>\` })
|
|
1263
|
+
export function StatementExample(_p, __bfKey) { return createComponent('StatementExample', _p, __bfKey) }"
|
|
1264
|
+
`;
|
|
1265
|
+
|
|
1266
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L229 — ✅ Value-producing block bodies normalize (let-inline) and lower everywhere 1`] = `
|
|
1167
1267
|
"import { $, createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, initChild, lazySlots, mapArray, renderChild } from '@barefootjs/client/runtime'
|
|
1168
1268
|
|
|
1169
1269
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -1222,7 +1322,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-
|
|
|
1222
1322
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1223
1323
|
`;
|
|
1224
1324
|
|
|
1225
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
1325
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L239 — ❌ BF021 on Go/Mojo — a JS-runtime target (Hono, CSR) runs the comparator 1`] = `
|
|
1226
1326
|
"import { $, createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, initChild, lazySlots, mapArray, renderChild } from '@barefootjs/client/runtime'
|
|
1227
1327
|
|
|
1228
1328
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -1281,7 +1381,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-
|
|
|
1281
1381
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1282
1382
|
`;
|
|
1283
1383
|
|
|
1284
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
1384
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L244 — ✅ Use /* @client */ 1`] = `
|
|
1285
1385
|
"import { $, createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, initChild, lazySlots, mapArray, renderChild } from '@barefootjs/client/runtime'
|
|
1286
1386
|
|
|
1287
1387
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -1340,7 +1440,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-
|
|
|
1340
1440
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1341
1441
|
`;
|
|
1342
1442
|
|
|
1343
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
1443
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L256 — ❌ BF021 on Go/Mojo — \`byPrice\` is imported, not declared in this file 1`] = `
|
|
1344
1444
|
"import { $, createComponent, createEffect, escapeAttr, escapeText, hydrate, lazySlots, mapArray } from '@barefootjs/client/runtime'
|
|
1345
1445
|
import { byPrice } from './comparators'
|
|
1346
1446
|
|
|
@@ -1368,7 +1468,7 @@ hydrate('SortedList__b3c36eee', { init: initSortedList, template: (_p) => \`<ul
|
|
|
1368
1468
|
export function SortedList(_p, __bfKey) { return createComponent('SortedList__b3c36eee', _p, __bfKey) }"
|
|
1369
1469
|
`;
|
|
1370
1470
|
|
|
1371
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
1471
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L262 — ✅ Use /* @client */, or inline / re-declare the comparator locally 1`] = `
|
|
1372
1472
|
"import { $, createComponent, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, initChild, lazySlots, mapArray, renderChild } from '@barefootjs/client/runtime'
|
|
1373
1473
|
|
|
1374
1474
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -1481,7 +1581,7 @@ export function initExample(__scope, _p = {}) {
|
|
|
1481
1581
|
|
|
1482
1582
|
}
|
|
1483
1583
|
|
|
1484
|
-
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"
|
|
1584
|
+
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"></div>\` })
|
|
1485
1585
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1486
1586
|
`;
|
|
1487
1587
|
|
|
@@ -1539,7 +1639,7 @@ export function initExample(__scope, _p = {}) {
|
|
|
1539
1639
|
|
|
1540
1640
|
}
|
|
1541
1641
|
|
|
1542
|
-
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"
|
|
1642
|
+
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"></div>\` })
|
|
1543
1643
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1544
1644
|
`;
|
|
1545
1645
|
|
|
@@ -1597,7 +1697,7 @@ export function initExample(__scope, _p = {}) {
|
|
|
1597
1697
|
|
|
1598
1698
|
}
|
|
1599
1699
|
|
|
1600
|
-
hydrate('Example', { init: initExample, template: (_p) => \`<div><strong bf="s1"
|
|
1700
|
+
hydrate('Example', { init: initExample, template: (_p) => \`<div><strong bf="s1"></strong></div>\` })
|
|
1601
1701
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
1602
1702
|
`;
|
|
1603
1703
|
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* pattern covers both.
|
|
13
13
|
* Modeled in spirit on `map-body-no-silent-divergence.test.ts`'s known-hole
|
|
14
14
|
* ledger: a known-inventory of the current reality that may only shrink as
|
|
15
|
-
* Stages 1-4 migrate call sites onto `BindingScope`, never grow.
|
|
15
|
+
* Stages 1-4 migrate call sites onto `BindingScope`, never grow. Stage 4
|
|
16
|
+
* drove this to its FLOOR — every remaining entry is a permanent, justified
|
|
17
|
+
* exception (see the `ALLOWLIST` doc comment below), not leftover debt.
|
|
16
18
|
*
|
|
17
19
|
* Scans every `.ts` file under `packages/jsx/src/` and each `packages/adapter-X/src/`
|
|
18
20
|
* (excluding `__tests__` directories, `*.test.ts` files, and
|
|
@@ -58,44 +60,167 @@ const PATTERNS = [
|
|
|
58
60
|
type Pattern = (typeof PATTERNS)[number]
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
|
-
* Exact per-file, per-pattern occurrence counts
|
|
62
|
-
*
|
|
63
|
-
*
|
|
63
|
+
* Exact per-file, per-pattern occurrence counts, updated through #2482
|
|
64
|
+
* Stage 4 (the FLOOR — see that stage's PR for the file-by-file
|
|
65
|
+
* MIGRATE-or-FLOOR review). Only ever shrinks — see header comment. A
|
|
66
|
+
* missing file or missing pattern key means an expected count of 0.
|
|
67
|
+
*
|
|
68
|
+
* ============================== FLOOR INVARIANT ==============================
|
|
69
|
+
* Every entry below is a PERMANENT, JUSTIFIED exception, not leftover debt —
|
|
70
|
+
* each one is annotated with why it can't (or shouldn't) thread a live
|
|
71
|
+
* `BindingScope` instead. Two shapes recur across the justifications:
|
|
72
|
+
*
|
|
73
|
+
* 1. NO LIVE SCOPE TO CONSULT: a prepass that runs once at `generate()` /
|
|
74
|
+
* constructor-generation time, OUTSIDE the render-time (or
|
|
75
|
+
* render-shaped) tree walk `BindingScope` is threaded through — there is
|
|
76
|
+
* no position to ask "what's bound HERE" about, so these fall back to a
|
|
77
|
+
* coarser, deliberately over-inclusive whole-component or whole-file
|
|
78
|
+
* scan (safe because over-exclusion only ever degrades to the
|
|
79
|
+
* already-accepted pre-#2482 residual, never to silently wrong output).
|
|
80
|
+
* 2. UNRELATED DOMAIN: a `localConstants.find(` matching the ledger's
|
|
81
|
+
* textual pattern by coincidence — const/prop resolution for SSR
|
|
82
|
+
* dead-code elimination, `Record[key]` lookup, cross-IR CSS class
|
|
83
|
+
* resolution, component-scope const-chain inlining — none of which ask
|
|
84
|
+
* "is this name shadowed by a loop callback param" at all, so
|
|
85
|
+
* `BindingScope` (a loop-row/callback binding stack) has no bearing on
|
|
86
|
+
* them.
|
|
87
|
+
* 3. ACCESSOR/REWRITE PAYLOAD, NOT AN EXISTENCE QUERY: `BindingScope` only
|
|
88
|
+
* carries binding EXISTENCE/kind/depth — never per-binding rendering
|
|
89
|
+
* payload (a Go template accessor string, an ordered client-JS
|
|
90
|
+
* accessor-rewrite spec). The Go adapter's `loopBindingStack` and the
|
|
91
|
+
* client-JS emitter's `loopParams: ReadonlyArray<string | LoopParamSpec>`
|
|
92
|
+
* (`ir-to-client-js/utils.ts`'s `wrapExprWithLoopParams` and its
|
|
93
|
+
* `collect-elements.ts`/`html-template.ts`/`build-event-delegation.ts`
|
|
94
|
+
* callers) are this shape — a `BindingScope`-adjacent but structurally
|
|
95
|
+
* distinct concern.
|
|
96
|
+
*
|
|
97
|
+
* New code must use `BindingScope` (`packages/jsx/src/scope/binding-scope.ts`)
|
|
98
|
+
* for any "is this name bound by an enclosing loop callback" question. Per
|
|
99
|
+
* the mechanics already documented below: entries may only shrink (or be
|
|
100
|
+
* removed outright when a file reaches 0), never grow — a grown or added
|
|
101
|
+
* entry is a regression to flag in review, not to allowlist away.
|
|
102
|
+
* ===============================================================================
|
|
64
103
|
*/
|
|
65
104
|
const ALLOWLIST: Record<string, Partial<Record<Pattern, number>>> = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
105
|
+
// FLOOR (shape 1, already guarded): `expandDynamicPropValue`'s
|
|
106
|
+
// `this.scope`-mirroring shadow guard (`erb-adapter.ts`'s own
|
|
107
|
+
// `!this.scope.isBound(trimmed)` check, #2489) precedes this `.find(` —
|
|
108
|
+
// the lookup itself is the legitimate const-resolution step once the
|
|
109
|
+
// shadow question is already answered by `scope`.
|
|
110
|
+
'packages/adapter-erb/src/adapter/erb-adapter.ts': { 'localConstants.find(': 1 },
|
|
111
|
+
// FLOOR (shape 2/3 boundary): `inlineLocalHelperCall` resolves a call's
|
|
112
|
+
// CALLEE name against module/component consts to inline a local
|
|
113
|
+
// arrow-helper's body (`sortClass(k)` → its substituted expression) — a
|
|
114
|
+
// "does a helper by this name exist" lookup, not itself a shadow guard.
|
|
115
|
+
// Flagged for the human maintainer (see Stage 4 report): unlike the
|
|
116
|
+
// sibling `litConst` fast path directly above its call site in
|
|
117
|
+
// `go-template-adapter.ts` (which DOES guard with `isLoopShadowedName`),
|
|
118
|
+
// this call site has no such guard — a `.map((sortClass) => ...)`
|
|
119
|
+
// shadowing a same-named module helper is a narrow, unverified gap left
|
|
120
|
+
// for a follow-up rather than fixed speculatively here.
|
|
69
121
|
'packages/adapter-go-template/src/adapter/expr/helper-inline.ts': { 'localConstants.find(': 1 },
|
|
70
122
|
'packages/adapter-go-template/src/adapter/go-template-adapter.ts': {
|
|
123
|
+
// FLOOR: of the 5 `.find(` sites — `resolveDynamicPropValue` (child-prop
|
|
124
|
+
// passthrough, called only from `generateNewPropsFunction`'s
|
|
125
|
+
// `emitStaticChildInstances`), `computeDerivedConstFields` and
|
|
126
|
+
// `isStringExpr` (same `generateNewPropsFunction` constructor-context
|
|
127
|
+
// bucket), and `resolveModuleNumericConst` (guarded in place by
|
|
128
|
+
// `isCurrentLoopItem`/`isOuterLoopParam`, both already `this.scope.lookup`-
|
|
129
|
+
// backed) — 3 are shape-1 (no live scope in the ctor-generation prepass)
|
|
130
|
+
// and 1 is already scope-guarded. The 5th, `renderLoop`'s loop-array
|
|
131
|
+
// const lookup, gained a `!this.scope.isBound(arrayName)` guard in
|
|
132
|
+
// Stage 4 (a real gap: an enclosing loop's own item param could shadow
|
|
133
|
+
// a same-named module const and misfire a BF101) — MIGRATED in place,
|
|
134
|
+
// the `.find(` call itself stays as the legitimate lookup once shadow
|
|
135
|
+
// is ruled out.
|
|
71
136
|
'localConstants.find(': 5,
|
|
72
|
-
|
|
73
|
-
|
|
137
|
+
// #2482 Stage 3: `loopParamStack` eliminated entirely (0, down from 35)
|
|
138
|
+
// — replaced by the threaded `this.scope: BindingScope`. The remaining
|
|
139
|
+
// 2 `staticLoopSourceBoundNames` uses (down from 3) are the
|
|
140
|
+
// `getBakedStaticChildLoop` shadow guard shared with two call sites
|
|
141
|
+
// OUTSIDE the live `renderLoop` tree walk (no live `scope` to consult
|
|
142
|
+
// there — shape 1) — a genuinely-legitimate surviving use, confirmed
|
|
143
|
+
// FLOOR in Stage 4 (`primeCompileState`'s own comment documents the
|
|
144
|
+
// three-call-site agreement requirement).
|
|
145
|
+
staticLoopSourceBoundNames: 2,
|
|
74
146
|
},
|
|
75
|
-
|
|
147
|
+
// FLOOR (shape 1 for the field; the doc-only `loopParamStack` mention this
|
|
148
|
+
// file's top-level comment used to carry was reworded in Stage 4 — that
|
|
149
|
+
// stack no longer exists anywhere, so the ledger's last trace of it is
|
|
150
|
+
// gone too).
|
|
151
|
+
'packages/adapter-go-template/src/adapter/lib/compile-state.ts': { staticLoopSourceBoundNames: 1 },
|
|
152
|
+
// FLOOR (shape 1): `lowerCtorExpr`/`lowerCtorStringArray` lower a derived-
|
|
153
|
+
// state memo's computation into Go CODE evaluated in the `NewXxxProps`
|
|
154
|
+
// constructor — the same no-live-scope bucket as `go-template-adapter.ts`'s
|
|
155
|
+
// ctor-context `.find(` calls above.
|
|
76
156
|
'packages/adapter-go-template/src/adapter/memo/ctor-lowering.ts': { 'localConstants.find(': 3 },
|
|
157
|
+
// FLOOR (shape 1): `packageModuleConst` — same `NewXxxProps` ctor-context
|
|
158
|
+
// bucket as `ctor-lowering.ts`.
|
|
77
159
|
'packages/adapter-go-template/src/adapter/memo/memo-value.ts': { 'localConstants.find(': 1 },
|
|
78
|
-
|
|
160
|
+
// FLOOR (shape 1, already guarded): same `this.scope.isBound(trimmed)`
|
|
161
|
+
// shadow guard as `erb-adapter.ts` (#2221 — `resolveLiteralConst` /
|
|
162
|
+
// `resolveStaticRecordLiteral`'s established pattern).
|
|
79
163
|
'packages/adapter-mojolicious/src/adapter/mojo-adapter.ts': {
|
|
80
164
|
'localConstants.find(': 1,
|
|
81
|
-
staticLoopSourceBoundNames: 1,
|
|
82
|
-
loopBoundNames: 23,
|
|
83
165
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
166
|
+
// FLOOR (shape 2): `generateSignalInitializers`'s reachability analysis
|
|
167
|
+
// for SSR no-op initializer dead-code elimination — module/component-level
|
|
168
|
+
// declaration reachability, unrelated to loop-row shadowing entirely.
|
|
87
169
|
'packages/jsx/src/adapters/jsx-adapter.ts': { 'localConstants.find(': 1 },
|
|
170
|
+
// FLOOR (shape 2): resolves a `Record<T,string>[key]`-shaped indexed
|
|
171
|
+
// lookup's IDENT operand to a module-scope object-literal const for
|
|
172
|
+
// `renderToTest`'s union-semantics resolution — a component-level static
|
|
173
|
+
// analysis pass, not a loop-row shadow question.
|
|
88
174
|
'packages/jsx/src/augment-inherited-props.ts': { 'localConstants.find(': 1 },
|
|
175
|
+
// FLOOR (shape 2): cross-IR CSS class-const resolution for the UnoCSS
|
|
176
|
+
// layer prefixer's transitive-reference walk — operates across whole
|
|
177
|
+
// components' `localConstants` lists, no loop-row scope involved.
|
|
89
178
|
'packages/jsx/src/css-layer-prefixer.ts': { 'localConstants.find(': 1 },
|
|
90
|
-
'packages/jsx/src/
|
|
91
|
-
|
|
179
|
+
'packages/jsx/src/free-refs.ts': {
|
|
180
|
+
// FLOOR (shape 2): `resolveConstantInitializerRefs`'s transitive-taint
|
|
181
|
+
// expansion — resolves a local constant's OWN initializer by name to
|
|
182
|
+
// recurse into its free refs, not a loop-row shadow check.
|
|
183
|
+
'localConstants.find(': 1,
|
|
184
|
+
// FLOOR: the `BindingEnvironment.loopParams` field itself was RENAMED
|
|
185
|
+
// (Stage 4) to `loopValueBoundNames` (it carries `scope.valueBoundNames()`
|
|
186
|
+
// — the field name now says so). This single remaining occurrence is the
|
|
187
|
+
// rename's own historical-name mention in `loopValueBoundNames`'s
|
|
188
|
+
// docstring, matching the established convention elsewhere (e.g.
|
|
189
|
+
// `binding-scope.ts`'s own header keeps six `ctx.loopParams` mentions as
|
|
190
|
+
// migration history).
|
|
191
|
+
loopParams: 1,
|
|
192
|
+
},
|
|
193
|
+
// FLOOR (shape 3): `irToPlaceholderTemplate`'s loop-param accessor-rewrite
|
|
194
|
+
// spec forwarding — see the canonical docstring on
|
|
195
|
+
// `wrapExprWithLoopParams` in `ir-to-client-js/utils.ts`.
|
|
92
196
|
'packages/jsx/src/ir-to-client-js/collect-elements.ts': { loopParams: 9 },
|
|
197
|
+
// FLOOR (shape 2): `computeCsrInlinability`'s fixed-point constant-chain
|
|
198
|
+
// inlining loop over `ctx.localConstants` — component-scope const
|
|
199
|
+
// resolution, not loop-row scope.
|
|
93
200
|
'packages/jsx/src/ir-to-client-js/compute-inlinability.ts': { 'localConstants.find(': 1 },
|
|
201
|
+
// FLOOR (shape 3): event-delegation plan building deliberately does NOT
|
|
202
|
+
// pass a loop-param accessor-rewrite spec (see the file's own comments) —
|
|
203
|
+
// the remaining mentions are all in that reasoning, not device usage.
|
|
94
204
|
'packages/jsx/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts': { loopParams: 4 },
|
|
95
|
-
|
|
205
|
+
// FLOOR (shape 3): `irToHtmlTemplate`/`irToPlaceholderTemplate`'s
|
|
206
|
+
// loop-param accessor-rewrite spec — see `ir-to-client-js/utils.ts`'s
|
|
207
|
+
// `wrapExprWithLoopParams` docstring (the canonical explanation, pointed
|
|
208
|
+
// to from this file's own function signature since Stage 4).
|
|
209
|
+
'packages/jsx/src/ir-to-client-js/html-template.ts': { loopParams: 17 },
|
|
210
|
+
// FLOOR (shape 1, already guarded): both `expandDynamicPropValue` and
|
|
211
|
+
// `expandConstantForReactivity` precede their `.find(` with
|
|
212
|
+
// `scope?.isBound(trimmedValue)` — see this file's own header comment
|
|
213
|
+
// (added Stage 1b) for the full SHADOW GUARD reasoning.
|
|
96
214
|
'packages/jsx/src/ir-to-client-js/prop-handling.ts': { 'localConstants.find(': 2 },
|
|
97
|
-
|
|
98
|
-
|
|
215
|
+
// FLOOR (shape 3): `wrapExprWithLoopParams` / `LoopParamSpec` — the
|
|
216
|
+
// canonical definition of the accessor-rewrite payload every other
|
|
217
|
+
// `loopParams`-named parameter in `ir-to-client-js/` forwards. See its
|
|
218
|
+
// docstring (added Stage 4) for the full shape-3 reasoning.
|
|
219
|
+
'packages/jsx/src/ir-to-client-js/utils.ts': { loopParams: 4 },
|
|
220
|
+
// `jsx-to-ir.ts` — MIGRATED to 0 in Stage 4: `makeBindingEnv`'s
|
|
221
|
+
// `loopParams: boundNames` field now reads `loopValueBoundNames:
|
|
222
|
+
// boundNames`, matching `free-refs.ts`'s renamed field. No entry needed
|
|
223
|
+
// (a missing key means an expected count of 0).
|
|
99
224
|
}
|
|
100
225
|
|
|
101
226
|
const SCOPE_MODULE_DIR = join(REPO_ROOT, 'packages', 'jsx', 'src', 'scope')
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #2573 (xyflow TS2304 family): a component function's
|
|
3
|
+
* own generic type parameters (`function Flow<NodeType, EdgeType>(...)`)
|
|
4
|
+
* were dropped from the emitted `.tsx` SSR template — the function
|
|
5
|
+
* signature came out as `function Flow(...)` even though its props type
|
|
6
|
+
* annotation (and often its body) kept referencing `NodeType`/`EdgeType`
|
|
7
|
+
* verbatim (`props: FlowComponentProps<NodeType, EdgeType>`,
|
|
8
|
+
* `createFlowStore<NodeType, EdgeType>(props)`). TypeScript then reported
|
|
9
|
+
* "Cannot find name 'NodeType'" (TS2304) at every such reference — the
|
|
10
|
+
* declaration that would have brought the name into scope was never
|
|
11
|
+
* emitted. Runtime output (client JS) was always correct; this is a
|
|
12
|
+
* type-level emission defect in the SSR template only.
|
|
13
|
+
*
|
|
14
|
+
* `IRMetadata.typeParameters` now carries the source's type parameter list
|
|
15
|
+
* verbatim (`node.getText()` per parameter, mirroring `ConstantInfo.
|
|
16
|
+
* typeAnnotation`'s #2589 precedent), and `HonoAdapter`/`TestAdapter` splice
|
|
17
|
+
* it between the function name and the parameter list.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, test, expect } from 'bun:test'
|
|
21
|
+
import { compileJSX } from '../compiler'
|
|
22
|
+
import { HonoAdapter } from '../../../../packages/adapter-hono/src/adapter/hono-adapter'
|
|
23
|
+
|
|
24
|
+
describe('component type parameter preservation in emitted templates (#2573)', () => {
|
|
25
|
+
test('a generic function component keeps its type parameters on the emitted signature', () => {
|
|
26
|
+
const honoAdapter = new HonoAdapter()
|
|
27
|
+
const source = `
|
|
28
|
+
interface NodeBase { id: string }
|
|
29
|
+
interface EdgeBase { id: string }
|
|
30
|
+
interface FlowProps<NodeType extends NodeBase, EdgeType extends EdgeBase> {
|
|
31
|
+
nodes: NodeType[]
|
|
32
|
+
edges: EdgeType[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function Flow<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
|
36
|
+
props: FlowProps<NodeType, EdgeType>,
|
|
37
|
+
) {
|
|
38
|
+
const count = props.nodes.length + props.edges.length
|
|
39
|
+
return <div>{count}</div>
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
const result = compileJSX(source, 'Flow.tsx', { adapter: honoAdapter })
|
|
44
|
+
expect(result.errors).toHaveLength(0)
|
|
45
|
+
|
|
46
|
+
const template = result.files.find((f) => f.type === 'markedTemplate')!
|
|
47
|
+
expect(template).toBeDefined()
|
|
48
|
+
expect(template.content).toContain(
|
|
49
|
+
'function Flow<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(',
|
|
50
|
+
)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('a non-generic function component gains no type parameter clause', () => {
|
|
54
|
+
const honoAdapter = new HonoAdapter()
|
|
55
|
+
const source = `
|
|
56
|
+
interface CardProps { title: string }
|
|
57
|
+
export function Card(props: CardProps) {
|
|
58
|
+
return <div>{props.title}</div>
|
|
59
|
+
}
|
|
60
|
+
`
|
|
61
|
+
|
|
62
|
+
const result = compileJSX(source, 'Card.tsx', { adapter: honoAdapter })
|
|
63
|
+
expect(result.errors).toHaveLength(0)
|
|
64
|
+
|
|
65
|
+
const template = result.files.find((f) => f.type === 'markedTemplate')!
|
|
66
|
+
expect(template).toBeDefined()
|
|
67
|
+
expect(template.content).toContain('function Card(')
|
|
68
|
+
expect(template.content).not.toMatch(/function Card</)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -69,7 +69,16 @@ describe('CSR materialize template vs .map() preamble-local shadowing a module c
|
|
|
69
69
|
|
|
70
70
|
// The row-local `label` (the preamble's OWN computed value) must
|
|
71
71
|
// drive the branch — never the outer module const's literal.
|
|
72
|
-
|
|
72
|
+
//
|
|
73
|
+
// The branch also carries a `bf-c="s0"` slot marker on both arms
|
|
74
|
+
// (#2596 follow-up): `label`'s initializer reads the real signal `on()`,
|
|
75
|
+
// so `markPreambleConditionalReactivity` now grants this conditional a
|
|
76
|
+
// slot id and the `reactive` flag it lacked before — the SAME
|
|
77
|
+
// observable-failure-mode class this file's docstring describes
|
|
78
|
+
// ("every row's branch condition evaluated the constant's fixed
|
|
79
|
+
// truthiness"), just for the signal dimension instead of the shadowing
|
|
80
|
+
// one. Nothing else about this fixture's shape changed.
|
|
81
|
+
expect(tpl).toContain('label ? `<span bf-c="s0">yes</span>` : `<span bf-c="s0">no</span>`')
|
|
73
82
|
expect(tpl).not.toContain("('MODULE_CONST')")
|
|
74
83
|
expect(tpl).not.toContain('MODULE_CONST')
|
|
75
84
|
})
|
|
@@ -460,6 +460,7 @@ const ERROR_CODES_DOC_TOO_MINIMAL: Record<string, string> = {
|
|
|
460
460
|
BF021: 'BF021 has multiple ❌ snippets in placeholder form (`.map(...)`) — covered concretely by `jsx-compatibility.md` tests; the doc-form snippets here remain placeholder',
|
|
461
461
|
BF043: 'BF043 fires only for STATEFUL components (signals/memos/effects). The doc snippet `function Child({ count }: Props)` has no reactivity, so the check is correctly silent — doc snippet needs enrichment to be testable as-is',
|
|
462
462
|
BF044: 'BF044 fires only when a real signal getter is bound. The doc snippet `<Child count={count} />` references an undeclared `count`, so the check is silent — doc snippet needs enrichment',
|
|
463
|
+
BF101: 'BF101 is raised by the non-JS template adapters\' own per-adapter checks (Go/Mojo/Xslate/… `renderLoop` / callback lowering), not by the shared `isSupported` gate every adapter shares — `TestAdapter` here is JS-runtime-style like Hono and executes both doc snippets verbatim, so the check is correctly silent. Covered concretely (compiled against the real 8 template adapters, with verbatim diagnostic text) by `jsx-compatibility.md`\'s doc-examples coverage and the `filter-nested-callback-predicate` / `filter-nested-find-predicate` / `static-array-from-props` adapter-conformance fixtures.',
|
|
463
464
|
}
|
|
464
465
|
|
|
465
466
|
// BFxxx codes that `errors.ts` defines but no production code in
|
|
@@ -110,7 +110,7 @@ describe('resolveFreeRefs — kind resolution', () => {
|
|
|
110
110
|
const env = {
|
|
111
111
|
...emptyEnv(),
|
|
112
112
|
localConstants: [mkConst('item')],
|
|
113
|
-
|
|
113
|
+
loopValueBoundNames: new Set(['item']),
|
|
114
114
|
}
|
|
115
115
|
const refs = resolveFreeRefs(parseExpression('item'), env)
|
|
116
116
|
expect(refs.find(r => r.name === 'item')?.kind).toBe('render-item')
|