@barefootjs/mojolicious 0.17.0 → 0.18.0
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/expr/array-method.d.ts +13 -1
- package/dist/adapter/expr/array-method.d.ts.map +1 -1
- package/dist/adapter/expr/emitters.d.ts +9 -4
- package/dist/adapter/expr/emitters.d.ts.map +1 -1
- package/dist/adapter/index.js +100 -68
- package/dist/adapter/lib/ir-scope.d.ts +0 -7
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -1
- package/dist/adapter/memo/seed.d.ts +10 -19
- package/dist/adapter/memo/seed.d.ts.map +1 -1
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/build.js +100 -68
- package/dist/conformance-pins.d.ts +10 -0
- package/dist/conformance-pins.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -68
- package/dist/test-render.d.ts.map +1 -1
- package/lib/BarefootJS/Backend/Mojo.pm +14 -5
- package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS.pm +55 -33
- package/package.json +3 -3
- package/src/__tests__/mojo-adapter.test.ts +180 -135
- package/src/__tests__/multi-component-registry.test.ts +215 -0
- package/src/__tests__/stock-route.test.ts +203 -0
- package/src/adapter/expr/array-method.ts +43 -1
- package/src/adapter/expr/emitters.ts +59 -15
- package/src/adapter/lib/ir-scope.ts +0 -13
- package/src/adapter/memo/seed.ts +20 -73
- package/src/adapter/mojo-adapter.ts +154 -34
- package/src/conformance-pins.ts +138 -0
- package/src/index.ts +1 -0
- package/src/test-render.ts +8 -0
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
CompilerError,
|
|
27
27
|
TypeInfo,
|
|
28
28
|
TemplatePrimitiveRegistry,
|
|
29
|
+
LoopBindingPathSegment,
|
|
29
30
|
} from '@barefootjs/jsx'
|
|
30
31
|
import {
|
|
31
32
|
BaseAdapter,
|
|
@@ -49,14 +50,15 @@ import {
|
|
|
49
50
|
parseRecordIndexAccess,
|
|
50
51
|
extractArrowBodyExpression,
|
|
51
52
|
collectContextConsumers,
|
|
52
|
-
isLowerableObjectRestDestructure,
|
|
53
53
|
type ContextConsumer,
|
|
54
54
|
collectModuleStringConsts,
|
|
55
55
|
lookupStaticRecordLiteral,
|
|
56
56
|
searchParamsLocalNames,
|
|
57
57
|
prepareLoweringMatchers,
|
|
58
58
|
queryHrefArgs,
|
|
59
|
+
isValidHelperId,
|
|
59
60
|
sortComparatorFromArrow,
|
|
61
|
+
isLowerableLoopDestructure,
|
|
60
62
|
} from '@barefootjs/jsx'
|
|
61
63
|
import { isAriaBooleanAttr, isBooleanResultExpr } from './boolean-result.ts'
|
|
62
64
|
import type { ParsedExpr, LoweringMatcher } from '@barefootjs/jsx'
|
|
@@ -94,6 +96,43 @@ import {
|
|
|
94
96
|
export type { MojoAdapterOptions } from './lib/types.ts'
|
|
95
97
|
import type { MojoAdapterOptions } from './lib/types.ts'
|
|
96
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Build a Perl accessor expression for a `.map()` destructure binding's
|
|
101
|
+
* structured `segments` path (#2087 Phase B), walking `field` (`->{key}`,
|
|
102
|
+
* quoted with `perlHashKey`'s convention when `!isIdent`) / `index`
|
|
103
|
+
* (`->[N]`) steps onto `base`. Never string-parses `LoopParamBinding.path` —
|
|
104
|
+
* see the repo-wide rule against regex-parsing JS/TS-derived syntax.
|
|
105
|
+
*
|
|
106
|
+
* Used both for a fixed binding's FULL accessor (`base` = `$__bf_item`,
|
|
107
|
+
* `segments` = the whole path) and a rest binding's PARENT-prefix accessor
|
|
108
|
+
* (`segments` may be empty, at the loop root, in which case this returns
|
|
109
|
+
* `base` unchanged) — see `LoopParamBinding.segments` jsdoc for which case
|
|
110
|
+
* a binding is in.
|
|
111
|
+
*/
|
|
112
|
+
function perlSegmentAccessor(base: string, segments: readonly LoopBindingPathSegment[]): string {
|
|
113
|
+
let expr = base
|
|
114
|
+
for (const seg of segments) {
|
|
115
|
+
expr +=
|
|
116
|
+
seg.kind === 'field'
|
|
117
|
+
? `->{${seg.isIdent ? seg.key : perlHashKey(seg.key)}}`
|
|
118
|
+
: `->[${seg.index}]`
|
|
119
|
+
}
|
|
120
|
+
return expr
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Quote a string as a Perl single-quoted literal (backslash first, then the
|
|
125
|
+
* quote, so the escape doesn't double up). Unlike `perlHashKey` — which
|
|
126
|
+
* intentionally leaves an identifier-safe name as a bareword for `->{key}` /
|
|
127
|
+
* `key => val` hash-key position — a plain list element (`bf->omit($x, [id,
|
|
128
|
+
* title])`) is NOT hash-key position: an unquoted bareword there is a sub
|
|
129
|
+
* call under `use strict subs`, so every object-rest exclude key needs an
|
|
130
|
+
* unconditional string literal.
|
|
131
|
+
*/
|
|
132
|
+
function perlStringLiteral(s: string): string {
|
|
133
|
+
return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
|
|
134
|
+
}
|
|
135
|
+
|
|
97
136
|
export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRenderCtx> {
|
|
98
137
|
name = 'mojolicious'
|
|
99
138
|
extension = '.html.ep'
|
|
@@ -695,44 +734,89 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
695
734
|
// ===========================================================================
|
|
696
735
|
|
|
697
736
|
renderLoop(loop: IRLoop): string {
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
737
|
+
// clientOnly loops must not render items at SSR time, but must still emit
|
|
738
|
+
// the `loop:`/`/loop:` boundary marker pair (Hono and Go parity) so the
|
|
739
|
+
// client runtime's mapArray() can locate the insertion anchor when
|
|
740
|
+
// hydrating the array. Without the markers, mapArray() resolves
|
|
741
|
+
// anchor = null and appends after sibling markers (#872). The marker id
|
|
742
|
+
// disambiguates sibling `.map()` calls under the same parent (#1087).
|
|
743
|
+
if (loop.clientOnly) {
|
|
744
|
+
return `<%== bf->comment("loop:${loop.markerId}") %><%== bf->comment("/loop:${loop.markerId}") %>`
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// A `.map()` destructure loop param (`([k, v]) => ...` / `({ id, user: {
|
|
748
|
+
// name } }) => ...` / `({ id, ...rest }) => ...`) lowers to a Perl `my`
|
|
749
|
+
// local per binding, walking each binding's structured `segments` path
|
|
750
|
+
// (#2087 Phase B) into a native `->{key}` / `->[N]` accessor off the
|
|
751
|
+
// per-item var — so the body's `$id` / `$name` / `$rest->{flag}` /
|
|
752
|
+
// `{...rest}` all resolve natively, at any nesting depth.
|
|
707
753
|
//
|
|
708
754
|
// Check the IR's structured `paramBindings` field rather than
|
|
709
755
|
// string-matching `loop.param`: Phase 1 populates `paramBindings`
|
|
710
756
|
// iff the param is a destructure pattern (array or object); a
|
|
711
757
|
// simple identifier leaves it `undefined`. The structured check is
|
|
712
758
|
// robust to whitespace / formatting variants in the source.
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
//
|
|
716
|
-
//
|
|
717
|
-
//
|
|
759
|
+
//
|
|
760
|
+
// `isLowerableLoopDestructure` (#2087) still refuses: an object-rest
|
|
761
|
+
// binding used any way other than member access (`rest.flag`) or a
|
|
762
|
+
// `{...rest}` spread onto an intrinsic element (that needs the actual
|
|
763
|
+
// residual *object*, which isn't always safe to materialize — see the
|
|
764
|
+
// gate's own jsdoc for the full list); a `.filter().map(destructure)`
|
|
765
|
+
// chain (the filter-param rewrite is out of scope here); and a
|
|
766
|
+
// computed property key (`{ [k]: v }`, refused earlier as BF025).
|
|
718
767
|
const destructure = !!(loop.paramBindings && loop.paramBindings.length > 0)
|
|
719
|
-
const supportableDestructure = destructure &&
|
|
768
|
+
const supportableDestructure = destructure && isLowerableLoopDestructure(loop)
|
|
720
769
|
if (destructure && !supportableDestructure) {
|
|
721
770
|
this.errors.push({
|
|
722
771
|
code: 'BF104',
|
|
723
772
|
severity: 'error',
|
|
724
|
-
message: `Loop callback uses
|
|
773
|
+
message: `Loop callback uses a destructure pattern (\`${loop.param}\`) that the Mojo adapter cannot lower — see the diagnostic detail for the specific shape.`,
|
|
725
774
|
loc: loop.loc ?? { file: this.componentName + '.tsx', start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
726
775
|
suggestion: {
|
|
727
776
|
message:
|
|
728
777
|
`Options:\n` +
|
|
729
|
-
` 1.
|
|
730
|
-
` 2.
|
|
731
|
-
` 3.
|
|
778
|
+
` 1. If this is an object-rest binding (\`{ ...rest }\`), only reading \`rest.field\` or spreading \`{...rest}\` onto an intrinsic element lowers — other uses (passing \`rest\` to a function, rendering it as text) need the client runtime.\n` +
|
|
779
|
+
` 2. If this is chained \`.filter().map(({ ... }) => ...)\`, hoist the destructure into a variable inside the callback body instead.\n` +
|
|
780
|
+
` 3. Mark the loop position as @client-only so the destructure runs in JS on the client.\n` +
|
|
781
|
+
` 4. Move the loop into a primitive that the adapter registers explicitly.`,
|
|
732
782
|
},
|
|
733
783
|
})
|
|
734
784
|
}
|
|
735
785
|
|
|
786
|
+
// A `.map()` loop whose array is a bare identifier bound to a
|
|
787
|
+
// FUNCTION-scope local const with a non-statically-evaluable initializer
|
|
788
|
+
// that reads props/signals (e.g. `const entries =
|
|
789
|
+
// Object.entries(props.x ?? {}).filter(...)`) can't render correctly.
|
|
790
|
+
// Module-scope consts (`isModule`, e.g. `const payments = [...]` at the
|
|
791
|
+
// top of the file) are a DIFFERENT, already-working case handled
|
|
792
|
+
// elsewhere. Function-scope locals get no per-render stash slot — this
|
|
793
|
+
// adapter's only "elsewhere" for a local const is inlining its value at
|
|
794
|
+
// the use site (`resolveLiteralConst`'s numeric/single-quoted-string fast
|
|
795
|
+
// path, or a static-record-literal lookup), never binding one as a `my`
|
|
796
|
+
// template local. Left unchecked, `% for my $_i (0..$#{$entries}) {`
|
|
797
|
+
// over an undeclared `$entries` faults under `use strict` at request
|
|
798
|
+
// time instead of failing loudly at build time. Pre-existing, general
|
|
799
|
+
// limitation, orthogonal to #2087's destructure-binding work — newly
|
|
800
|
+
// reachable in this adapter's test corpus only because the widened
|
|
801
|
+
// destructure gate (#2087 Phase A/B) no longer refuses this fixture's
|
|
802
|
+
// `([emoji, users]) => ...` param first.
|
|
803
|
+
const arrayName = loop.array.trim()
|
|
804
|
+
if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
805
|
+
const arrayConst = (this.localConstants ?? []).find(c => c.name === arrayName)
|
|
806
|
+
if (arrayConst && !arrayConst.isModule && this.resolveLiteralConst(arrayName) === null) {
|
|
807
|
+
this.errors.push({
|
|
808
|
+
code: 'BF101',
|
|
809
|
+
severity: 'error',
|
|
810
|
+
message: `Loop array \`${arrayName}\` is a local computed value (\`${arrayConst.value}\`) that the Mojo adapter cannot bind as a template variable — only numeric/string-literal locals inline at their use site.`,
|
|
811
|
+
loc: loop.loc ?? { file: this.componentName + '.tsx', start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
812
|
+
suggestion: {
|
|
813
|
+
message:
|
|
814
|
+
'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.',
|
|
815
|
+
},
|
|
816
|
+
})
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
736
820
|
const rawArray = this.convertExpressionToPerl(loop.array)
|
|
737
821
|
// Apply sort if present (#1448 Tier B): wrap the loop array in the
|
|
738
822
|
// shared sort helper. The same `renderSortEval` / `renderSortMethod`
|
|
@@ -837,15 +921,31 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
837
921
|
lines.push(`% for my ${indexVar} (0..$#{${array}}) {`)
|
|
838
922
|
if (loop.iterationShape !== 'keys') {
|
|
839
923
|
if (supportableDestructure) {
|
|
840
|
-
// Per-item var + one `my` local per binding
|
|
841
|
-
//
|
|
924
|
+
// Per-item var + one `my` local per binding, walking each binding's
|
|
925
|
+
// `segments` path (#2087 Phase B):
|
|
926
|
+
// - fixed (`b.rest` unset): the FULL accessor from `$__bf_item`.
|
|
927
|
+
// - array-rest: `bf->slice(parent, from, undef)` — the same
|
|
928
|
+
// runtime helper `.slice()` JS-method calls lower to (see
|
|
929
|
+
// `array-method.ts`), so the "no end → to length" arithmetic
|
|
930
|
+
// stays in one place.
|
|
931
|
+
// - object-rest: `bf->omit(parent, [...excluded keys...])` — a
|
|
932
|
+
// TRUE residual hashref (not the whole item aliased), so both
|
|
933
|
+
// `$rest->{flag}` (member-access use) and `bf->spread_attrs($rest)`
|
|
934
|
+
// (spread-onto-element use) see only the non-destructured keys.
|
|
935
|
+
// `parent` is `$__bf_item` walked through the binding's PARENT-prefix
|
|
936
|
+
// `segments` (empty at the loop root, per the `LoopParamBinding`
|
|
937
|
+
// jsdoc) — NOT the same as a fixed binding's full-accessor segments.
|
|
842
938
|
lines.push(`% my $__bf_item = ${array}->[${indexVar}];`)
|
|
843
939
|
for (const b of loop.paramBindings ?? []) {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
)
|
|
940
|
+
const parent = perlSegmentAccessor('$__bf_item', b.segments ?? [])
|
|
941
|
+
if (b.rest?.kind === 'object') {
|
|
942
|
+
const exclude = b.rest.exclude.map(k => perlStringLiteral(k.key)).join(', ')
|
|
943
|
+
lines.push(`% my $${b.name} = bf->omit(${parent}, [${exclude}]);`)
|
|
944
|
+
} else if (b.rest?.kind === 'array') {
|
|
945
|
+
lines.push(`% my $${b.name} = bf->slice(${parent}, ${b.rest.from}, undef);`)
|
|
946
|
+
} else {
|
|
947
|
+
lines.push(`% my $${b.name} = ${perlSegmentAccessor('$__bf_item', b.segments ?? [])};`)
|
|
948
|
+
}
|
|
849
949
|
}
|
|
850
950
|
} else {
|
|
851
951
|
lines.push(`% my $${param} = ${array}->[${indexVar}];`)
|
|
@@ -1389,13 +1489,22 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1389
1489
|
// now lowering `.length` on a higher-order object to
|
|
1390
1490
|
// `scalar(@{...})`, the canonical
|
|
1391
1491
|
// `x.tags.filter(t => t.active).length > 0` shape lowers
|
|
1392
|
-
// cleanly
|
|
1393
|
-
//
|
|
1394
|
-
//
|
|
1395
|
-
//
|
|
1396
|
-
//
|
|
1397
|
-
// canonical case
|
|
1398
|
-
|
|
1492
|
+
// cleanly, and nested `filter` / `every` / `some` lower to real
|
|
1493
|
+
// inline `grep` forms. The shapes the emitter can only DEGRADE
|
|
1494
|
+
// (nested `find*`, sort / reduce / flatMap inside a predicate)
|
|
1495
|
+
// surface BF101 through the hook below instead of silently
|
|
1496
|
+
// rewriting the predicate (#2038). Wholesale refusal would block
|
|
1497
|
+
// the canonical case #1443 exists to enable, so the gate lives at
|
|
1498
|
+
// the exact degrade points inside `MojoFilterEmitter.callbackMethod`.
|
|
1499
|
+
return emitParsedExpr(
|
|
1500
|
+
expr,
|
|
1501
|
+
new MojoFilterEmitter(
|
|
1502
|
+
param,
|
|
1503
|
+
localVarMap,
|
|
1504
|
+
n => this._isStringValueName(n),
|
|
1505
|
+
(message, reason) => this._recordExprBF101(message, reason),
|
|
1506
|
+
),
|
|
1507
|
+
)
|
|
1399
1508
|
}
|
|
1400
1509
|
|
|
1401
1510
|
// ===========================================================================
|
|
@@ -1595,6 +1704,17 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1595
1704
|
const argsGo = queryHrefArgs(node, n => this.renderParsedExprToPerl(n))
|
|
1596
1705
|
return `bf->query(${argsGo.join(', ')})`
|
|
1597
1706
|
}
|
|
1707
|
+
// Generic `helper-call` (#2069) — the neutral vocabulary's escape
|
|
1708
|
+
// hatch for a userland `LoweringPlugin` that lowers to a single
|
|
1709
|
+
// runtime-helper invocation. `bf-><helper>(args…)` mirrors the
|
|
1710
|
+
// `query` helper's own naming convention exactly: the framework
|
|
1711
|
+
// renders the call, the plugin author registers `<helper>` as a
|
|
1712
|
+
// method on their own Mojolicious `bf` helper object — same
|
|
1713
|
+
// contract as `bf->query` itself, just not built in.
|
|
1714
|
+
if (node?.kind === 'helper-call' && isValidHelperId(node.helper)) {
|
|
1715
|
+
const argsX = node.args.map(a => this.renderParsedExprToPerl(a))
|
|
1716
|
+
return `bf->${node.helper}(${argsX.join(', ')})`
|
|
1717
|
+
}
|
|
1598
1718
|
}
|
|
1599
1719
|
}
|
|
1600
1720
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-fixture build-time contracts for shapes the Mojo adapter
|
|
3
|
+
* intentionally refuses to lower. Owned by this module (not by the
|
|
4
|
+
* shared fixtures) so adding a new adapter doesn't require touching any
|
|
5
|
+
* cross-adapter file. Consumed by this package's own conformance test
|
|
6
|
+
* (as `expectedDiagnostics`) and by `bf compat` (issue-URL attribution).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ConformancePins } from '@barefootjs/jsx'
|
|
10
|
+
|
|
11
|
+
export const conformancePins: ConformancePins = {
|
|
12
|
+
// Sibling-imported child component in a loop body: Mojo emits
|
|
13
|
+
// a cross-template call that needs separate registration. BF103
|
|
14
|
+
// makes the requirement loud. (The barefoot CLI passes
|
|
15
|
+
// `siblingTemplatesRegistered: true` so CLI builds suppress it.)
|
|
16
|
+
'static-array-children': [{ code: 'BF103', severity: 'error' }],
|
|
17
|
+
// TodoApp / TodoAppSSR import `TodoItem` from a sibling file and
|
|
18
|
+
// call it inside a keyed `.map`. Same BF103 surface as the
|
|
19
|
+
// synthetic `static-array-children` above — pinned at adapter
|
|
20
|
+
// level so the shared-component corpus stays adapter-neutral.
|
|
21
|
+
'todo-app': [{ code: 'BF103', severity: 'error' }],
|
|
22
|
+
'todo-app-ssr': [{ code: 'BF103', severity: 'error' }],
|
|
23
|
+
// `([emoji, users]) => ...` / `([id, t]) => ...` are plain array-index
|
|
24
|
+
// (tuple) destructures, no rest — #2087 Phase B's `segments`-walking
|
|
25
|
+
// accessor lowers both to `$__bf_item->[0]` / `$__bf_item->[1]` `my`
|
|
26
|
+
// locals like any other fixed binding, so BF104 no longer fires for
|
|
27
|
+
// either fixture. Each now hits a DIFFERENT, pre-existing, orthogonal
|
|
28
|
+
// gap instead: the loop array (`entries`) is a function-scope local
|
|
29
|
+
// const with a computed initializer
|
|
30
|
+
// (`Object.entries(props.x ?? {}).filter(...)`) that the adapter can't
|
|
31
|
+
// bind as a template variable — see the dedicated `arrayConst` BF101
|
|
32
|
+
// check in `renderLoop`. This was always true; it was simply
|
|
33
|
+
// unreachable before because BF104 refused the destructure shape first.
|
|
34
|
+
'static-array-from-props': [{ code: 'BF101', severity: 'error' }],
|
|
35
|
+
// Both BF103 (sibling-imported `<Tag>` child component) and the BF101
|
|
36
|
+
// above fire; BF104 no longer does (see above).
|
|
37
|
+
'static-array-from-props-with-component': [
|
|
38
|
+
{ code: 'BF103', severity: 'error' },
|
|
39
|
+
{ code: 'BF101', severity: 'error' },
|
|
40
|
+
],
|
|
41
|
+
// #1310 / #2087: rest destructure in .map() callback. All four shapes
|
|
42
|
+
// now lower via #2087 Phase B's `segments`-walking accessor:
|
|
43
|
+
// - object-rest read via member access (`rest-destructure-object-in-map`):
|
|
44
|
+
// `bf->omit($__bf_item, [...exclude keys...])`, `$rest->{flag}` reads
|
|
45
|
+
// the residual hashref.
|
|
46
|
+
// - object-rest spread onto the root element
|
|
47
|
+
// (`rest-destructure-object-spread-in-map`): same `bf->omit(...)`
|
|
48
|
+
// residual, forwarded via the existing `bf->spread_attrs($rest)` path.
|
|
49
|
+
// - array-rest (`rest-destructure-array-in-map`): `bf->slice($__bf_item,
|
|
50
|
+
// N, undef)` — the same runtime helper `.slice()` JS-method calls use.
|
|
51
|
+
// - nested rest inside an object pattern (`rest-destructure-nested-in-map`):
|
|
52
|
+
// the parent-prefix accessor (`$__bf_item->{cells}`) feeds the same
|
|
53
|
+
// `bf->slice(...)` call.
|
|
54
|
+
// None of these are pinned here anymore.
|
|
55
|
+
// `style-3-signals` / `style-object-dynamic` no longer pinned — a
|
|
56
|
+
// `style={{ … }}` object literal now lowers to a CSS string with dynamic
|
|
57
|
+
// values interpolated (`background-color:<%= $color %>;padding:8px`) via
|
|
58
|
+
// `tryLowerStyleObject` (#1322).
|
|
59
|
+
// (`tagged-template-classname` graduated by #2092 — the tag resolves
|
|
60
|
+
// through the interleave-tag catalogue and desugars to an untagged
|
|
61
|
+
// template literal, so it lowers like any other className template.)
|
|
62
|
+
// #2038: a filter predicate containing a nested `.find(...)` callback.
|
|
63
|
+
// `find*` returns an element, not a boolean — there is no inline grep
|
|
64
|
+
// form, and the emitter used to degrade the call to its receiver.
|
|
65
|
+
// The nested `.some` sibling (`filter-nested-callback-predicate`) is
|
|
66
|
+
// NOT pinned: Mojo lowers it to a real inline Perl `grep` and must
|
|
67
|
+
// render to Hono parity instead.
|
|
68
|
+
// https://github.com/piconic-ai/barefootjs/issues/2038
|
|
69
|
+
'filter-nested-find-predicate': [
|
|
70
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
|
|
71
|
+
],
|
|
72
|
+
// #1467 demo-corpus context providers (`radio-group`, `accordion`,
|
|
73
|
+
// `dialog`, `popover`, `select`, `dropdown-menu`, `combobox`,
|
|
74
|
+
// `command`) are no longer pinned — an object-literal provider value
|
|
75
|
+
// (`{ open: () => props.open ?? false, onOpenChange: (v) => {…} }`)
|
|
76
|
+
// lowers to a Perl hashref via `parseProviderObjectLiteral` (#1897):
|
|
77
|
+
// getter members snapshot their body's SSR value, handler /
|
|
78
|
+
// function-shaped members lower to `undef`. The command demo's
|
|
79
|
+
// `ref={(el) => {…}}` function prop on an imported component is
|
|
80
|
+
// skipped at SSR like `on*` handlers.
|
|
81
|
+
//
|
|
82
|
+
// #1467 Phase 2e: `data-table` is no longer pinned here either — it
|
|
83
|
+
// compiles clean (`selected()[index]` → `index-access`,
|
|
84
|
+
// `.toFixed(2)` → `bf->to_fixed`, `/* @client */` memo SSR-folded)
|
|
85
|
+
// and renders to Hono parity on real Mojolicious. The keyed-loop
|
|
86
|
+
// scope-ID divergence (#1896) was fixed by the body-children
|
|
87
|
+
// `inLoop` reset (loop-item children get `_bf_slot`); data-table is
|
|
88
|
+
// off `skipJsx` entirely and only kept in `skipMarkerConformance`
|
|
89
|
+
// below for the shared `/* @client */` keyed-map slot-id elision
|
|
90
|
+
// contract (same as `todo-app`), not a render or BF101 gap.
|
|
91
|
+
// #1443: `[a, b].filter(Boolean).join(' ')` (the registry Slot's
|
|
92
|
+
// shape) now lowers to `join(' ', @{[grep { $_ } @{[$a, $b]}]})`.
|
|
93
|
+
// No BF101 expected — pinned positively via the
|
|
94
|
+
// `branch-local-filter-join` template-output test below.
|
|
95
|
+
//
|
|
96
|
+
// #1448 Tier A — JS Array / String methods that the Mojo adapter
|
|
97
|
+
// hasn't lowered yet. Each row drops once the corresponding
|
|
98
|
+
// method PR lands. Hono / CSR pass these out of the box (they
|
|
99
|
+
// evaluate JS at runtime) so the pin only applies here.
|
|
100
|
+
//
|
|
101
|
+
// `array-includes` / `string-includes` no longer pinned — both
|
|
102
|
+
// shapes lower via the shared `array-method` IR + `bf->includes`
|
|
103
|
+
// runtime dispatch (#1448 Tier A first PR).
|
|
104
|
+
// `array-indexOf` / `array-lastIndexOf` no longer pinned —
|
|
105
|
+
// value-equality `bf->index_of` / `bf->last_index_of` helpers
|
|
106
|
+
// handle the shape (#1448 Tier A second PR).
|
|
107
|
+
// `array-at` no longer pinned — `bf->at` (Mojo) / `bf_at` (Go)
|
|
108
|
+
// handle the negative-index lookup (#1448 Tier A third PR).
|
|
109
|
+
// `array-concat` no longer pinned — `bf->concat` (Mojo) /
|
|
110
|
+
// `bf_concat` (Go) merge two arrays into a new array
|
|
111
|
+
// (#1448 Tier A fourth PR).
|
|
112
|
+
// `array-slice` no longer pinned — `bf->slice` (Mojo) /
|
|
113
|
+
// `bf_slice` (Go) carve out a sub-range with JS-compat
|
|
114
|
+
// negative-index / out-of-bounds clamping (#1448 Tier A
|
|
115
|
+
// fifth PR).
|
|
116
|
+
// `array-reverse` / `array-toReversed` no longer pinned —
|
|
117
|
+
// both share the `bf->reverse` / `bf_reverse` helper since
|
|
118
|
+
// SSR templates render a snapshot and the JS mutate-vs-new
|
|
119
|
+
// distinction has no template-level meaning (#1448 Tier A
|
|
120
|
+
// sixth PR).
|
|
121
|
+
// `string-toLowerCase` / `string-toUpperCase` no longer pinned —
|
|
122
|
+
// Perl's native `lc` / `uc` (Mojo) and pre-existing
|
|
123
|
+
// `bf_lower` / `bf_upper` (Go) handle the JS method names
|
|
124
|
+
// (#1448 Tier A seventh + eighth PRs).
|
|
125
|
+
// `string-trim` no longer pinned — pre-existing `bf_trim`
|
|
126
|
+
// (Go) and new `bf->trim` helper (Mojo) handle the strip
|
|
127
|
+
// (#1448 Tier A ninth PR, closing out Tier A).
|
|
128
|
+
// `.find` / `.findIndex` / `.findLast` / `.findLastIndex` are no longer
|
|
129
|
+
// pinned — the Mojo `callbackMethod` predicate arm now lowers them to the
|
|
130
|
+
// runtime `bf->find` / `find_index` / `find_last` / `find_last_index` helpers
|
|
131
|
+
// (per-element coderef predicate), matching Xslate. `.join` was never
|
|
132
|
+
// pinned (handled by `renderArrayMethod`'s `case 'join'`).
|
|
133
|
+
// #2073 follow-up: a function-reference `.map(format)` callback has no
|
|
134
|
+
// arrow body to serialize — not a CALLBACK_METHODS shape — so the
|
|
135
|
+
// UNSUPPORTED_METHODS gate refuses it with BF101 rather than emitting
|
|
136
|
+
// a broken template.
|
|
137
|
+
'array-map-function-reference': [{ code: 'BF101', severity: 'error' }],
|
|
138
|
+
}
|
package/src/index.ts
CHANGED
package/src/test-render.ts
CHANGED
|
@@ -213,6 +213,14 @@ export async function renderMojoComponent(options: RenderOptions): Promise<strin
|
|
|
213
213
|
await Bun.write(resolve(tempDir, `${toSnakeCase(childName)}.html.ep`), patchTemplate(template))
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// Surface every `props.<x>` access as a propsParam before building
|
|
217
|
+
// the top-level stash (idempotent; the same shared pass the adapter's
|
|
218
|
+
// `generate` applies to its own IR instance — this round-tripped IR
|
|
219
|
+
// hasn't been augmented). Without it, a read reachable only through a
|
|
220
|
+
// dynamic text child / condition / loop array (#2126 follow-up) has
|
|
221
|
+
// no param entry, `buildPerlProps` seeds nothing, and the template's
|
|
222
|
+
// bare `$var` aborts under Mojo::Template's strict vars.
|
|
223
|
+
augmentInheritedPropAccesses(ir)
|
|
216
224
|
// Build props hash for Perl
|
|
217
225
|
const propsPerl = buildPerlProps(componentName, props, ir)
|
|
218
226
|
|