@barefootjs/xslate 0.17.1 → 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 +3 -1
- package/dist/adapter/expr/array-method.d.ts.map +1 -1
- package/dist/adapter/expr/emitters.d.ts +7 -3
- package/dist/adapter/expr/emitters.d.ts.map +1 -1
- package/dist/adapter/index.js +90 -30
- package/dist/adapter/xslate-adapter.d.ts +30 -0
- package/dist/adapter/xslate-adapter.d.ts.map +1 -1
- package/dist/build.js +90 -30
- 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 +109 -30
- package/lib/BarefootJS/Backend/Xslate.pm +1 -1
- package/package.json +3 -3
- package/src/__tests__/xslate-adapter.test.ts +13 -103
- package/src/adapter/expr/array-method.ts +22 -1
- package/src/adapter/expr/emitters.ts +23 -11
- package/src/adapter/xslate-adapter.ts +232 -48
- package/src/conformance-pins.ts +112 -0
- package/src/index.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -187302,12 +187302,13 @@ import {
|
|
|
187302
187302
|
emitAttrValue,
|
|
187303
187303
|
augmentInheritedPropAccesses,
|
|
187304
187304
|
collectModuleStringConsts,
|
|
187305
|
-
isLowerableObjectRestDestructure,
|
|
187306
187305
|
lookupStaticRecordLiteral,
|
|
187307
187306
|
searchParamsLocalNames,
|
|
187308
187307
|
prepareLoweringMatchers,
|
|
187309
187308
|
queryHrefArgs,
|
|
187310
|
-
|
|
187309
|
+
isValidHelperId,
|
|
187310
|
+
sortComparatorFromArrow as sortComparatorFromArrow2,
|
|
187311
|
+
isLowerableLoopDestructure
|
|
187311
187312
|
} from "@barefootjs/jsx";
|
|
187312
187313
|
|
|
187313
187314
|
// src/adapter/boolean-result.ts
|
|
@@ -187600,7 +187601,10 @@ function renderSortMethod(recv, c) {
|
|
|
187600
187601
|
});
|
|
187601
187602
|
return `$bf.sort(${recv}, { keys => [${keyHashes.join(", ")}] })`;
|
|
187602
187603
|
}
|
|
187603
|
-
function renderFlatMethod(recv, depth) {
|
|
187604
|
+
function renderFlatMethod(recv, depth, emit) {
|
|
187605
|
+
if (typeof depth === "object") {
|
|
187606
|
+
return `$bf.flat_dynamic(${recv}, ${emit(depth.expr)})`;
|
|
187607
|
+
}
|
|
187604
187608
|
const d = depth === "infinity" ? -1 : depth;
|
|
187605
187609
|
return `$bf.flat(${recv}, ${d})`;
|
|
187606
187610
|
}
|
|
@@ -187711,7 +187715,7 @@ class XslateFilterEmitter {
|
|
|
187711
187715
|
return renderArrayMethod(method, object, args, emit);
|
|
187712
187716
|
}
|
|
187713
187717
|
flatMethod(object, depth, emit) {
|
|
187714
|
-
return renderFlatMethod(emit(object), depth);
|
|
187718
|
+
return renderFlatMethod(emit(object), depth, emit);
|
|
187715
187719
|
}
|
|
187716
187720
|
conditional(_test, _consequent, _alternate) {
|
|
187717
187721
|
return "1";
|
|
@@ -187911,7 +187915,7 @@ class XslateTopLevelEmitter {
|
|
|
187911
187915
|
return renderArrayMethod(method, object, args, emit);
|
|
187912
187916
|
}
|
|
187913
187917
|
flatMethod(object, depth, emit) {
|
|
187914
|
-
return renderFlatMethod(emit(object), depth);
|
|
187918
|
+
return renderFlatMethod(emit(object), depth, emit);
|
|
187915
187919
|
}
|
|
187916
187920
|
conditional(test, consequent, alternate, emit) {
|
|
187917
187921
|
return `(${emit(test)} ? ${emit(consequent)} : ${emit(alternate)})`;
|
|
@@ -187942,8 +187946,8 @@ class XslateTopLevelEmitter {
|
|
|
187942
187946
|
unsupported(_raw, _reason) {
|
|
187943
187947
|
return "''";
|
|
187944
187948
|
}
|
|
187945
|
-
objectLiteral(
|
|
187946
|
-
return "''";
|
|
187949
|
+
objectLiteral(properties, _raw, _emit) {
|
|
187950
|
+
return properties.length === 0 ? "{}" : "''";
|
|
187947
187951
|
}
|
|
187948
187952
|
}
|
|
187949
187953
|
|
|
@@ -188174,6 +188178,17 @@ function collectStringValueNames(ir) {
|
|
|
188174
188178
|
}
|
|
188175
188179
|
|
|
188176
188180
|
// src/adapter/xslate-adapter.ts
|
|
188181
|
+
function kolonSegmentAccessor(base, segments) {
|
|
188182
|
+
let expr = base;
|
|
188183
|
+
for (const seg of segments) {
|
|
188184
|
+
expr += seg.kind === "field" ? seg.isIdent ? `.${seg.key}` : `[${kolonHashKey(seg.key)}]` : `[${seg.index}]`;
|
|
188185
|
+
}
|
|
188186
|
+
return expr;
|
|
188187
|
+
}
|
|
188188
|
+
function kolonStringLiteral(s) {
|
|
188189
|
+
return `'${escapeKolonSingleQuoted(s)}'`;
|
|
188190
|
+
}
|
|
188191
|
+
|
|
188177
188192
|
class XslateAdapter extends BaseAdapter {
|
|
188178
188193
|
name = "xslate";
|
|
188179
188194
|
extension = ".tx";
|
|
@@ -188442,21 +188457,37 @@ ${whenTrue}
|
|
|
188442
188457
|
return `<: $bf.comment("loop:${loop.markerId}") | mark_raw :><: $bf.comment("/loop:${loop.markerId}") | mark_raw :>`;
|
|
188443
188458
|
}
|
|
188444
188459
|
const destructure = !!(loop.paramBindings && loop.paramBindings.length > 0);
|
|
188445
|
-
const supportableDestructure = destructure &&
|
|
188460
|
+
const supportableDestructure = destructure && isLowerableLoopDestructure(loop);
|
|
188446
188461
|
if (destructure && !supportableDestructure) {
|
|
188447
188462
|
this.errors.push({
|
|
188448
188463
|
code: "BF104",
|
|
188449
188464
|
severity: "error",
|
|
188450
|
-
message: `Loop callback uses
|
|
188465
|
+
message: `Loop callback uses a destructure pattern (\`${loop.param}\`) that the Xslate adapter cannot lower — see the diagnostic detail for the specific shape.`,
|
|
188451
188466
|
loc: loop.loc ?? { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188452
188467
|
suggestion: {
|
|
188453
188468
|
message: `Options:
|
|
188454
|
-
` + ` 1.
|
|
188455
|
-
` + ` 2.
|
|
188456
|
-
` + ` 3.
|
|
188469
|
+
` + ` 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.
|
|
188470
|
+
` + ` 2. If this is chained \`.filter().map(({ ... }) => ...)\`, hoist the destructure into a variable inside the callback body instead.
|
|
188471
|
+
` + ` 3. Mark the loop position as @client-only so the destructure runs in JS on the client.
|
|
188472
|
+
` + ` 4. Move the loop into a primitive that the adapter registers explicitly.`
|
|
188457
188473
|
}
|
|
188458
188474
|
});
|
|
188459
188475
|
}
|
|
188476
|
+
const arrayName = loop.array.trim();
|
|
188477
|
+
if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
188478
|
+
const arrayConst = (this.localConstants ?? []).find((c) => c.name === arrayName);
|
|
188479
|
+
if (arrayConst && !arrayConst.isModule && this._resolveLiteralConst(arrayName) === null) {
|
|
188480
|
+
this.errors.push({
|
|
188481
|
+
code: "BF101",
|
|
188482
|
+
severity: "error",
|
|
188483
|
+
message: `Loop array \`${arrayName}\` is a local computed value (\`${arrayConst.value}\`) that the Xslate adapter cannot bind as a template variable — only numeric/string-literal locals inline at their use site.`,
|
|
188484
|
+
loc: loop.loc ?? { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188485
|
+
suggestion: {
|
|
188486
|
+
message: "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."
|
|
188487
|
+
}
|
|
188488
|
+
});
|
|
188489
|
+
}
|
|
188490
|
+
}
|
|
188460
188491
|
const rawArray = this.convertExpressionToKolon(loop.array);
|
|
188461
188492
|
let array = rawArray;
|
|
188462
188493
|
if (loop.sortComparator) {
|
|
@@ -188478,7 +188509,15 @@ ${whenTrue}
|
|
|
188478
188509
|
}
|
|
188479
188510
|
if (supportableDestructure) {
|
|
188480
188511
|
for (const b of loop.paramBindings ?? []) {
|
|
188481
|
-
|
|
188512
|
+
const parent = kolonSegmentAccessor(`$${loopVar}`, b.segments ?? []);
|
|
188513
|
+
if (b.rest?.kind === "object") {
|
|
188514
|
+
const exclude = b.rest.exclude.map((k) => kolonStringLiteral(k.key)).join(", ");
|
|
188515
|
+
indexLocalLines.push(`: my $${b.name} = $bf.omit(${parent}, [${exclude}]);`);
|
|
188516
|
+
} else if (b.rest?.kind === "array") {
|
|
188517
|
+
indexLocalLines.push(`: my $${b.name} = $bf.slice(${parent}, ${b.rest.from}, nil);`);
|
|
188518
|
+
} else {
|
|
188519
|
+
indexLocalLines.push(`: my $${b.name} = ${kolonSegmentAccessor(`$${loopVar}`, b.segments ?? [])};`);
|
|
188520
|
+
}
|
|
188482
188521
|
}
|
|
188483
188522
|
}
|
|
188484
188523
|
const prevInLoop = this.inLoop;
|
|
@@ -188534,8 +188573,32 @@ ${childrenUnderLoop}` : childrenUnderLoop;
|
|
|
188534
188573
|
emitBooleanShorthand: (_value, name) => `${kolonHashKey(name)} => 1`,
|
|
188535
188574
|
emitJsxChildren: () => ""
|
|
188536
188575
|
};
|
|
188576
|
+
componentPropSegmentEntries(segments) {
|
|
188577
|
+
const last = segments[segments.length - 1];
|
|
188578
|
+
if (last && last.kind === "entries")
|
|
188579
|
+
return last.parts;
|
|
188580
|
+
const seg = { kind: "entries", parts: [] };
|
|
188581
|
+
segments.push(seg);
|
|
188582
|
+
return seg.parts;
|
|
188583
|
+
}
|
|
188584
|
+
combineComponentPropSegments(segments) {
|
|
188585
|
+
let acc = null;
|
|
188586
|
+
for (const seg of segments) {
|
|
188587
|
+
if (seg.kind === "entries") {
|
|
188588
|
+
if (seg.parts.length === 0)
|
|
188589
|
+
continue;
|
|
188590
|
+
const text = `{ ${seg.parts.join(", ")} }`;
|
|
188591
|
+
acc = acc === null ? text : `${acc}.merge(${text})`;
|
|
188592
|
+
} else {
|
|
188593
|
+
const text = `(${seg.expr} // {})`;
|
|
188594
|
+
acc = acc === null ? text : `${acc}.merge(${text})`;
|
|
188595
|
+
}
|
|
188596
|
+
}
|
|
188597
|
+
return acc ?? "{}";
|
|
188598
|
+
}
|
|
188537
188599
|
renderComponent(comp) {
|
|
188538
|
-
const
|
|
188600
|
+
const segments = [{ kind: "entries", parts: [] }];
|
|
188601
|
+
const currentEntries = () => this.componentPropSegmentEntries(segments);
|
|
188539
188602
|
for (const p of comp.props) {
|
|
188540
188603
|
if ((p.name.match(/^on[A-Z]/) || p.name === "ref") && p.value.kind === "expression")
|
|
188541
188604
|
continue;
|
|
@@ -188543,27 +188606,19 @@ ${childrenUnderLoop}` : childrenUnderLoop;
|
|
|
188543
188606
|
const trimmed = p.value.expr.trim();
|
|
188544
188607
|
if (this.propsObjectName && this.propsObjectName === trimmed) {
|
|
188545
188608
|
for (const pp of this.propsParams) {
|
|
188546
|
-
|
|
188609
|
+
currentEntries().push(`${pp.name} => $${pp.name}`);
|
|
188547
188610
|
}
|
|
188548
188611
|
continue;
|
|
188549
188612
|
}
|
|
188550
|
-
|
|
188551
|
-
code: "BF101",
|
|
188552
|
-
severity: "error",
|
|
188553
|
-
message: `Spread props (\`{...${trimmed}}\`) on a child component cannot be lowered to Kolon — Kolon hashref method args can't splat a runtime hash into named entries.`,
|
|
188554
|
-
loc: comp.loc ?? { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188555
|
-
suggestion: {
|
|
188556
|
-
message: "Pass the child component its props explicitly rather than spreading a runtime object."
|
|
188557
|
-
}
|
|
188558
|
-
});
|
|
188613
|
+
segments.push({ kind: "spread", expr: this.convertExpressionToKolon(p.value.expr) });
|
|
188559
188614
|
continue;
|
|
188560
188615
|
}
|
|
188561
188616
|
const lowered = emitAttrValue(p.value, this.componentPropEmitter, p.name);
|
|
188562
188617
|
if (lowered)
|
|
188563
|
-
|
|
188618
|
+
currentEntries().push(lowered);
|
|
188564
188619
|
}
|
|
188565
188620
|
if (comp.slotId && !this.inLoop) {
|
|
188566
|
-
|
|
188621
|
+
currentEntries().push(`_bf_slot => '${comp.slotId}'`);
|
|
188567
188622
|
}
|
|
188568
188623
|
const tplName = this.toTemplateName(comp.name);
|
|
188569
188624
|
const effectiveChildren = comp.children.length > 0 ? comp.children : resolveJsxChildrenProp(comp.props);
|
|
@@ -188573,11 +188628,12 @@ ${childrenUnderLoop}` : childrenUnderLoop;
|
|
|
188573
188628
|
const childrenBody = this.renderChildren(effectiveChildren);
|
|
188574
188629
|
this.inLoop = prevInLoop;
|
|
188575
188630
|
const macroName = `bf_children_${comp.slotId ?? "c" + this.childrenCaptureCounter++}`;
|
|
188576
|
-
|
|
188577
|
-
const
|
|
188578
|
-
return `<: macro ${macroName} -> () { :>${childrenBody}<: } :><: $bf.render_child('${tplName}',
|
|
188631
|
+
currentEntries().push(`children => ${macroName}()`);
|
|
188632
|
+
const dict = this.combineComponentPropSegments(segments);
|
|
188633
|
+
return `<: macro ${macroName} -> () { :>${childrenBody}<: } :><: $bf.render_child('${tplName}', ${dict}) | mark_raw :>`;
|
|
188579
188634
|
}
|
|
188580
|
-
const
|
|
188635
|
+
const isEmpty = segments.every((s) => s.kind === "entries" && s.parts.length === 0);
|
|
188636
|
+
const hashEntries = isEmpty ? "" : `, ${this.combineComponentPropSegments(segments)}`;
|
|
188581
188637
|
return `<: $bf.render_child('${tplName}'${hashEntries}) | mark_raw :>`;
|
|
188582
188638
|
}
|
|
188583
188639
|
childrenCaptureCounter = 0;
|
|
@@ -188852,6 +188908,10 @@ ${reason}` : "";
|
|
|
188852
188908
|
const qArgs = queryHrefArgs(node, (n) => this.renderParsedExprToKolon(n));
|
|
188853
188909
|
return `$bf.query(${qArgs.join(", ")})`;
|
|
188854
188910
|
}
|
|
188911
|
+
if (node?.kind === "helper-call" && isValidHelperId(node.helper)) {
|
|
188912
|
+
const argsX = node.args.map((a) => this.renderParsedExprToKolon(a));
|
|
188913
|
+
return `$bf.${node.helper}(${argsX.join(", ")})`;
|
|
188914
|
+
}
|
|
188855
188915
|
}
|
|
188856
188916
|
}
|
|
188857
188917
|
const support = isSupported(parsed);
|
|
@@ -188951,7 +189011,26 @@ Options:
|
|
|
188951
189011
|
}
|
|
188952
189012
|
}
|
|
188953
189013
|
var xslateAdapter = new XslateAdapter;
|
|
189014
|
+
// src/conformance-pins.ts
|
|
189015
|
+
var conformancePins = {
|
|
189016
|
+
"static-array-children": [{ code: "BF103", severity: "error" }],
|
|
189017
|
+
"todo-app": [{ code: "BF103", severity: "error" }],
|
|
189018
|
+
"todo-app-ssr": [{ code: "BF103", severity: "error" }],
|
|
189019
|
+
"static-array-from-props": [{ code: "BF101", severity: "error" }],
|
|
189020
|
+
"static-array-from-props-with-component": [
|
|
189021
|
+
{ code: "BF103", severity: "error" },
|
|
189022
|
+
{ code: "BF101", severity: "error" }
|
|
189023
|
+
],
|
|
189024
|
+
"filter-nested-callback-predicate": [
|
|
189025
|
+
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2038" }
|
|
189026
|
+
],
|
|
189027
|
+
"filter-nested-find-predicate": [
|
|
189028
|
+
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2038" }
|
|
189029
|
+
],
|
|
189030
|
+
"array-map-function-reference": [{ code: "BF101", severity: "error" }]
|
|
189031
|
+
};
|
|
188954
189032
|
export {
|
|
188955
189033
|
xslateAdapter,
|
|
189034
|
+
conformancePins,
|
|
188956
189035
|
XslateAdapter
|
|
188957
189036
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/xslate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Text::Xslate (Kolon) adapter for BarefootJS — compiles IR to .tx templates and ships the Xslate rendering backend; runs under any PSGI/Plack app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,14 +55,14 @@
|
|
|
55
55
|
"directory": "packages/adapter-xslate"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@barefootjs/shared": "0.
|
|
58
|
+
"@barefootjs/shared": "0.18.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@barefootjs/jsx": ">=0.2.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
65
|
-
"@barefootjs/jsx": "0.
|
|
65
|
+
"@barefootjs/jsx": "0.18.0",
|
|
66
66
|
"typescript": "^5.0.0"
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -13,13 +13,11 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { describe, test, expect } from 'bun:test'
|
|
16
|
-
import {
|
|
17
|
-
runAdapterConformanceTests,
|
|
18
|
-
TemplatePrimitiveCaseId,
|
|
19
|
-
} from '@barefootjs/adapter-tests'
|
|
16
|
+
import { runAdapterConformanceTests } from '@barefootjs/adapter-tests'
|
|
20
17
|
import { XslateAdapter } from '../adapter'
|
|
21
18
|
import { renderXslateComponent, XslateNotAvailableError } from '../test-render'
|
|
22
19
|
import { compileJSX, type ComponentIR } from '@barefootjs/jsx'
|
|
20
|
+
import { conformancePins } from '../conformance-pins'
|
|
23
21
|
|
|
24
22
|
runAdapterConformanceTests({
|
|
25
23
|
name: 'xslate',
|
|
@@ -33,105 +31,17 @@ runAdapterConformanceTests({
|
|
|
33
31
|
// body children (TableCell) now receive `_bf_slot` for deterministic
|
|
34
32
|
// parent-scope-derived IDs matching Hono.
|
|
35
33
|
// Per-fixture build-time contracts for shapes the Xslate adapter
|
|
36
|
-
// intentionally refuses to lower.
|
|
37
|
-
// gates are shared code paths in
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// lowers and the only remaining gate is the imported-child one.
|
|
48
|
-
'todo-app': [{ code: 'BF103', severity: 'error' }],
|
|
49
|
-
'todo-app-ssr': [{ code: 'BF103', severity: 'error' }],
|
|
50
|
-
// Array-destructure loop param (`([k, v]) => …`) can't lower to a
|
|
51
|
-
// single Kolon loop variable (same BF104 as mojo).
|
|
52
|
-
'static-array-from-props': [{ code: 'BF104', severity: 'error' }],
|
|
53
|
-
// Both BF103 (imported child) and BF104 (destructure) fire.
|
|
54
|
-
'static-array-from-props-with-component': [
|
|
55
|
-
{ code: 'BF103', severity: 'error' },
|
|
56
|
-
{ code: 'BF104', severity: 'error' },
|
|
57
|
-
],
|
|
58
|
-
// Rest-destructure `.map()` callbacks — the object-rest shape read via
|
|
59
|
-
// member access (`rest-destructure-object-in-map`) now lowers via Kolon
|
|
60
|
-
// `: my` binding locals (`$rest` aliases the item). The other three stay
|
|
61
|
-
// refused: rest SPREAD needs a residual object, array-index / nested paths
|
|
62
|
-
// can't unpack a tuple (same surface as mojo).
|
|
63
|
-
'rest-destructure-object-spread-in-map': [{ code: 'BF104', severity: 'error' }],
|
|
64
|
-
'rest-destructure-array-in-map': [{ code: 'BF104', severity: 'error' }],
|
|
65
|
-
'rest-destructure-nested-in-map': [{ code: 'BF104', severity: 'error' }],
|
|
66
|
-
// XSLATE-SPECIFIC (mojo passes this): the site/ui Button auto-infers a
|
|
67
|
-
// `<Slot>` sibling that spreads `{...props}` / `{...children.props}`
|
|
68
|
-
// onto its root element. Kolon hashref method args can't splat a
|
|
69
|
-
// runtime hash into named entries (no `%$h`-into-call-args form), so
|
|
70
|
-
// the adapter refuses the spread with BF101 rather than emit a broken
|
|
71
|
-
// render_child call. Mojo's EP `%= include` accepts a flat stash, so it
|
|
72
|
-
// lowers the same shape; this is a genuine engine divergence, pinned
|
|
73
|
-
// declaratively here.
|
|
74
|
-
'button': [{ code: 'BF101', severity: 'error' }],
|
|
75
|
-
// `kbd` auto-infers the same `<Slot>` `{...props}` spread as `button`
|
|
76
|
-
// above — refused with BF101 for the identical Kolon engine reason, not a
|
|
77
|
-
// render-mismatch (so it's pinned here, not in `skipJsx`).
|
|
78
|
-
'kbd': [{ code: 'BF101', severity: 'error' }],
|
|
79
|
-
// #1467 demo-corpus context providers (`radio-group`, `select`,
|
|
80
|
-
// `dropdown-menu`, `combobox`, `command`) are no longer pinned — an
|
|
81
|
-
// object-literal provider value (`{ value: currentValue,
|
|
82
|
-
// onValueChange: (v) => {…} }`) lowers to a Kolon hashref via
|
|
83
|
-
// `parseProviderObjectLiteral` (#1897): getter members snapshot
|
|
84
|
-
// their body's SSR value, handler / function-shaped members lower
|
|
85
|
-
// to `nil`. The command demo's `ref={(el) => {…}}` function prop on
|
|
86
|
-
// an imported component is skipped at SSR like `on*` handlers.
|
|
87
|
-
//
|
|
88
|
-
// #1467 Phase 2e: `data-table` is no longer pinned here — it
|
|
89
|
-
// compiles clean now (`selected()[index]` → `index-access`,
|
|
90
|
-
// `.toFixed(2)` → `$bf.to_fixed`, `/* @client */` memo SSR-folded)
|
|
91
|
-
// and renders to Hono parity on real Text::Xslate. The keyed-loop
|
|
92
|
-
// scope-ID divergence (#1896) was fixed by the body-children
|
|
93
|
-
// `inLoop` reset (loop-item children get `_bf_slot`); data-table is
|
|
94
|
-
// off `skipJsx` entirely and only kept in `skipMarkerConformance`
|
|
95
|
-
// below for the shared `/* @client */` keyed-map slot-id elision
|
|
96
|
-
// contract (same as `todo-app`), not a render or BF101 gap.
|
|
97
|
-
// `style-3-signals` / `style-object-dynamic` no longer pinned — a
|
|
98
|
-
// `style={{ … }}` object literal now lowers to a CSS string with dynamic
|
|
99
|
-
// values interpolated (`background-color:<: $color :>;padding:8px`) via
|
|
100
|
-
// `tryLowerStyleObject` (#1322).
|
|
101
|
-
// Tagged-template-literal call in a className — same family, same
|
|
102
|
-
// refusal (BF101).
|
|
103
|
-
'tagged-template-classname': [{ code: 'BF101', severity: 'error' }],
|
|
104
|
-
// #2038: a filter predicate whose body contains a NESTED callback call
|
|
105
|
-
// (`t => !picked().some(p => …)` / `t => picked().find(p => …)`). Kolon
|
|
106
|
-
// has no inline `grep` form, so `XslateFilterEmitter.callbackMethod` used
|
|
107
|
-
// to degrade the inner call to its receiver, silently changing predicate
|
|
108
|
-
// semantics — the compiler is loud instead of lossy. (Mojo is pinned only
|
|
109
|
-
// for the `.find` variant: it lowers a nested `.some` to a real inline
|
|
110
|
-
// Perl `grep`.) The `/* @client */` twin
|
|
111
|
-
// (`filter-nested-callback-predicate-client`) has no pin here: it must
|
|
112
|
-
// render clean on every adapter, which asserts the suppression contract.
|
|
113
|
-
// https://github.com/piconic-ai/barefootjs/issues/2038
|
|
114
|
-
'filter-nested-callback-predicate': [{ code: 'BF101', severity: 'error' }],
|
|
115
|
-
'filter-nested-find-predicate': [{ code: 'BF101', severity: 'error' }],
|
|
116
|
-
// NB: TOP-LEVEL `.find` / `.findIndex` / `.findLast` / `.findLastIndex`
|
|
117
|
-
// (text position) are NOT pinned here — unlike mojo (which refuses them),
|
|
118
|
-
// Xslate lowers them to `$bf.find` / `find_index` / `find_last` /
|
|
119
|
-
// `find_last_index` via the same Kolon-lambda mechanism as `.filter` /
|
|
120
|
-
// `.every` / `.some`, so they render. Only the NESTED-in-a-predicate form
|
|
121
|
-
// above is refused (#2038).
|
|
122
|
-
// #2073 follow-up: a function-reference `.map(format)` callback has no
|
|
123
|
-
// arrow body to serialize — not a CALLBACK_METHODS shape — so the
|
|
124
|
-
// UNSUPPORTED_METHODS gate refuses it with BF101 rather than emitting
|
|
125
|
-
// a broken template.
|
|
126
|
-
'array-map-function-reference': [{ code: 'BF101', severity: 'error' }],
|
|
127
|
-
},
|
|
128
|
-
// Template-primitive registry parity: same V1 surface as mojo, so the
|
|
129
|
-
// same two cases stay skipped (bespoke user import + customSerialize
|
|
130
|
-
// can't render server-side without user-supplied helper mappings).
|
|
131
|
-
skipTemplatePrimitives: new Set([
|
|
132
|
-
TemplatePrimitiveCaseId.USER_IMPORT_VIA_CONST,
|
|
133
|
-
TemplatePrimitiveCaseId.NO_DOUBLE_REWRITE_OF_PROPS_OBJECT,
|
|
134
|
-
]),
|
|
34
|
+
// intentionally refuses to lower. Lives in `../conformance-pins` —
|
|
35
|
+
// mirrors mojo's set (the lowering gates are shared code paths in
|
|
36
|
+
// the ported adapter).
|
|
37
|
+
expectedDiagnostics: conformancePins,
|
|
38
|
+
// Template-primitive registry: `USER_IMPORT_VIA_CONST` and
|
|
39
|
+
// `NO_DOUBLE_REWRITE_OF_PROPS_OBJECT` now pass (#2069) — a bespoke user
|
|
40
|
+
// import can never be added to the string-keyed registry, but the
|
|
41
|
+
// shared `RelocateEnv.loweringMatchers` acceptance path recognises it
|
|
42
|
+
// via a `LoweringPlugin` the case setup registers around the compile
|
|
43
|
+
// (see `packages/adapter-tests/src/cases/template-primitives.ts`). No
|
|
44
|
+
// skips left, so `skipTemplatePrimitives` is omitted entirely.
|
|
135
45
|
// `client-only` / `client-only-loop-with-sibling-cond` /
|
|
136
46
|
// `filter-nested-callback-predicate-client` are no longer skipped —
|
|
137
47
|
// `renderLoop` now emits the `$bf.comment("loop:<id>")` boundary pair
|
|
@@ -326,7 +326,28 @@ export function renderSortMethod(recv: string, c: SortComparator): string {
|
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
// `.flat(depth?)` → `$bf.flat($recv, $depth)`.
|
|
329
|
-
export function renderFlatMethod(
|
|
329
|
+
export function renderFlatMethod(
|
|
330
|
+
recv: string,
|
|
331
|
+
depth: FlatDepth | { expr: ParsedExpr },
|
|
332
|
+
emit: (e: ParsedExpr) => string,
|
|
333
|
+
): string {
|
|
334
|
+
if (typeof depth === 'object') {
|
|
335
|
+
// Dynamic depth (#2094): routed to a SEPARATE runtime helper
|
|
336
|
+
// (`$bf.flat_dynamic`), not `$bf.flat`. The literal-depth path below
|
|
337
|
+
// already uses `-1` as a compile-time SENTINEL baked into the template
|
|
338
|
+
// source meaning "the source literally said `Infinity`". A genuinely
|
|
339
|
+
// dynamic depth that happens to evaluate to `-1` at render time means
|
|
340
|
+
// the OPPOSITE in real JS (`[1,[2]].flat(-1)` never recurses — same as
|
|
341
|
+
// `.flat(0)`). Since both paths would otherwise hand the same
|
|
342
|
+
// literal-looking argument to one shared helper, that helper couldn't
|
|
343
|
+
// tell which case it's in — so `flat_dynamic` performs its own
|
|
344
|
+
// `ToIntegerOrInfinity`-style coercion (truncate toward zero; negative
|
|
345
|
+
// -> 0; NaN/non-numeric -> 0; +Infinity or a huge finite value ->
|
|
346
|
+
// flatten fully) and then delegates to `flat`. See `sub flat_dynamic`
|
|
347
|
+
// in BarefootJS.pm and the Go reference (`FlatDynamicDepth`/
|
|
348
|
+
// `coerceFlatDepth` in bf.go).
|
|
349
|
+
return `$bf.flat_dynamic(${recv}, ${emit(depth.expr)})`
|
|
350
|
+
}
|
|
330
351
|
const d = depth === 'infinity' ? -1 : depth
|
|
331
352
|
return `$bf.flat(${recv}, ${d})`
|
|
332
353
|
}
|
|
@@ -183,8 +183,12 @@ export class XslateFilterEmitter implements ParsedExprEmitter {
|
|
|
183
183
|
return renderArrayMethod(method, object, args, emit)
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
flatMethod(
|
|
187
|
-
|
|
186
|
+
flatMethod(
|
|
187
|
+
object: ParsedExpr,
|
|
188
|
+
depth: FlatDepth | { expr: ParsedExpr },
|
|
189
|
+
emit: (e: ParsedExpr) => string,
|
|
190
|
+
): string {
|
|
191
|
+
return renderFlatMethod(emit(object), depth, emit)
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
conditional(_test: ParsedExpr, _consequent: ParsedExpr, _alternate: ParsedExpr): string {
|
|
@@ -486,8 +490,12 @@ export class XslateTopLevelEmitter implements ParsedExprEmitter {
|
|
|
486
490
|
return renderArrayMethod(method, object, args, emit)
|
|
487
491
|
}
|
|
488
492
|
|
|
489
|
-
flatMethod(
|
|
490
|
-
|
|
493
|
+
flatMethod(
|
|
494
|
+
object: ParsedExpr,
|
|
495
|
+
depth: FlatDepth | { expr: ParsedExpr },
|
|
496
|
+
emit: (e: ParsedExpr) => string,
|
|
497
|
+
): string {
|
|
498
|
+
return renderFlatMethod(emit(object), depth, emit)
|
|
491
499
|
}
|
|
492
500
|
|
|
493
501
|
conditional(
|
|
@@ -537,12 +545,16 @@ export class XslateTopLevelEmitter implements ParsedExprEmitter {
|
|
|
537
545
|
return "''"
|
|
538
546
|
}
|
|
539
547
|
|
|
540
|
-
objectLiteral(
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
|
|
548
|
+
objectLiteral(properties: ObjectLiteralProperty[], _raw: string, _emit: (e: ParsedExpr) => string): string {
|
|
549
|
+
// The shared `isSupported` gate only ever lets this dispatcher see an
|
|
550
|
+
// object literal as the EMPTY (`?? {}`) fallback operand of `??`
|
|
551
|
+
// (expression-parser.ts, `logical` case) — any other object literal is
|
|
552
|
+
// refused before reaching here. Emit Kolon's real empty hashref
|
|
553
|
+
// literal, matching the `'{}'` convention `objectLiteralToKolonHashref`
|
|
554
|
+
// already uses for the zero-property case in the spread path. A
|
|
555
|
+
// populated literal is structurally unreachable given the gate, but
|
|
556
|
+
// still degrades safely to the pre-existing empty-string sentinel
|
|
557
|
+
// rather than silently dropping keys.
|
|
558
|
+
return properties.length === 0 ? '{}' : "''"
|
|
547
559
|
}
|
|
548
560
|
}
|