@barefootjs/xslate 0.16.0 → 0.17.1
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/analysis/component-tree.d.ts +30 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +94 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/array-method.d.ts +73 -0
- package/dist/adapter/expr/array-method.d.ts.map +1 -0
- package/dist/adapter/expr/emitters.d.ts +96 -0
- package/dist/adapter/expr/emitters.d.ts.map +1 -0
- package/dist/adapter/expr/operand.d.ts +25 -0
- package/dist/adapter/expr/operand.d.ts.map +1 -0
- package/dist/adapter/index.js +1412 -1323
- package/dist/adapter/lib/constants.d.ts +22 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +29 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/kolon-naming.d.ts +22 -0
- package/dist/adapter/lib/kolon-naming.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +27 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +38 -0
- package/dist/adapter/memo/seed.d.ts.map +1 -0
- package/dist/adapter/props/prop-classes.d.ts +32 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +69 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal.d.ts +32 -0
- package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
- package/dist/adapter/xslate-adapter.d.ts +38 -92
- package/dist/adapter/xslate-adapter.d.ts.map +1 -1
- package/dist/build.js +1412 -1323
- package/dist/index.js +1412 -1323
- package/lib/BarefootJS/Backend/Xslate.pm +1 -1
- package/package.json +3 -3
- package/src/__tests__/query-href.test.ts +96 -0
- package/src/__tests__/xslate-adapter.test.ts +200 -8
- package/src/adapter/analysis/component-tree.ts +123 -0
- package/src/adapter/emit-context.ts +104 -0
- package/src/adapter/expr/array-method.ts +332 -0
- package/src/adapter/expr/emitters.ts +548 -0
- package/src/adapter/expr/operand.ts +35 -0
- package/src/adapter/lib/constants.ts +34 -0
- package/src/adapter/lib/ir-scope.ts +53 -0
- package/src/adapter/lib/kolon-naming.ts +27 -0
- package/src/adapter/lib/types.ts +30 -0
- package/src/adapter/memo/seed.ts +78 -0
- package/src/adapter/props/prop-classes.ts +64 -0
- package/src/adapter/spread/spread-codegen.ts +179 -0
- package/src/adapter/value/parsed-literal.ts +80 -0
- package/src/adapter/xslate-adapter.ts +190 -1236
- package/src/test-render.ts +3 -0
package/dist/adapter/index.js
CHANGED
|
@@ -187292,24 +187292,22 @@ import {
|
|
|
187292
187292
|
BaseAdapter,
|
|
187293
187293
|
isBooleanAttr,
|
|
187294
187294
|
parseExpression as parseExpression2,
|
|
187295
|
+
stringifyParsedExpr as stringifyParsedExpr2,
|
|
187295
187296
|
exprToString,
|
|
187296
187297
|
parseProviderObjectLiteral,
|
|
187297
187298
|
parseStyleObjectEntries,
|
|
187298
187299
|
isSupported,
|
|
187299
|
-
identifierPath,
|
|
187300
187300
|
emitParsedExpr,
|
|
187301
187301
|
emitIRNode,
|
|
187302
187302
|
emitAttrValue,
|
|
187303
187303
|
augmentInheritedPropAccesses,
|
|
187304
|
-
parseRecordIndexAccess,
|
|
187305
|
-
evalStringArrayJoin,
|
|
187306
187304
|
collectModuleStringConsts,
|
|
187307
|
-
extractArrowBodyExpression,
|
|
187308
|
-
collectContextConsumers,
|
|
187309
187305
|
isLowerableObjectRestDestructure,
|
|
187310
187306
|
lookupStaticRecordLiteral,
|
|
187311
187307
|
searchParamsLocalNames,
|
|
187312
|
-
|
|
187308
|
+
prepareLoweringMatchers,
|
|
187309
|
+
queryHrefArgs,
|
|
187310
|
+
sortComparatorFromArrow as sortComparatorFromArrow2
|
|
187313
187311
|
} from "@barefootjs/jsx";
|
|
187314
187312
|
|
|
187315
187313
|
// src/adapter/boolean-result.ts
|
|
@@ -187366,8 +187364,9 @@ function isAriaBooleanAttr(name) {
|
|
|
187366
187364
|
}
|
|
187367
187365
|
|
|
187368
187366
|
// src/adapter/xslate-adapter.ts
|
|
187369
|
-
var import_typescript = __toESM(require_typescript(), 1);
|
|
187370
187367
|
import { BF_SLOT, BF_COND, BF_REGION } from "@barefootjs/shared";
|
|
187368
|
+
|
|
187369
|
+
// src/adapter/lib/constants.ts
|
|
187371
187370
|
var XSLATE_TEMPLATE_PRIMITIVES = {
|
|
187372
187371
|
"JSON.stringify": { arity: 1, emit: (args) => `$bf.json(${args[0]})` },
|
|
187373
187372
|
String: { arity: 1, emit: (args) => `$bf.string(${args[0]})` },
|
|
@@ -187377,12 +187376,16 @@ var XSLATE_TEMPLATE_PRIMITIVES = {
|
|
|
187377
187376
|
"Math.round": { arity: 1, emit: (args) => `$bf.round(${args[0]})` }
|
|
187378
187377
|
};
|
|
187379
187378
|
var XSLATE_PRIMITIVE_EMIT_MAP = Object.fromEntries(Object.entries(XSLATE_TEMPLATE_PRIMITIVES).map(([k, v]) => [k, v.emit]));
|
|
187379
|
+
|
|
187380
|
+
// src/adapter/lib/kolon-naming.ts
|
|
187380
187381
|
function escapeKolonSingleQuoted(s) {
|
|
187381
187382
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
187382
187383
|
}
|
|
187383
187384
|
function kolonHashKey(name) {
|
|
187384
187385
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `'${escapeKolonSingleQuoted(name)}'`;
|
|
187385
187386
|
}
|
|
187387
|
+
|
|
187388
|
+
// src/adapter/lib/ir-scope.ts
|
|
187386
187389
|
function resolveJsxChildrenProp(props) {
|
|
187387
187390
|
const prop = props.find((p) => p.name === "children");
|
|
187388
187391
|
if (!prop)
|
|
@@ -187414,1451 +187417,1537 @@ function collectRootScopeNodes(node) {
|
|
|
187414
187417
|
visit(node);
|
|
187415
187418
|
return out;
|
|
187416
187419
|
}
|
|
187417
|
-
function referencedVarsAreAvailable(expr, available) {
|
|
187418
|
-
for (const m of expr.matchAll(/\$([A-Za-z_]\w*)/g)) {
|
|
187419
|
-
if (!available.has(m[1]))
|
|
187420
|
-
return false;
|
|
187421
|
-
}
|
|
187422
|
-
return true;
|
|
187423
|
-
}
|
|
187424
187420
|
|
|
187425
|
-
|
|
187426
|
-
|
|
187427
|
-
|
|
187428
|
-
|
|
187429
|
-
|
|
187430
|
-
|
|
187431
|
-
|
|
187432
|
-
|
|
187433
|
-
|
|
187434
|
-
|
|
187435
|
-
|
|
187436
|
-
propsObjectName = null;
|
|
187437
|
-
propsParams = [];
|
|
187438
|
-
booleanTypedProps = new Set;
|
|
187439
|
-
stringValueNames = new Set;
|
|
187440
|
-
moduleStringConsts = new Map;
|
|
187441
|
-
_searchParamsLocals = new Set;
|
|
187442
|
-
localConstants = [];
|
|
187443
|
-
nullableOptionalProps = new Set;
|
|
187444
|
-
constructor(options = {}) {
|
|
187445
|
-
super();
|
|
187446
|
-
this.options = {
|
|
187447
|
-
clientJsBasePath: options.clientJsBasePath ?? "/static/components/",
|
|
187448
|
-
barefootJsPath: options.barefootJsPath ?? "/static/components/barefoot.js"
|
|
187449
|
-
};
|
|
187450
|
-
}
|
|
187451
|
-
generate(ir, options) {
|
|
187452
|
-
this.componentName = ir.metadata.componentName;
|
|
187453
|
-
this.propsObjectName = ir.metadata.propsObjectName ?? null;
|
|
187454
|
-
augmentInheritedPropAccesses(ir);
|
|
187455
|
-
this.propsParams = ir.metadata.propsParams.map((p) => ({ name: p.name }));
|
|
187456
|
-
this.booleanTypedProps = new Set(ir.metadata.propsParams.filter((prop) => prop.type?.primitive === "boolean" || prop.type?.raw === "boolean").map((prop) => prop.name));
|
|
187457
|
-
this.localConstants = ir.metadata.localConstants ?? [];
|
|
187458
|
-
this.nullableOptionalProps = new Set(ir.metadata.propsParams.filter((p) => p.defaultValue === undefined && !p.isRest && p.type?.kind !== "primitive").map((p) => p.name));
|
|
187459
|
-
this.stringValueNames = new Set;
|
|
187460
|
-
for (const s of ir.metadata.signals) {
|
|
187461
|
-
if (isStringTypeInfo(s.type) || isBareStringLiteral(s.initialValue)) {
|
|
187462
|
-
this.stringValueNames.add(s.getter);
|
|
187463
|
-
}
|
|
187421
|
+
// src/adapter/expr/array-method.ts
|
|
187422
|
+
import {
|
|
187423
|
+
serializeParsedExpr,
|
|
187424
|
+
freeVarsInBody
|
|
187425
|
+
} from "@barefootjs/jsx";
|
|
187426
|
+
function renderArrayMethod(method, object, args, emit) {
|
|
187427
|
+
switch (method) {
|
|
187428
|
+
case "join": {
|
|
187429
|
+
const obj = emit(object);
|
|
187430
|
+
const sep = args.length >= 1 ? emit(args[0]) : `','`;
|
|
187431
|
+
return `$bf.join(${obj}, ${sep})`;
|
|
187464
187432
|
}
|
|
187465
|
-
|
|
187466
|
-
|
|
187467
|
-
|
|
187433
|
+
case "includes": {
|
|
187434
|
+
const obj = emit(object);
|
|
187435
|
+
const needle = emit(args[0]);
|
|
187436
|
+
return `$bf.includes(${obj}, ${needle})`;
|
|
187468
187437
|
}
|
|
187469
|
-
|
|
187470
|
-
|
|
187471
|
-
|
|
187472
|
-
|
|
187473
|
-
|
|
187474
|
-
|
|
187438
|
+
case "indexOf":
|
|
187439
|
+
case "lastIndexOf": {
|
|
187440
|
+
const fn = method === "indexOf" ? "index_of" : "last_index_of";
|
|
187441
|
+
const obj = emit(object);
|
|
187442
|
+
const needle = emit(args[0]);
|
|
187443
|
+
return `$bf.${fn}(${obj}, ${needle})`;
|
|
187475
187444
|
}
|
|
187476
|
-
|
|
187477
|
-
|
|
187478
|
-
|
|
187479
|
-
|
|
187480
|
-
const memoSeed = this.generateDerivedMemoSeed(ir);
|
|
187481
|
-
const template = `${scriptReg}${ctxSeed}${memoSeed}${templateBody}
|
|
187482
|
-
`;
|
|
187483
|
-
if (this.errors.length > 0) {
|
|
187484
|
-
ir.errors.push(...this.errors);
|
|
187445
|
+
case "at": {
|
|
187446
|
+
const obj = emit(object);
|
|
187447
|
+
const idx = args.length >= 1 ? emit(args[0]) : "0";
|
|
187448
|
+
return `$bf.at(${obj}, ${idx})`;
|
|
187485
187449
|
}
|
|
187486
|
-
|
|
187487
|
-
|
|
187488
|
-
|
|
187489
|
-
|
|
187490
|
-
|
|
187491
|
-
|
|
187492
|
-
|
|
187493
|
-
template,
|
|
187494
|
-
sections,
|
|
187495
|
-
extension: this.extension
|
|
187496
|
-
};
|
|
187497
|
-
}
|
|
187498
|
-
generateScriptRegistrations(ir, scriptBaseName) {
|
|
187499
|
-
const hasInteractivity = this.hasClientInteractivity(ir);
|
|
187500
|
-
if (!hasInteractivity)
|
|
187501
|
-
return "";
|
|
187502
|
-
const name = scriptBaseName ?? ir.metadata.componentName;
|
|
187503
|
-
const runtimePath = this.options.barefootJsPath;
|
|
187504
|
-
const clientJsPath = `${this.options.clientJsBasePath}${name}.client.js`;
|
|
187505
|
-
const lines = [];
|
|
187506
|
-
lines.push(`: my $_bf_reg0 = $bf.register_script('${runtimePath}');`);
|
|
187507
|
-
lines.push(`: my $_bf_reg1 = $bf.register_script('${clientJsPath}');`);
|
|
187508
|
-
lines.push("");
|
|
187509
|
-
return lines.join(`
|
|
187510
|
-
`);
|
|
187511
|
-
}
|
|
187512
|
-
hasClientInteractivity(ir) {
|
|
187513
|
-
return ir.metadata.signals.length > 0 || ir.metadata.effects.length > 0 || ir.metadata.onMounts.length > 0 || (ir.metadata.clientAnalysis?.needsInit ?? false);
|
|
187514
|
-
}
|
|
187515
|
-
renderNode(node) {
|
|
187516
|
-
return emitIRNode(node, this, {});
|
|
187517
|
-
}
|
|
187518
|
-
emitElement(node, _ctx, _emit) {
|
|
187519
|
-
return this.renderElement(node);
|
|
187520
|
-
}
|
|
187521
|
-
emitText(node) {
|
|
187522
|
-
return node.value;
|
|
187523
|
-
}
|
|
187524
|
-
emitExpression(node) {
|
|
187525
|
-
return this.renderExpression(node);
|
|
187526
|
-
}
|
|
187527
|
-
emitConditional(node, _ctx, _emit) {
|
|
187528
|
-
return this.renderConditional(node);
|
|
187529
|
-
}
|
|
187530
|
-
emitLoop(node, _ctx, _emit) {
|
|
187531
|
-
return this.renderLoop(node);
|
|
187532
|
-
}
|
|
187533
|
-
emitComponent(node, _ctx, _emit) {
|
|
187534
|
-
return this.renderComponent(node);
|
|
187535
|
-
}
|
|
187536
|
-
emitFragment(node, _ctx, _emit) {
|
|
187537
|
-
return this.renderFragment(node);
|
|
187538
|
-
}
|
|
187539
|
-
emitSlot(node) {
|
|
187540
|
-
return this.renderSlot(node);
|
|
187541
|
-
}
|
|
187542
|
-
emitIfStatement(node, _ctx, _emit) {
|
|
187543
|
-
return this.renderIfStatement(node);
|
|
187544
|
-
}
|
|
187545
|
-
emitProvider(node, _ctx, _emit) {
|
|
187546
|
-
const value = this.providerValueKolon(node.valueProp);
|
|
187547
|
-
const children = this.renderChildren(node.children);
|
|
187548
|
-
const name = node.contextName;
|
|
187549
|
-
return `<: $bf.provide_context('${name}', ${value}) :>` + children + `<: $bf.revoke_context('${name}') :>`;
|
|
187550
|
-
}
|
|
187551
|
-
providerValueKolon(valueProp) {
|
|
187552
|
-
const v = valueProp.value;
|
|
187553
|
-
if (v.kind === "literal") {
|
|
187554
|
-
return typeof v.value === "string" ? `'${v.value.replace(/[\\']/g, (m) => `\\${m}`)}'` : String(v.value);
|
|
187450
|
+
case "concat": {
|
|
187451
|
+
if (args.length === 0) {
|
|
187452
|
+
return emit(object);
|
|
187453
|
+
}
|
|
187454
|
+
const a = emit(object);
|
|
187455
|
+
const b = emit(args[0]);
|
|
187456
|
+
return `$bf.concat(${a}, ${b})`;
|
|
187555
187457
|
}
|
|
187556
|
-
|
|
187557
|
-
const
|
|
187558
|
-
|
|
187559
|
-
|
|
187560
|
-
return
|
|
187458
|
+
case "slice": {
|
|
187459
|
+
const recv = emit(object);
|
|
187460
|
+
const start = args.length >= 1 ? emit(args[0]) : "0";
|
|
187461
|
+
const end = args.length >= 2 ? emit(args[1]) : "nil";
|
|
187462
|
+
return `$bf.slice(${recv}, ${start}, ${end})`;
|
|
187561
187463
|
}
|
|
187562
|
-
|
|
187563
|
-
|
|
187564
|
-
|
|
187565
|
-
|
|
187566
|
-
providerObjectLiteralKolon(expr) {
|
|
187567
|
-
const members = parseProviderObjectLiteral(expr.trim());
|
|
187568
|
-
if (members === null)
|
|
187569
|
-
return null;
|
|
187570
|
-
const entries = members.map((m) => {
|
|
187571
|
-
const key = `'${m.name.replace(/[\\']/g, (c) => `\\${c}`)}'`;
|
|
187572
|
-
if (m.kind === "function" || /^on[A-Z]/.test(m.name))
|
|
187573
|
-
return `${key} => nil`;
|
|
187574
|
-
const src = m.kind === "getter" ? m.body : m.expr;
|
|
187575
|
-
return `${key} => ${this.convertExpressionToKolon(src)}`;
|
|
187576
|
-
});
|
|
187577
|
-
return `{ ${entries.join(", ")} }`;
|
|
187578
|
-
}
|
|
187579
|
-
contextDefaultKolon(c) {
|
|
187580
|
-
const d = c.defaultValue;
|
|
187581
|
-
if (d === null || d === undefined)
|
|
187582
|
-
return "nil";
|
|
187583
|
-
if (typeof d === "string")
|
|
187584
|
-
return `'${d.replace(/[\\']/g, (m) => `\\${m}`)}'`;
|
|
187585
|
-
if (typeof d === "boolean")
|
|
187586
|
-
return d ? "1" : "0";
|
|
187587
|
-
return String(d);
|
|
187588
|
-
}
|
|
187589
|
-
generateContextConsumerSeed(ir) {
|
|
187590
|
-
const consumers = collectContextConsumers(ir.metadata);
|
|
187591
|
-
if (consumers.length === 0)
|
|
187592
|
-
return "";
|
|
187593
|
-
return consumers.map((c) => `: my $${c.localName} = $bf.use_context('${c.contextName}', ${this.contextDefaultKolon(c)});`).join(`
|
|
187594
|
-
`) + `
|
|
187595
|
-
`;
|
|
187596
|
-
}
|
|
187597
|
-
generateDerivedMemoSeed(ir) {
|
|
187598
|
-
const memos = ir.metadata.memos ?? [];
|
|
187599
|
-
const signals = ir.metadata.signals ?? [];
|
|
187600
|
-
if (memos.length === 0 && signals.length === 0)
|
|
187601
|
-
return "";
|
|
187602
|
-
const available = new Set(ir.metadata.propsParams.map((p) => p.name));
|
|
187603
|
-
const lines = [];
|
|
187604
|
-
for (const signal of signals) {
|
|
187605
|
-
const kolon = this.tryLowerToKolon(signal.initialValue, available);
|
|
187606
|
-
const refsSelf = kolon !== null && new RegExp(`\\$${signal.getter}\\b`).test(kolon);
|
|
187607
|
-
if (kolon !== null && !refsSelf)
|
|
187608
|
-
lines.push(`: my $${signal.getter} = ${kolon};`);
|
|
187609
|
-
available.add(signal.getter);
|
|
187610
|
-
}
|
|
187611
|
-
for (const memo of memos) {
|
|
187612
|
-
const body = extractArrowBodyExpression(memo.computation);
|
|
187613
|
-
if (body !== null) {
|
|
187614
|
-
const kolon = this.tryLowerToKolon(body, available);
|
|
187615
|
-
const refsSelf = kolon !== null && new RegExp(`\\$${memo.name}\\b`).test(kolon);
|
|
187616
|
-
if (kolon !== null && !refsSelf)
|
|
187617
|
-
lines.push(`: my $${memo.name} = ${kolon};`);
|
|
187618
|
-
}
|
|
187619
|
-
available.add(memo.name);
|
|
187620
|
-
}
|
|
187621
|
-
return lines.length > 0 ? lines.join(`
|
|
187622
|
-
`) + `
|
|
187623
|
-
` : "";
|
|
187624
|
-
}
|
|
187625
|
-
tryLowerToKolon(expr, available) {
|
|
187626
|
-
const trimmed = expr.trim();
|
|
187627
|
-
if (!trimmed)
|
|
187628
|
-
return null;
|
|
187629
|
-
if (!isSupported(parseExpression2(trimmed)).supported)
|
|
187630
|
-
return null;
|
|
187631
|
-
const kolon = this.convertExpressionToKolon(trimmed);
|
|
187632
|
-
if (kolon === "" || !/\$[A-Za-z_]\w*/.test(kolon))
|
|
187633
|
-
return null;
|
|
187634
|
-
return referencedVarsAreAvailable(kolon, available) ? kolon : null;
|
|
187635
|
-
}
|
|
187636
|
-
emitAsync(node, _ctx, _emit) {
|
|
187637
|
-
return this.renderAsync(node);
|
|
187638
|
-
}
|
|
187639
|
-
renderElement(element) {
|
|
187640
|
-
const tag = element.tag;
|
|
187641
|
-
const attrs = this.renderAttributes(element);
|
|
187642
|
-
const children = this.renderChildren(element.children);
|
|
187643
|
-
let hydrationAttrs = "";
|
|
187644
|
-
if (element.needsScope) {
|
|
187645
|
-
hydrationAttrs += ` ${this.renderScopeMarker("")}`;
|
|
187464
|
+
case "reverse":
|
|
187465
|
+
case "toReversed": {
|
|
187466
|
+
const recv = emit(object);
|
|
187467
|
+
return `$bf.reverse(${recv})`;
|
|
187646
187468
|
}
|
|
187647
|
-
|
|
187648
|
-
|
|
187469
|
+
case "toLowerCase": {
|
|
187470
|
+
const recv = emit(object);
|
|
187471
|
+
return `$bf.lc(${recv})`;
|
|
187649
187472
|
}
|
|
187650
|
-
|
|
187651
|
-
|
|
187473
|
+
case "toUpperCase": {
|
|
187474
|
+
const recv = emit(object);
|
|
187475
|
+
return `$bf.uc(${recv})`;
|
|
187652
187476
|
}
|
|
187653
|
-
|
|
187654
|
-
|
|
187477
|
+
case "trim": {
|
|
187478
|
+
const recv = emit(object);
|
|
187479
|
+
return `$bf.trim(${recv})`;
|
|
187655
187480
|
}
|
|
187656
|
-
|
|
187657
|
-
|
|
187658
|
-
"
|
|
187659
|
-
|
|
187660
|
-
"col",
|
|
187661
|
-
"embed",
|
|
187662
|
-
"hr",
|
|
187663
|
-
"img",
|
|
187664
|
-
"input",
|
|
187665
|
-
"link",
|
|
187666
|
-
"meta",
|
|
187667
|
-
"param",
|
|
187668
|
-
"source",
|
|
187669
|
-
"track",
|
|
187670
|
-
"wbr"
|
|
187671
|
-
];
|
|
187672
|
-
if (voidElements.includes(tag.toLowerCase())) {
|
|
187673
|
-
return `<${tag}${attrs}${hydrationAttrs}>`;
|
|
187481
|
+
case "toFixed": {
|
|
187482
|
+
const recv = emit(object);
|
|
187483
|
+
const digits = args.length >= 1 ? emit(args[0]) : "0";
|
|
187484
|
+
return `$bf.to_fixed(${recv}, ${digits})`;
|
|
187674
187485
|
}
|
|
187675
|
-
|
|
187676
|
-
|
|
187677
|
-
|
|
187678
|
-
|
|
187679
|
-
if (expr.slotId) {
|
|
187680
|
-
return `<: $bf.comment("client:${expr.slotId}") | mark_raw :>`;
|
|
187486
|
+
case "split": {
|
|
187487
|
+
const recv = emit(object);
|
|
187488
|
+
if (args.length === 0) {
|
|
187489
|
+
return `$bf.split(${recv})`;
|
|
187681
187490
|
}
|
|
187682
|
-
|
|
187491
|
+
const sep = emit(args[0]);
|
|
187492
|
+
if (args.length === 1) {
|
|
187493
|
+
return `$bf.split(${recv}, ${sep})`;
|
|
187494
|
+
}
|
|
187495
|
+
const limit = emit(args[1]);
|
|
187496
|
+
return `$bf.split(${recv}, ${sep}, ${limit})`;
|
|
187683
187497
|
}
|
|
187684
|
-
|
|
187685
|
-
|
|
187686
|
-
|
|
187498
|
+
case "startsWith":
|
|
187499
|
+
case "endsWith": {
|
|
187500
|
+
const fn = method === "startsWith" ? "starts_with" : "ends_with";
|
|
187501
|
+
const recv = emit(object);
|
|
187502
|
+
const arg = emit(args[0]);
|
|
187503
|
+
if (args.length >= 2) {
|
|
187504
|
+
return `$bf.${fn}(${recv}, ${arg}, ${emit(args[1])})`;
|
|
187505
|
+
}
|
|
187506
|
+
return `$bf.${fn}(${recv}, ${arg})`;
|
|
187687
187507
|
}
|
|
187688
|
-
|
|
187689
|
-
|
|
187690
|
-
|
|
187691
|
-
|
|
187692
|
-
return
|
|
187508
|
+
case "replace": {
|
|
187509
|
+
const recv = emit(object);
|
|
187510
|
+
const oldS = emit(args[0]);
|
|
187511
|
+
const newS = emit(args[1]);
|
|
187512
|
+
return `$bf.replace(${recv}, ${oldS}, ${newS})`;
|
|
187693
187513
|
}
|
|
187694
|
-
|
|
187695
|
-
|
|
187696
|
-
|
|
187697
|
-
|
|
187698
|
-
const useCommentMarkers = cond.slotId && isFragmentBranch;
|
|
187699
|
-
let markedTrue = whenTrue;
|
|
187700
|
-
let markedFalse = whenFalse;
|
|
187701
|
-
if (cond.slotId && !useCommentMarkers) {
|
|
187702
|
-
markedTrue = this.addCondMarkerToFirstElement(whenTrue, cond.slotId);
|
|
187703
|
-
markedFalse = whenFalse ? this.addCondMarkerToFirstElement(whenFalse, cond.slotId) : whenFalse;
|
|
187514
|
+
case "repeat": {
|
|
187515
|
+
const recv = emit(object);
|
|
187516
|
+
const count = args.length === 0 ? "0" : emit(args[0]);
|
|
187517
|
+
return `$bf.repeat(${recv}, ${count})`;
|
|
187704
187518
|
}
|
|
187705
|
-
|
|
187706
|
-
|
|
187707
|
-
const
|
|
187708
|
-
|
|
187709
|
-
|
|
187710
|
-
|
|
187711
|
-
|
|
187712
|
-
|
|
187713
|
-
|
|
187714
|
-
|
|
187715
|
-
|
|
187716
|
-
|
|
187717
|
-
`;
|
|
187718
|
-
|
|
187719
|
-
|
|
187720
|
-
|
|
187721
|
-
:
|
|
187722
|
-
${markedTrue}
|
|
187723
|
-
: } else {
|
|
187724
|
-
${markedFalse}
|
|
187725
|
-
: }
|
|
187726
|
-
`;
|
|
187727
|
-
} else if (cond.slotId) {
|
|
187728
|
-
result = `<: $bf.comment("cond-start:${cond.slotId}") | mark_raw :>
|
|
187729
|
-
: if (${condition}) {
|
|
187730
|
-
${whenTrue}
|
|
187731
|
-
: }
|
|
187732
|
-
<: $bf.comment("cond-end:${cond.slotId}") | mark_raw :>`;
|
|
187733
|
-
} else {
|
|
187734
|
-
result = `
|
|
187735
|
-
: if (${condition}) {
|
|
187736
|
-
${whenTrue}
|
|
187737
|
-
: }
|
|
187738
|
-
`;
|
|
187519
|
+
case "padStart":
|
|
187520
|
+
case "padEnd": {
|
|
187521
|
+
const fn = method === "padStart" ? "pad_start" : "pad_end";
|
|
187522
|
+
const recv = emit(object);
|
|
187523
|
+
if (args.length === 0) {
|
|
187524
|
+
return `$bf.${fn}(${recv}, 0)`;
|
|
187525
|
+
}
|
|
187526
|
+
const target = emit(args[0]);
|
|
187527
|
+
if (args.length === 1) {
|
|
187528
|
+
return `$bf.${fn}(${recv}, ${target})`;
|
|
187529
|
+
}
|
|
187530
|
+
const pad = emit(args[1]);
|
|
187531
|
+
return `$bf.${fn}(${recv}, ${target}, ${pad})`;
|
|
187532
|
+
}
|
|
187533
|
+
default: {
|
|
187534
|
+
const _exhaustive = method;
|
|
187535
|
+
throw new Error(`renderArrayMethod: unhandled ArrayMethod '${_exhaustive}'`);
|
|
187739
187536
|
}
|
|
187740
|
-
return result;
|
|
187741
187537
|
}
|
|
187742
|
-
|
|
187743
|
-
|
|
187744
|
-
|
|
187538
|
+
}
|
|
187539
|
+
function escapePerlSingleQuote(s) {
|
|
187540
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
187541
|
+
}
|
|
187542
|
+
function emitEvalEnvArg(body, params, emit) {
|
|
187543
|
+
const free = freeVarsInBody(body, new Set(params));
|
|
187544
|
+
if (free.length === 0)
|
|
187545
|
+
return "{}";
|
|
187546
|
+
const pairs = free.map((n) => `'${escapePerlSingleQuote(n)}' => ${emit({ kind: "identifier", name: n })}`);
|
|
187547
|
+
return `{ ${pairs.join(", ")} }`;
|
|
187548
|
+
}
|
|
187549
|
+
function renderSortEval(recv, body, params, emit) {
|
|
187550
|
+
const json = serializeParsedExpr(body);
|
|
187551
|
+
if (json === null)
|
|
187552
|
+
return null;
|
|
187553
|
+
if (params.length < 2)
|
|
187554
|
+
return null;
|
|
187555
|
+
const [paramA, paramB] = params;
|
|
187556
|
+
const env = emitEvalEnvArg(body, params, emit);
|
|
187557
|
+
return `$bf.sort_eval(${recv}, '${escapePerlSingleQuote(json)}', '${paramA}', '${paramB}', ${env})`;
|
|
187558
|
+
}
|
|
187559
|
+
function renderReduceEval(recv, body, params, init, direction, emit) {
|
|
187560
|
+
const json = serializeParsedExpr(body);
|
|
187561
|
+
if (json === null)
|
|
187562
|
+
return null;
|
|
187563
|
+
if (init.kind !== "literal")
|
|
187564
|
+
return null;
|
|
187565
|
+
const initOut = init.literalType === "string" ? `'${escapePerlSingleQuote(String(init.value))}'` : init.literalType === "number" ? String(init.value) : null;
|
|
187566
|
+
if (initOut === null)
|
|
187567
|
+
return null;
|
|
187568
|
+
if (params.length < 2)
|
|
187569
|
+
return null;
|
|
187570
|
+
const [paramAcc, paramItem] = params;
|
|
187571
|
+
const env = emitEvalEnvArg(body, params, emit);
|
|
187572
|
+
return `$bf.reduce_eval(${recv}, '${escapePerlSingleQuote(json)}', '${paramAcc}', '${paramItem}', ${initOut}, '${direction}', ${env})`;
|
|
187573
|
+
}
|
|
187574
|
+
function renderPredicateEval(funcName, recv, predicate, param, emit, forward) {
|
|
187575
|
+
const json = serializeParsedExpr(predicate);
|
|
187576
|
+
if (json === null)
|
|
187577
|
+
return null;
|
|
187578
|
+
const env = emitEvalEnvArg(predicate, [param], emit);
|
|
187579
|
+
const fwd = forward === undefined ? "" : `, ${forward ? 1 : 0}`;
|
|
187580
|
+
return `$bf.${funcName}(${recv}, '${escapePerlSingleQuote(json)}', '${param}'${fwd}, ${env})`;
|
|
187581
|
+
}
|
|
187582
|
+
function renderFlatMapEval(recv, body, param, emit) {
|
|
187583
|
+
const json = serializeParsedExpr(body);
|
|
187584
|
+
if (json === null)
|
|
187585
|
+
return null;
|
|
187586
|
+
const env = emitEvalEnvArg(body, [param], emit);
|
|
187587
|
+
return `$bf.flat_map_eval(${recv}, '${escapePerlSingleQuote(json)}', '${param}', ${env})`;
|
|
187588
|
+
}
|
|
187589
|
+
function renderMapEval(recv, body, param, emit) {
|
|
187590
|
+
const json = serializeParsedExpr(body);
|
|
187591
|
+
if (json === null)
|
|
187592
|
+
return null;
|
|
187593
|
+
const env = emitEvalEnvArg(body, [param], emit);
|
|
187594
|
+
return `$bf.map_eval(${recv}, '${escapePerlSingleQuote(json)}', '${param}', ${env})`;
|
|
187595
|
+
}
|
|
187596
|
+
function renderSortMethod(recv, c) {
|
|
187597
|
+
const keyHashes = c.keys.map((k) => {
|
|
187598
|
+
const keyEntry = k.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${k.key.field}'`;
|
|
187599
|
+
return `{ ${keyEntry}, compare_type => '${k.type}', direction => '${k.direction}' }`;
|
|
187600
|
+
});
|
|
187601
|
+
return `$bf.sort(${recv}, { keys => [${keyHashes.join(", ")}] })`;
|
|
187602
|
+
}
|
|
187603
|
+
function renderFlatMethod(recv, depth) {
|
|
187604
|
+
const d = depth === "infinity" ? -1 : depth;
|
|
187605
|
+
return `$bf.flat(${recv}, ${d})`;
|
|
187606
|
+
}
|
|
187607
|
+
|
|
187608
|
+
// src/adapter/expr/emitters.ts
|
|
187609
|
+
import {
|
|
187610
|
+
identifierPath,
|
|
187611
|
+
matchSearchParamsMethodCall,
|
|
187612
|
+
sortComparatorFromArrow
|
|
187613
|
+
} from "@barefootjs/jsx";
|
|
187614
|
+
var PREDICATE_METHODS = new Set([
|
|
187615
|
+
"filter",
|
|
187616
|
+
"find",
|
|
187617
|
+
"findIndex",
|
|
187618
|
+
"findLast",
|
|
187619
|
+
"findLastIndex",
|
|
187620
|
+
"every",
|
|
187621
|
+
"some"
|
|
187622
|
+
]);
|
|
187623
|
+
|
|
187624
|
+
class XslateFilterEmitter {
|
|
187625
|
+
param;
|
|
187626
|
+
localVarMap;
|
|
187627
|
+
isStringName;
|
|
187628
|
+
onUnsupported;
|
|
187629
|
+
constructor(param, localVarMap, isStringName = () => false, onUnsupported) {
|
|
187630
|
+
this.param = param;
|
|
187631
|
+
this.localVarMap = localVarMap;
|
|
187632
|
+
this.isStringName = isStringName;
|
|
187633
|
+
this.onUnsupported = onUnsupported;
|
|
187634
|
+
}
|
|
187635
|
+
identifier(name) {
|
|
187636
|
+
if (name === this.param)
|
|
187637
|
+
return `$${this.param}`;
|
|
187638
|
+
const signal = this.localVarMap.get(name);
|
|
187639
|
+
if (signal)
|
|
187640
|
+
return `$${signal}`;
|
|
187641
|
+
return `$${name}`;
|
|
187642
|
+
}
|
|
187643
|
+
literal(value, literalType) {
|
|
187644
|
+
if (literalType === "string")
|
|
187645
|
+
return `'${value}'`;
|
|
187646
|
+
if (literalType === "boolean")
|
|
187647
|
+
return value ? "1" : "0";
|
|
187648
|
+
if (literalType === "null")
|
|
187649
|
+
return "nil";
|
|
187650
|
+
return String(value);
|
|
187651
|
+
}
|
|
187652
|
+
member(object, property, _computed, emit) {
|
|
187653
|
+
if (property === "length") {
|
|
187654
|
+
return `$bf.length(${emit(object)})`;
|
|
187745
187655
|
}
|
|
187746
|
-
return
|
|
187656
|
+
return `${emit(object)}.${property}`;
|
|
187747
187657
|
}
|
|
187748
|
-
|
|
187749
|
-
|
|
187750
|
-
|
|
187751
|
-
|
|
187658
|
+
indexAccess(object, index, emit) {
|
|
187659
|
+
return `${emit(object)}[${emit(index)}]`;
|
|
187660
|
+
}
|
|
187661
|
+
call(callee, args, emit) {
|
|
187662
|
+
if (callee.kind === "identifier" && args.length === 0) {
|
|
187663
|
+
return `$${callee.name}`;
|
|
187752
187664
|
}
|
|
187753
|
-
return
|
|
187665
|
+
return emit(callee);
|
|
187754
187666
|
}
|
|
187755
|
-
|
|
187756
|
-
const
|
|
187757
|
-
|
|
187758
|
-
|
|
187759
|
-
|
|
187760
|
-
if (imp.isTypeOnly)
|
|
187761
|
-
continue;
|
|
187762
|
-
for (const spec of imp.specifiers) {
|
|
187763
|
-
relativeImports.add(spec.alias ?? spec.name);
|
|
187764
|
-
}
|
|
187667
|
+
unary(op, argument, emit) {
|
|
187668
|
+
const arg = emit(argument);
|
|
187669
|
+
if (op === "!") {
|
|
187670
|
+
const needsParens = argument.kind === "binary" || argument.kind === "logical";
|
|
187671
|
+
return needsParens ? `!(${arg})` : `!${arg}`;
|
|
187765
187672
|
}
|
|
187766
|
-
if (
|
|
187767
|
-
return
|
|
187768
|
-
|
|
187769
|
-
|
|
187770
|
-
|
|
187771
|
-
|
|
187772
|
-
|
|
187773
|
-
|
|
187774
|
-
|
|
187775
|
-
|
|
187776
|
-
|
|
187777
|
-
|
|
187778
|
-
|
|
187779
|
-
|
|
187780
|
-
|
|
187781
|
-
|
|
187782
|
-
|
|
187783
|
-
|
|
187784
|
-
}
|
|
187785
|
-
});
|
|
187786
|
-
}
|
|
187787
|
-
for (const child of comp.children)
|
|
187788
|
-
visit(child, inLoop);
|
|
187789
|
-
break;
|
|
187790
|
-
}
|
|
187791
|
-
case "element":
|
|
187792
|
-
for (const child of node.children)
|
|
187793
|
-
visit(child, inLoop);
|
|
187794
|
-
break;
|
|
187795
|
-
case "fragment":
|
|
187796
|
-
for (const child of node.children)
|
|
187797
|
-
visit(child, inLoop);
|
|
187798
|
-
break;
|
|
187799
|
-
case "conditional": {
|
|
187800
|
-
const cond = node;
|
|
187801
|
-
visit(cond.whenTrue, inLoop);
|
|
187802
|
-
if (cond.whenFalse)
|
|
187803
|
-
visit(cond.whenFalse, inLoop);
|
|
187804
|
-
break;
|
|
187805
|
-
}
|
|
187806
|
-
case "loop":
|
|
187807
|
-
for (const child of node.children)
|
|
187808
|
-
visit(child, true);
|
|
187809
|
-
break;
|
|
187810
|
-
case "if-statement": {
|
|
187811
|
-
const stmt = node;
|
|
187812
|
-
visit(stmt.consequent, inLoop);
|
|
187813
|
-
if (stmt.alternate)
|
|
187814
|
-
visit(stmt.alternate, inLoop);
|
|
187815
|
-
break;
|
|
187816
|
-
}
|
|
187817
|
-
case "provider":
|
|
187818
|
-
for (const child of node.children)
|
|
187819
|
-
visit(child, inLoop);
|
|
187820
|
-
break;
|
|
187821
|
-
case "async": {
|
|
187822
|
-
const a = node;
|
|
187823
|
-
visit(a.fallback, inLoop);
|
|
187824
|
-
for (const child of a.children)
|
|
187825
|
-
visit(child, inLoop);
|
|
187826
|
-
break;
|
|
187827
|
-
}
|
|
187828
|
-
}
|
|
187673
|
+
if (op === "-")
|
|
187674
|
+
return `-${arg}`;
|
|
187675
|
+
return arg;
|
|
187676
|
+
}
|
|
187677
|
+
binary(op, left, right, emit) {
|
|
187678
|
+
const l = emit(left);
|
|
187679
|
+
const r = emit(right);
|
|
187680
|
+
const opMap = {
|
|
187681
|
+
"===": "==",
|
|
187682
|
+
"!==": "!=",
|
|
187683
|
+
">": ">",
|
|
187684
|
+
"<": "<",
|
|
187685
|
+
">=": ">=",
|
|
187686
|
+
"<=": "<=",
|
|
187687
|
+
"+": "+",
|
|
187688
|
+
"-": "-",
|
|
187689
|
+
"*": "*",
|
|
187690
|
+
"/": "/"
|
|
187829
187691
|
};
|
|
187830
|
-
|
|
187692
|
+
return `${l} ${opMap[op] ?? op} ${r}`;
|
|
187831
187693
|
}
|
|
187832
|
-
|
|
187833
|
-
|
|
187834
|
-
|
|
187835
|
-
|
|
187836
|
-
|
|
187837
|
-
if (
|
|
187838
|
-
|
|
187839
|
-
|
|
187840
|
-
severity: "error",
|
|
187841
|
-
message: `Loop callback uses an array/object destructure pattern (\`${loop.param}\`) that the Xslate adapter cannot lower — Kolon \`for LIST -> $item\` binds a single scalar and can't unpack a tuple.`,
|
|
187842
|
-
loc: loop.loc ?? { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
187843
|
-
suggestion: {
|
|
187844
|
-
message: `Options:
|
|
187845
|
-
` + ` 1. Rename the parameter to a single name and access tuple elements with index syntax in the body (e.g. \`entry => entry[0]\` instead of \`([k, v]) => ...\`).
|
|
187846
|
-
` + ` 2. Mark the loop position as @client-only so the destructure runs in JS on the client.
|
|
187847
|
-
` + ` 3. Move the loop into a primitive that the adapter registers explicitly.`
|
|
187848
|
-
}
|
|
187849
|
-
});
|
|
187850
|
-
}
|
|
187851
|
-
const rawArray = this.convertExpressionToKolon(loop.array);
|
|
187852
|
-
let array = rawArray;
|
|
187853
|
-
if (loop.sortComparator) {
|
|
187854
|
-
array = renderSortMethod(rawArray, loop.sortComparator);
|
|
187855
|
-
}
|
|
187856
|
-
const param = loop.param;
|
|
187857
|
-
const renderedChildren = this.renderChildren(loop.children);
|
|
187858
|
-
const loopVar = loop.iterationShape === "keys" ? "__bf_item" : supportableDestructure ? "__bf_item" : param;
|
|
187859
|
-
const indexLocalLines = [];
|
|
187860
|
-
if (loop.iterationShape === "keys") {
|
|
187861
|
-
indexLocalLines.push(`: my $${param} = $~${loopVar}.index;`);
|
|
187862
|
-
} else if (loop.index) {
|
|
187863
|
-
indexLocalLines.push(`: my $${loop.index} = $~${loopVar}.index;`);
|
|
187864
|
-
}
|
|
187865
|
-
if (supportableDestructure) {
|
|
187866
|
-
for (const b of loop.paramBindings ?? []) {
|
|
187867
|
-
indexLocalLines.push(b.rest ? `: my $${b.name} = $${loopVar};` : `: my $${b.name} = $${loopVar}${b.path};`);
|
|
187868
|
-
}
|
|
187869
|
-
}
|
|
187870
|
-
const prevInLoop = this.inLoop;
|
|
187871
|
-
this.inLoop = true;
|
|
187872
|
-
const childrenUnderLoop = this.renderChildren(loop.children);
|
|
187873
|
-
this.inLoop = prevInLoop;
|
|
187874
|
-
const bodyChildren = loop.bodyIsItemConditional && loop.key ? `<: $bf.comment("loop-i:" ~ ${this.convertExpressionToKolon(loop.key)}) | mark_raw :>
|
|
187875
|
-
${childrenUnderLoop}` : childrenUnderLoop;
|
|
187876
|
-
const lines = [];
|
|
187877
|
-
lines.push(`<: $bf.comment("loop:${loop.markerId}") | mark_raw :>`);
|
|
187878
|
-
lines.push(`: for ${array} -> $${loopVar} {`);
|
|
187879
|
-
for (const il of indexLocalLines)
|
|
187880
|
-
lines.push(il);
|
|
187881
|
-
if (loop.filterPredicate) {
|
|
187882
|
-
let filterCond;
|
|
187883
|
-
if (loop.filterPredicate.blockBody) {
|
|
187884
|
-
filterCond = this.renderBlockBodyCondition(loop.filterPredicate.blockBody, loop.filterPredicate.param);
|
|
187885
|
-
} else if (loop.filterPredicate.predicate) {
|
|
187886
|
-
filterCond = this.renderKolonFilterExpr(loop.filterPredicate.predicate, loop.filterPredicate.param);
|
|
187887
|
-
} else {
|
|
187888
|
-
filterCond = "1";
|
|
187889
|
-
}
|
|
187890
|
-
if (loop.filterPredicate.param !== param) {
|
|
187891
|
-
filterCond = filterCond.replace(new RegExp(`\\$${loop.filterPredicate.param}\\b`, "g"), `$${param}`);
|
|
187892
|
-
}
|
|
187893
|
-
lines.push(`: if (${filterCond}) {`);
|
|
187894
|
-
lines.push(bodyChildren);
|
|
187895
|
-
lines.push(`: }`);
|
|
187896
|
-
} else {
|
|
187897
|
-
lines.push(bodyChildren);
|
|
187898
|
-
}
|
|
187899
|
-
lines.push(`: }`);
|
|
187900
|
-
lines.push(`<: $bf.comment("/loop:${loop.markerId}") | mark_raw :>`);
|
|
187901
|
-
return lines.join(`
|
|
187902
|
-
`);
|
|
187694
|
+
logical(op, left, right, emit) {
|
|
187695
|
+
const l = emit(left);
|
|
187696
|
+
const r = emit(right);
|
|
187697
|
+
if (op === "&&")
|
|
187698
|
+
return `(${l} && ${r})`;
|
|
187699
|
+
if (op === "||")
|
|
187700
|
+
return `(${l} || ${r})`;
|
|
187701
|
+
return `(${l} // ${r})`;
|
|
187903
187702
|
}
|
|
187904
|
-
|
|
187905
|
-
|
|
187906
|
-
|
|
187907
|
-
if (value.parts) {
|
|
187908
|
-
return `${kolonHashKey(name)} => ${this.convertTemplateLiteralPartsToKolon(value.parts)}`;
|
|
187909
|
-
}
|
|
187910
|
-
return `${kolonHashKey(name)} => ${this.convertExpressionToKolon(value.expr)}`;
|
|
187911
|
-
},
|
|
187912
|
-
emitSpread: (value) => {
|
|
187913
|
-
return this.convertExpressionToKolon(value.expr);
|
|
187914
|
-
},
|
|
187915
|
-
emitTemplate: (value, name) => `${kolonHashKey(name)} => ${this.convertTemplateLiteralPartsToKolon(value.parts)}`,
|
|
187916
|
-
emitBooleanAttr: (_value, name) => `${kolonHashKey(name)} => 1`,
|
|
187917
|
-
emitBooleanShorthand: (_value, name) => `${kolonHashKey(name)} => 1`,
|
|
187918
|
-
emitJsxChildren: () => ""
|
|
187919
|
-
};
|
|
187920
|
-
renderComponent(comp) {
|
|
187921
|
-
const propParts = [];
|
|
187922
|
-
for (const p of comp.props) {
|
|
187923
|
-
if ((p.name.match(/^on[A-Z]/) || p.name === "ref") && p.value.kind === "expression")
|
|
187924
|
-
continue;
|
|
187925
|
-
if (p.value.kind === "spread") {
|
|
187926
|
-
const trimmed = p.value.expr.trim();
|
|
187927
|
-
if (this.propsObjectName && this.propsObjectName === trimmed) {
|
|
187928
|
-
for (const pp of this.propsParams) {
|
|
187929
|
-
propParts.push(`${pp.name} => $${pp.name}`);
|
|
187930
|
-
}
|
|
187931
|
-
continue;
|
|
187932
|
-
}
|
|
187933
|
-
this.errors.push({
|
|
187934
|
-
code: "BF101",
|
|
187935
|
-
severity: "error",
|
|
187936
|
-
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.`,
|
|
187937
|
-
loc: comp.loc ?? { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
187938
|
-
suggestion: {
|
|
187939
|
-
message: "Pass the child component its props explicitly rather than spreading a runtime object."
|
|
187940
|
-
}
|
|
187941
|
-
});
|
|
187942
|
-
continue;
|
|
187943
|
-
}
|
|
187944
|
-
const lowered = emitAttrValue(p.value, this.componentPropEmitter, p.name);
|
|
187945
|
-
if (lowered)
|
|
187946
|
-
propParts.push(lowered);
|
|
187947
|
-
}
|
|
187948
|
-
if (comp.slotId && !this.inLoop) {
|
|
187949
|
-
propParts.push(`_bf_slot => '${comp.slotId}'`);
|
|
187950
|
-
}
|
|
187951
|
-
const tplName = this.toTemplateName(comp.name);
|
|
187952
|
-
const effectiveChildren = comp.children.length > 0 ? comp.children : resolveJsxChildrenProp(comp.props);
|
|
187953
|
-
if (effectiveChildren.length > 0) {
|
|
187954
|
-
const prevInLoop = this.inLoop;
|
|
187955
|
-
this.inLoop = false;
|
|
187956
|
-
const childrenBody = this.renderChildren(effectiveChildren);
|
|
187957
|
-
this.inLoop = prevInLoop;
|
|
187958
|
-
const macroName = `bf_children_${comp.slotId ?? "c" + this.childrenCaptureCounter++}`;
|
|
187959
|
-
const childrenEntry = `children => ${macroName}()`;
|
|
187960
|
-
const allParts = [...propParts, childrenEntry];
|
|
187961
|
-
return `<: macro ${macroName} -> () { :>${childrenBody}<: } :><: $bf.render_child('${tplName}', { ${allParts.join(", ")} }) | mark_raw :>`;
|
|
187962
|
-
}
|
|
187963
|
-
const hashEntries = propParts.length > 0 ? `, { ${propParts.join(", ")} }` : "";
|
|
187964
|
-
return `<: $bf.render_child('${tplName}'${hashEntries}) | mark_raw :>`;
|
|
187703
|
+
callbackMethod(method, object, _arrow, _restArgs, emit) {
|
|
187704
|
+
this.onUnsupported?.(`Filter predicate contains a nested '.${method}(...)' callback, which has no Kolon scalar form`, `Rewrite the predicate without a nested callback method, or add /* @client */ for client-only evaluation (no SSR).`);
|
|
187705
|
+
return emit(object);
|
|
187965
187706
|
}
|
|
187966
|
-
|
|
187967
|
-
|
|
187968
|
-
toTemplateName(componentName) {
|
|
187969
|
-
return componentName.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
187707
|
+
arrayLiteral(elements, emit) {
|
|
187708
|
+
return `[${elements.map(emit).join(", ")}]`;
|
|
187970
187709
|
}
|
|
187971
|
-
|
|
187972
|
-
|
|
187973
|
-
const consequent = ifStmt.consequent.type === "if-statement" ? this.renderIfStatement(ifStmt.consequent) : this.renderNode(ifStmt.consequent);
|
|
187974
|
-
let result = `: if (${condition}) {
|
|
187975
|
-
${consequent}
|
|
187976
|
-
`;
|
|
187977
|
-
if (ifStmt.alternate) {
|
|
187978
|
-
if (ifStmt.alternate.type === "if-statement") {
|
|
187979
|
-
const altResult = this.renderIfStatement(ifStmt.alternate);
|
|
187980
|
-
result += altResult.replace(/^: if/, ": } elsif");
|
|
187981
|
-
} else {
|
|
187982
|
-
const alternate = this.renderNode(ifStmt.alternate);
|
|
187983
|
-
result += `: } else {
|
|
187984
|
-
${alternate}
|
|
187985
|
-
`;
|
|
187986
|
-
}
|
|
187987
|
-
}
|
|
187988
|
-
result += `: }`;
|
|
187989
|
-
return result;
|
|
187710
|
+
arrayMethod(method, object, args, emit) {
|
|
187711
|
+
return renderArrayMethod(method, object, args, emit);
|
|
187990
187712
|
}
|
|
187991
|
-
|
|
187992
|
-
|
|
187993
|
-
if (fragment.needsScopeComment) {
|
|
187994
|
-
return `<: $bf.scope_comment() | mark_raw :>${children}`;
|
|
187995
|
-
}
|
|
187996
|
-
return children;
|
|
187713
|
+
flatMethod(object, depth, emit) {
|
|
187714
|
+
return renderFlatMethod(emit(object), depth);
|
|
187997
187715
|
}
|
|
187998
|
-
|
|
187999
|
-
return
|
|
187716
|
+
conditional(_test, _consequent, _alternate) {
|
|
187717
|
+
return "1";
|
|
188000
187718
|
}
|
|
188001
|
-
|
|
188002
|
-
|
|
188003
|
-
const children = this.renderChildren(node.children);
|
|
188004
|
-
const macroName = `bf_async_fallback_${node.id}`;
|
|
188005
|
-
return `<: macro ${macroName} -> () { :>${fallback}<: } :><: $bf.async_boundary('${node.id}', ${macroName}()) | mark_raw :>
|
|
188006
|
-
${children}`;
|
|
187719
|
+
templateLiteral(_parts) {
|
|
187720
|
+
return "1";
|
|
188007
187721
|
}
|
|
188008
|
-
|
|
188009
|
-
|
|
188010
|
-
|
|
188011
|
-
|
|
188012
|
-
|
|
188013
|
-
|
|
188014
|
-
|
|
188015
|
-
|
|
188016
|
-
|
|
188017
|
-
|
|
188018
|
-
|
|
188019
|
-
|
|
188020
|
-
|
|
188021
|
-
|
|
188022
|
-
|
|
188023
|
-
|
|
188024
|
-
|
|
188025
|
-
|
|
188026
|
-
|
|
188027
|
-
|
|
188028
|
-
|
|
188029
|
-
|
|
188030
|
-
|
|
188031
|
-
|
|
188032
|
-
|
|
188033
|
-
|
|
188034
|
-
|
|
188035
|
-
|
|
188036
|
-
|
|
188037
|
-
|
|
188038
|
-
|
|
188039
|
-
|
|
188040
|
-
${
|
|
188041
|
-
|
|
188042
|
-
|
|
188043
|
-
|
|
188044
|
-
|
|
188045
|
-
|
|
188046
|
-
|
|
188047
|
-
|
|
188048
|
-
|
|
188049
|
-
|
|
188050
|
-
|
|
188051
|
-
|
|
188052
|
-
|
|
188053
|
-
|
|
188054
|
-
|
|
188055
|
-
|
|
188056
|
-
|
|
188057
|
-
|
|
188058
|
-
|
|
188059
|
-
|
|
188060
|
-
|
|
188061
|
-
|
|
188062
|
-
|
|
188063
|
-
|
|
188064
|
-
|
|
188065
|
-
|
|
188066
|
-
|
|
188067
|
-
|
|
188068
|
-
|
|
188069
|
-
|
|
188070
|
-
|
|
188071
|
-
return
|
|
188072
|
-
}
|
|
188073
|
-
const ternaryHashref = this.conditionalSpreadToKolon(trimmed);
|
|
188074
|
-
if (ternaryHashref !== null) {
|
|
188075
|
-
return `<: $bf.spread_attrs(${ternaryHashref}) | mark_raw :>`;
|
|
187722
|
+
arrow(_params, _body) {
|
|
187723
|
+
return "1";
|
|
187724
|
+
}
|
|
187725
|
+
regex(_raw) {
|
|
187726
|
+
return "1";
|
|
187727
|
+
}
|
|
187728
|
+
unsupported(_raw, _reason) {
|
|
187729
|
+
return "1";
|
|
187730
|
+
}
|
|
187731
|
+
objectLiteral(_properties, _raw, _emit) {
|
|
187732
|
+
return "1";
|
|
187733
|
+
}
|
|
187734
|
+
}
|
|
187735
|
+
|
|
187736
|
+
class XslateTopLevelEmitter {
|
|
187737
|
+
ctx;
|
|
187738
|
+
constructor(ctx) {
|
|
187739
|
+
this.ctx = ctx;
|
|
187740
|
+
}
|
|
187741
|
+
identifier(name) {
|
|
187742
|
+
if (name === "undefined" || name === "null")
|
|
187743
|
+
return "nil";
|
|
187744
|
+
const inlined = this.ctx._resolveModuleStringConst(name);
|
|
187745
|
+
if (inlined !== null)
|
|
187746
|
+
return inlined;
|
|
187747
|
+
const literalConst = this.ctx._resolveLiteralConst(name);
|
|
187748
|
+
if (literalConst !== null)
|
|
187749
|
+
return literalConst;
|
|
187750
|
+
return `$${name}`;
|
|
187751
|
+
}
|
|
187752
|
+
literal(value, literalType) {
|
|
187753
|
+
if (literalType === "string")
|
|
187754
|
+
return `'${value}'`;
|
|
187755
|
+
if (literalType === "boolean")
|
|
187756
|
+
return value ? "1" : "0";
|
|
187757
|
+
if (literalType === "null")
|
|
187758
|
+
return "nil";
|
|
187759
|
+
return String(value);
|
|
187760
|
+
}
|
|
187761
|
+
member(object, property, _computed, emit) {
|
|
187762
|
+
if (object.kind === "identifier" && object.name === "props") {
|
|
187763
|
+
return `$${property}`;
|
|
187764
|
+
}
|
|
187765
|
+
if (object.kind === "identifier") {
|
|
187766
|
+
const staticValue = this.ctx._resolveStaticRecordLiteral(object.name, property);
|
|
187767
|
+
if (staticValue !== null)
|
|
187768
|
+
return staticValue;
|
|
187769
|
+
}
|
|
187770
|
+
const obj = emit(object);
|
|
187771
|
+
if (property === "length")
|
|
187772
|
+
return `$bf.length(${obj})`;
|
|
187773
|
+
return `${obj}.${property}`;
|
|
187774
|
+
}
|
|
187775
|
+
indexAccess(object, index, emit) {
|
|
187776
|
+
return `${emit(object)}[${emit(index)}]`;
|
|
187777
|
+
}
|
|
187778
|
+
call(callee, args, emit) {
|
|
187779
|
+
if (callee.kind === "identifier" && args.length === 0) {
|
|
187780
|
+
return `$${callee.name}`;
|
|
187781
|
+
}
|
|
187782
|
+
if (this.ctx._searchParamsLocals.size > 0) {
|
|
187783
|
+
const sp = matchSearchParamsMethodCall(callee, args, this.ctx._searchParamsLocals);
|
|
187784
|
+
if (sp) {
|
|
187785
|
+
return `$searchParams.${sp.method}(${sp.args.map(emit).join(", ")})`;
|
|
188076
187786
|
}
|
|
188077
|
-
|
|
188078
|
-
|
|
188079
|
-
|
|
188080
|
-
|
|
188081
|
-
|
|
188082
|
-
|
|
188083
|
-
if (resolved !== null) {
|
|
188084
|
-
return `<: $bf.spread_attrs(${resolved}) | mark_raw :>`;
|
|
188085
|
-
}
|
|
188086
|
-
}
|
|
188087
|
-
}
|
|
187787
|
+
}
|
|
187788
|
+
const path = identifierPath(callee);
|
|
187789
|
+
const spec = path ? XSLATE_TEMPLATE_PRIMITIVES[path] : undefined;
|
|
187790
|
+
if (path && spec) {
|
|
187791
|
+
if (args.length === spec.arity) {
|
|
187792
|
+
return spec.emit(args.map(emit));
|
|
188088
187793
|
}
|
|
188089
|
-
|
|
188090
|
-
return
|
|
188091
|
-
},
|
|
188092
|
-
emitBooleanShorthand: () => "",
|
|
188093
|
-
emitJsxChildren: () => ""
|
|
188094
|
-
};
|
|
188095
|
-
tryLowerStyleObject(expr) {
|
|
188096
|
-
const entries = parseStyleObjectEntries(expr);
|
|
188097
|
-
if (!entries)
|
|
188098
|
-
return null;
|
|
188099
|
-
for (const e of entries) {
|
|
188100
|
-
if (e.kind === "expr" && !isSupported(parseExpression2(e.expr)).supported)
|
|
188101
|
-
return null;
|
|
187794
|
+
this.ctx._recordExprBF101(`templatePrimitive '${path}' expects ${spec.arity} arg(s), got ${args.length}`, `Call '${path}' with exactly ${spec.arity} argument(s).`);
|
|
187795
|
+
return "''";
|
|
188102
187796
|
}
|
|
188103
|
-
return
|
|
187797
|
+
return emit(callee);
|
|
188104
187798
|
}
|
|
188105
|
-
|
|
188106
|
-
|
|
187799
|
+
unary(op, argument, emit) {
|
|
187800
|
+
const arg = emit(argument);
|
|
187801
|
+
if (op === "!")
|
|
187802
|
+
return `!${arg}`;
|
|
187803
|
+
if (op === "-")
|
|
187804
|
+
return `-${arg}`;
|
|
187805
|
+
return arg;
|
|
188107
187806
|
}
|
|
188108
|
-
|
|
188109
|
-
const
|
|
188110
|
-
|
|
188111
|
-
|
|
188112
|
-
|
|
188113
|
-
|
|
188114
|
-
|
|
188115
|
-
|
|
188116
|
-
|
|
188117
|
-
|
|
188118
|
-
|
|
188119
|
-
|
|
188120
|
-
|
|
188121
|
-
|
|
188122
|
-
|
|
187807
|
+
binary(op, left, right, emit) {
|
|
187808
|
+
const l = emit(left);
|
|
187809
|
+
const r = emit(right);
|
|
187810
|
+
const opMap = {
|
|
187811
|
+
"===": "==",
|
|
187812
|
+
"!==": "!=",
|
|
187813
|
+
">": ">",
|
|
187814
|
+
"<": "<",
|
|
187815
|
+
">=": ">=",
|
|
187816
|
+
"<=": "<=",
|
|
187817
|
+
"+": "+",
|
|
187818
|
+
"-": "-",
|
|
187819
|
+
"*": "*"
|
|
187820
|
+
};
|
|
187821
|
+
return `${l} ${opMap[op] ?? op} ${r}`;
|
|
187822
|
+
}
|
|
187823
|
+
logical(op, left, right, emit) {
|
|
187824
|
+
const l = emit(left);
|
|
187825
|
+
const r = emit(right);
|
|
187826
|
+
if (op === "&&")
|
|
187827
|
+
return `(${l} && ${r})`;
|
|
187828
|
+
if (op === "||")
|
|
187829
|
+
return `(${l} || ${r})`;
|
|
187830
|
+
return `(${l} // ${r})`;
|
|
187831
|
+
}
|
|
187832
|
+
callbackMethod(method, object, arrow, restArgs, emit) {
|
|
187833
|
+
const recv = emit(object);
|
|
187834
|
+
const body = arrow.body;
|
|
187835
|
+
const params = arrow.params;
|
|
187836
|
+
if (PREDICATE_METHODS.has(method)) {
|
|
187837
|
+
return this._emitPredicateCallback({ method, object, param: params[0], predicate: body }, recv, emit);
|
|
187838
|
+
}
|
|
187839
|
+
if (method === "sort" || method === "toSorted") {
|
|
187840
|
+
const evalForm = renderSortEval(recv, body, params, emit);
|
|
187841
|
+
if (evalForm !== null)
|
|
187842
|
+
return evalForm;
|
|
187843
|
+
const structured = sortComparatorFromArrow(arrow);
|
|
187844
|
+
if (structured !== null)
|
|
187845
|
+
return renderSortMethod(recv, structured);
|
|
187846
|
+
this.ctx._recordExprBF101(`'.${method}(...)' comparator is outside the Xslate adapter's evaluable / structured surface`, `Pre-compute the sorted array, or move this position to a '/* @client */' boundary.`);
|
|
187847
|
+
return "''";
|
|
188123
187848
|
}
|
|
188124
|
-
|
|
187849
|
+
if (method === "reduce" || method === "reduceRight") {
|
|
187850
|
+
const direction = method === "reduceRight" ? "right" : "left";
|
|
187851
|
+
const init = restArgs[0];
|
|
187852
|
+
const evalForm = init !== undefined ? renderReduceEval(recv, body, params, init, direction, emit) : null;
|
|
187853
|
+
if (evalForm !== null)
|
|
187854
|
+
return evalForm;
|
|
187855
|
+
this.ctx._recordExprBF101(`'.${method}(...)' is outside the Xslate adapter's evaluable surface (needs a literal initial value and an evaluable reducer body)`, `Pre-compute the reduced value, or move this position to a '/* @client */' boundary.`);
|
|
187856
|
+
return "''";
|
|
187857
|
+
}
|
|
187858
|
+
if (method === "flatMap") {
|
|
187859
|
+
const evalForm = renderFlatMapEval(recv, body, params[0], emit);
|
|
187860
|
+
if (evalForm !== null)
|
|
187861
|
+
return evalForm;
|
|
187862
|
+
this.ctx._recordExprBF101(`'.flatMap(...)' projection is outside the Xslate adapter's evaluable surface`, `Pre-compute the projected array, or move this position to a '/* @client */' boundary.`);
|
|
187863
|
+
return "''";
|
|
187864
|
+
}
|
|
187865
|
+
if (method === "map") {
|
|
187866
|
+
const evalForm = renderMapEval(recv, body, params[0], emit);
|
|
187867
|
+
if (evalForm !== null)
|
|
187868
|
+
return evalForm;
|
|
187869
|
+
this.ctx._recordExprBF101(`'.map(...)' projection is outside the Xslate adapter's evaluable surface`, `Pre-compute the projected array, or move this position to a '/* @client */' boundary.`);
|
|
187870
|
+
return "''";
|
|
187871
|
+
}
|
|
187872
|
+
return recv;
|
|
188125
187873
|
}
|
|
188126
|
-
|
|
188127
|
-
|
|
187874
|
+
_emitPredicateCallback(call, arrayExpr, emit) {
|
|
187875
|
+
const { method, object, param, predicate } = call;
|
|
187876
|
+
const evalFn = {
|
|
187877
|
+
filter: ["filter_eval"],
|
|
187878
|
+
every: ["every_eval"],
|
|
187879
|
+
some: ["some_eval"],
|
|
187880
|
+
find: ["find_eval", true],
|
|
187881
|
+
findLast: ["find_eval", false],
|
|
187882
|
+
findIndex: ["find_index_eval", true],
|
|
187883
|
+
findLastIndex: ["find_index_eval", false]
|
|
187884
|
+
};
|
|
187885
|
+
const isIdentity = method === "filter" && predicate.kind === "identifier" && predicate.name === param;
|
|
187886
|
+
const spec = evalFn[method];
|
|
187887
|
+
if (spec && !isIdentity) {
|
|
187888
|
+
const evalForm = renderPredicateEval(spec[0], arrayExpr, predicate, param, emit, spec[1]);
|
|
187889
|
+
if (evalForm !== null)
|
|
187890
|
+
return evalForm;
|
|
187891
|
+
}
|
|
187892
|
+
const predBody = this.ctx._renderKolonFilterExprPublic(predicate, param);
|
|
187893
|
+
const lambda = `-> $${param} { ${predBody} }`;
|
|
187894
|
+
const fn = {
|
|
187895
|
+
filter: "filter",
|
|
187896
|
+
every: "every",
|
|
187897
|
+
some: "some",
|
|
187898
|
+
find: "find",
|
|
187899
|
+
findIndex: "find_index",
|
|
187900
|
+
findLast: "find_last",
|
|
187901
|
+
findLastIndex: "find_last_index"
|
|
187902
|
+
};
|
|
187903
|
+
if (fn[method])
|
|
187904
|
+
return `$bf.${fn[method]}(${arrayExpr}, ${lambda})`;
|
|
187905
|
+
return emit(object);
|
|
188128
187906
|
}
|
|
188129
|
-
|
|
188130
|
-
return
|
|
187907
|
+
arrayLiteral(elements, emit) {
|
|
187908
|
+
return `[${elements.map(emit).join(", ")}]`;
|
|
188131
187909
|
}
|
|
188132
|
-
|
|
188133
|
-
return
|
|
187910
|
+
arrayMethod(method, object, args, emit) {
|
|
187911
|
+
return renderArrayMethod(method, object, args, emit);
|
|
188134
187912
|
}
|
|
188135
|
-
|
|
188136
|
-
return
|
|
187913
|
+
flatMethod(object, depth, emit) {
|
|
187914
|
+
return renderFlatMethod(emit(object), depth);
|
|
188137
187915
|
}
|
|
188138
|
-
|
|
188139
|
-
|
|
188140
|
-
const paths = this.collectReturnPaths(statements, [], localVarMap, param);
|
|
188141
|
-
if (paths.length === 0)
|
|
188142
|
-
return "1";
|
|
188143
|
-
if (paths.length === 1)
|
|
188144
|
-
return this.buildSinglePathCondition(paths[0], param, localVarMap);
|
|
188145
|
-
const parts = [];
|
|
188146
|
-
for (const path of paths) {
|
|
188147
|
-
if (path.result.kind === "literal" && path.result.literalType === "boolean" && path.result.value === false)
|
|
188148
|
-
continue;
|
|
188149
|
-
const cond = this.buildSinglePathCondition(path, param, localVarMap);
|
|
188150
|
-
if (cond !== "0")
|
|
188151
|
-
parts.push(cond);
|
|
188152
|
-
}
|
|
188153
|
-
if (parts.length === 0)
|
|
188154
|
-
return "0";
|
|
188155
|
-
if (parts.length === 1)
|
|
188156
|
-
return parts[0];
|
|
188157
|
-
return `(${parts.join(" || ")})`;
|
|
187916
|
+
conditional(test, consequent, alternate, emit) {
|
|
187917
|
+
return `(${emit(test)} ? ${emit(consequent)} : ${emit(alternate)})`;
|
|
188158
187918
|
}
|
|
188159
|
-
|
|
188160
|
-
const
|
|
188161
|
-
for (const
|
|
188162
|
-
if (
|
|
188163
|
-
if (
|
|
188164
|
-
|
|
188165
|
-
}
|
|
188166
|
-
} else if (stmt.kind === "return") {
|
|
188167
|
-
paths.push({ conditions: [...currentConditions], result: stmt.value });
|
|
188168
|
-
break;
|
|
188169
|
-
} else if (stmt.kind === "if") {
|
|
188170
|
-
const thenPaths = this.collectReturnPaths(stmt.consequent, [...currentConditions, stmt.condition], localVarMap, param);
|
|
188171
|
-
paths.push(...thenPaths);
|
|
188172
|
-
if (stmt.alternate) {
|
|
188173
|
-
const negated = { kind: "unary", op: "!", argument: stmt.condition };
|
|
188174
|
-
const elsePaths = this.collectReturnPaths(stmt.alternate, [...currentConditions, negated], localVarMap, param);
|
|
188175
|
-
paths.push(...elsePaths);
|
|
188176
|
-
} else {
|
|
188177
|
-
currentConditions.push({ kind: "unary", op: "!", argument: stmt.condition });
|
|
187919
|
+
templateLiteral(parts, emit) {
|
|
187920
|
+
const terms = [];
|
|
187921
|
+
for (const part of parts) {
|
|
187922
|
+
if (part.type === "string") {
|
|
187923
|
+
if (part.value !== "") {
|
|
187924
|
+
terms.push(`'${part.value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`);
|
|
188178
187925
|
}
|
|
187926
|
+
} else {
|
|
187927
|
+
const rendered = emit(part.expr);
|
|
187928
|
+
const needsParens = part.expr.kind === "binary" || part.expr.kind === "logical" || part.expr.kind === "conditional";
|
|
187929
|
+
terms.push(needsParens ? `(${rendered})` : rendered);
|
|
188179
187930
|
}
|
|
188180
187931
|
}
|
|
188181
|
-
|
|
187932
|
+
if (terms.length === 0)
|
|
187933
|
+
return `''`;
|
|
187934
|
+
return terms.join(" ~ ");
|
|
187935
|
+
}
|
|
187936
|
+
arrow(_params, _body) {
|
|
187937
|
+
return "''";
|
|
187938
|
+
}
|
|
187939
|
+
regex(_raw) {
|
|
187940
|
+
return "''";
|
|
187941
|
+
}
|
|
187942
|
+
unsupported(_raw, _reason) {
|
|
187943
|
+
return "''";
|
|
187944
|
+
}
|
|
187945
|
+
objectLiteral(_properties, _raw, _emit) {
|
|
187946
|
+
return "''";
|
|
187947
|
+
}
|
|
187948
|
+
}
|
|
187949
|
+
|
|
187950
|
+
// src/adapter/analysis/component-tree.ts
|
|
187951
|
+
function hasClientInteractivity(ir) {
|
|
187952
|
+
return ir.metadata.signals.length > 0 || ir.metadata.effects.length > 0 || ir.metadata.onMounts.length > 0 || (ir.metadata.clientAnalysis?.needsInit ?? false);
|
|
187953
|
+
}
|
|
187954
|
+
function collectImportedLoopChildComponentErrors(ir, componentName) {
|
|
187955
|
+
const errors = [];
|
|
187956
|
+
const relativeImports = new Set;
|
|
187957
|
+
for (const imp of ir.metadata.templateImports ?? ir.metadata.imports ?? []) {
|
|
187958
|
+
if (!imp.source.startsWith("./") && !imp.source.startsWith("../"))
|
|
187959
|
+
continue;
|
|
187960
|
+
if (imp.isTypeOnly)
|
|
187961
|
+
continue;
|
|
187962
|
+
for (const spec of imp.specifiers) {
|
|
187963
|
+
relativeImports.add(spec.alias ?? spec.name);
|
|
187964
|
+
}
|
|
188182
187965
|
}
|
|
188183
|
-
|
|
188184
|
-
|
|
188185
|
-
|
|
188186
|
-
|
|
188187
|
-
|
|
188188
|
-
|
|
187966
|
+
if (relativeImports.size === 0)
|
|
187967
|
+
return errors;
|
|
187968
|
+
const loc = { file: componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } };
|
|
187969
|
+
const visit = (node, inLoop) => {
|
|
187970
|
+
switch (node.type) {
|
|
187971
|
+
case "component": {
|
|
187972
|
+
const comp = node;
|
|
187973
|
+
if (inLoop && relativeImports.has(comp.name)) {
|
|
187974
|
+
errors.push({
|
|
187975
|
+
code: "BF103",
|
|
187976
|
+
severity: "error",
|
|
187977
|
+
message: `Component <${comp.name}> is imported from a sibling module and used inside a loop. The Xslate adapter emits a cross-template call; the child template must be registered alongside the parent at render time.`,
|
|
187978
|
+
loc: comp.loc ?? loc,
|
|
187979
|
+
suggestion: {
|
|
187980
|
+
message: `Options:
|
|
187981
|
+
` + ` 1. Compile '${comp.name}' (its source file) with the same adapter and register the resulting Xslate template alongside the parent at render time.
|
|
187982
|
+
` + ` 2. Inline <${comp.name}> directly inside the loop body so no cross-file template lookup is needed.
|
|
187983
|
+
` + ` 3. Mark the loop position as @client-only so the template is materialised on the client instead of at SSR time.`
|
|
187984
|
+
}
|
|
187985
|
+
});
|
|
187986
|
+
}
|
|
187987
|
+
for (const child of comp.children)
|
|
187988
|
+
visit(child, inLoop);
|
|
187989
|
+
break;
|
|
187990
|
+
}
|
|
187991
|
+
case "element":
|
|
187992
|
+
for (const child of node.children)
|
|
187993
|
+
visit(child, inLoop);
|
|
187994
|
+
break;
|
|
187995
|
+
case "fragment":
|
|
187996
|
+
for (const child of node.children)
|
|
187997
|
+
visit(child, inLoop);
|
|
187998
|
+
break;
|
|
187999
|
+
case "conditional": {
|
|
188000
|
+
const cond = node;
|
|
188001
|
+
visit(cond.whenTrue, inLoop);
|
|
188002
|
+
if (cond.whenFalse)
|
|
188003
|
+
visit(cond.whenFalse, inLoop);
|
|
188004
|
+
break;
|
|
188005
|
+
}
|
|
188006
|
+
case "loop":
|
|
188007
|
+
for (const child of node.children)
|
|
188008
|
+
visit(child, true);
|
|
188009
|
+
break;
|
|
188010
|
+
case "if-statement": {
|
|
188011
|
+
const stmt = node;
|
|
188012
|
+
visit(stmt.consequent, inLoop);
|
|
188013
|
+
if (stmt.alternate)
|
|
188014
|
+
visit(stmt.alternate, inLoop);
|
|
188015
|
+
break;
|
|
188016
|
+
}
|
|
188017
|
+
case "provider":
|
|
188018
|
+
for (const child of node.children)
|
|
188019
|
+
visit(child, inLoop);
|
|
188020
|
+
break;
|
|
188021
|
+
case "async": {
|
|
188022
|
+
const a = node;
|
|
188023
|
+
visit(a.fallback, inLoop);
|
|
188024
|
+
for (const child of a.children)
|
|
188025
|
+
visit(child, inLoop);
|
|
188026
|
+
break;
|
|
188189
188027
|
}
|
|
188190
|
-
return "0";
|
|
188191
188028
|
}
|
|
188192
|
-
|
|
188193
|
-
|
|
188029
|
+
};
|
|
188030
|
+
visit(ir.root, false);
|
|
188031
|
+
return errors;
|
|
188032
|
+
}
|
|
188033
|
+
|
|
188034
|
+
// src/adapter/spread/spread-codegen.ts
|
|
188035
|
+
var import_typescript = __toESM(require_typescript(), 1);
|
|
188036
|
+
import { parseRecordIndexAccess, stringifyParsedExpr } from "@barefootjs/jsx";
|
|
188037
|
+
function conditionalSpreadToKolon(ctx, expr) {
|
|
188038
|
+
if (!expr || expr.kind !== "conditional")
|
|
188039
|
+
return null;
|
|
188040
|
+
const whenTrue = expr.consequent;
|
|
188041
|
+
const whenFalse = expr.alternate;
|
|
188042
|
+
if (whenTrue.kind !== "object-literal" || whenFalse.kind !== "object-literal") {
|
|
188043
|
+
return null;
|
|
188044
|
+
}
|
|
188045
|
+
const condKolon = ctx.convertExpressionToKolon("", expr.test);
|
|
188046
|
+
const trueKolon = objectLiteralToKolonHashref(ctx, whenTrue);
|
|
188047
|
+
const falseKolon = objectLiteralToKolonHashref(ctx, whenFalse);
|
|
188048
|
+
if (trueKolon === null || falseKolon === null)
|
|
188049
|
+
return null;
|
|
188050
|
+
return `${condKolon} ? ${trueKolon} : ${falseKolon}`;
|
|
188051
|
+
}
|
|
188052
|
+
function objectLiteralExprToKolonHashref(ctx, expr) {
|
|
188053
|
+
if (!expr || expr.kind !== "object-literal")
|
|
188054
|
+
return null;
|
|
188055
|
+
return objectLiteralToKolonHashref(ctx, expr);
|
|
188056
|
+
}
|
|
188057
|
+
function objectLiteralToKolonHashref(ctx, obj) {
|
|
188058
|
+
const entries = [];
|
|
188059
|
+
for (const prop of obj.properties) {
|
|
188060
|
+
if (prop.shorthand)
|
|
188061
|
+
return null;
|
|
188062
|
+
if (prop.keyKind === "numeric")
|
|
188063
|
+
return null;
|
|
188064
|
+
const key = prop.key;
|
|
188065
|
+
const val = prop.value;
|
|
188066
|
+
const indexed = recordIndexAccessToKolon(ctx, val);
|
|
188067
|
+
if (indexed === null && val.kind === "index-access" && !isLiteralIndex(val.index)) {
|
|
188068
|
+
ctx.errors.push({
|
|
188069
|
+
code: "BF101",
|
|
188070
|
+
severity: "error",
|
|
188071
|
+
message: `Spread object value '${stringifyParsedExpr(val)}' indexes a record map whose values aren't scalar literals — it can't lower to an inline Kolon hashref.`,
|
|
188072
|
+
loc: { file: ctx.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188073
|
+
suggestion: {
|
|
188074
|
+
message: "Index a record whose values are number/string literals, or move the spread into a `'use client'` component so hydration computes it."
|
|
188075
|
+
}
|
|
188076
|
+
});
|
|
188077
|
+
return null;
|
|
188194
188078
|
}
|
|
188195
|
-
const
|
|
188196
|
-
|
|
188197
|
-
return `(${condPart} && ${resultPart})`;
|
|
188079
|
+
const valKolon = indexed !== null ? indexed : ctx.convertExpressionToKolon("", val);
|
|
188080
|
+
entries.push(`'${escapeKolonSingleQuoted(key)}' => ${valKolon}`);
|
|
188198
188081
|
}
|
|
188199
|
-
|
|
188200
|
-
|
|
188201
|
-
|
|
188202
|
-
|
|
188203
|
-
|
|
188204
|
-
|
|
188205
|
-
|
|
188082
|
+
return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
|
|
188083
|
+
}
|
|
188084
|
+
function isLiteralIndex(index) {
|
|
188085
|
+
return index.kind === "literal" && (index.literalType === "number" || index.literalType === "string");
|
|
188086
|
+
}
|
|
188087
|
+
function recordIndexAccessToKolon(ctx, val) {
|
|
188088
|
+
if (val.kind !== "index-access" || val.object.kind !== "identifier" || val.index.kind !== "identifier") {
|
|
188089
|
+
return null;
|
|
188206
188090
|
}
|
|
188207
|
-
|
|
188208
|
-
|
|
188209
|
-
|
|
188210
|
-
|
|
188211
|
-
|
|
188212
|
-
|
|
188213
|
-
|
|
188214
|
-
|
|
188215
|
-
|
|
188216
|
-
|
|
188217
|
-
|
|
188218
|
-
|
|
188219
|
-
|
|
188091
|
+
const tsVal = import_typescript.default.factory.createElementAccessExpression(import_typescript.default.factory.createIdentifier(val.object.name), import_typescript.default.factory.createIdentifier(val.index.name));
|
|
188092
|
+
const parsed = parseRecordIndexAccess(tsVal, ctx.localConstants ?? [], ctx.propsParams);
|
|
188093
|
+
if (!parsed)
|
|
188094
|
+
return null;
|
|
188095
|
+
const entries = parsed.entries.map((e) => {
|
|
188096
|
+
const mapVal = e.value.kind === "number" ? e.value.text : `'${escapeKolonSingleQuoted(e.value.text)}'`;
|
|
188097
|
+
return `'${escapeKolonSingleQuoted(e.key)}' => ${mapVal}`;
|
|
188098
|
+
});
|
|
188099
|
+
return `{ ${entries.join(", ")} }[$${parsed.indexPropName}]`;
|
|
188100
|
+
}
|
|
188101
|
+
|
|
188102
|
+
// src/adapter/memo/seed.ts
|
|
188103
|
+
import {
|
|
188104
|
+
collectContextConsumers,
|
|
188105
|
+
computeSsrSeedPlan
|
|
188106
|
+
} from "@barefootjs/jsx";
|
|
188107
|
+
function contextDefaultKolon(c) {
|
|
188108
|
+
const d = c.defaultValue;
|
|
188109
|
+
if (d === null || d === undefined)
|
|
188110
|
+
return "nil";
|
|
188111
|
+
if (typeof d === "string")
|
|
188112
|
+
return `'${d.replace(/[\\']/g, (m) => `\\${m}`)}'`;
|
|
188113
|
+
if (typeof d === "boolean")
|
|
188114
|
+
return d ? "1" : "0";
|
|
188115
|
+
return String(d);
|
|
188116
|
+
}
|
|
188117
|
+
function generateContextConsumerSeed(ir) {
|
|
188118
|
+
const consumers = collectContextConsumers(ir.metadata);
|
|
188119
|
+
if (consumers.length === 0)
|
|
188120
|
+
return "";
|
|
188121
|
+
return consumers.map((c) => `: my $${c.localName} = $bf.use_context('${c.contextName}', ${contextDefaultKolon(c)});`).join(`
|
|
188122
|
+
`) + `
|
|
188123
|
+
`;
|
|
188124
|
+
}
|
|
188125
|
+
function generateDerivedMemoSeed(ctx, ir) {
|
|
188126
|
+
const plan = ir.metadata.ssrSeedPlan ?? computeSsrSeedPlan(ir.metadata);
|
|
188127
|
+
const lines = [];
|
|
188128
|
+
for (const step of plan.steps) {
|
|
188129
|
+
if (step.kind !== "derived")
|
|
188130
|
+
continue;
|
|
188131
|
+
const kolon = ctx.convertExpressionToKolon(step.expr, step.parsed);
|
|
188132
|
+
if (kolon === "" || !/\$[A-Za-z_]\w*/.test(kolon))
|
|
188133
|
+
continue;
|
|
188134
|
+
if (new RegExp(`\\$${step.name}\\b`).test(kolon))
|
|
188135
|
+
continue;
|
|
188136
|
+
lines.push(`: my $${step.name} = ${kolon};`);
|
|
188137
|
+
}
|
|
188138
|
+
return lines.length > 0 ? lines.join(`
|
|
188139
|
+
`) + `
|
|
188140
|
+
` : "";
|
|
188141
|
+
}
|
|
188142
|
+
|
|
188143
|
+
// src/adapter/value/parsed-literal.ts
|
|
188144
|
+
import { evalStringArrayJoin } from "@barefootjs/jsx";
|
|
188145
|
+
function isStringTypeInfo(type2) {
|
|
188146
|
+
return type2?.kind === "primitive" && type2.primitive === "string";
|
|
188147
|
+
}
|
|
188148
|
+
function isBareStringLiteral(initialValue) {
|
|
188149
|
+
if (!initialValue)
|
|
188150
|
+
return false;
|
|
188151
|
+
const v = initialValue.trim();
|
|
188152
|
+
return v.startsWith("'") && v.endsWith("'") || v.startsWith('"') && v.endsWith('"');
|
|
188153
|
+
}
|
|
188154
|
+
|
|
188155
|
+
// src/adapter/props/prop-classes.ts
|
|
188156
|
+
function collectBooleanTypedProps(ir) {
|
|
188157
|
+
return new Set(ir.metadata.propsParams.filter((prop) => prop.type?.primitive === "boolean" || prop.type?.raw === "boolean").map((prop) => prop.name));
|
|
188158
|
+
}
|
|
188159
|
+
function collectNullableOptionalProps(ir) {
|
|
188160
|
+
return new Set(ir.metadata.propsParams.filter((p) => p.defaultValue === undefined && !p.isRest && p.type?.kind !== "primitive").map((p) => p.name));
|
|
188161
|
+
}
|
|
188162
|
+
function collectStringValueNames(ir) {
|
|
188163
|
+
const names = new Set;
|
|
188164
|
+
for (const s of ir.metadata.signals) {
|
|
188165
|
+
if (isStringTypeInfo(s.type) || isBareStringLiteral(s.initialValue)) {
|
|
188166
|
+
names.add(s.getter);
|
|
188220
188167
|
}
|
|
188221
|
-
return parts.length === 1 ? parts[0] : parts.join(" ~ ");
|
|
188222
188168
|
}
|
|
188223
|
-
|
|
188224
|
-
|
|
188225
|
-
|
|
188226
|
-
|
|
188227
|
-
|
|
188228
|
-
|
|
188229
|
-
|
|
188230
|
-
|
|
188231
|
-
|
|
188232
|
-
|
|
188233
|
-
|
|
188169
|
+
for (const p of ir.metadata.propsParams) {
|
|
188170
|
+
if (isStringTypeInfo(p.type))
|
|
188171
|
+
names.add(p.name);
|
|
188172
|
+
}
|
|
188173
|
+
return names;
|
|
188174
|
+
}
|
|
188175
|
+
|
|
188176
|
+
// src/adapter/xslate-adapter.ts
|
|
188177
|
+
class XslateAdapter extends BaseAdapter {
|
|
188178
|
+
name = "xslate";
|
|
188179
|
+
extension = ".tx";
|
|
188180
|
+
templatesPerComponent = true;
|
|
188181
|
+
importMapInjection = "html-snippet";
|
|
188182
|
+
templatePrimitives = XSLATE_PRIMITIVE_EMIT_MAP;
|
|
188183
|
+
componentName = "";
|
|
188184
|
+
rootScopeNodes = new Set;
|
|
188185
|
+
options;
|
|
188186
|
+
errors = [];
|
|
188187
|
+
inLoop = false;
|
|
188188
|
+
propsObjectName = null;
|
|
188189
|
+
propsParams = [];
|
|
188190
|
+
booleanTypedProps = new Set;
|
|
188191
|
+
stringValueNames = new Set;
|
|
188192
|
+
moduleStringConsts = new Map;
|
|
188193
|
+
_searchParamsLocals = new Set;
|
|
188194
|
+
_loweringMatchers = [];
|
|
188195
|
+
localConstants = [];
|
|
188196
|
+
nullableOptionalProps = new Set;
|
|
188197
|
+
constructor(options = {}) {
|
|
188198
|
+
super();
|
|
188199
|
+
this.options = {
|
|
188200
|
+
clientJsBasePath: options.clientJsBasePath ?? "/static/components/",
|
|
188201
|
+
barefootJsPath: options.barefootJsPath ?? "/static/components/barefoot.js"
|
|
188202
|
+
};
|
|
188203
|
+
}
|
|
188204
|
+
generate(ir, options) {
|
|
188205
|
+
this.componentName = ir.metadata.componentName;
|
|
188206
|
+
this.propsObjectName = ir.metadata.propsObjectName ?? null;
|
|
188207
|
+
augmentInheritedPropAccesses(ir);
|
|
188208
|
+
this.propsParams = ir.metadata.propsParams.map((p) => ({ name: p.name }));
|
|
188209
|
+
this.booleanTypedProps = collectBooleanTypedProps(ir);
|
|
188210
|
+
this.localConstants = ir.metadata.localConstants ?? [];
|
|
188211
|
+
this.nullableOptionalProps = collectNullableOptionalProps(ir);
|
|
188212
|
+
this.stringValueNames = collectStringValueNames(ir);
|
|
188213
|
+
this.moduleStringConsts = collectModuleStringConsts(ir.metadata.localConstants);
|
|
188214
|
+
this._searchParamsLocals = searchParamsLocalNames(ir.metadata);
|
|
188215
|
+
this._loweringMatchers = prepareLoweringMatchers(ir.metadata);
|
|
188216
|
+
this.errors = [];
|
|
188217
|
+
this.childrenCaptureCounter = 0;
|
|
188218
|
+
if (!options?.siblingTemplatesRegistered) {
|
|
188219
|
+
this.errors.push(...collectImportedLoopChildComponentErrors(ir, this.componentName));
|
|
188234
188220
|
}
|
|
188235
|
-
|
|
188236
|
-
|
|
188221
|
+
this.rootScopeNodes = collectRootScopeNodes(ir.root);
|
|
188222
|
+
const templateBody = ir.root.type === "if-statement" ? this.renderIfStatement(ir.root) : this.renderNode(ir.root);
|
|
188223
|
+
const scriptReg = options?.skipScriptRegistration ? "" : this.generateScriptRegistrations(ir, options?.scriptBaseName);
|
|
188224
|
+
const ctxSeed = generateContextConsumerSeed(ir);
|
|
188225
|
+
const memoSeed = generateDerivedMemoSeed(this.memoCtx, ir);
|
|
188226
|
+
const template = `${scriptReg}${ctxSeed}${memoSeed}${templateBody}
|
|
188227
|
+
`;
|
|
188228
|
+
if (this.errors.length > 0) {
|
|
188229
|
+
ir.errors.push(...this.errors);
|
|
188237
188230
|
}
|
|
188238
|
-
|
|
188239
|
-
|
|
188240
|
-
|
|
188231
|
+
const sections = {
|
|
188232
|
+
imports: "",
|
|
188233
|
+
types: "",
|
|
188234
|
+
component: template,
|
|
188235
|
+
defaultExport: ""
|
|
188236
|
+
};
|
|
188237
|
+
return {
|
|
188238
|
+
template,
|
|
188239
|
+
sections,
|
|
188240
|
+
extension: this.extension
|
|
188241
|
+
};
|
|
188242
|
+
}
|
|
188243
|
+
generateScriptRegistrations(ir, scriptBaseName) {
|
|
188244
|
+
const hasInteractivity = hasClientInteractivity(ir);
|
|
188245
|
+
if (!hasInteractivity)
|
|
188246
|
+
return "";
|
|
188247
|
+
const name = scriptBaseName ?? ir.metadata.componentName;
|
|
188248
|
+
const runtimePath = this.options.barefootJsPath;
|
|
188249
|
+
const clientJsPath = `${this.options.clientJsBasePath}${name}.client.js`;
|
|
188250
|
+
const lines = [];
|
|
188251
|
+
lines.push(`: my $_bf_reg0 = $bf.register_script('${runtimePath}');`);
|
|
188252
|
+
lines.push(`: my $_bf_reg1 = $bf.register_script('${clientJsPath}');`);
|
|
188253
|
+
lines.push("");
|
|
188254
|
+
return lines.join(`
|
|
188255
|
+
`);
|
|
188256
|
+
}
|
|
188257
|
+
renderNode(node) {
|
|
188258
|
+
return emitIRNode(node, this, {});
|
|
188241
188259
|
}
|
|
188242
|
-
|
|
188243
|
-
|
|
188244
|
-
while (probe.startsWith("("))
|
|
188245
|
-
probe = probe.slice(1).trimStart();
|
|
188246
|
-
const startsAsObjectLiteral = probe.startsWith("{");
|
|
188247
|
-
const hasTaggedTemplate = /[A-Za-z_$][\w$]*\s*`/.test(probe);
|
|
188248
|
-
if (!startsAsObjectLiteral && !hasTaggedTemplate)
|
|
188249
|
-
return false;
|
|
188250
|
-
const parsed = parseExpression2(expr.trim());
|
|
188251
|
-
const support = isSupported(parsed);
|
|
188252
|
-
if (parsed.kind !== "unsupported" && support.supported)
|
|
188253
|
-
return false;
|
|
188254
|
-
const reason = support.reason ?? (parsed.kind === "unsupported" ? parsed.reason : undefined);
|
|
188255
|
-
const reasonLine = reason ? `
|
|
188256
|
-
${reason}` : "";
|
|
188257
|
-
this.errors.push({
|
|
188258
|
-
code: "BF101",
|
|
188259
|
-
severity: "error",
|
|
188260
|
-
message: `Expression not supported on attribute '${attrName}': ${expr.trim()}${reasonLine}`,
|
|
188261
|
-
loc: { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188262
|
-
suggestion: {
|
|
188263
|
-
message: "The Xslate adapter cannot lower JS object literals or tagged-template-literal expressions into Kolon. Move the expression into a `'use client'` component (so hydration computes it), or expand it into discrete attributes whose values are values the adapter can lower."
|
|
188264
|
-
}
|
|
188265
|
-
});
|
|
188266
|
-
return true;
|
|
188260
|
+
emitElement(node, _ctx, _emit) {
|
|
188261
|
+
return this.renderElement(node);
|
|
188267
188262
|
}
|
|
188268
|
-
|
|
188269
|
-
|
|
188270
|
-
if (sf.statements.length !== 1)
|
|
188271
|
-
return null;
|
|
188272
|
-
const stmt = sf.statements[0];
|
|
188273
|
-
if (!import_typescript.default.isExpressionStatement(stmt))
|
|
188274
|
-
return null;
|
|
188275
|
-
let node = stmt.expression;
|
|
188276
|
-
while (import_typescript.default.isParenthesizedExpression(node))
|
|
188277
|
-
node = node.expression;
|
|
188278
|
-
if (!import_typescript.default.isConditionalExpression(node))
|
|
188279
|
-
return null;
|
|
188280
|
-
const unwrap = (e) => {
|
|
188281
|
-
let n = e;
|
|
188282
|
-
while (import_typescript.default.isParenthesizedExpression(n))
|
|
188283
|
-
n = n.expression;
|
|
188284
|
-
return n;
|
|
188285
|
-
};
|
|
188286
|
-
const whenTrue = unwrap(node.whenTrue);
|
|
188287
|
-
const whenFalse = unwrap(node.whenFalse);
|
|
188288
|
-
if (!import_typescript.default.isObjectLiteralExpression(whenTrue) || !import_typescript.default.isObjectLiteralExpression(whenFalse)) {
|
|
188289
|
-
return null;
|
|
188290
|
-
}
|
|
188291
|
-
const condPerl = this.convertExpressionToKolon(node.condition.getText(sf));
|
|
188292
|
-
const truePerl = this.objectLiteralToKolonHashref(whenTrue, sf);
|
|
188293
|
-
const falsePerl = this.objectLiteralToKolonHashref(whenFalse, sf);
|
|
188294
|
-
if (truePerl === null || falsePerl === null)
|
|
188295
|
-
return null;
|
|
188296
|
-
return `${condPerl} ? ${truePerl} : ${falsePerl}`;
|
|
188263
|
+
emitText(node) {
|
|
188264
|
+
return node.value;
|
|
188297
188265
|
}
|
|
188298
|
-
|
|
188299
|
-
|
|
188300
|
-
for (const prop of obj.properties) {
|
|
188301
|
-
if (!import_typescript.default.isPropertyAssignment(prop))
|
|
188302
|
-
return null;
|
|
188303
|
-
let key;
|
|
188304
|
-
if (import_typescript.default.isIdentifier(prop.name)) {
|
|
188305
|
-
key = prop.name.text;
|
|
188306
|
-
} else if (import_typescript.default.isStringLiteral(prop.name) || import_typescript.default.isNoSubstitutionTemplateLiteral(prop.name)) {
|
|
188307
|
-
key = prop.name.text;
|
|
188308
|
-
} else {
|
|
188309
|
-
return null;
|
|
188310
|
-
}
|
|
188311
|
-
const initNode = (() => {
|
|
188312
|
-
let n = prop.initializer;
|
|
188313
|
-
while (import_typescript.default.isParenthesizedExpression(n))
|
|
188314
|
-
n = n.expression;
|
|
188315
|
-
return n;
|
|
188316
|
-
})();
|
|
188317
|
-
const indexed = this.recordIndexAccessToKolon(initNode);
|
|
188318
|
-
if (indexed === null && import_typescript.default.isElementAccessExpression(initNode) && initNode.argumentExpression && !import_typescript.default.isNumericLiteral(initNode.argumentExpression) && !import_typescript.default.isStringLiteral(initNode.argumentExpression)) {
|
|
188319
|
-
this.errors.push({
|
|
188320
|
-
code: "BF101",
|
|
188321
|
-
severity: "error",
|
|
188322
|
-
message: `Spread object value '${initNode.getText(sf)}' indexes a record map whose values aren't scalar literals — it can't lower to an inline Kolon hashref.`,
|
|
188323
|
-
loc: { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188324
|
-
suggestion: {
|
|
188325
|
-
message: "Index a record whose values are number/string literals, or move the spread into a `'use client'` component so hydration computes it."
|
|
188326
|
-
}
|
|
188327
|
-
});
|
|
188328
|
-
return null;
|
|
188329
|
-
}
|
|
188330
|
-
const valPerl = indexed !== null ? indexed : this.convertExpressionToKolon(prop.initializer.getText(sf));
|
|
188331
|
-
entries.push(`'${escapeKolonSingleQuoted(key)}' => ${valPerl}`);
|
|
188332
|
-
}
|
|
188333
|
-
return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
|
|
188266
|
+
emitExpression(node) {
|
|
188267
|
+
return this.renderExpression(node);
|
|
188334
188268
|
}
|
|
188335
|
-
|
|
188336
|
-
|
|
188337
|
-
if (!parsed)
|
|
188338
|
-
return null;
|
|
188339
|
-
const entries = parsed.entries.map((e) => {
|
|
188340
|
-
const mapVal = e.value.kind === "number" ? e.value.text : `'${escapeKolonSingleQuoted(e.value.text)}'`;
|
|
188341
|
-
return `'${escapeKolonSingleQuoted(e.key)}' => ${mapVal}`;
|
|
188342
|
-
});
|
|
188343
|
-
return `{ ${entries.join(", ")} }[$${parsed.indexPropName}]`;
|
|
188269
|
+
emitConditional(node, _ctx, _emit) {
|
|
188270
|
+
return this.renderConditional(node);
|
|
188344
188271
|
}
|
|
188345
|
-
|
|
188346
|
-
|
|
188347
|
-
if (trimmed === "")
|
|
188348
|
-
return "''";
|
|
188349
|
-
const parsed = parseExpression2(trimmed);
|
|
188350
|
-
const support = isSupported(parsed);
|
|
188351
|
-
if (!support.supported) {
|
|
188352
|
-
this.errors.push({
|
|
188353
|
-
code: "BF101",
|
|
188354
|
-
severity: "error",
|
|
188355
|
-
message: `Expression not supported: ${trimmed}`,
|
|
188356
|
-
loc: { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188357
|
-
suggestion: {
|
|
188358
|
-
message: support.reason ? `${support.reason}
|
|
188359
|
-
|
|
188360
|
-
Options:
|
|
188361
|
-
1. Use /* @client */ for client-side evaluation
|
|
188362
|
-
2. Pre-compute the value in the backend` : `Options:
|
|
188363
|
-
1. Use /* @client */ for client-side evaluation
|
|
188364
|
-
2. Pre-compute the value in the backend`
|
|
188365
|
-
}
|
|
188366
|
-
});
|
|
188367
|
-
return "''";
|
|
188368
|
-
}
|
|
188369
|
-
return this.renderParsedExprToKolon(parsed);
|
|
188272
|
+
emitLoop(node, _ctx, _emit) {
|
|
188273
|
+
return this.renderLoop(node);
|
|
188370
188274
|
}
|
|
188371
|
-
|
|
188372
|
-
return
|
|
188275
|
+
emitComponent(node, _ctx, _emit) {
|
|
188276
|
+
return this.renderComponent(node);
|
|
188373
188277
|
}
|
|
188374
|
-
|
|
188375
|
-
return this.
|
|
188278
|
+
emitFragment(node, _ctx, _emit) {
|
|
188279
|
+
return this.renderFragment(node);
|
|
188376
188280
|
}
|
|
188377
|
-
|
|
188378
|
-
|
|
188379
|
-
if (parsed?.kind !== "conditional")
|
|
188380
|
-
return null;
|
|
188381
|
-
const alt = parsed.alternate;
|
|
188382
|
-
const isUndef = alt.kind === "identifier" && (alt.name === "undefined" || alt.name === "null") || alt.kind === "literal" && (alt.value === null || alt.value === undefined);
|
|
188383
|
-
if (!isUndef)
|
|
188384
|
-
return null;
|
|
188385
|
-
return {
|
|
188386
|
-
condition: exprToString(parsed.test),
|
|
188387
|
-
consequent: exprToString(parsed.consequent)
|
|
188388
|
-
};
|
|
188281
|
+
emitSlot(node) {
|
|
188282
|
+
return this.renderSlot(node);
|
|
188389
188283
|
}
|
|
188390
|
-
|
|
188391
|
-
|
|
188392
|
-
if (this.propsObjectName && bare.startsWith(`${this.propsObjectName}.`)) {
|
|
188393
|
-
bare = bare.slice(this.propsObjectName.length + 1);
|
|
188394
|
-
}
|
|
188395
|
-
if (!/^[A-Za-z_$][\w$]*$/.test(bare))
|
|
188396
|
-
return false;
|
|
188397
|
-
return this.booleanTypedProps.has(bare);
|
|
188284
|
+
emitIfStatement(node, _ctx, _emit) {
|
|
188285
|
+
return this.renderIfStatement(node);
|
|
188398
188286
|
}
|
|
188399
|
-
|
|
188400
|
-
const
|
|
188401
|
-
|
|
188402
|
-
|
|
188403
|
-
|
|
188404
|
-
if (/^-?\d+(\.\d+)?$/.test(v))
|
|
188405
|
-
return v;
|
|
188406
|
-
const strLit = /^'([^'\\]*)'$/.exec(v) ?? /^"([^"\\]*)"$/.exec(v);
|
|
188407
|
-
if (strLit)
|
|
188408
|
-
return `'${strLit[1].replace(/[\\']/g, (m) => `\\${m}`)}'`;
|
|
188409
|
-
return null;
|
|
188287
|
+
emitProvider(node, _ctx, _emit) {
|
|
188288
|
+
const value = this.providerValueKolon(node.valueProp);
|
|
188289
|
+
const children = this.renderChildren(node.children);
|
|
188290
|
+
const name = node.contextName;
|
|
188291
|
+
return `<: $bf.provide_context('${name}', ${value}) :>` + children + `<: $bf.revoke_context('${name}') :>`;
|
|
188410
188292
|
}
|
|
188411
|
-
|
|
188412
|
-
const
|
|
188413
|
-
if (
|
|
188414
|
-
return
|
|
188415
|
-
|
|
188293
|
+
providerValueKolon(valueProp) {
|
|
188294
|
+
const v = valueProp.value;
|
|
188295
|
+
if (v.kind === "literal") {
|
|
188296
|
+
return typeof v.value === "string" ? `'${v.value.replace(/[\\']/g, (m) => `\\${m}`)}'` : String(v.value);
|
|
188297
|
+
}
|
|
188298
|
+
if (v.kind === "expression") {
|
|
188299
|
+
const hashref = this.providerObjectLiteralKolon(v.expr);
|
|
188300
|
+
if (hashref !== null)
|
|
188301
|
+
return hashref;
|
|
188302
|
+
return this.convertExpressionToKolon(v.expr);
|
|
188303
|
+
}
|
|
188304
|
+
if (v.kind === "template")
|
|
188305
|
+
return this.convertTemplateLiteralPartsToKolon(v.parts);
|
|
188306
|
+
return "nil";
|
|
188416
188307
|
}
|
|
188417
|
-
|
|
188418
|
-
|
|
188419
|
-
|
|
188420
|
-
const value = this.moduleStringConsts.get(name);
|
|
188421
|
-
if (value === undefined)
|
|
188308
|
+
providerObjectLiteralKolon(expr) {
|
|
188309
|
+
const members = parseProviderObjectLiteral(expr.trim());
|
|
188310
|
+
if (members === null)
|
|
188422
188311
|
return null;
|
|
188423
|
-
|
|
188424
|
-
|
|
188425
|
-
|
|
188426
|
-
|
|
188427
|
-
|
|
188428
|
-
|
|
188429
|
-
message,
|
|
188430
|
-
loc: { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188431
|
-
suggestion: {
|
|
188432
|
-
message: reason ? `${reason}
|
|
188433
|
-
|
|
188434
|
-
Options:
|
|
188435
|
-
1. Use /* @client */ for client-side evaluation
|
|
188436
|
-
2. Pre-compute the value in the backend` : `Options:
|
|
188437
|
-
1. Use /* @client */ for client-side evaluation
|
|
188438
|
-
2. Pre-compute the value in the backend`
|
|
188439
|
-
}
|
|
188312
|
+
const entries = members.map((m) => {
|
|
188313
|
+
const key = `'${m.name.replace(/[\\']/g, (c) => `\\${c}`)}'`;
|
|
188314
|
+
if (m.kind === "function" || /^on[A-Z]/.test(m.name))
|
|
188315
|
+
return `${key} => nil`;
|
|
188316
|
+
const src = m.kind === "getter" ? m.body : m.expr;
|
|
188317
|
+
return `${key} => ${this.convertExpressionToKolon(src)}`;
|
|
188440
188318
|
});
|
|
188319
|
+
return `{ ${entries.join(", ")} }`;
|
|
188441
188320
|
}
|
|
188442
|
-
|
|
188443
|
-
return this.
|
|
188321
|
+
emitAsync(node, _ctx, _emit) {
|
|
188322
|
+
return this.renderAsync(node);
|
|
188444
188323
|
}
|
|
188445
|
-
|
|
188446
|
-
|
|
188447
|
-
|
|
188448
|
-
|
|
188449
|
-
|
|
188450
|
-
|
|
188451
|
-
|
|
188324
|
+
renderElement(element) {
|
|
188325
|
+
const tag = element.tag;
|
|
188326
|
+
const attrs = this.renderAttributes(element);
|
|
188327
|
+
const children = this.renderChildren(element.children);
|
|
188328
|
+
let hydrationAttrs = "";
|
|
188329
|
+
if (element.needsScope) {
|
|
188330
|
+
hydrationAttrs += ` ${this.renderScopeMarker("")}`;
|
|
188452
188331
|
}
|
|
188453
|
-
|
|
188454
|
-
|
|
188455
|
-
const needle = emit(args[0]);
|
|
188456
|
-
return `$bf.includes(${obj}, ${needle})`;
|
|
188332
|
+
if (this.rootScopeNodes.has(element) && element.needsScope) {
|
|
188333
|
+
hydrationAttrs += ` <: $bf.data_key_attr() | mark_raw :>`;
|
|
188457
188334
|
}
|
|
188458
|
-
|
|
188459
|
-
|
|
188460
|
-
const fn = method === "indexOf" ? "index_of" : "last_index_of";
|
|
188461
|
-
const obj = emit(object);
|
|
188462
|
-
const needle = emit(args[0]);
|
|
188463
|
-
return `$bf.${fn}(${obj}, ${needle})`;
|
|
188335
|
+
if (element.slotId) {
|
|
188336
|
+
hydrationAttrs += ` ${this.renderSlotMarker(element.slotId)}`;
|
|
188464
188337
|
}
|
|
188465
|
-
|
|
188466
|
-
|
|
188467
|
-
const idx = args.length >= 1 ? emit(args[0]) : "0";
|
|
188468
|
-
return `$bf.at(${obj}, ${idx})`;
|
|
188338
|
+
if (element.regionId) {
|
|
188339
|
+
hydrationAttrs += ` ${BF_REGION}="${element.regionId}"`;
|
|
188469
188340
|
}
|
|
188470
|
-
|
|
188471
|
-
|
|
188472
|
-
|
|
188341
|
+
const voidElements = [
|
|
188342
|
+
"area",
|
|
188343
|
+
"base",
|
|
188344
|
+
"br",
|
|
188345
|
+
"col",
|
|
188346
|
+
"embed",
|
|
188347
|
+
"hr",
|
|
188348
|
+
"img",
|
|
188349
|
+
"input",
|
|
188350
|
+
"link",
|
|
188351
|
+
"meta",
|
|
188352
|
+
"param",
|
|
188353
|
+
"source",
|
|
188354
|
+
"track",
|
|
188355
|
+
"wbr"
|
|
188356
|
+
];
|
|
188357
|
+
if (voidElements.includes(tag.toLowerCase())) {
|
|
188358
|
+
return `<${tag}${attrs}${hydrationAttrs}>`;
|
|
188359
|
+
}
|
|
188360
|
+
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
|
|
188361
|
+
}
|
|
188362
|
+
renderExpression(expr) {
|
|
188363
|
+
if (expr.clientOnly) {
|
|
188364
|
+
if (expr.slotId) {
|
|
188365
|
+
return `<: $bf.comment("client:${expr.slotId}") | mark_raw :>`;
|
|
188473
188366
|
}
|
|
188474
|
-
|
|
188475
|
-
const b = emit(args[0]);
|
|
188476
|
-
return `$bf.concat(${a}, ${b})`;
|
|
188367
|
+
return "";
|
|
188477
188368
|
}
|
|
188478
|
-
|
|
188479
|
-
|
|
188480
|
-
|
|
188481
|
-
const end = args.length >= 2 ? emit(args[1]) : "nil";
|
|
188482
|
-
return `$bf.slice(${recv}, ${start}, ${end})`;
|
|
188369
|
+
const perlExpr = this.convertExpressionToKolon(expr.expr);
|
|
188370
|
+
if (expr.slotId) {
|
|
188371
|
+
return `<: $bf.text_start("${expr.slotId}") | mark_raw :><: ${perlExpr} :><: $bf.text_end() | mark_raw :>`;
|
|
188483
188372
|
}
|
|
188484
|
-
|
|
188485
|
-
|
|
188486
|
-
|
|
188487
|
-
|
|
188373
|
+
return `<: ${perlExpr} :>`;
|
|
188374
|
+
}
|
|
188375
|
+
renderConditional(cond) {
|
|
188376
|
+
if (cond.clientOnly && cond.slotId) {
|
|
188377
|
+
return `<: $bf.comment("cond-start:${cond.slotId}") | mark_raw :><: $bf.comment("cond-end:${cond.slotId}") | mark_raw :>`;
|
|
188488
188378
|
}
|
|
188489
|
-
|
|
188490
|
-
|
|
188491
|
-
|
|
188379
|
+
const condition = this.convertExpressionToKolon(cond.condition);
|
|
188380
|
+
const whenTrue = this.renderNode(cond.whenTrue);
|
|
188381
|
+
const whenFalse = this.renderNodeOrNull(cond.whenFalse);
|
|
188382
|
+
const isFragmentBranch = cond.whenTrue.type === "fragment" || cond.whenFalse.type === "fragment";
|
|
188383
|
+
const useCommentMarkers = cond.slotId && isFragmentBranch;
|
|
188384
|
+
let markedTrue = whenTrue;
|
|
188385
|
+
let markedFalse = whenFalse;
|
|
188386
|
+
if (cond.slotId && !useCommentMarkers) {
|
|
188387
|
+
markedTrue = this.addCondMarkerToFirstElement(whenTrue, cond.slotId);
|
|
188388
|
+
markedFalse = whenFalse ? this.addCondMarkerToFirstElement(whenFalse, cond.slotId) : whenFalse;
|
|
188492
188389
|
}
|
|
188493
|
-
|
|
188494
|
-
|
|
188495
|
-
|
|
188390
|
+
let result;
|
|
188391
|
+
if (useCommentMarkers) {
|
|
188392
|
+
const inner = whenFalse ? `
|
|
188393
|
+
: if (${condition}) {
|
|
188394
|
+
${whenTrue}
|
|
188395
|
+
: } else {
|
|
188396
|
+
${whenFalse}
|
|
188397
|
+
: }
|
|
188398
|
+
` : `
|
|
188399
|
+
: if (${condition}) {
|
|
188400
|
+
${whenTrue}
|
|
188401
|
+
: }
|
|
188402
|
+
`;
|
|
188403
|
+
result = `<: $bf.comment("cond-start:${cond.slotId}") | mark_raw :>${inner}<: $bf.comment("cond-end:${cond.slotId}") | mark_raw :>`;
|
|
188404
|
+
} else if (markedFalse) {
|
|
188405
|
+
result = `
|
|
188406
|
+
: if (${condition}) {
|
|
188407
|
+
${markedTrue}
|
|
188408
|
+
: } else {
|
|
188409
|
+
${markedFalse}
|
|
188410
|
+
: }
|
|
188411
|
+
`;
|
|
188412
|
+
} else if (cond.slotId) {
|
|
188413
|
+
result = `<: $bf.comment("cond-start:${cond.slotId}") | mark_raw :>
|
|
188414
|
+
: if (${condition}) {
|
|
188415
|
+
${whenTrue}
|
|
188416
|
+
: }
|
|
188417
|
+
<: $bf.comment("cond-end:${cond.slotId}") | mark_raw :>`;
|
|
188418
|
+
} else {
|
|
188419
|
+
result = `
|
|
188420
|
+
: if (${condition}) {
|
|
188421
|
+
${whenTrue}
|
|
188422
|
+
: }
|
|
188423
|
+
`;
|
|
188496
188424
|
}
|
|
188497
|
-
|
|
188498
|
-
|
|
188499
|
-
|
|
188425
|
+
return result;
|
|
188426
|
+
}
|
|
188427
|
+
renderNodeOrNull(node) {
|
|
188428
|
+
if (node.type === "expression" && (node.expr === "null" || node.expr === "undefined")) {
|
|
188429
|
+
return null;
|
|
188500
188430
|
}
|
|
188501
|
-
|
|
188502
|
-
|
|
188503
|
-
|
|
188504
|
-
|
|
188431
|
+
return this.renderNode(node);
|
|
188432
|
+
}
|
|
188433
|
+
addCondMarkerToFirstElement(content, condId) {
|
|
188434
|
+
const match = content.match(/^(<\w+)([\s>])/);
|
|
188435
|
+
if (match) {
|
|
188436
|
+
return content.replace(/^(<\w+)([\s>])/, `$1 ${BF_COND}="${condId}"$2`);
|
|
188505
188437
|
}
|
|
188506
|
-
|
|
188507
|
-
|
|
188508
|
-
|
|
188509
|
-
|
|
188510
|
-
}
|
|
188511
|
-
const sep2 = emit(args[0]);
|
|
188512
|
-
if (args.length === 1) {
|
|
188513
|
-
return `$bf.split(${recv}, ${sep2})`;
|
|
188514
|
-
}
|
|
188515
|
-
const limit = emit(args[1]);
|
|
188516
|
-
return `$bf.split(${recv}, ${sep2}, ${limit})`;
|
|
188438
|
+
return `<: $bf.comment("cond-start:${condId}") | mark_raw :>${content}<: $bf.comment("cond-end:${condId}") | mark_raw :>`;
|
|
188439
|
+
}
|
|
188440
|
+
renderLoop(loop) {
|
|
188441
|
+
if (loop.clientOnly) {
|
|
188442
|
+
return `<: $bf.comment("loop:${loop.markerId}") | mark_raw :><: $bf.comment("/loop:${loop.markerId}") | mark_raw :>`;
|
|
188517
188443
|
}
|
|
188518
|
-
|
|
188519
|
-
|
|
188520
|
-
|
|
188521
|
-
|
|
188522
|
-
|
|
188523
|
-
|
|
188524
|
-
|
|
188525
|
-
|
|
188526
|
-
|
|
188444
|
+
const destructure = !!(loop.paramBindings && loop.paramBindings.length > 0);
|
|
188445
|
+
const supportableDestructure = destructure && isLowerableObjectRestDestructure(loop);
|
|
188446
|
+
if (destructure && !supportableDestructure) {
|
|
188447
|
+
this.errors.push({
|
|
188448
|
+
code: "BF104",
|
|
188449
|
+
severity: "error",
|
|
188450
|
+
message: `Loop callback uses an array/object destructure pattern (\`${loop.param}\`) that the Xslate adapter cannot lower — Kolon \`for LIST -> $item\` binds a single scalar and can't unpack a tuple.`,
|
|
188451
|
+
loc: loop.loc ?? { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188452
|
+
suggestion: {
|
|
188453
|
+
message: `Options:
|
|
188454
|
+
` + ` 1. Rename the parameter to a single name and access tuple elements with index syntax in the body (e.g. \`entry => entry[0]\` instead of \`([k, v]) => ...\`).
|
|
188455
|
+
` + ` 2. Mark the loop position as @client-only so the destructure runs in JS on the client.
|
|
188456
|
+
` + ` 3. Move the loop into a primitive that the adapter registers explicitly.`
|
|
188457
|
+
}
|
|
188458
|
+
});
|
|
188527
188459
|
}
|
|
188528
|
-
|
|
188529
|
-
|
|
188530
|
-
|
|
188531
|
-
const
|
|
188532
|
-
|
|
188460
|
+
const rawArray = this.convertExpressionToKolon(loop.array);
|
|
188461
|
+
let array = rawArray;
|
|
188462
|
+
if (loop.sortComparator) {
|
|
188463
|
+
const sort = loop.sortComparator;
|
|
188464
|
+
const sortEmit = (e) => this.convertExpressionToKolon("", e);
|
|
188465
|
+
const arrow = sort.arrow;
|
|
188466
|
+
const params = arrow.kind === "arrow" ? arrow.params : [sort.paramA, sort.paramB];
|
|
188467
|
+
const structured = sortComparatorFromArrow2(arrow);
|
|
188468
|
+
array = renderSortEval(rawArray, arrow.kind === "arrow" ? arrow.body : arrow, params, sortEmit) ?? (structured !== null ? renderSortMethod(rawArray, structured) : rawArray);
|
|
188533
188469
|
}
|
|
188534
|
-
|
|
188535
|
-
|
|
188536
|
-
|
|
188537
|
-
|
|
188470
|
+
const param = loop.param;
|
|
188471
|
+
const renderedChildren = this.renderChildren(loop.children);
|
|
188472
|
+
const loopVar = loop.iterationShape === "keys" ? "__bf_item" : supportableDestructure ? "__bf_item" : param;
|
|
188473
|
+
const indexLocalLines = [];
|
|
188474
|
+
if (loop.iterationShape === "keys") {
|
|
188475
|
+
indexLocalLines.push(`: my $${param} = $~${loopVar}.index;`);
|
|
188476
|
+
} else if (loop.index) {
|
|
188477
|
+
indexLocalLines.push(`: my $${loop.index} = $~${loopVar}.index;`);
|
|
188538
188478
|
}
|
|
188539
|
-
|
|
188540
|
-
|
|
188541
|
-
|
|
188542
|
-
const recv = emit(object);
|
|
188543
|
-
if (args.length === 0) {
|
|
188544
|
-
return `$bf.${fn}(${recv}, 0)`;
|
|
188545
|
-
}
|
|
188546
|
-
const target = emit(args[0]);
|
|
188547
|
-
if (args.length === 1) {
|
|
188548
|
-
return `$bf.${fn}(${recv}, ${target})`;
|
|
188479
|
+
if (supportableDestructure) {
|
|
188480
|
+
for (const b of loop.paramBindings ?? []) {
|
|
188481
|
+
indexLocalLines.push(b.rest ? `: my $${b.name} = $${loopVar};` : `: my $${b.name} = $${loopVar}${b.path};`);
|
|
188549
188482
|
}
|
|
188550
|
-
const pad2 = emit(args[1]);
|
|
188551
|
-
return `$bf.${fn}(${recv}, ${target}, ${pad2})`;
|
|
188552
188483
|
}
|
|
188553
|
-
|
|
188554
|
-
|
|
188555
|
-
|
|
188484
|
+
const prevInLoop = this.inLoop;
|
|
188485
|
+
this.inLoop = true;
|
|
188486
|
+
const childrenUnderLoop = this.renderChildren(loop.children);
|
|
188487
|
+
this.inLoop = prevInLoop;
|
|
188488
|
+
const bodyChildren = loop.bodyIsItemConditional && loop.key ? `<: $bf.comment("loop-i:" ~ ${this.convertExpressionToKolon(loop.key)}) | mark_raw :>
|
|
188489
|
+
${childrenUnderLoop}` : childrenUnderLoop;
|
|
188490
|
+
const lines = [];
|
|
188491
|
+
lines.push(`<: $bf.comment("loop:${loop.markerId}") | mark_raw :>`);
|
|
188492
|
+
lines.push(`: for ${array} -> $${loopVar} {`);
|
|
188493
|
+
for (const il of indexLocalLines)
|
|
188494
|
+
lines.push(il);
|
|
188495
|
+
if (loop.filterPredicate) {
|
|
188496
|
+
let filterCond;
|
|
188497
|
+
if (loop.filterPredicate.predicate) {
|
|
188498
|
+
filterCond = this.renderKolonFilterExpr(loop.filterPredicate.predicate, loop.filterPredicate.param);
|
|
188499
|
+
} else {
|
|
188500
|
+
filterCond = "1";
|
|
188501
|
+
}
|
|
188502
|
+
if (loop.filterPredicate.param !== param) {
|
|
188503
|
+
filterCond = filterCond.replace(new RegExp(`\\$${loop.filterPredicate.param}\\b`, "g"), `$${param}`);
|
|
188504
|
+
}
|
|
188505
|
+
lines.push(`: if (${filterCond}) {`);
|
|
188506
|
+
lines.push(bodyChildren);
|
|
188507
|
+
lines.push(`: }`);
|
|
188508
|
+
} else {
|
|
188509
|
+
lines.push(bodyChildren);
|
|
188556
188510
|
}
|
|
188511
|
+
lines.push(`: }`);
|
|
188512
|
+
lines.push(`<: $bf.comment("/loop:${loop.markerId}") | mark_raw :>`);
|
|
188513
|
+
return lines.join(`
|
|
188514
|
+
`);
|
|
188557
188515
|
}
|
|
188558
|
-
|
|
188559
|
-
|
|
188560
|
-
|
|
188561
|
-
|
|
188562
|
-
|
|
188563
|
-
|
|
188564
|
-
|
|
188565
|
-
|
|
188566
|
-
|
|
188567
|
-
|
|
188568
|
-
|
|
188569
|
-
|
|
188570
|
-
}
|
|
188571
|
-
|
|
188572
|
-
|
|
188573
|
-
|
|
188574
|
-
}
|
|
188575
|
-
|
|
188576
|
-
|
|
188577
|
-
|
|
188578
|
-
|
|
188579
|
-
|
|
188580
|
-
|
|
188581
|
-
|
|
188582
|
-
|
|
188583
|
-
|
|
188584
|
-
|
|
188585
|
-
|
|
188586
|
-
|
|
188587
|
-
|
|
188588
|
-
|
|
188589
|
-
|
|
188590
|
-
|
|
188591
|
-
|
|
188592
|
-
|
|
188593
|
-
|
|
188594
|
-
|
|
188595
|
-
|
|
188596
|
-
|
|
188597
|
-
|
|
188598
|
-
|
|
188599
|
-
|
|
188600
|
-
|
|
188601
|
-
|
|
188602
|
-
|
|
188603
|
-
|
|
188604
|
-
|
|
188605
|
-
|
|
188606
|
-
const signal = this.localVarMap.get(name);
|
|
188607
|
-
if (signal)
|
|
188608
|
-
return `$${signal}`;
|
|
188609
|
-
return `$${name}`;
|
|
188610
|
-
}
|
|
188611
|
-
literal(value, literalType) {
|
|
188612
|
-
if (literalType === "string")
|
|
188613
|
-
return `'${value}'`;
|
|
188614
|
-
if (literalType === "boolean")
|
|
188615
|
-
return value ? "1" : "0";
|
|
188616
|
-
if (literalType === "null")
|
|
188617
|
-
return "nil";
|
|
188618
|
-
return String(value);
|
|
188619
|
-
}
|
|
188620
|
-
member(object, property, _computed, emit) {
|
|
188621
|
-
if (property === "length") {
|
|
188622
|
-
return `$bf.length(${emit(object)})`;
|
|
188516
|
+
componentPropEmitter = {
|
|
188517
|
+
emitLiteral: (value, name) => `${kolonHashKey(name)} => '${value.value}'`,
|
|
188518
|
+
emitExpression: (value, name) => {
|
|
188519
|
+
if (value.parts) {
|
|
188520
|
+
return `${kolonHashKey(name)} => ${this.convertTemplateLiteralPartsToKolon(value.parts)}`;
|
|
188521
|
+
}
|
|
188522
|
+
if (value.parsed) {
|
|
188523
|
+
const hashref = objectLiteralExprToKolonHashref(this.spreadCtx, value.parsed);
|
|
188524
|
+
if (hashref !== null)
|
|
188525
|
+
return `${kolonHashKey(name)} => ${hashref}`;
|
|
188526
|
+
}
|
|
188527
|
+
return `${kolonHashKey(name)} => ${this.convertExpressionToKolon(value.expr)}`;
|
|
188528
|
+
},
|
|
188529
|
+
emitSpread: (value) => {
|
|
188530
|
+
return this.convertExpressionToKolon(value.expr);
|
|
188531
|
+
},
|
|
188532
|
+
emitTemplate: (value, name) => `${kolonHashKey(name)} => ${this.convertTemplateLiteralPartsToKolon(value.parts)}`,
|
|
188533
|
+
emitBooleanAttr: (_value, name) => `${kolonHashKey(name)} => 1`,
|
|
188534
|
+
emitBooleanShorthand: (_value, name) => `${kolonHashKey(name)} => 1`,
|
|
188535
|
+
emitJsxChildren: () => ""
|
|
188536
|
+
};
|
|
188537
|
+
renderComponent(comp) {
|
|
188538
|
+
const propParts = [];
|
|
188539
|
+
for (const p of comp.props) {
|
|
188540
|
+
if ((p.name.match(/^on[A-Z]/) || p.name === "ref") && p.value.kind === "expression")
|
|
188541
|
+
continue;
|
|
188542
|
+
if (p.value.kind === "spread") {
|
|
188543
|
+
const trimmed = p.value.expr.trim();
|
|
188544
|
+
if (this.propsObjectName && this.propsObjectName === trimmed) {
|
|
188545
|
+
for (const pp of this.propsParams) {
|
|
188546
|
+
propParts.push(`${pp.name} => $${pp.name}`);
|
|
188547
|
+
}
|
|
188548
|
+
continue;
|
|
188549
|
+
}
|
|
188550
|
+
this.errors.push({
|
|
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
|
+
});
|
|
188559
|
+
continue;
|
|
188560
|
+
}
|
|
188561
|
+
const lowered = emitAttrValue(p.value, this.componentPropEmitter, p.name);
|
|
188562
|
+
if (lowered)
|
|
188563
|
+
propParts.push(lowered);
|
|
188623
188564
|
}
|
|
188624
|
-
|
|
188625
|
-
|
|
188626
|
-
indexAccess(object, index, emit) {
|
|
188627
|
-
return `${emit(object)}[${emit(index)}]`;
|
|
188628
|
-
}
|
|
188629
|
-
call(callee, args, emit) {
|
|
188630
|
-
if (callee.kind === "identifier" && args.length === 0) {
|
|
188631
|
-
return `$${callee.name}`;
|
|
188565
|
+
if (comp.slotId && !this.inLoop) {
|
|
188566
|
+
propParts.push(`_bf_slot => '${comp.slotId}'`);
|
|
188632
188567
|
}
|
|
188633
|
-
|
|
188634
|
-
|
|
188635
|
-
|
|
188636
|
-
|
|
188637
|
-
|
|
188638
|
-
const
|
|
188639
|
-
|
|
188568
|
+
const tplName = this.toTemplateName(comp.name);
|
|
188569
|
+
const effectiveChildren = comp.children.length > 0 ? comp.children : resolveJsxChildrenProp(comp.props);
|
|
188570
|
+
if (effectiveChildren.length > 0) {
|
|
188571
|
+
const prevInLoop = this.inLoop;
|
|
188572
|
+
this.inLoop = false;
|
|
188573
|
+
const childrenBody = this.renderChildren(effectiveChildren);
|
|
188574
|
+
this.inLoop = prevInLoop;
|
|
188575
|
+
const macroName = `bf_children_${comp.slotId ?? "c" + this.childrenCaptureCounter++}`;
|
|
188576
|
+
const childrenEntry = `children => ${macroName}()`;
|
|
188577
|
+
const allParts = [...propParts, childrenEntry];
|
|
188578
|
+
return `<: macro ${macroName} -> () { :>${childrenBody}<: } :><: $bf.render_child('${tplName}', { ${allParts.join(", ")} }) | mark_raw :>`;
|
|
188640
188579
|
}
|
|
188641
|
-
|
|
188642
|
-
|
|
188643
|
-
return arg;
|
|
188644
|
-
}
|
|
188645
|
-
binary(op, left, right, emit) {
|
|
188646
|
-
const l = emit(left);
|
|
188647
|
-
const r = emit(right);
|
|
188648
|
-
const opMap = {
|
|
188649
|
-
"===": "==",
|
|
188650
|
-
"!==": "!=",
|
|
188651
|
-
">": ">",
|
|
188652
|
-
"<": "<",
|
|
188653
|
-
">=": ">=",
|
|
188654
|
-
"<=": "<=",
|
|
188655
|
-
"+": "+",
|
|
188656
|
-
"-": "-",
|
|
188657
|
-
"*": "*",
|
|
188658
|
-
"/": "/"
|
|
188659
|
-
};
|
|
188660
|
-
return `${l} ${opMap[op] ?? op} ${r}`;
|
|
188661
|
-
}
|
|
188662
|
-
logical(op, left, right, emit) {
|
|
188663
|
-
const l = emit(left);
|
|
188664
|
-
const r = emit(right);
|
|
188665
|
-
if (op === "&&")
|
|
188666
|
-
return `(${l} && ${r})`;
|
|
188667
|
-
if (op === "||")
|
|
188668
|
-
return `(${l} || ${r})`;
|
|
188669
|
-
return `(${l} // ${r})`;
|
|
188670
|
-
}
|
|
188671
|
-
higherOrder(method, object, param, predicate, emit) {
|
|
188672
|
-
return emit(object);
|
|
188673
|
-
}
|
|
188674
|
-
arrayLiteral(elements, emit) {
|
|
188675
|
-
return `[${elements.map(emit).join(", ")}]`;
|
|
188676
|
-
}
|
|
188677
|
-
arrayMethod(method, object, args, emit) {
|
|
188678
|
-
return renderArrayMethod(method, object, args, emit);
|
|
188679
|
-
}
|
|
188680
|
-
sortMethod(_method, object, comparator, emit) {
|
|
188681
|
-
return renderSortMethod(emit(object), comparator);
|
|
188580
|
+
const hashEntries = propParts.length > 0 ? `, { ${propParts.join(", ")} }` : "";
|
|
188581
|
+
return `<: $bf.render_child('${tplName}'${hashEntries}) | mark_raw :>`;
|
|
188682
188582
|
}
|
|
188683
|
-
|
|
188684
|
-
|
|
188583
|
+
childrenCaptureCounter = 0;
|
|
188584
|
+
presenceVarCounter = 0;
|
|
188585
|
+
toTemplateName(componentName) {
|
|
188586
|
+
return componentName.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
188685
188587
|
}
|
|
188686
|
-
|
|
188687
|
-
|
|
188588
|
+
renderIfStatement(ifStmt) {
|
|
188589
|
+
const condition = this.convertExpressionToKolon(ifStmt.condition);
|
|
188590
|
+
const consequent = ifStmt.consequent.type === "if-statement" ? this.renderIfStatement(ifStmt.consequent) : this.renderNode(ifStmt.consequent);
|
|
188591
|
+
let result = `: if (${condition}) {
|
|
188592
|
+
${consequent}
|
|
188593
|
+
`;
|
|
188594
|
+
if (ifStmt.alternate) {
|
|
188595
|
+
if (ifStmt.alternate.type === "if-statement") {
|
|
188596
|
+
const altResult = this.renderIfStatement(ifStmt.alternate);
|
|
188597
|
+
result += altResult.replace(/^: if/, ": } elsif");
|
|
188598
|
+
} else {
|
|
188599
|
+
const alternate = this.renderNode(ifStmt.alternate);
|
|
188600
|
+
result += `: } else {
|
|
188601
|
+
${alternate}
|
|
188602
|
+
`;
|
|
188603
|
+
}
|
|
188604
|
+
}
|
|
188605
|
+
result += `: }`;
|
|
188606
|
+
return result;
|
|
188688
188607
|
}
|
|
188689
|
-
|
|
188690
|
-
|
|
188608
|
+
renderFragment(fragment) {
|
|
188609
|
+
const children = this.renderChildren(fragment.children);
|
|
188610
|
+
if (fragment.needsScopeComment) {
|
|
188611
|
+
return `<: $bf.scope_comment() | mark_raw :>${children}`;
|
|
188612
|
+
}
|
|
188613
|
+
return children;
|
|
188691
188614
|
}
|
|
188692
|
-
|
|
188693
|
-
return
|
|
188615
|
+
renderSlot(_slot) {
|
|
188616
|
+
return `<: $children | mark_raw :>`;
|
|
188694
188617
|
}
|
|
188695
|
-
|
|
188696
|
-
|
|
188618
|
+
renderAsync(node) {
|
|
188619
|
+
const fallback = this.renderNode(node.fallback);
|
|
188620
|
+
const children = this.renderChildren(node.children);
|
|
188621
|
+
const macroName = `bf_async_fallback_${node.id}`;
|
|
188622
|
+
return `<: macro ${macroName} -> () { :>${fallback}<: } :><: $bf.async_boundary('${node.id}', ${macroName}()) | mark_raw :>
|
|
188623
|
+
${children}`;
|
|
188697
188624
|
}
|
|
188698
|
-
|
|
188699
|
-
|
|
188625
|
+
elementAttrEmitter = {
|
|
188626
|
+
emitLiteral: (value, name) => `${name}="${value.value}"`,
|
|
188627
|
+
emitExpression: (value, name) => {
|
|
188628
|
+
if (name === "style") {
|
|
188629
|
+
const css = this.tryLowerStyleObject(value.expr);
|
|
188630
|
+
if (css !== null)
|
|
188631
|
+
return `style="${css}"`;
|
|
188632
|
+
}
|
|
188633
|
+
if (this.refuseUnsupportedAttrExpression(value.expr, name)) {
|
|
188634
|
+
return "";
|
|
188635
|
+
}
|
|
188636
|
+
const bareId = value.expr.trim();
|
|
188637
|
+
const normalizedBareId = this.propsObjectName && bareId.startsWith(`${this.propsObjectName}.`) ? bareId.slice(this.propsObjectName.length + 1) : bareId;
|
|
188638
|
+
if (!isBooleanAttr(name) && !value.presenceOrUndefined && /^[A-Za-z_$][\w$]*$/.test(normalizedBareId) && this.nullableOptionalProps.has(normalizedBareId)) {
|
|
188639
|
+
const perl2 = this.convertExpressionToKolon(value.expr);
|
|
188640
|
+
const body = isBooleanResultExpr(value.expr) || isAriaBooleanAttr(name) || this.isBooleanTypedPropRef(value.expr) ? `${name}="<: $bf.bool_str(${perl2}) :>"` : `${name}="<: ${perl2} :>"`;
|
|
188641
|
+
return `
|
|
188642
|
+
: if (defined ${perl2}) {
|
|
188643
|
+
${body}
|
|
188644
|
+
: }
|
|
188645
|
+
`;
|
|
188646
|
+
}
|
|
188647
|
+
if (isBooleanAttr(name)) {
|
|
188648
|
+
return `<: ${this.convertExpressionToKolon(value.expr)} ? '${name}' : '' :>`;
|
|
188649
|
+
}
|
|
188650
|
+
if (value.presenceOrUndefined) {
|
|
188651
|
+
const perl2 = this.convertExpressionToKolon(value.expr);
|
|
188652
|
+
const tmp = `$bf_pu${this.presenceVarCounter++}`;
|
|
188653
|
+
const body = isBooleanResultExpr(value.expr) || isAriaBooleanAttr(name) || this.isBooleanTypedPropRef(value.expr) ? `${name}="<: $bf.bool_str(${tmp}) :>"` : `${name}="<: ${tmp} :>"`;
|
|
188654
|
+
return `
|
|
188655
|
+
: my ${tmp} = ${perl2};
|
|
188656
|
+
: if (${tmp}) {
|
|
188657
|
+
${body}
|
|
188658
|
+
: }
|
|
188659
|
+
`;
|
|
188660
|
+
}
|
|
188661
|
+
{
|
|
188662
|
+
const m = this.parseUndefinedAlternateTernary(value.expr);
|
|
188663
|
+
if (m) {
|
|
188664
|
+
const cond = this.convertExpressionToKolon(m.condition);
|
|
188665
|
+
const val = this.convertExpressionToKolon(m.consequent);
|
|
188666
|
+
return `
|
|
188667
|
+
: if (${cond}) {
|
|
188668
|
+
${name}="<: ${val} :>"
|
|
188669
|
+
: }
|
|
188670
|
+
`;
|
|
188671
|
+
}
|
|
188672
|
+
}
|
|
188673
|
+
const perl = this.convertExpressionToKolon(value.expr);
|
|
188674
|
+
if (isBooleanResultExpr(value.expr) || isAriaBooleanAttr(name) || this.isBooleanTypedPropRef(value.expr)) {
|
|
188675
|
+
return `${name}="<: $bf.bool_str(${perl}) :>"`;
|
|
188676
|
+
}
|
|
188677
|
+
return `${name}="<: ${perl} :>"`;
|
|
188678
|
+
},
|
|
188679
|
+
emitBooleanAttr: (_value, name) => name,
|
|
188680
|
+
emitTemplate: (value, name) => `${name}="<: ${this.convertTemplateLiteralPartsToKolon(value.parts)} :>"`,
|
|
188681
|
+
emitSpread: (value) => {
|
|
188682
|
+
if (this.refuseUnsupportedAttrExpression(value.expr, "...")) {
|
|
188683
|
+
return "";
|
|
188684
|
+
}
|
|
188685
|
+
const trimmed = value.expr.trim();
|
|
188686
|
+
if (this.propsObjectName && this.propsObjectName === trimmed) {
|
|
188687
|
+
const entries = this.propsParams.map((p) => `${JSON.stringify(p.name)} => $${p.name}`);
|
|
188688
|
+
return `<: $bf.spread_attrs({${entries.join(", ")}}) | mark_raw :>`;
|
|
188689
|
+
}
|
|
188690
|
+
const ternaryHashref = conditionalSpreadToKolon(this.spreadCtx, value.parsed);
|
|
188691
|
+
if (ternaryHashref !== null) {
|
|
188692
|
+
return `<: $bf.spread_attrs(${ternaryHashref}) | mark_raw :>`;
|
|
188693
|
+
}
|
|
188694
|
+
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
188695
|
+
const localConst = (this.localConstants ?? []).find((c) => c.name === trimmed && !c.isModule);
|
|
188696
|
+
if (localConst?.value !== undefined) {
|
|
188697
|
+
const initTrimmed = localConst.value.trim();
|
|
188698
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(initTrimmed)) {
|
|
188699
|
+
const resolved = conditionalSpreadToKolon(this.spreadCtx, parseExpression2(initTrimmed));
|
|
188700
|
+
if (resolved !== null) {
|
|
188701
|
+
return `<: $bf.spread_attrs(${resolved}) | mark_raw :>`;
|
|
188702
|
+
}
|
|
188703
|
+
}
|
|
188704
|
+
}
|
|
188705
|
+
}
|
|
188706
|
+
const perlExpr = this.convertExpressionToKolon(value.expr);
|
|
188707
|
+
return `<: $bf.spread_attrs(${perlExpr}) | mark_raw :>`;
|
|
188708
|
+
},
|
|
188709
|
+
emitBooleanShorthand: () => "",
|
|
188710
|
+
emitJsxChildren: () => ""
|
|
188711
|
+
};
|
|
188712
|
+
tryLowerStyleObject(expr) {
|
|
188713
|
+
const entries = parseStyleObjectEntries(expr);
|
|
188714
|
+
if (!entries)
|
|
188715
|
+
return null;
|
|
188716
|
+
for (const e of entries) {
|
|
188717
|
+
if (e.kind === "expr" && !isSupported(parseExpression2(e.expr)).supported)
|
|
188718
|
+
return null;
|
|
188719
|
+
}
|
|
188720
|
+
return entries.map((e) => e.kind === "literal" ? `${this.escapeAttrText(e.cssKey)}:${this.escapeAttrText(e.value)}` : `${this.escapeAttrText(e.cssKey)}:<: ${this.convertExpressionToKolon(e.expr)} :>`).join(";");
|
|
188700
188721
|
}
|
|
188701
|
-
|
|
188702
|
-
return "
|
|
188722
|
+
escapeAttrText(s) {
|
|
188723
|
+
return s.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
188703
188724
|
}
|
|
188704
|
-
|
|
188705
|
-
|
|
188706
|
-
|
|
188707
|
-
|
|
188708
|
-
|
|
188709
|
-
|
|
188725
|
+
renderAttributes(element) {
|
|
188726
|
+
const parts = [];
|
|
188727
|
+
for (const attr of element.attrs) {
|
|
188728
|
+
if (attr.clientOnly)
|
|
188729
|
+
continue;
|
|
188730
|
+
let attrName;
|
|
188731
|
+
if (attr.name === "className")
|
|
188732
|
+
attrName = "class";
|
|
188733
|
+
else if (attr.name === "key")
|
|
188734
|
+
attrName = "data-key";
|
|
188735
|
+
else
|
|
188736
|
+
attrName = attr.name;
|
|
188737
|
+
const lowered = emitAttrValue(attr.value, this.elementAttrEmitter, attrName);
|
|
188738
|
+
if (lowered)
|
|
188739
|
+
parts.push(lowered);
|
|
188740
|
+
}
|
|
188741
|
+
return parts.length > 0 ? " " + parts.join(" ") : "";
|
|
188710
188742
|
}
|
|
188711
|
-
|
|
188712
|
-
|
|
188713
|
-
return "nil";
|
|
188714
|
-
const inlined = this.adapter._resolveModuleStringConst(name);
|
|
188715
|
-
if (inlined !== null)
|
|
188716
|
-
return inlined;
|
|
188717
|
-
const literalConst = this.adapter._resolveLiteralConst(name);
|
|
188718
|
-
if (literalConst !== null)
|
|
188719
|
-
return literalConst;
|
|
188720
|
-
return `$${name}`;
|
|
188743
|
+
renderScopeMarker(_instanceIdExpr) {
|
|
188744
|
+
return `bf-s="<: $bf.scope_attr() :>" <: $bf.hydration_attrs() | mark_raw :> <: $bf.props_attr() | mark_raw :>`;
|
|
188721
188745
|
}
|
|
188722
|
-
|
|
188723
|
-
|
|
188724
|
-
return `'${value}'`;
|
|
188725
|
-
if (literalType === "boolean")
|
|
188726
|
-
return value ? "1" : "0";
|
|
188727
|
-
if (literalType === "null")
|
|
188728
|
-
return "nil";
|
|
188729
|
-
return String(value);
|
|
188746
|
+
renderSlotMarker(slotId) {
|
|
188747
|
+
return `${BF_SLOT}="${slotId}"`;
|
|
188730
188748
|
}
|
|
188731
|
-
|
|
188732
|
-
|
|
188733
|
-
return `$${property}`;
|
|
188734
|
-
}
|
|
188735
|
-
if (object.kind === "identifier") {
|
|
188736
|
-
const staticValue = this.adapter._resolveStaticRecordLiteral(object.name, property);
|
|
188737
|
-
if (staticValue !== null)
|
|
188738
|
-
return staticValue;
|
|
188739
|
-
}
|
|
188740
|
-
const obj = emit(object);
|
|
188741
|
-
if (property === "length")
|
|
188742
|
-
return `$bf.length(${obj})`;
|
|
188743
|
-
return `${obj}.${property}`;
|
|
188749
|
+
renderCondMarker(condId) {
|
|
188750
|
+
return `${BF_COND}="${condId}"`;
|
|
188744
188751
|
}
|
|
188745
|
-
|
|
188746
|
-
return
|
|
188752
|
+
renderKolonFilterExpr(expr, param, localVarMap = new Map) {
|
|
188753
|
+
return emitParsedExpr(expr, new XslateFilterEmitter(param, localVarMap, (n) => this._isStringValueName(n), (message, reason) => this._recordExprBF101(message, reason)));
|
|
188747
188754
|
}
|
|
188748
|
-
|
|
188749
|
-
|
|
188750
|
-
|
|
188751
|
-
|
|
188752
|
-
|
|
188753
|
-
|
|
188754
|
-
|
|
188755
|
-
|
|
188755
|
+
convertTemplateLiteralPartsToKolon(literalParts) {
|
|
188756
|
+
const parts = [];
|
|
188757
|
+
for (const part of literalParts) {
|
|
188758
|
+
if (part.type === "string") {
|
|
188759
|
+
parts.push(this.substituteJsInterpolationsToKolon(part.value));
|
|
188760
|
+
} else if (part.type === "ternary") {
|
|
188761
|
+
const cond = this.convertExpressionToKolon(part.condition);
|
|
188762
|
+
parts.push(`(${cond} ? '${part.whenTrue}' : '${part.whenFalse}')`);
|
|
188763
|
+
} else if (part.type === "lookup") {
|
|
188764
|
+
const keyExpr = this.convertExpressionToKolon(part.key);
|
|
188765
|
+
const entries = Object.entries(part.cases).map(([k, v]) => `'${k}' => '${v}'`).join(", ");
|
|
188766
|
+
parts.push(`({ ${entries} }[${keyExpr}] // '')`);
|
|
188756
188767
|
}
|
|
188757
188768
|
}
|
|
188758
|
-
|
|
188759
|
-
|
|
188760
|
-
|
|
188761
|
-
|
|
188762
|
-
|
|
188769
|
+
return parts.length === 1 ? parts[0] : parts.join(" ~ ");
|
|
188770
|
+
}
|
|
188771
|
+
substituteJsInterpolationsToKolon(s) {
|
|
188772
|
+
const segments = [];
|
|
188773
|
+
const re = /\$\{([^}]+)\}/g;
|
|
188774
|
+
let lastIndex = 0;
|
|
188775
|
+
let m;
|
|
188776
|
+
while ((m = re.exec(s)) !== null) {
|
|
188777
|
+
if (m.index > lastIndex) {
|
|
188778
|
+
segments.push(`'${s.slice(lastIndex, m.index)}'`);
|
|
188763
188779
|
}
|
|
188764
|
-
|
|
188765
|
-
|
|
188780
|
+
segments.push(this.convertExpressionToKolon(m[1].trim()));
|
|
188781
|
+
lastIndex = re.lastIndex;
|
|
188766
188782
|
}
|
|
188767
|
-
|
|
188783
|
+
if (lastIndex < s.length) {
|
|
188784
|
+
segments.push(`'${s.slice(lastIndex)}'`);
|
|
188785
|
+
}
|
|
188786
|
+
if (segments.length === 0)
|
|
188787
|
+
return `''`;
|
|
188788
|
+
return segments.length === 1 ? segments[0] : `(${segments.join(" ~ ")})`;
|
|
188768
188789
|
}
|
|
188769
|
-
|
|
188770
|
-
|
|
188771
|
-
|
|
188772
|
-
|
|
188773
|
-
|
|
188774
|
-
|
|
188775
|
-
|
|
188790
|
+
refuseUnsupportedAttrExpression(expr, attrName) {
|
|
188791
|
+
let probe = expr.trim();
|
|
188792
|
+
while (probe.startsWith("("))
|
|
188793
|
+
probe = probe.slice(1).trimStart();
|
|
188794
|
+
const startsAsObjectLiteral = probe.startsWith("{");
|
|
188795
|
+
const hasTaggedTemplate = /[A-Za-z_$][\w$]*\s*`/.test(probe);
|
|
188796
|
+
if (!startsAsObjectLiteral && !hasTaggedTemplate)
|
|
188797
|
+
return false;
|
|
188798
|
+
const parsed = parseExpression2(expr.trim());
|
|
188799
|
+
const support = isSupported(parsed);
|
|
188800
|
+
if (parsed.kind !== "unsupported" && support.supported)
|
|
188801
|
+
return false;
|
|
188802
|
+
const reason = support.reason ?? (parsed.kind === "unsupported" ? parsed.reason : undefined);
|
|
188803
|
+
const reasonLine = reason ? `
|
|
188804
|
+
${reason}` : "";
|
|
188805
|
+
this.errors.push({
|
|
188806
|
+
code: "BF101",
|
|
188807
|
+
severity: "error",
|
|
188808
|
+
message: `Expression not supported on attribute '${attrName}': ${expr.trim()}${reasonLine}`,
|
|
188809
|
+
loc: { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188810
|
+
suggestion: {
|
|
188811
|
+
message: "The Xslate adapter cannot lower JS object literals or tagged-template-literal expressions into Kolon. Move the expression into a `'use client'` component (so hydration computes it), or expand it into discrete attributes whose values are values the adapter can lower."
|
|
188812
|
+
}
|
|
188813
|
+
});
|
|
188814
|
+
return true;
|
|
188776
188815
|
}
|
|
188777
|
-
|
|
188778
|
-
|
|
188779
|
-
|
|
188780
|
-
|
|
188781
|
-
|
|
188782
|
-
|
|
188783
|
-
|
|
188784
|
-
|
|
188785
|
-
">=": ">=",
|
|
188786
|
-
"<=": "<=",
|
|
188787
|
-
"+": "+",
|
|
188788
|
-
"-": "-",
|
|
188789
|
-
"*": "*"
|
|
188816
|
+
get emitCtx() {
|
|
188817
|
+
return {
|
|
188818
|
+
_searchParamsLocals: this._searchParamsLocals,
|
|
188819
|
+
_resolveModuleStringConst: (name) => this._resolveModuleStringConst(name),
|
|
188820
|
+
_resolveLiteralConst: (name) => this._resolveLiteralConst(name),
|
|
188821
|
+
_resolveStaticRecordLiteral: (o, k) => this._resolveStaticRecordLiteral(o, k),
|
|
188822
|
+
_recordExprBF101: (message, reason) => this._recordExprBF101(message, reason),
|
|
188823
|
+
_renderKolonFilterExprPublic: (e, p) => this._renderKolonFilterExprPublic(e, p)
|
|
188790
188824
|
};
|
|
188791
|
-
return `${l} ${opMap[op] ?? op} ${r}`;
|
|
188792
|
-
}
|
|
188793
|
-
logical(op, left, right, emit) {
|
|
188794
|
-
const l = emit(left);
|
|
188795
|
-
const r = emit(right);
|
|
188796
|
-
if (op === "&&")
|
|
188797
|
-
return `(${l} && ${r})`;
|
|
188798
|
-
if (op === "||")
|
|
188799
|
-
return `(${l} || ${r})`;
|
|
188800
|
-
return `(${l} // ${r})`;
|
|
188801
188825
|
}
|
|
188802
|
-
|
|
188803
|
-
|
|
188804
|
-
|
|
188805
|
-
|
|
188806
|
-
|
|
188807
|
-
|
|
188808
|
-
|
|
188809
|
-
some: "some",
|
|
188810
|
-
find: "find",
|
|
188811
|
-
findIndex: "find_index",
|
|
188812
|
-
findLast: "find_last",
|
|
188813
|
-
findLastIndex: "find_last_index"
|
|
188826
|
+
get spreadCtx() {
|
|
188827
|
+
return {
|
|
188828
|
+
componentName: this.componentName,
|
|
188829
|
+
errors: this.errors,
|
|
188830
|
+
localConstants: this.localConstants,
|
|
188831
|
+
propsParams: this.propsParams,
|
|
188832
|
+
convertExpressionToKolon: (e, preParsed) => this.convertExpressionToKolon(e, preParsed)
|
|
188814
188833
|
};
|
|
188815
|
-
if (fn[method])
|
|
188816
|
-
return `$bf.${fn[method]}(${arrayExpr}, ${lambda})`;
|
|
188817
|
-
return emit(object);
|
|
188818
188834
|
}
|
|
188819
|
-
|
|
188820
|
-
return
|
|
188835
|
+
get memoCtx() {
|
|
188836
|
+
return { convertExpressionToKolon: (e, preParsed) => this.convertExpressionToKolon(e, preParsed) };
|
|
188821
188837
|
}
|
|
188822
|
-
|
|
188823
|
-
|
|
188838
|
+
convertExpressionToKolon(expr, preParsed) {
|
|
188839
|
+
let parsed;
|
|
188840
|
+
if (preParsed) {
|
|
188841
|
+
parsed = preParsed;
|
|
188842
|
+
} else {
|
|
188843
|
+
const trimmed = expr.trim();
|
|
188844
|
+
if (trimmed === "")
|
|
188845
|
+
return "''";
|
|
188846
|
+
parsed = parseExpression2(trimmed);
|
|
188847
|
+
}
|
|
188848
|
+
if (parsed.kind === "call") {
|
|
188849
|
+
for (const matcher of this._loweringMatchers) {
|
|
188850
|
+
const node = matcher(parsed.callee, parsed.args);
|
|
188851
|
+
if (node?.kind === "guard-list" && node.helper === "query") {
|
|
188852
|
+
const qArgs = queryHrefArgs(node, (n) => this.renderParsedExprToKolon(n));
|
|
188853
|
+
return `$bf.query(${qArgs.join(", ")})`;
|
|
188854
|
+
}
|
|
188855
|
+
}
|
|
188856
|
+
}
|
|
188857
|
+
const support = isSupported(parsed);
|
|
188858
|
+
if (!support.supported) {
|
|
188859
|
+
this.errors.push({
|
|
188860
|
+
code: "BF101",
|
|
188861
|
+
severity: "error",
|
|
188862
|
+
message: `Expression not supported: ${preParsed ? stringifyParsedExpr2(parsed) : expr.trim()}`,
|
|
188863
|
+
loc: { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188864
|
+
suggestion: {
|
|
188865
|
+
message: support.reason ? `${support.reason}
|
|
188866
|
+
|
|
188867
|
+
Options:
|
|
188868
|
+
1. Use /* @client */ for client-side evaluation
|
|
188869
|
+
2. Pre-compute the value in the backend` : `Options:
|
|
188870
|
+
1. Use /* @client */ for client-side evaluation
|
|
188871
|
+
2. Pre-compute the value in the backend`
|
|
188872
|
+
}
|
|
188873
|
+
});
|
|
188874
|
+
return "''";
|
|
188875
|
+
}
|
|
188876
|
+
return this.renderParsedExprToKolon(parsed);
|
|
188824
188877
|
}
|
|
188825
|
-
|
|
188826
|
-
return
|
|
188878
|
+
renderParsedExprToKolon(expr) {
|
|
188879
|
+
return emitParsedExpr(expr, new XslateTopLevelEmitter(this.emitCtx));
|
|
188827
188880
|
}
|
|
188828
|
-
|
|
188829
|
-
return
|
|
188881
|
+
_isStringValueName(name) {
|
|
188882
|
+
return this.stringValueNames.has(name);
|
|
188830
188883
|
}
|
|
188831
|
-
|
|
188832
|
-
|
|
188884
|
+
parseUndefinedAlternateTernary(expr) {
|
|
188885
|
+
const parsed = parseExpression2(expr.trim());
|
|
188886
|
+
if (parsed?.kind !== "conditional")
|
|
188887
|
+
return null;
|
|
188888
|
+
const alt = parsed.alternate;
|
|
188889
|
+
const isUndef = alt.kind === "identifier" && (alt.name === "undefined" || alt.name === "null") || alt.kind === "literal" && (alt.value === null || alt.value === undefined);
|
|
188890
|
+
if (!isUndef)
|
|
188891
|
+
return null;
|
|
188892
|
+
return {
|
|
188893
|
+
condition: exprToString(parsed.test),
|
|
188894
|
+
consequent: exprToString(parsed.consequent)
|
|
188895
|
+
};
|
|
188896
|
+
}
|
|
188897
|
+
isBooleanTypedPropRef(expr) {
|
|
188898
|
+
let bare = expr.trim();
|
|
188899
|
+
if (this.propsObjectName && bare.startsWith(`${this.propsObjectName}.`)) {
|
|
188900
|
+
bare = bare.slice(this.propsObjectName.length + 1);
|
|
188901
|
+
}
|
|
188902
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(bare))
|
|
188903
|
+
return false;
|
|
188904
|
+
return this.booleanTypedProps.has(bare);
|
|
188833
188905
|
}
|
|
188834
|
-
|
|
188835
|
-
|
|
188906
|
+
_resolveLiteralConst(name) {
|
|
188907
|
+
const c = (this.localConstants ?? []).find((lc) => lc.name === name);
|
|
188908
|
+
if (c?.value === undefined)
|
|
188909
|
+
return null;
|
|
188910
|
+
const v = c.value.trim();
|
|
188911
|
+
if (/^-?\d+(\.\d+)?$/.test(v))
|
|
188912
|
+
return v;
|
|
188913
|
+
const strLit = /^'([^'\\]*)'$/.exec(v) ?? /^"([^"\\]*)"$/.exec(v);
|
|
188914
|
+
if (strLit)
|
|
188915
|
+
return `'${strLit[1].replace(/[\\']/g, (m) => `\\${m}`)}'`;
|
|
188916
|
+
return null;
|
|
188836
188917
|
}
|
|
188837
|
-
|
|
188838
|
-
|
|
188918
|
+
_resolveStaticRecordLiteral(objectName, key) {
|
|
188919
|
+
const hit = lookupStaticRecordLiteral(objectName, key, this.localConstants);
|
|
188920
|
+
if (!hit)
|
|
188921
|
+
return null;
|
|
188922
|
+
return hit.kind === "number" ? hit.text : `'${hit.text.replace(/[\\']/g, (m) => `\\${m}`)}'`;
|
|
188839
188923
|
}
|
|
188840
|
-
|
|
188841
|
-
|
|
188842
|
-
|
|
188843
|
-
|
|
188844
|
-
|
|
188845
|
-
|
|
188846
|
-
|
|
188847
|
-
} else {
|
|
188848
|
-
const rendered = emit(part.expr);
|
|
188849
|
-
const needsParens = part.expr.kind === "binary" || part.expr.kind === "logical" || part.expr.kind === "conditional";
|
|
188850
|
-
terms.push(needsParens ? `(${rendered})` : rendered);
|
|
188851
|
-
}
|
|
188852
|
-
}
|
|
188853
|
-
if (terms.length === 0)
|
|
188854
|
-
return `''`;
|
|
188855
|
-
return terms.join(" ~ ");
|
|
188924
|
+
_resolveModuleStringConst(name) {
|
|
188925
|
+
if (this.inLoop)
|
|
188926
|
+
return null;
|
|
188927
|
+
const value = this.moduleStringConsts.get(name);
|
|
188928
|
+
if (value === undefined)
|
|
188929
|
+
return null;
|
|
188930
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
188856
188931
|
}
|
|
188857
|
-
|
|
188858
|
-
|
|
188932
|
+
_recordExprBF101(message, reason) {
|
|
188933
|
+
this.errors.push({
|
|
188934
|
+
code: "BF101",
|
|
188935
|
+
severity: "error",
|
|
188936
|
+
message,
|
|
188937
|
+
loc: { file: this.componentName + ".tsx", start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
188938
|
+
suggestion: {
|
|
188939
|
+
message: reason ? `${reason}
|
|
188940
|
+
|
|
188941
|
+
Options:
|
|
188942
|
+
1. Use /* @client */ for client-side evaluation
|
|
188943
|
+
2. Pre-compute the value in the backend` : `Options:
|
|
188944
|
+
1. Use /* @client */ for client-side evaluation
|
|
188945
|
+
2. Pre-compute the value in the backend`
|
|
188946
|
+
}
|
|
188947
|
+
});
|
|
188859
188948
|
}
|
|
188860
|
-
|
|
188861
|
-
return
|
|
188949
|
+
_renderKolonFilterExprPublic(expr, param) {
|
|
188950
|
+
return this.renderKolonFilterExpr(expr, param);
|
|
188862
188951
|
}
|
|
188863
188952
|
}
|
|
188864
188953
|
var xslateAdapter = new XslateAdapter;
|