@barefootjs/mojolicious 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/adapter/index.js +18 -36
- package/dist/adapter/mojo-adapter.d.ts +19 -12
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +29 -41
- package/dist/vite.js +149 -120
- 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 +57 -63
- package/src/conformance-pins.ts +14 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/mojolicious",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.6",
|
|
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.6"
|
|
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.6",
|
|
74
|
+
"@barefootjs/vite": "0.31.6",
|
|
75
|
+
"@barefootjs/client": "0.31.6",
|
|
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
|
|
|
@@ -921,6 +928,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
921
928
|
suggestion: {
|
|
922
929
|
message:
|
|
923
930
|
'Pre-compute the array server-side and pass it as a prop, or mark the loop position as @client-only so it runs in JS on the client.',
|
|
931
|
+
escape: [{ kind: 'prop-precompute' }, { kind: 'client-directive' }],
|
|
924
932
|
},
|
|
925
933
|
})
|
|
926
934
|
}
|
|
@@ -951,26 +959,21 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
951
959
|
const indexVar = loop.iterationShape === 'keys'
|
|
952
960
|
? `$${param}`
|
|
953
961
|
: 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.
|
|
962
|
+
// This loop's row scope. Guards module-const inlining for the whole
|
|
963
|
+
// body (children + key + filter) so a same-named loop variable isn't
|
|
964
|
+
// replaced by the const literal (#1749 review). `IRLoop` already
|
|
965
|
+
// structurally satisfies `LoopBindingSource`
|
|
966
|
+
// (`param`/`index`/`paramBindings`/`preamble`) — `enterLoopRow(loop)`
|
|
967
|
+
// binds exactly what this renderLoop's header + preamble locals
|
|
968
|
+
// introduce (#2482 Stage 2 — replaces the ref-counted `Map` this
|
|
969
|
+
// adapter used to carry). Restored by reference (immutable — no
|
|
970
|
+
// ref-count bookkeeping) at the matching pop below, once the WHOLE
|
|
971
|
+
// body (including the filter predicate) has been rendered — except for
|
|
972
|
+
// one temporary drop-to-outer window around the hoisted sort-comparator
|
|
973
|
+
// emission below, since that runs OUTSIDE this loop.
|
|
969
974
|
const preambleDecls = loop.preamble?.declarations ?? []
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1)
|
|
973
|
-
}
|
|
975
|
+
const prevScope = this.scope
|
|
976
|
+
this.scope = prevScope.enterLoopRow(loop)
|
|
974
977
|
const prevLoopKeyDepth = this.currentLoopKeyDepth
|
|
975
978
|
this.currentLoopKeyDepth = loop.depth
|
|
976
979
|
const renderedChildren = this.renderChildren(loop.children)
|
|
@@ -995,18 +998,15 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
995
998
|
// `bf->sort_eval`; fall back to the structured `bf->sort` for a
|
|
996
999
|
// comparator the evaluator can't model (e.g. `localeCompare`).
|
|
997
1000
|
//
|
|
998
|
-
// The hoisted sort runs OUTSIDE this loop, so this loop's
|
|
1001
|
+
// The hoisted sort runs OUTSIDE this loop, so this loop's row scope
|
|
999
1002
|
// must not shadow the comparator's captured free vars while emitting the
|
|
1000
1003
|
// env — otherwise a captured var that happens to share a loop-param name
|
|
1001
1004
|
// 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
|
-
}
|
|
1005
|
+
// `$name` (strict-mode fault, Copilot review #2035). Drop back to the
|
|
1006
|
+
// outer (pre-row) scope for the sort emit, then restore the row scope
|
|
1007
|
+
// below (a nested loop's own outer bindings stay in effect throughout,
|
|
1008
|
+
// since `prevScope` already carries them).
|
|
1009
|
+
this.scope = prevScope
|
|
1010
1010
|
const sortEmit = (e: ParsedExpr) => this.convertExpressionToPerl('', e)
|
|
1011
1011
|
// `loop.sortComparator` is the generic `IRLoopSort` (#2018 P5): serialize
|
|
1012
1012
|
// the arrow body for the evaluator (eval-first), recover the structured
|
|
@@ -1030,9 +1030,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1030
1030
|
)
|
|
1031
1031
|
sorted = rawArray
|
|
1032
1032
|
}
|
|
1033
|
-
|
|
1034
|
-
this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1)
|
|
1035
|
-
}
|
|
1033
|
+
this.scope = prevScope.enterLoopRow(loop)
|
|
1036
1034
|
lines.push(`% my $${sortedHoist} = ${sorted};`)
|
|
1037
1035
|
}
|
|
1038
1036
|
if (loop.objectIteration) {
|
|
@@ -1121,12 +1119,8 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1121
1119
|
lines.push(children)
|
|
1122
1120
|
}
|
|
1123
1121
|
|
|
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
|
-
}
|
|
1122
|
+
// Body fully rendered — restore the outer (pre-row) scope.
|
|
1123
|
+
this.scope = prevScope
|
|
1130
1124
|
|
|
1131
1125
|
lines.push(`% }`)
|
|
1132
1126
|
lines.push(`<%== bf->comment("/loop:${loop.markerId}") %>`)
|
|
@@ -1395,7 +1389,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1395
1389
|
// Inside a `.map()` callback, a param that shadows a nullable
|
|
1396
1390
|
// optional prop's name is the row binding, not the prop — skip the
|
|
1397
1391
|
// spurious `defined` guard (#2488).
|
|
1398
|
-
!this.
|
|
1392
|
+
!this.scope.isBound(normalizedBareId)
|
|
1399
1393
|
) {
|
|
1400
1394
|
const perl = this.convertExpressionToPerl(value.expr)
|
|
1401
1395
|
const body =
|
|
@@ -1527,14 +1521,14 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1527
1521
|
// lowering. Only function-scope (`!isModule`) consts whose value is
|
|
1528
1522
|
// NOT itself a bare identifier (loop guard) are considered.
|
|
1529
1523
|
//
|
|
1530
|
-
// `
|
|
1524
|
+
// `this.scope` shadow guard (#2221): an enclosing `.map()` callback's
|
|
1531
1525
|
// own param can shadow this outer const's name (`.map((sizeAttrs)
|
|
1532
1526
|
// => <li {...sizeAttrs} />)`) — without the guard this forwarded
|
|
1533
1527
|
// the OUTER const's hashref at every iteration instead of the
|
|
1534
|
-
// per-item `$sizeAttrs` value. Same
|
|
1535
|
-
// `resolveLiteralConst` / `resolveStaticRecordLiteral` already
|
|
1528
|
+
// per-item `$sizeAttrs` value. Same threaded, position-accurate
|
|
1529
|
+
// scope `resolveLiteralConst` / `resolveStaticRecordLiteral` already
|
|
1536
1530
|
// consult for this hazard class (#1749).
|
|
1537
|
-
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.
|
|
1531
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.scope.isBound(trimmed)) {
|
|
1538
1532
|
const localConst = this.localConstants.find(
|
|
1539
1533
|
c => c.name === trimmed && !c.isModule,
|
|
1540
1534
|
)
|
package/src/conformance-pins.ts
CHANGED
|
@@ -13,6 +13,9 @@ export const conformancePins: ConformancePins = {
|
|
|
13
13
|
// JS-runtime target runs it, a DSL adapter surfaces BF021 + `/* @client */`.
|
|
14
14
|
// See spec/callback-fidelity.md.
|
|
15
15
|
'filter-typeof-predicate': [{ code: 'BF021', severity: 'error' }],
|
|
16
|
+
// Array-builder `.map()` body (imperative `push`-into-array preamble):
|
|
17
|
+
// BF021, with a verified `/* @client */` escape — `map-array-builder-
|
|
18
|
+
// body-client` (#2613). See that fixture's docstring.
|
|
16
19
|
'map-array-builder-body': [{ code: 'BF021', severity: 'error' }],
|
|
17
20
|
'map-array-builder-escaping': [{ code: 'BF021', severity: 'error' }],
|
|
18
21
|
// `.fill(value)` mutates the receiver in place — no template lowering
|
|
@@ -70,14 +73,22 @@ export const conformancePins: ConformancePins = {
|
|
|
70
73
|
// check in `renderLoop`. This was always true; it was simply
|
|
71
74
|
// unreachable before because BF104 refused the destructure shape first.
|
|
72
75
|
'static-array-from-props': [
|
|
73
|
-
{
|
|
76
|
+
{
|
|
77
|
+
code: 'BF101',
|
|
78
|
+
severity: 'error',
|
|
79
|
+
issue: 'https://github.com/piconic-ai/barefootjs/issues/2321',
|
|
80
|
+
},
|
|
74
81
|
],
|
|
75
82
|
// The BF101 above fires; BF104 no longer does (see above), and BF103
|
|
76
83
|
// (sibling-imported `<Tag>` child component in the loop body) no longer
|
|
77
84
|
// does either now that the conformance harness passes
|
|
78
85
|
// `siblingTemplatesRegistered: true` (#2205).
|
|
79
86
|
'static-array-from-props-with-component': [
|
|
80
|
-
{
|
|
87
|
+
{
|
|
88
|
+
code: 'BF101',
|
|
89
|
+
severity: 'error',
|
|
90
|
+
issue: 'https://github.com/piconic-ai/barefootjs/issues/2321',
|
|
91
|
+
},
|
|
81
92
|
],
|
|
82
93
|
// #1310 / #2087: rest destructure in .map() callback. All four shapes
|
|
83
94
|
// now lower via #2087 Phase B's `segments`-walking accessor:
|
|
@@ -107,9 +118,7 @@ export const conformancePins: ConformancePins = {
|
|
|
107
118
|
// NOT pinned: Mojo lowers it to a real inline Perl `grep` and must
|
|
108
119
|
// render to Hono parity instead.
|
|
109
120
|
// Faithful lowering tracked: https://github.com/piconic-ai/barefootjs/issues/2320 (successor to #2038)
|
|
110
|
-
'filter-nested-find-predicate': [
|
|
111
|
-
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' },
|
|
112
|
-
],
|
|
121
|
+
'filter-nested-find-predicate': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' }],
|
|
113
122
|
// #1467 demo-corpus context providers (`radio-group`, `accordion`,
|
|
114
123
|
// `dialog`, `popover`, `select`, `dropdown-menu`, `combobox`,
|
|
115
124
|
// `command`) are no longer pinned — an object-literal provider value
|