@barefootjs/rust 0.31.3 → 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.
- package/dist/adapter/index.js +12 -43
- package/dist/adapter/minijinja-adapter.d.ts +33 -37
- package/dist/adapter/minijinja-adapter.d.ts.map +1 -1
- package/dist/index.js +12 -43
- package/dist/vite.js +237 -271
- package/package.json +5 -5
- package/src/__tests__/minijinja-adapter-unit.test.ts +58 -20
- package/src/adapter/minijinja-adapter.ts +77 -87
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/rust",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.5",
|
|
4
4
|
"description": "minijinja (Rust) adapter for BarefootJS — compiles IR to .j2 templates and ships a Rust rendering runtime (packages/adapter-rust/runtime/); runs under any Rust web framework (axum, etc.)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"directory": "packages/adapter-rust"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@barefootjs/shared": "0.31.
|
|
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.
|
|
75
|
-
"@barefootjs/vite": "0.31.
|
|
76
|
-
"@barefootjs/client": "0.31.
|
|
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
|
}
|
|
@@ -416,10 +416,11 @@ export function Parent() {
|
|
|
416
416
|
// `ir.metadata.localConstants` with no notion of AST scope — it used to
|
|
417
417
|
// substitute an outer const's literal value even at an occurrence that is
|
|
418
418
|
// actually an enclosing loop callback's own (shadowing) parameter, so every
|
|
419
|
-
// iteration rendered the same hard-coded literal. Guarded
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
419
|
+
// iteration rendered the same hard-coded literal. Guarded via the threaded,
|
|
420
|
+
// position-accurate `BindingScope` (#2482 Stage 2 — previously a coarse
|
|
421
|
+
// whole-component exclusion, same as #2212's, that also suppressed a
|
|
422
|
+
// genuinely non-shadowed occurrence outside the loop; the threaded scope
|
|
423
|
+
// only shadows AT the enclosing loop's own body).
|
|
423
424
|
describe('MinijinjaAdapter - const inlining vs loop-param shadowing (#2221)', () => {
|
|
424
425
|
test('a loop param shadowing an outer literal const emits the identifier, not the const value', () => {
|
|
425
426
|
const { template } = compileAndGenerate(`
|
|
@@ -458,11 +459,11 @@ function Widget({ values }: { values: number[] }) {
|
|
|
458
459
|
expect(template).toContain('1 + 5')
|
|
459
460
|
})
|
|
460
461
|
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
test('a const referenced outside the loop whose name is loop-bound elsewhere
|
|
462
|
+
// Position-accurate scope (#2482 Stage 2): a name that is loop-bound
|
|
463
|
+
// elsewhere in the component but NOT at THIS occurrence still inlines
|
|
464
|
+
// here — the coarse whole-component exclusion this used to hit (and
|
|
465
|
+
// accept as a trade-off) is gone; only the loop's own body shadows.
|
|
466
|
+
test('a const referenced outside the loop whose name is loop-bound elsewhere still inlines outside, stays the identifier inside', () => {
|
|
466
467
|
const { template } = compileAndGenerate(`
|
|
467
468
|
function Widget({ values }: { values: number[] }) {
|
|
468
469
|
const label: string = 'x'
|
|
@@ -472,7 +473,7 @@ function Widget({ values }: { values: number[] }) {
|
|
|
472
473
|
</div>
|
|
473
474
|
}
|
|
474
475
|
`)
|
|
475
|
-
expect(template).
|
|
476
|
+
expect(template).toContain("1 + 'x'")
|
|
476
477
|
expect(template).toContain('2 + label')
|
|
477
478
|
})
|
|
478
479
|
})
|
|
@@ -484,9 +485,8 @@ function Widget({ values }: { values: number[] }) {
|
|
|
484
485
|
// substitute the outer const's member value even at an occurrence that is
|
|
485
486
|
// actually an enclosing loop callback's own (shadowing) parameter, so every
|
|
486
487
|
// iteration rendered the same hard-coded literal instead of the per-item
|
|
487
|
-
// value. Guarded
|
|
488
|
-
//
|
|
489
|
-
// never inlines, falling back to the bare `cfg.x` member expression.
|
|
488
|
+
// value. Guarded via the threaded, position-accurate `BindingScope` (#2482
|
|
489
|
+
// Stage 2), same as #2221's fix above.
|
|
490
490
|
describe('MinijinjaAdapter - record-literal member lookup vs loop-param shadowing (#2237)', () => {
|
|
491
491
|
test('a loop param shadowing an outer module object const emits the member access, not the outer literal', () => {
|
|
492
492
|
const { template } = compileAndGenerate(`
|
|
@@ -511,12 +511,12 @@ function Widget({ variant }: { variant: 'solid' | 'ghost' }) {
|
|
|
511
511
|
expect(template).toContain("bf.string('bg-ghost')")
|
|
512
512
|
})
|
|
513
513
|
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
// inlines its member
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
test('a record member referenced outside the loop whose object name is loop-bound elsewhere
|
|
514
|
+
// Position-accurate scope (#2482 Stage 2): an object name that is
|
|
515
|
+
// loop-bound elsewhere in the component but NOT at THIS occurrence still
|
|
516
|
+
// inlines its member lookup here — the coarse whole-component exclusion
|
|
517
|
+
// this used to hit (and accept as a trade-off) is gone; only the loop's
|
|
518
|
+
// own body shadows.
|
|
519
|
+
test('a record member referenced outside the loop whose object name is loop-bound elsewhere still inlines outside, stays the member expression inside', () => {
|
|
520
520
|
const { template } = compileAndGenerate(`
|
|
521
521
|
const cfg = { x: 'outer-lit' }
|
|
522
522
|
function Widget({ rows }: { rows: { x: string }[] }) {
|
|
@@ -526,11 +526,49 @@ function Widget({ rows }: { rows: { x: string }[] }) {
|
|
|
526
526
|
</div>
|
|
527
527
|
}
|
|
528
528
|
`)
|
|
529
|
-
expect(template).
|
|
529
|
+
expect(template).toContain("bf.string('outer-lit')")
|
|
530
530
|
expect(template).toContain('bf.string(cfg.x)')
|
|
531
531
|
})
|
|
532
532
|
})
|
|
533
533
|
|
|
534
|
+
// Copilot review on #2600 (#2482 Stage 2 follow-up): the position-accurate
|
|
535
|
+
// `this.scope` (replacing the old coarse `staticLoopSourceBoundNames` set)
|
|
536
|
+
// was only threaded around `renderChildren(loop.children)` in `renderLoop`
|
|
537
|
+
// — narrower than the coarse set's always-on coverage. Two row-context
|
|
538
|
+
// conversions sit OUTSIDE that window: a `.map()` preamble local's own
|
|
539
|
+
// initializer, and the whole-item-conditional `loop-i:` key anchor
|
|
540
|
+
// (`loop.key`). Both genuinely evaluate PER ROW and must see the row's own
|
|
541
|
+
// bindings — a loop param shadowing a same-named outer const must resolve
|
|
542
|
+
// to the row value there too, exactly as it already does inside the loop
|
|
543
|
+
// body proper. `renderLoop` now enters the row scope before converting
|
|
544
|
+
// either and pops it after, closing the gap.
|
|
545
|
+
describe('MinijinjaAdapter - row scope covers preamble/key conversions outside renderChildren (#2482 Stage 2 Copilot follow-up)', () => {
|
|
546
|
+
test('a loop param shadowing an outer const resolves to the row value inside a whole-item-conditional loop-i: key anchor', () => {
|
|
547
|
+
const { template } = compileAndGenerate(`
|
|
548
|
+
function Widget({ values }: { values: number[] }) {
|
|
549
|
+
const label: string = 'x'
|
|
550
|
+
return <ul>{values.map((label) => (label > 0 ? <li key={label}>{label}</li> : null))}</ul>
|
|
551
|
+
}
|
|
552
|
+
`)
|
|
553
|
+
expect(template).toContain('bf.comment("loop-i:" ~ bf.string(label))')
|
|
554
|
+
expect(template).not.toContain("bf.comment(\"loop-i:\" ~ bf.string('x'))")
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
test('a loop param shadowing an outer const resolves to the row value inside a .map() preamble local initializer', () => {
|
|
558
|
+
const { template } = compileAndGenerate(`
|
|
559
|
+
function Widget({ values }: { values: number[] }) {
|
|
560
|
+
const label: string = 'x'
|
|
561
|
+
return <ul>{values.map((label) => {
|
|
562
|
+
const cls = label
|
|
563
|
+
return <li key={label} class={cls}>{label}</li>
|
|
564
|
+
})}</ul>
|
|
565
|
+
}
|
|
566
|
+
`)
|
|
567
|
+
expect(template).toContain('{% set cls = label %}')
|
|
568
|
+
expect(template).not.toContain("{% set cls = 'x' %}")
|
|
569
|
+
})
|
|
570
|
+
})
|
|
571
|
+
|
|
534
572
|
describe('MinijinjaAdapter - fragment-root scope comment end marker (#2289)', () => {
|
|
535
573
|
// A fragment-rooted component (multiple top-level nodes) uses a
|
|
536
574
|
// comment-based scope marker instead of a real DOM element, so it needs a
|
|
@@ -170,8 +170,8 @@ import {
|
|
|
170
170
|
dangerousInnerHtmlMetacharViolation,
|
|
171
171
|
dangerousInnerHtmlDiagnostic,
|
|
172
172
|
resolveStaticLoopSource,
|
|
173
|
-
collectLoopBoundNames,
|
|
174
173
|
derivesScopeFromSlot,
|
|
174
|
+
BindingScope,
|
|
175
175
|
} from '@barefootjs/jsx'
|
|
176
176
|
import { isAriaBooleanAttr, isBooleanResultExpr, isExplicitStringCall } from './boolean-result.ts'
|
|
177
177
|
import type { ParsedExpr, LoweringMatcher, LoopBindingPathSegment } from '@barefootjs/jsx'
|
|
@@ -320,30 +320,24 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
320
320
|
private localConstants: IRMetadata['localConstants'] = []
|
|
321
321
|
|
|
322
322
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
323
|
+
* The one canonical, position-accurate "names bound by an enclosing loop
|
|
324
|
+
* callback" service (#2482 Stage 2) — replaces the two ad-hoc devices
|
|
325
|
+
* this adapter used to carry side by side: a coarse whole-component
|
|
326
|
+
* shadow-name Set (built once at `generate()` entry, used only to guard
|
|
327
|
+
* static-const inlining) and a ref-counted, position-accurate live map
|
|
328
|
+
* (pushed/popped around `renderLoop`'s body, used for the boolean-prop/
|
|
329
|
+
* nullable-optional classification sites (#2488) and `emitSpread`'s
|
|
330
|
+
* local-const fallback (#2489)). Both consulted the "is this name
|
|
331
|
+
* loop-bound" question with DIFFERENT meanings — the coarse/live drift
|
|
332
|
+
* #2482 Stage 2 ends. Every shadow-guard site now reads this ONE
|
|
333
|
+
* threaded, immutable scope: `enterLoopRow`/pop-by-reference around
|
|
334
|
+
* `renderChildren(loop.children)` in `renderLoop`, mirroring the Stage
|
|
335
|
+
* 1a/1b `ctx.scope` precedent in `jsx-to-ir.ts`. `IRLoop` already
|
|
336
|
+
* structurally satisfies `LoopBindingSource`
|
|
337
|
+
* (`param`/`index`/`paramBindings`/`preamble`), so `renderLoop` passes
|
|
338
|
+
* the loop node straight to `enterLoopRow`.
|
|
330
339
|
*/
|
|
331
|
-
private
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Names currently bound by an enclosing loop body — the block-param
|
|
335
|
-
* locals `renderLoop` introduces (item, index, per-binding destructure
|
|
336
|
-
* fields, `.map()` preamble declarations) — ref-counted so nested loops
|
|
337
|
-
* compose. This is the POSITION-ACCURATE twin of the coarse
|
|
338
|
-
* `staticLoopSourceBoundNames` above: that set is a whole-component
|
|
339
|
-
* union used only to guard static-const inlining (safe to over-suppress
|
|
340
|
-
* there — the fallback is just "don't inline"), whereas the boolean-prop
|
|
341
|
-
* and nullable-optional classification sites (#2488) and `emitSpread`'s
|
|
342
|
-
* local-const fallback (#2489) need to know whether a name is loop-bound
|
|
343
|
-
* AT THIS POSITION, because for those the coarse fallback would silently
|
|
344
|
-
* mis-render a genuine occurrence of the same name outside the loop too.
|
|
345
|
-
*/
|
|
346
|
-
private loopBoundNames: Map<string, number> = new Map()
|
|
340
|
+
private scope: BindingScope = BindingScope.EMPTY
|
|
347
341
|
|
|
348
342
|
/**
|
|
349
343
|
* Optional, no-default props that are `None` when the caller omits them.
|
|
@@ -377,8 +371,7 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
377
371
|
// ("True"/"False") (#1897, pagination's data-active).
|
|
378
372
|
this.booleanTypedProps = collectBooleanTypedProps(ir)
|
|
379
373
|
this.localConstants = ir.metadata.localConstants ?? []
|
|
380
|
-
this.
|
|
381
|
-
this.loopBoundNames.clear()
|
|
374
|
+
this.scope = BindingScope.EMPTY
|
|
382
375
|
this.nullableOptionalProps = collectNullableOptionalProps(ir)
|
|
383
376
|
this.stringValueNames = collectStringValueNames(ir)
|
|
384
377
|
this.moduleStringConsts = collectModuleStringConsts(ir.metadata.localConstants)
|
|
@@ -878,9 +871,11 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
878
871
|
// bare identifier expression below, same as before #2208 — which
|
|
879
872
|
// still trips the pre-existing BF101 gate for an unresolvable local
|
|
880
873
|
// const reference (a loud, conservative refusal, not a silent wrong
|
|
881
|
-
// value).
|
|
874
|
+
// value). Canonical, position-accurate predicate (#2482 Stage 2) — the
|
|
875
|
+
// enclosing loop scope's own membership, not a coarse whole-component
|
|
876
|
+
// union.
|
|
882
877
|
const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
|
|
883
|
-
isNameShadowed:
|
|
878
|
+
isNameShadowed: this.scope.asShadowPredicate(),
|
|
884
879
|
})
|
|
885
880
|
const staticArray = staticItems !== null ? staticValueToMinijinja(staticItems) : null
|
|
886
881
|
|
|
@@ -985,6 +980,34 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
985
980
|
// loop-child naming convention). renderedChildren above was computed with
|
|
986
981
|
// the previous flag; recompute under the loop flag.
|
|
987
982
|
|
|
983
|
+
// This loop's row scope, for the position-accurate shadow guard
|
|
984
|
+
// (#2488/#2489, canonicalized on `BindingScope` in #2482 Stage 2).
|
|
985
|
+
// `IRLoop` already structurally satisfies `LoopBindingSource`
|
|
986
|
+
// (`param`/`index`/`paramBindings`/`preamble`) — `enterLoopRow(loop)`
|
|
987
|
+
// binds exactly the for-header target(s), each destructure binding,
|
|
988
|
+
// the index (when present), and the `.map()` preamble's declared
|
|
989
|
+
// locals, mirroring what THIS renderLoop's for-header + `indexLocalLines`
|
|
990
|
+
// actually introduce. Restored by reference (immutable — no ref-count
|
|
991
|
+
// bookkeeping) so nested loops compose for free. Entered BEFORE
|
|
992
|
+
// `preambleLines`/`bodyChildren` are converted and popped AFTER, not
|
|
993
|
+
// just around `renderChildren` — Copilot review on #2600 caught that
|
|
994
|
+
// the narrower window silently regressed the module-const shadow guard
|
|
995
|
+
// (`_resolveLiteralConst`/`_resolveStaticRecordLiteral`, now
|
|
996
|
+
// scope-driven instead of the old coarse whole-component set) for two
|
|
997
|
+
// row-context conversions that sit OUTSIDE `renderChildren`: a
|
|
998
|
+
// `.map()` preamble local's own initializer (`d.raw`, below) and the
|
|
999
|
+
// whole-item-conditional `loop-i:` key anchor (`loop.key`, in
|
|
1000
|
+
// `bodyChildren` below) — both genuinely evaluate PER ROW and must see
|
|
1001
|
+
// the row's own bindings. `loop.filterPredicate` deliberately stays
|
|
1002
|
+
// OUTSIDE this window (further down, after the pop): per the Stage 0
|
|
1003
|
+
// design a filter/sort callback's own param is a separate `callback`
|
|
1004
|
+
// frame, never folded into the `.map()` row — and empirically
|
|
1005
|
+
// `JinjaFilterEmitter` (the filter predicate's dedicated,
|
|
1006
|
+
// self-contained emitter) never consults `this.scope` at all, so its
|
|
1007
|
+
// position relative to the pop is inert either way.
|
|
1008
|
+
const prevScope = this.scope
|
|
1009
|
+
this.scope = prevScope.enterLoopRow(loop)
|
|
1010
|
+
|
|
988
1011
|
// Per-row locals for a `.map()` callback preamble (#2447), in source
|
|
989
1012
|
// order so a later initializer sees an earlier local — same as the
|
|
990
1013
|
// source block. Phase 1 refuses the loop outright when the preamble
|
|
@@ -993,42 +1016,7 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
993
1016
|
const preambleLines = (loop.preamble?.declarations ?? []).map(
|
|
994
1017
|
d => `{% set ${minijinjaIdent(d.name)} = ${this.convertExpressionToJinja(d.raw, d.valueParsed)} %}`,
|
|
995
1018
|
)
|
|
996
|
-
// Names this loop binds in body scope, for the position-accurate
|
|
997
|
-
// `loopBoundNames` guard (#2488) — mirrors the ERB adapter's `loopBound`
|
|
998
|
-
// derivation, adapted to what THIS renderLoop actually binds: the
|
|
999
|
-
// for-header target(s) (`loopVar` / `objectIteration` key+value /
|
|
1000
|
-
// `iterationShape === 'keys'`'s `param`), each `indexLocalLines` name
|
|
1001
|
-
// (explicit index, or a destructure binding), and the `.map()`
|
|
1002
|
-
// preamble's declared locals. Ref-counted so nested loops compose.
|
|
1003
|
-
const loopBound: string[] = []
|
|
1004
|
-
if (loop.objectIteration === 'entries') {
|
|
1005
|
-
loopBound.push(loop.index ?? param, param)
|
|
1006
|
-
} else if (loop.objectIteration === 'keys') {
|
|
1007
|
-
// `|items` binds both slots; the unused one is a throwaway (#2488).
|
|
1008
|
-
loopBound.push(param, '__bf_v')
|
|
1009
|
-
} else if (loop.objectIteration === 'values') {
|
|
1010
|
-
loopBound.push('__bf_k', param)
|
|
1011
|
-
} else if (loop.iterationShape === 'keys') {
|
|
1012
|
-
// The header still binds the throwaway loop var; `param` is only the
|
|
1013
|
-
// index alias set beneath it (#2488).
|
|
1014
|
-
loopBound.push('__bf_item', param)
|
|
1015
|
-
} else if (supportableDestructure) {
|
|
1016
|
-
loopBound.push('__bf_item', ...(loop.paramBindings ?? []).map(b => b.name))
|
|
1017
|
-
if (loop.index) loopBound.push(loop.index)
|
|
1018
|
-
} else {
|
|
1019
|
-
loopBound.push(param)
|
|
1020
|
-
if (loop.index) loopBound.push(loop.index)
|
|
1021
|
-
}
|
|
1022
|
-
for (const d of loop.preamble?.declarations ?? []) loopBound.push(d.name)
|
|
1023
|
-
for (const n of loopBound) {
|
|
1024
|
-
this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1)
|
|
1025
|
-
}
|
|
1026
1019
|
const childrenUnderLoop = this.renderChildren(loop.children)
|
|
1027
|
-
for (const n of loopBound) {
|
|
1028
|
-
const c = (this.loopBoundNames.get(n) ?? 1) - 1
|
|
1029
|
-
if (c <= 0) this.loopBoundNames.delete(n)
|
|
1030
|
-
else this.loopBoundNames.set(n, c)
|
|
1031
|
-
}
|
|
1032
1020
|
this.currentLoopKeyDepth = prevLoopKeyDepth
|
|
1033
1021
|
this.inLoop = prevInLoop
|
|
1034
1022
|
void renderedChildren
|
|
@@ -1036,11 +1024,13 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
1036
1024
|
// Whole-item conditional: prepend an always-present `<!--bf-loop-i:KEY-->`
|
|
1037
1025
|
// anchor before each item's (possibly empty) conditional content so the
|
|
1038
1026
|
// client's `mapArrayAnchored` can hydrate every SSR-rendered item by its
|
|
1039
|
-
// anchor.
|
|
1027
|
+
// anchor. Still under the row scope (see above) — `loop.key` is a
|
|
1028
|
+
// per-row expression.
|
|
1040
1029
|
const bodyChildren =
|
|
1041
1030
|
loop.bodyIsItemConditional && loop.key
|
|
1042
1031
|
? `{{ bf.comment("loop-i:" ~ bf.string(${this.convertExpressionToJinja(loop.key)})) | safe }}\n${childrenUnderLoop}`
|
|
1043
1032
|
: childrenUnderLoop
|
|
1033
|
+
this.scope = prevScope
|
|
1044
1034
|
|
|
1045
1035
|
const lines: string[] = []
|
|
1046
1036
|
// Scoped per-call-site marker so sibling `.map()`s under the same parent
|
|
@@ -1527,13 +1517,13 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
1527
1517
|
// function-scope (`!isModule`) consts whose value is NOT itself a bare
|
|
1528
1518
|
// identifier (loop guard) are considered.
|
|
1529
1519
|
//
|
|
1530
|
-
// `
|
|
1531
|
-
// param can shadow this outer const's name (`.map((attrs) => <p
|
|
1520
|
+
// `this.scope` shadow guard (#2489): an enclosing `.map()` callback's
|
|
1521
|
+
// own param can shadow this outer const's name (`.map((attrs) => <p
|
|
1532
1522
|
// {...attrs} />)`) — without the guard this forwarded the OUTER
|
|
1533
1523
|
// const's value at every iteration instead of the per-item value.
|
|
1534
|
-
// Must be the
|
|
1535
|
-
//
|
|
1536
|
-
if (/^[A-Za-z_$][\w$]*$/.test(trimmed) && !this.
|
|
1524
|
+
// Must be the threaded, position-accurate scope — the same name
|
|
1525
|
+
// spread at ROOT (no enclosing loop) must still resolve the const.
|
|
1526
|
+
if (/^[A-Za-z_$][\w$]*$/.test(trimmed) && !this.scope.isBound(trimmed)) {
|
|
1537
1527
|
const localConst = (this.localConstants ?? []).find(
|
|
1538
1528
|
c => c.name === trimmed && !c.isModule,
|
|
1539
1529
|
)
|
|
@@ -1955,9 +1945,9 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
1955
1945
|
return this.booleanTypedProps.has(bare)
|
|
1956
1946
|
}
|
|
1957
1947
|
|
|
1958
|
-
/** Position-accurate loop-bound-name check — see `
|
|
1948
|
+
/** Position-accurate loop-bound-name check — see `this.scope`'s docstring. */
|
|
1959
1949
|
private isLoopBoundName(name: string): boolean {
|
|
1960
|
-
return this.
|
|
1950
|
+
return this.scope.isBound(name)
|
|
1961
1951
|
}
|
|
1962
1952
|
|
|
1963
1953
|
/**
|
|
@@ -1984,17 +1974,16 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
1984
1974
|
* context, so a bare reference would resolve to Undefined.
|
|
1985
1975
|
*
|
|
1986
1976
|
* The lookup is a flat name match with no notion of AST scope, so a
|
|
1987
|
-
* name
|
|
1988
|
-
* inlines (#2221) — the occurrence may be the
|
|
1989
|
-
* binding, and substituting the outer const's
|
|
1990
|
-
* iteration with the same hard-coded literal.
|
|
1991
|
-
*
|
|
1992
|
-
*
|
|
1993
|
-
*
|
|
1994
|
-
* `collectStringValueNames`.
|
|
1977
|
+
* name bound by the CURRENTLY ENCLOSING loop's item/index/destructure/
|
|
1978
|
+
* preamble param never inlines (#2221) — the occurrence may be the
|
|
1979
|
+
* loop's own (shadowing) binding, and substituting the outer const's
|
|
1980
|
+
* value there renders every iteration with the same hard-coded literal.
|
|
1981
|
+
* Position-accurate via the threaded `this.scope` (#2482 Stage 2) — a
|
|
1982
|
+
* same-named const elsewhere in the component, outside any loop that
|
|
1983
|
+
* shadows it, still inlines.
|
|
1995
1984
|
*/
|
|
1996
1985
|
private _resolveLiteralConst(name: string): string | null {
|
|
1997
|
-
if (this.
|
|
1986
|
+
if (this.scope.isBound(name)) return null
|
|
1998
1987
|
const c = (this.localConstants ?? []).find(lc => lc.name === name)
|
|
1999
1988
|
if (c?.value === undefined) return null
|
|
2000
1989
|
const v = c.value.trim()
|
|
@@ -2012,15 +2001,16 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
2012
2001
|
* scope, so an enclosing loop callback's own param of the same name
|
|
2013
2002
|
* (`.map((cfg) => <li>{cfg.x}</li>)` shadowing a module `const cfg = {…}`)
|
|
2014
2003
|
* still resolved to the OUTER const's member value at every iteration
|
|
2015
|
-
* (#2237) — the sibling hazard to #2221's `_resolveLiteralConst`.
|
|
2016
|
-
*
|
|
2017
|
-
*
|
|
2018
|
-
*
|
|
2019
|
-
* at the
|
|
2004
|
+
* (#2237) — the sibling hazard to #2221's `_resolveLiteralConst`.
|
|
2005
|
+
* Position-accurate via the threaded `this.scope` (#2482 Stage 2), passed
|
|
2006
|
+
* as `lookupStaticRecordLiteral`'s required guard: a name the CURRENTLY
|
|
2007
|
+
* ENCLOSING loop binds never inlines, falling back to the bare `cfg.x`
|
|
2008
|
+
* member expression (which a minijinja `for` loop binds correctly at the
|
|
2009
|
+
* shadowed occurrence); a same-named const elsewhere, outside any loop
|
|
2010
|
+
* that shadows it, still inlines.
|
|
2020
2011
|
*/
|
|
2021
2012
|
private _resolveStaticRecordLiteral(objectName: string, key: string): string | null {
|
|
2022
|
-
|
|
2023
|
-
const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants)
|
|
2013
|
+
const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants, name => this.scope.isBound(name))
|
|
2024
2014
|
if (!hit) return null
|
|
2025
2015
|
return hit.kind === 'number'
|
|
2026
2016
|
? hit.text
|