@barefootjs/mojolicious 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 +16 -35
- package/dist/adapter/mojo-adapter.d.ts +19 -12
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/index.js +16 -35
- package/dist/vite.js +241 -198
- package/lib/BarefootJS/Backend/Mojo.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS.pm +1 -1
- package/package.json +5 -5
- package/src/adapter/mojo-adapter.ts +56 -63
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/mojolicious",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.5",
|
|
4
4
|
"description": "Mojolicious EP template adapter for BarefootJS - generates .html.ep files from IR",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"directory": "packages/adapter-mojolicious"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@barefootjs/shared": "0.31.
|
|
55
|
+
"@barefootjs/shared": "0.31.5"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@barefootjs/jsx": ">=0.2.0",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
73
|
-
"@barefootjs/jsx": "0.31.
|
|
74
|
-
"@barefootjs/vite": "0.31.
|
|
75
|
-
"@barefootjs/client": "0.31.
|
|
73
|
+
"@barefootjs/jsx": "0.31.5",
|
|
74
|
+
"@barefootjs/vite": "0.31.5",
|
|
75
|
+
"@barefootjs/client": "0.31.5",
|
|
76
76
|
"vite": "^6.0.0"
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
dangerousInnerHtmlDiagnostic,
|
|
66
66
|
resolveStaticLoopSource,
|
|
67
67
|
derivesScopeFromSlot,
|
|
68
|
+
BindingScope,
|
|
68
69
|
} from '@barefootjs/jsx'
|
|
69
70
|
import { isAriaBooleanAttr, isBooleanResultExpr } from './boolean-result.ts'
|
|
70
71
|
import type { ParsedExpr, LoweringMatcher } from '@barefootjs/jsx'
|
|
@@ -238,14 +239,22 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
238
239
|
*/
|
|
239
240
|
private localConstants: IRMetadata['localConstants'] = []
|
|
240
241
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
242
|
+
* The one canonical, position-accurate "names bound by an enclosing loop
|
|
243
|
+
* callback" service (#2482 Stage 2) — replaces the ref-counted
|
|
244
|
+
* `Map<string, number>` this adapter used to push/pop around
|
|
245
|
+
* `renderLoop`'s body (the `my $<param>` / `my $<index>` bindings it
|
|
246
|
+
* introduces). `resolveModuleStringConst` consults this so a loop
|
|
244
247
|
* variable whose name happens to match a module string const is NOT
|
|
245
248
|
* inlined as the const literal (mirrors the Go adapter's loop-param /
|
|
246
|
-
* loop-var shadowing guards). (#1749 review)
|
|
249
|
+
* loop-var shadowing guards). (#1749 review) Threaded via
|
|
250
|
+
* save/restore-by-reference around `renderChildren(loop.children)` in
|
|
251
|
+
* `renderLoop` (immutable — no ref-count bookkeeping), mirroring the
|
|
252
|
+
* Stage 1a/1b `ctx.scope` precedent in `jsx-to-ir.ts`. `IRLoop` already
|
|
253
|
+
* structurally satisfies `LoopBindingSource`
|
|
254
|
+
* (`param`/`index`/`paramBindings`/`preamble`), so `renderLoop` passes
|
|
255
|
+
* the loop node straight to `enterLoopRow`.
|
|
247
256
|
*/
|
|
248
|
-
private
|
|
257
|
+
private scope: BindingScope = BindingScope.EMPTY
|
|
249
258
|
/**
|
|
250
259
|
* Prop names whose value is `undef` in the template body when the caller
|
|
251
260
|
* omits them — so a bare-reference attribute should be dropped rather
|
|
@@ -293,7 +302,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
293
302
|
this._searchParamsLocals = searchParamsLocalNames(ir.metadata)
|
|
294
303
|
this._loweringMatchers = prepareLoweringMatchers(ir.metadata)
|
|
295
304
|
this.localConstants = ir.metadata.localConstants ?? []
|
|
296
|
-
this.
|
|
305
|
+
this.scope = BindingScope.EMPTY
|
|
297
306
|
this.errors = []
|
|
298
307
|
this.childrenCaptureCounter = 0
|
|
299
308
|
|
|
@@ -388,7 +397,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
388
397
|
// Inside a `.map()` callback, a param that shares a boolean prop's name
|
|
389
398
|
// is the ROW binding, not the prop — route it through plain string
|
|
390
399
|
// emission instead of `bf->bool_str` (#2488).
|
|
391
|
-
if (this.
|
|
400
|
+
if (this.scope.isBound(bare)) return false
|
|
392
401
|
return this.booleanTypedProps.has(bare)
|
|
393
402
|
}
|
|
394
403
|
|
|
@@ -423,16 +432,15 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
423
432
|
* function-scope consts never reach the per-render stash, so a bare
|
|
424
433
|
* `$totalPages` faults under strict mode.
|
|
425
434
|
*
|
|
426
|
-
* The
|
|
427
|
-
* callback's own param shadowing this outer const's name):
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
* no separate `staticLoopSourceBoundNames`-style field is needed here.
|
|
435
|
+
* The threaded scope's shadow guard also covers the #2221 hazard (a loop
|
|
436
|
+
* callback's own param shadowing this outer const's name): `this.scope`
|
|
437
|
+
* is position-accurate (#2482 Stage 2) — pushed/popped by reference as
|
|
438
|
+
* `renderLoop` descends/ascends into each loop body — so it's already
|
|
439
|
+
* scope-precise for this call site; no separate coarse whole-component
|
|
440
|
+
* field is needed here.
|
|
433
441
|
*/
|
|
434
442
|
private resolveLiteralConst(name: string): string | null {
|
|
435
|
-
if (this.
|
|
443
|
+
if (this.scope.isBound(name)) return null
|
|
436
444
|
const c = (this.localConstants ?? []).find(lc => lc.name === name)
|
|
437
445
|
if (c?.value === undefined) return null
|
|
438
446
|
const v = c.value.trim()
|
|
@@ -443,8 +451,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
443
451
|
}
|
|
444
452
|
|
|
445
453
|
private resolveStaticRecordLiteral(objectName: string, key: string): string | null {
|
|
446
|
-
|
|
447
|
-
const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants)
|
|
454
|
+
const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants, name => this.scope.isBound(name))
|
|
448
455
|
if (!hit) return null
|
|
449
456
|
return hit.kind === 'number'
|
|
450
457
|
? hit.text
|
|
@@ -454,7 +461,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
454
461
|
private resolveModuleStringConst(name: string): string | null {
|
|
455
462
|
// A loop body introduces `my $<param>` / `my $<index>` bindings that
|
|
456
463
|
// shadow a module const of the same name — never inline inside one.
|
|
457
|
-
if (this.
|
|
464
|
+
if (this.scope.isBound(name)) return null
|
|
458
465
|
const value = this.moduleStringConsts.get(name)
|
|
459
466
|
if (value === undefined) return null
|
|
460
467
|
return `'${value.replace(/[\\']/g, m => `\\${m}`)}'`
|
|
@@ -902,10 +909,10 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
902
909
|
// `Object.entries(props.tags).filter(...)`) still refuses below.
|
|
903
910
|
// `isNameShadowed` guards a DIFFERENT, enclosing loop's own callback
|
|
904
911
|
// param shadowing this identifier (fable review) — reuses the same
|
|
905
|
-
//
|
|
906
|
-
// already consults for this hazard class (#1749).
|
|
912
|
+
// threaded, position-accurate `this.scope` `resolveModuleStringConst`
|
|
913
|
+
// already consults for this hazard class (#1749/#2482 Stage 2).
|
|
907
914
|
const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
|
|
908
|
-
isNameShadowed:
|
|
915
|
+
isNameShadowed: this.scope.asShadowPredicate(),
|
|
909
916
|
})
|
|
910
917
|
const staticArray = staticItems !== null ? staticValueToPerl(staticItems) : null
|
|
911
918
|
|
|
@@ -951,26 +958,21 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
951
958
|
const indexVar = loop.iterationShape === 'keys'
|
|
952
959
|
? `$${param}`
|
|
953
960
|
: loop.index ? `$${loop.index}` : '$_i'
|
|
954
|
-
//
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
//
|
|
966
|
-
// declaration (#2447). Loop-binding the names keeps a same-named module
|
|
967
|
-
// const from inlining over the local the loop just declared — the same
|
|
968
|
-
// hazard class `resolveModuleStringConst` already consults this map for.
|
|
961
|
+
// This loop's row scope. Guards module-const inlining for the whole
|
|
962
|
+
// body (children + key + filter) so a same-named loop variable isn't
|
|
963
|
+
// replaced by the const literal (#1749 review). `IRLoop` already
|
|
964
|
+
// structurally satisfies `LoopBindingSource`
|
|
965
|
+
// (`param`/`index`/`paramBindings`/`preamble`) — `enterLoopRow(loop)`
|
|
966
|
+
// binds exactly what this renderLoop's header + preamble locals
|
|
967
|
+
// introduce (#2482 Stage 2 — replaces the ref-counted `Map` this
|
|
968
|
+
// adapter used to carry). Restored by reference (immutable — no
|
|
969
|
+
// ref-count bookkeeping) at the matching pop below, once the WHOLE
|
|
970
|
+
// body (including the filter predicate) has been rendered — except for
|
|
971
|
+
// one temporary drop-to-outer window around the hoisted sort-comparator
|
|
972
|
+
// emission below, since that runs OUTSIDE this loop.
|
|
969
973
|
const preambleDecls = loop.preamble?.declarations ?? []
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1)
|
|
973
|
-
}
|
|
974
|
+
const prevScope = this.scope
|
|
975
|
+
this.scope = prevScope.enterLoopRow(loop)
|
|
974
976
|
const prevLoopKeyDepth = this.currentLoopKeyDepth
|
|
975
977
|
this.currentLoopKeyDepth = loop.depth
|
|
976
978
|
const renderedChildren = this.renderChildren(loop.children)
|
|
@@ -995,18 +997,15 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
995
997
|
// `bf->sort_eval`; fall back to the structured `bf->sort` for a
|
|
996
998
|
// comparator the evaluator can't model (e.g. `localeCompare`).
|
|
997
999
|
//
|
|
998
|
-
// The hoisted sort runs OUTSIDE this loop, so this loop's
|
|
1000
|
+
// The hoisted sort runs OUTSIDE this loop, so this loop's row scope
|
|
999
1001
|
// must not shadow the comparator's captured free vars while emitting the
|
|
1000
1002
|
// env — otherwise a captured var that happens to share a loop-param name
|
|
1001
1003
|
// is blocked from inlining its module const and renders as an undefined
|
|
1002
|
-
// `$name` (strict-mode fault, Copilot review #2035). Drop
|
|
1003
|
-
//
|
|
1004
|
-
// bindings
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
if (c <= 0) this.loopBoundNames.delete(n)
|
|
1008
|
-
else this.loopBoundNames.set(n, c)
|
|
1009
|
-
}
|
|
1004
|
+
// `$name` (strict-mode fault, Copilot review #2035). Drop back to the
|
|
1005
|
+
// outer (pre-row) scope for the sort emit, then restore the row scope
|
|
1006
|
+
// below (a nested loop's own outer bindings stay in effect throughout,
|
|
1007
|
+
// since `prevScope` already carries them).
|
|
1008
|
+
this.scope = prevScope
|
|
1010
1009
|
const sortEmit = (e: ParsedExpr) => this.convertExpressionToPerl('', e)
|
|
1011
1010
|
// `loop.sortComparator` is the generic `IRLoopSort` (#2018 P5): serialize
|
|
1012
1011
|
// the arrow body for the evaluator (eval-first), recover the structured
|
|
@@ -1030,9 +1029,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1030
1029
|
)
|
|
1031
1030
|
sorted = rawArray
|
|
1032
1031
|
}
|
|
1033
|
-
|
|
1034
|
-
this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1)
|
|
1035
|
-
}
|
|
1032
|
+
this.scope = prevScope.enterLoopRow(loop)
|
|
1036
1033
|
lines.push(`% my $${sortedHoist} = ${sorted};`)
|
|
1037
1034
|
}
|
|
1038
1035
|
if (loop.objectIteration) {
|
|
@@ -1121,12 +1118,8 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1121
1118
|
lines.push(children)
|
|
1122
1119
|
}
|
|
1123
1120
|
|
|
1124
|
-
// Body fully rendered —
|
|
1125
|
-
|
|
1126
|
-
const c = (this.loopBoundNames.get(n) ?? 1) - 1
|
|
1127
|
-
if (c <= 0) this.loopBoundNames.delete(n)
|
|
1128
|
-
else this.loopBoundNames.set(n, c)
|
|
1129
|
-
}
|
|
1121
|
+
// Body fully rendered — restore the outer (pre-row) scope.
|
|
1122
|
+
this.scope = prevScope
|
|
1130
1123
|
|
|
1131
1124
|
lines.push(`% }`)
|
|
1132
1125
|
lines.push(`<%== bf->comment("/loop:${loop.markerId}") %>`)
|
|
@@ -1395,7 +1388,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1395
1388
|
// Inside a `.map()` callback, a param that shadows a nullable
|
|
1396
1389
|
// optional prop's name is the row binding, not the prop — skip the
|
|
1397
1390
|
// spurious `defined` guard (#2488).
|
|
1398
|
-
!this.
|
|
1391
|
+
!this.scope.isBound(normalizedBareId)
|
|
1399
1392
|
) {
|
|
1400
1393
|
const perl = this.convertExpressionToPerl(value.expr)
|
|
1401
1394
|
const body =
|
|
@@ -1527,14 +1520,14 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1527
1520
|
// lowering. Only function-scope (`!isModule`) consts whose value is
|
|
1528
1521
|
// NOT itself a bare identifier (loop guard) are considered.
|
|
1529
1522
|
//
|
|
1530
|
-
// `
|
|
1523
|
+
// `this.scope` shadow guard (#2221): an enclosing `.map()` callback's
|
|
1531
1524
|
// own param can shadow this outer const's name (`.map((sizeAttrs)
|
|
1532
1525
|
// => <li {...sizeAttrs} />)`) — without the guard this forwarded
|
|
1533
1526
|
// the OUTER const's hashref at every iteration instead of the
|
|
1534
|
-
// per-item `$sizeAttrs` value. Same
|
|
1535
|
-
// `resolveLiteralConst` / `resolveStaticRecordLiteral` already
|
|
1527
|
+
// per-item `$sizeAttrs` value. Same threaded, position-accurate
|
|
1528
|
+
// scope `resolveLiteralConst` / `resolveStaticRecordLiteral` already
|
|
1536
1529
|
// consult for this hazard class (#1749).
|
|
1537
|
-
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.
|
|
1530
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.scope.isBound(trimmed)) {
|
|
1538
1531
|
const localConst = this.localConstants.find(
|
|
1539
1532
|
c => c.name === trimmed && !c.isModule,
|
|
1540
1533
|
)
|