@barefootjs/mojolicious 0.18.3 → 0.18.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/expr/array-method.d.ts.map +1 -1
- package/dist/adapter/expr/emitters.d.ts +2 -2
- package/dist/adapter/expr/emitters.d.ts.map +1 -1
- package/dist/adapter/expr/operand.d.ts +6 -17
- package/dist/adapter/expr/operand.d.ts.map +1 -1
- package/dist/adapter/index.js +88 -45
- package/dist/adapter/lib/constants.d.ts.map +1 -1
- package/dist/adapter/mojo-adapter.d.ts +8 -0
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/build.js +88 -45
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +90 -64
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/test-render.d.ts.map +1 -1
- package/lib/BarefootJS/Backend/Mojo.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS.pm +1 -1
- package/package.json +3 -3
- package/src/__tests__/mojo-adapter.test.ts +21 -1
- package/src/adapter/expr/array-method.ts +19 -0
- package/src/adapter/expr/emitters.ts +44 -9
- package/src/adapter/expr/operand.ts +6 -27
- package/src/adapter/lib/constants.ts +3 -0
- package/src/adapter/mojo-adapter.ts +71 -11
- package/src/conformance-pins.ts +0 -5
- package/src/render-divergences.ts +1 -32
- package/src/test-render.ts +5 -0
package/dist/build.js
CHANGED
|
@@ -76,7 +76,7 @@ function isAriaBooleanAttr(name) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// src/adapter/mojo-adapter.ts
|
|
79
|
-
import { BF_SLOT, BF_COND, BF_REGION } from "@barefootjs/shared";
|
|
79
|
+
import { BF_SLOT, BF_COND, BF_REGION, escapeHtml } from "@barefootjs/shared";
|
|
80
80
|
|
|
81
81
|
// src/adapter/lib/constants.ts
|
|
82
82
|
var MOJO_TEMPLATE_PRIMITIVES = {
|
|
@@ -85,7 +85,10 @@ var MOJO_TEMPLATE_PRIMITIVES = {
|
|
|
85
85
|
Number: { arity: 1, emit: (args) => `bf->number(${args[0]})` },
|
|
86
86
|
"Math.floor": { arity: 1, emit: (args) => `bf->floor(${args[0]})` },
|
|
87
87
|
"Math.ceil": { arity: 1, emit: (args) => `bf->ceil(${args[0]})` },
|
|
88
|
-
"Math.round": { arity: 1, emit: (args) => `bf->round(${args[0]})` }
|
|
88
|
+
"Math.round": { arity: 1, emit: (args) => `bf->round(${args[0]})` },
|
|
89
|
+
"Math.min": { arity: 2, emit: (args) => `bf->min(${args[0]}, ${args[1]})` },
|
|
90
|
+
"Math.max": { arity: 2, emit: (args) => `bf->max(${args[0]}, ${args[1]})` },
|
|
91
|
+
"Math.abs": { arity: 1, emit: (args) => `bf->abs(${args[0]})` }
|
|
89
92
|
};
|
|
90
93
|
var MOJO_PRIMITIVE_EMIT_MAP = Object.fromEntries(Object.entries(MOJO_TEMPLATE_PRIMITIVES).map(([k, v]) => [k, v.emit]));
|
|
91
94
|
|
|
@@ -190,6 +193,12 @@ function renderArrayMethod(method, object, args, emit) {
|
|
|
190
193
|
const recv = emit(object);
|
|
191
194
|
return `bf->trim(${recv})`;
|
|
192
195
|
}
|
|
196
|
+
case "trimStart":
|
|
197
|
+
case "trimEnd": {
|
|
198
|
+
const fn = method === "trimStart" ? "trim_start" : "trim_end";
|
|
199
|
+
const recv = emit(object);
|
|
200
|
+
return `bf->${fn}(${recv})`;
|
|
201
|
+
}
|
|
193
202
|
case "toFixed": {
|
|
194
203
|
const recv = emit(object);
|
|
195
204
|
const digits = args.length >= 1 ? emit(args[0]) : "0";
|
|
@@ -223,6 +232,12 @@ function renderArrayMethod(method, object, args, emit) {
|
|
|
223
232
|
const newS = emit(args[1]);
|
|
224
233
|
return `bf->replace(${recv}, ${oldS}, ${newS})`;
|
|
225
234
|
}
|
|
235
|
+
case "replaceAll": {
|
|
236
|
+
const recv = emit(object);
|
|
237
|
+
const oldS = emit(args[0]);
|
|
238
|
+
const newS = emit(args[1]);
|
|
239
|
+
return `bf->replace_all(${recv}, ${oldS}, ${newS})`;
|
|
240
|
+
}
|
|
226
241
|
case "repeat": {
|
|
227
242
|
const recv = emit(object);
|
|
228
243
|
const count = args.length === 0 ? "0" : emit(args[0]);
|
|
@@ -325,6 +340,9 @@ function renderFlatMethod(recv, depth, emit) {
|
|
|
325
340
|
|
|
326
341
|
// src/adapter/expr/emitters.ts
|
|
327
342
|
import {
|
|
343
|
+
groupBinaryOperand,
|
|
344
|
+
isStringTypedOperand as isStringTypedOperand2,
|
|
345
|
+
isStringConcatBinary,
|
|
328
346
|
emitParsedExpr,
|
|
329
347
|
identifierPath,
|
|
330
348
|
matchSearchParamsMethodCall,
|
|
@@ -333,17 +351,7 @@ import {
|
|
|
333
351
|
} from "@barefootjs/jsx";
|
|
334
352
|
|
|
335
353
|
// src/adapter/expr/operand.ts
|
|
336
|
-
|
|
337
|
-
if (expr.kind === "literal" && expr.literalType === "string")
|
|
338
|
-
return true;
|
|
339
|
-
if (expr.kind === "call" && expr.callee.kind === "identifier" && expr.args.length === 0) {
|
|
340
|
-
return isStringName(expr.callee.name);
|
|
341
|
-
}
|
|
342
|
-
if (expr.kind === "member" && expr.object.kind === "identifier" && expr.object.name === "props") {
|
|
343
|
-
return isStringName(expr.property);
|
|
344
|
-
}
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
354
|
+
import { isStringTypedOperand } from "@barefootjs/jsx";
|
|
347
355
|
function emitIndexAccessPerl(object, index, emit, isStringName) {
|
|
348
356
|
const i = emit(index);
|
|
349
357
|
return isStringTypedOperand(index, isStringName) ? `${emit(object)}->{${i}}` : `${emit(object)}->[${i}]`;
|
|
@@ -388,7 +396,7 @@ class MojoFilterEmitter {
|
|
|
388
396
|
return "undef";
|
|
389
397
|
return String(value);
|
|
390
398
|
}
|
|
391
|
-
member(object, property, _computed, emit) {
|
|
399
|
+
member(object, property, _computed, _optional, emit) {
|
|
392
400
|
if (property === "length" && (asCallbackMethodCall(object) !== null || object.kind === "array-literal")) {
|
|
393
401
|
return `scalar(@{${emit(object)}})`;
|
|
394
402
|
}
|
|
@@ -414,9 +422,9 @@ class MojoFilterEmitter {
|
|
|
414
422
|
return arg;
|
|
415
423
|
}
|
|
416
424
|
binary(op, left, right, emit) {
|
|
417
|
-
const l = emit(left);
|
|
418
|
-
const r = emit(right);
|
|
419
|
-
const isStr = (e) =>
|
|
425
|
+
const l = groupBinaryOperand(left, emit(left));
|
|
426
|
+
const r = groupBinaryOperand(right, emit(right));
|
|
427
|
+
const isStr = (e) => isStringTypedOperand2(e, this.isStringName);
|
|
420
428
|
const stringCmp = isStr(left) || isStr(right);
|
|
421
429
|
if ((op === "===" || op === "==") && stringCmp) {
|
|
422
430
|
return `${l} eq ${r}`;
|
|
@@ -424,6 +432,9 @@ class MojoFilterEmitter {
|
|
|
424
432
|
if ((op === "!==" || op === "!=") && stringCmp) {
|
|
425
433
|
return `${l} ne ${r}`;
|
|
426
434
|
}
|
|
435
|
+
if (isStringConcatBinary(op, left, right, this.isStringName)) {
|
|
436
|
+
return `${l} . ${r}`;
|
|
437
|
+
}
|
|
427
438
|
const opMap = {
|
|
428
439
|
"===": "==",
|
|
429
440
|
"!==": "!=",
|
|
@@ -520,7 +531,7 @@ class MojoTopLevelEmitter {
|
|
|
520
531
|
return "undef";
|
|
521
532
|
return String(value);
|
|
522
533
|
}
|
|
523
|
-
member(object, property, _computed, emit) {
|
|
534
|
+
member(object, property, _computed, _optional, emit) {
|
|
524
535
|
if (object.kind === "identifier" && object.name === "props") {
|
|
525
536
|
return `$${property}`;
|
|
526
537
|
}
|
|
@@ -530,8 +541,13 @@ class MojoTopLevelEmitter {
|
|
|
530
541
|
return staticValue;
|
|
531
542
|
}
|
|
532
543
|
const obj = emit(object);
|
|
533
|
-
if (property === "length")
|
|
544
|
+
if (property === "length") {
|
|
545
|
+
const isStr = (e) => isStringTypedOperand2(e, (n) => this.ctx._isStringValueName(n));
|
|
546
|
+
const isStringReceiver = isStr(object) || object.kind === "identifier" && this.ctx._isStringValueName(object.name);
|
|
547
|
+
if (isStringReceiver)
|
|
548
|
+
return `length(${obj})`;
|
|
534
549
|
return `scalar(@{${obj}})`;
|
|
550
|
+
}
|
|
535
551
|
return `${obj}->{${property}}`;
|
|
536
552
|
}
|
|
537
553
|
indexAccess(object, index, emit) {
|
|
@@ -567,9 +583,9 @@ class MojoTopLevelEmitter {
|
|
|
567
583
|
return arg;
|
|
568
584
|
}
|
|
569
585
|
binary(op, left, right, emit) {
|
|
570
|
-
const l = emit(left);
|
|
571
|
-
const r = emit(right);
|
|
572
|
-
const isStr = (e) =>
|
|
586
|
+
const l = groupBinaryOperand(left, emit(left));
|
|
587
|
+
const r = groupBinaryOperand(right, emit(right));
|
|
588
|
+
const isStr = (e) => isStringTypedOperand2(e, (n) => this.ctx._isStringValueName(n));
|
|
573
589
|
const stringCmp = isStr(left) || isStr(right);
|
|
574
590
|
if ((op === "===" || op === "==") && stringCmp) {
|
|
575
591
|
return `${l} eq ${r}`;
|
|
@@ -577,6 +593,9 @@ class MojoTopLevelEmitter {
|
|
|
577
593
|
if ((op === "!==" || op === "!=") && stringCmp) {
|
|
578
594
|
return `${l} ne ${r}`;
|
|
579
595
|
}
|
|
596
|
+
if (isStringConcatBinary(op, left, right, (n) => this.ctx._isStringValueName(n))) {
|
|
597
|
+
return `${l} . ${r}`;
|
|
598
|
+
}
|
|
580
599
|
const opMap = {
|
|
581
600
|
"===": "==",
|
|
582
601
|
"!==": "!=",
|
|
@@ -977,6 +996,7 @@ class MojoAdapter extends BaseAdapter {
|
|
|
977
996
|
options;
|
|
978
997
|
errors = [];
|
|
979
998
|
inLoop = false;
|
|
999
|
+
currentLoopKeyDepth = 0;
|
|
980
1000
|
propsObjectName = null;
|
|
981
1001
|
propsParams = [];
|
|
982
1002
|
booleanTypedProps = new Set;
|
|
@@ -1109,7 +1129,7 @@ class MojoAdapter extends BaseAdapter {
|
|
|
1109
1129
|
return this.renderElement(node);
|
|
1110
1130
|
}
|
|
1111
1131
|
emitText(node) {
|
|
1112
|
-
return node.value;
|
|
1132
|
+
return escapeHtml(node.value);
|
|
1113
1133
|
}
|
|
1114
1134
|
emitExpression(node) {
|
|
1115
1135
|
return this.renderExpression(node);
|
|
@@ -1338,13 +1358,16 @@ ${whenTrue}
|
|
|
1338
1358
|
}
|
|
1339
1359
|
const param = loop.param;
|
|
1340
1360
|
const indexVar = loop.iterationShape === "keys" ? `$${param}` : loop.index ? `$${loop.index}` : "$_i";
|
|
1341
|
-
const loopBound = loop.iterationShape === "keys" ? [param] : supportableDestructure ? ["__bf_item", ...(loop.paramBindings ?? []).map((b) => b.name), loop.index ?? "_i"] : [param, loop.index ?? "_i"];
|
|
1361
|
+
const loopBound = loop.objectIteration === "entries" ? [param, loop.index ?? "_k"] : loop.objectIteration === "keys" || loop.objectIteration === "values" || loop.iterationShape === "keys" ? [param] : supportableDestructure ? ["__bf_item", ...(loop.paramBindings ?? []).map((b) => b.name), loop.index ?? "_i"] : [param, loop.index ?? "_i"];
|
|
1342
1362
|
for (const n of loopBound) {
|
|
1343
1363
|
this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
|
|
1344
1364
|
}
|
|
1345
1365
|
const prevInLoop = this.inLoop;
|
|
1346
1366
|
this.inLoop = true;
|
|
1367
|
+
const prevLoopKeyDepth = this.currentLoopKeyDepth;
|
|
1368
|
+
this.currentLoopKeyDepth = loop.depth;
|
|
1347
1369
|
const renderedChildren = this.renderChildren(loop.children);
|
|
1370
|
+
this.currentLoopKeyDepth = prevLoopKeyDepth;
|
|
1348
1371
|
this.inLoop = prevInLoop;
|
|
1349
1372
|
const children = loop.bodyIsItemConditional && loop.key ? `<%== bf->comment("loop-i:" . ${this.convertExpressionToPerl(loop.key)}) %>
|
|
1350
1373
|
${renderedChildren}` : renderedChildren;
|
|
@@ -1378,23 +1401,31 @@ ${renderedChildren}` : renderedChildren;
|
|
|
1378
1401
|
}
|
|
1379
1402
|
lines.push(`% my $${sortedHoist} = ${sorted};`);
|
|
1380
1403
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1404
|
+
if (loop.objectIteration) {
|
|
1405
|
+
const keyVar = loop.objectIteration === "values" ? "$__bf_k" : `$${loop.index ?? param}`;
|
|
1406
|
+
lines.push(`% for my ${keyVar} (sort keys %{${array}}) {`);
|
|
1407
|
+
if (loop.objectIteration === "entries" || loop.objectIteration === "values") {
|
|
1408
|
+
lines.push(`% my $${param} = ${array}->{${keyVar}};`);
|
|
1409
|
+
}
|
|
1410
|
+
} else {
|
|
1411
|
+
lines.push(`% for my ${indexVar} (0..$#{${array}}) {`);
|
|
1412
|
+
if (loop.iterationShape !== "keys") {
|
|
1413
|
+
if (supportableDestructure) {
|
|
1414
|
+
lines.push(`% my $__bf_item = ${array}->[${indexVar}];`);
|
|
1415
|
+
for (const b of loop.paramBindings ?? []) {
|
|
1416
|
+
const parent = perlSegmentAccessor("$__bf_item", b.segments ?? []);
|
|
1417
|
+
if (b.rest?.kind === "object") {
|
|
1418
|
+
const exclude = b.rest.exclude.map((k) => perlStringLiteral(k.key)).join(", ");
|
|
1419
|
+
lines.push(`% my $${b.name} = bf->omit(${parent}, [${exclude}]);`);
|
|
1420
|
+
} else if (b.rest?.kind === "array") {
|
|
1421
|
+
lines.push(`% my $${b.name} = bf->slice(${parent}, ${b.rest.from}, undef);`);
|
|
1422
|
+
} else {
|
|
1423
|
+
lines.push(`% my $${b.name} = ${perlSegmentAccessor("$__bf_item", b.segments ?? [])};`);
|
|
1424
|
+
}
|
|
1394
1425
|
}
|
|
1426
|
+
} else {
|
|
1427
|
+
lines.push(`% my $${param} = ${array}->[${indexVar}];`);
|
|
1395
1428
|
}
|
|
1396
|
-
} else {
|
|
1397
|
-
lines.push(`% my $${param} = ${array}->[${indexVar}];`);
|
|
1398
1429
|
}
|
|
1399
1430
|
}
|
|
1400
1431
|
if (loop.filterPredicate) {
|
|
@@ -1449,9 +1480,20 @@ ${renderedChildren}` : renderedChildren;
|
|
|
1449
1480
|
};
|
|
1450
1481
|
renderComponent(comp) {
|
|
1451
1482
|
const propParts = [];
|
|
1483
|
+
const namedSlotCaptures = [];
|
|
1452
1484
|
for (const p of comp.props) {
|
|
1453
1485
|
if ((p.name.match(/^on[A-Z]/) || p.name === "ref") && p.value.kind === "expression")
|
|
1454
1486
|
continue;
|
|
1487
|
+
if (p.value.kind === "jsx-children" && p.name !== "children") {
|
|
1488
|
+
const prevInLoop = this.inLoop;
|
|
1489
|
+
this.inLoop = false;
|
|
1490
|
+
const slotBody = this.renderChildren(p.value.children);
|
|
1491
|
+
this.inLoop = prevInLoop;
|
|
1492
|
+
const varName = `$bf_prop_${this.childrenCaptureCounter++}`;
|
|
1493
|
+
namedSlotCaptures.push(`<% my ${varName} = begin %>${slotBody}<% end %>`);
|
|
1494
|
+
propParts.push(`${perlHashKey(p.name)} => ${varName}`);
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1455
1497
|
const lowered = emitAttrValue(p.value, this.componentPropEmitter, p.name);
|
|
1456
1498
|
if (lowered)
|
|
1457
1499
|
propParts.push(lowered);
|
|
@@ -1468,9 +1510,9 @@ ${renderedChildren}` : renderedChildren;
|
|
|
1468
1510
|
const childrenBody = this.renderChildren(effectiveChildren);
|
|
1469
1511
|
this.inLoop = prevInLoop;
|
|
1470
1512
|
const varName = `$bf_children_${comp.slotId ?? "c" + this.childrenCaptureCounter++}`;
|
|
1471
|
-
return
|
|
1513
|
+
return `${namedSlotCaptures.join("")}<% my ${varName} = begin %>${childrenBody}<% end %><%== bf->render_child('${tplName}'${propsStr}, children => ${varName}) %>`;
|
|
1472
1514
|
}
|
|
1473
|
-
return
|
|
1515
|
+
return `${namedSlotCaptures.join("")}<%== bf->render_child('${tplName}'${propsStr}) %>`;
|
|
1474
1516
|
}
|
|
1475
1517
|
childrenCaptureCounter = 0;
|
|
1476
1518
|
presenceVarCounter = 0;
|
|
@@ -1515,7 +1557,7 @@ ${alternate}
|
|
|
1515
1557
|
${children}`;
|
|
1516
1558
|
}
|
|
1517
1559
|
elementAttrEmitter = {
|
|
1518
|
-
emitLiteral: (value, name) => `${name}="${value.value}"`,
|
|
1560
|
+
emitLiteral: (value, name) => `${name}="${escapeHtml(value.value)}"`,
|
|
1519
1561
|
emitExpression: (value, name) => {
|
|
1520
1562
|
if (name === "style") {
|
|
1521
1563
|
const css = this.tryLowerStyleObject(value.expr);
|
|
@@ -1609,9 +1651,10 @@ ${children}`;
|
|
|
1609
1651
|
let attrName;
|
|
1610
1652
|
if (attr.name === "className")
|
|
1611
1653
|
attrName = "class";
|
|
1612
|
-
else if (attr.name === "key")
|
|
1613
|
-
|
|
1614
|
-
|
|
1654
|
+
else if (attr.name === "key") {
|
|
1655
|
+
const depth = this.currentLoopKeyDepth;
|
|
1656
|
+
attrName = depth > 0 ? `data-key-${depth}` : "data-key";
|
|
1657
|
+
} else
|
|
1615
1658
|
attrName = attr.name;
|
|
1616
1659
|
const lowered = emitAttrValue(attr.value, this.elementAttrEmitter, attrName);
|
|
1617
1660
|
if (lowered)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,eAqI7B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -76,7 +76,7 @@ function isAriaBooleanAttr(name) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// src/adapter/mojo-adapter.ts
|
|
79
|
-
import { BF_SLOT, BF_COND, BF_REGION } from "@barefootjs/shared";
|
|
79
|
+
import { BF_SLOT, BF_COND, BF_REGION, escapeHtml } from "@barefootjs/shared";
|
|
80
80
|
|
|
81
81
|
// src/adapter/lib/constants.ts
|
|
82
82
|
var MOJO_TEMPLATE_PRIMITIVES = {
|
|
@@ -85,7 +85,10 @@ var MOJO_TEMPLATE_PRIMITIVES = {
|
|
|
85
85
|
Number: { arity: 1, emit: (args) => `bf->number(${args[0]})` },
|
|
86
86
|
"Math.floor": { arity: 1, emit: (args) => `bf->floor(${args[0]})` },
|
|
87
87
|
"Math.ceil": { arity: 1, emit: (args) => `bf->ceil(${args[0]})` },
|
|
88
|
-
"Math.round": { arity: 1, emit: (args) => `bf->round(${args[0]})` }
|
|
88
|
+
"Math.round": { arity: 1, emit: (args) => `bf->round(${args[0]})` },
|
|
89
|
+
"Math.min": { arity: 2, emit: (args) => `bf->min(${args[0]}, ${args[1]})` },
|
|
90
|
+
"Math.max": { arity: 2, emit: (args) => `bf->max(${args[0]}, ${args[1]})` },
|
|
91
|
+
"Math.abs": { arity: 1, emit: (args) => `bf->abs(${args[0]})` }
|
|
89
92
|
};
|
|
90
93
|
var MOJO_PRIMITIVE_EMIT_MAP = Object.fromEntries(Object.entries(MOJO_TEMPLATE_PRIMITIVES).map(([k, v]) => [k, v.emit]));
|
|
91
94
|
|
|
@@ -190,6 +193,12 @@ function renderArrayMethod(method, object, args, emit) {
|
|
|
190
193
|
const recv = emit(object);
|
|
191
194
|
return `bf->trim(${recv})`;
|
|
192
195
|
}
|
|
196
|
+
case "trimStart":
|
|
197
|
+
case "trimEnd": {
|
|
198
|
+
const fn = method === "trimStart" ? "trim_start" : "trim_end";
|
|
199
|
+
const recv = emit(object);
|
|
200
|
+
return `bf->${fn}(${recv})`;
|
|
201
|
+
}
|
|
193
202
|
case "toFixed": {
|
|
194
203
|
const recv = emit(object);
|
|
195
204
|
const digits = args.length >= 1 ? emit(args[0]) : "0";
|
|
@@ -223,6 +232,12 @@ function renderArrayMethod(method, object, args, emit) {
|
|
|
223
232
|
const newS = emit(args[1]);
|
|
224
233
|
return `bf->replace(${recv}, ${oldS}, ${newS})`;
|
|
225
234
|
}
|
|
235
|
+
case "replaceAll": {
|
|
236
|
+
const recv = emit(object);
|
|
237
|
+
const oldS = emit(args[0]);
|
|
238
|
+
const newS = emit(args[1]);
|
|
239
|
+
return `bf->replace_all(${recv}, ${oldS}, ${newS})`;
|
|
240
|
+
}
|
|
226
241
|
case "repeat": {
|
|
227
242
|
const recv = emit(object);
|
|
228
243
|
const count = args.length === 0 ? "0" : emit(args[0]);
|
|
@@ -325,6 +340,9 @@ function renderFlatMethod(recv, depth, emit) {
|
|
|
325
340
|
|
|
326
341
|
// src/adapter/expr/emitters.ts
|
|
327
342
|
import {
|
|
343
|
+
groupBinaryOperand,
|
|
344
|
+
isStringTypedOperand as isStringTypedOperand2,
|
|
345
|
+
isStringConcatBinary,
|
|
328
346
|
emitParsedExpr,
|
|
329
347
|
identifierPath,
|
|
330
348
|
matchSearchParamsMethodCall,
|
|
@@ -333,17 +351,7 @@ import {
|
|
|
333
351
|
} from "@barefootjs/jsx";
|
|
334
352
|
|
|
335
353
|
// src/adapter/expr/operand.ts
|
|
336
|
-
|
|
337
|
-
if (expr.kind === "literal" && expr.literalType === "string")
|
|
338
|
-
return true;
|
|
339
|
-
if (expr.kind === "call" && expr.callee.kind === "identifier" && expr.args.length === 0) {
|
|
340
|
-
return isStringName(expr.callee.name);
|
|
341
|
-
}
|
|
342
|
-
if (expr.kind === "member" && expr.object.kind === "identifier" && expr.object.name === "props") {
|
|
343
|
-
return isStringName(expr.property);
|
|
344
|
-
}
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
354
|
+
import { isStringTypedOperand } from "@barefootjs/jsx";
|
|
347
355
|
function emitIndexAccessPerl(object, index, emit, isStringName) {
|
|
348
356
|
const i = emit(index);
|
|
349
357
|
return isStringTypedOperand(index, isStringName) ? `${emit(object)}->{${i}}` : `${emit(object)}->[${i}]`;
|
|
@@ -388,7 +396,7 @@ class MojoFilterEmitter {
|
|
|
388
396
|
return "undef";
|
|
389
397
|
return String(value);
|
|
390
398
|
}
|
|
391
|
-
member(object, property, _computed, emit) {
|
|
399
|
+
member(object, property, _computed, _optional, emit) {
|
|
392
400
|
if (property === "length" && (asCallbackMethodCall(object) !== null || object.kind === "array-literal")) {
|
|
393
401
|
return `scalar(@{${emit(object)}})`;
|
|
394
402
|
}
|
|
@@ -414,9 +422,9 @@ class MojoFilterEmitter {
|
|
|
414
422
|
return arg;
|
|
415
423
|
}
|
|
416
424
|
binary(op, left, right, emit) {
|
|
417
|
-
const l = emit(left);
|
|
418
|
-
const r = emit(right);
|
|
419
|
-
const isStr = (e) =>
|
|
425
|
+
const l = groupBinaryOperand(left, emit(left));
|
|
426
|
+
const r = groupBinaryOperand(right, emit(right));
|
|
427
|
+
const isStr = (e) => isStringTypedOperand2(e, this.isStringName);
|
|
420
428
|
const stringCmp = isStr(left) || isStr(right);
|
|
421
429
|
if ((op === "===" || op === "==") && stringCmp) {
|
|
422
430
|
return `${l} eq ${r}`;
|
|
@@ -424,6 +432,9 @@ class MojoFilterEmitter {
|
|
|
424
432
|
if ((op === "!==" || op === "!=") && stringCmp) {
|
|
425
433
|
return `${l} ne ${r}`;
|
|
426
434
|
}
|
|
435
|
+
if (isStringConcatBinary(op, left, right, this.isStringName)) {
|
|
436
|
+
return `${l} . ${r}`;
|
|
437
|
+
}
|
|
427
438
|
const opMap = {
|
|
428
439
|
"===": "==",
|
|
429
440
|
"!==": "!=",
|
|
@@ -520,7 +531,7 @@ class MojoTopLevelEmitter {
|
|
|
520
531
|
return "undef";
|
|
521
532
|
return String(value);
|
|
522
533
|
}
|
|
523
|
-
member(object, property, _computed, emit) {
|
|
534
|
+
member(object, property, _computed, _optional, emit) {
|
|
524
535
|
if (object.kind === "identifier" && object.name === "props") {
|
|
525
536
|
return `$${property}`;
|
|
526
537
|
}
|
|
@@ -530,8 +541,13 @@ class MojoTopLevelEmitter {
|
|
|
530
541
|
return staticValue;
|
|
531
542
|
}
|
|
532
543
|
const obj = emit(object);
|
|
533
|
-
if (property === "length")
|
|
544
|
+
if (property === "length") {
|
|
545
|
+
const isStr = (e) => isStringTypedOperand2(e, (n) => this.ctx._isStringValueName(n));
|
|
546
|
+
const isStringReceiver = isStr(object) || object.kind === "identifier" && this.ctx._isStringValueName(object.name);
|
|
547
|
+
if (isStringReceiver)
|
|
548
|
+
return `length(${obj})`;
|
|
534
549
|
return `scalar(@{${obj}})`;
|
|
550
|
+
}
|
|
535
551
|
return `${obj}->{${property}}`;
|
|
536
552
|
}
|
|
537
553
|
indexAccess(object, index, emit) {
|
|
@@ -567,9 +583,9 @@ class MojoTopLevelEmitter {
|
|
|
567
583
|
return arg;
|
|
568
584
|
}
|
|
569
585
|
binary(op, left, right, emit) {
|
|
570
|
-
const l = emit(left);
|
|
571
|
-
const r = emit(right);
|
|
572
|
-
const isStr = (e) =>
|
|
586
|
+
const l = groupBinaryOperand(left, emit(left));
|
|
587
|
+
const r = groupBinaryOperand(right, emit(right));
|
|
588
|
+
const isStr = (e) => isStringTypedOperand2(e, (n) => this.ctx._isStringValueName(n));
|
|
573
589
|
const stringCmp = isStr(left) || isStr(right);
|
|
574
590
|
if ((op === "===" || op === "==") && stringCmp) {
|
|
575
591
|
return `${l} eq ${r}`;
|
|
@@ -577,6 +593,9 @@ class MojoTopLevelEmitter {
|
|
|
577
593
|
if ((op === "!==" || op === "!=") && stringCmp) {
|
|
578
594
|
return `${l} ne ${r}`;
|
|
579
595
|
}
|
|
596
|
+
if (isStringConcatBinary(op, left, right, (n) => this.ctx._isStringValueName(n))) {
|
|
597
|
+
return `${l} . ${r}`;
|
|
598
|
+
}
|
|
580
599
|
const opMap = {
|
|
581
600
|
"===": "==",
|
|
582
601
|
"!==": "!=",
|
|
@@ -977,6 +996,7 @@ class MojoAdapter extends BaseAdapter {
|
|
|
977
996
|
options;
|
|
978
997
|
errors = [];
|
|
979
998
|
inLoop = false;
|
|
999
|
+
currentLoopKeyDepth = 0;
|
|
980
1000
|
propsObjectName = null;
|
|
981
1001
|
propsParams = [];
|
|
982
1002
|
booleanTypedProps = new Set;
|
|
@@ -1109,7 +1129,7 @@ class MojoAdapter extends BaseAdapter {
|
|
|
1109
1129
|
return this.renderElement(node);
|
|
1110
1130
|
}
|
|
1111
1131
|
emitText(node) {
|
|
1112
|
-
return node.value;
|
|
1132
|
+
return escapeHtml(node.value);
|
|
1113
1133
|
}
|
|
1114
1134
|
emitExpression(node) {
|
|
1115
1135
|
return this.renderExpression(node);
|
|
@@ -1338,13 +1358,16 @@ ${whenTrue}
|
|
|
1338
1358
|
}
|
|
1339
1359
|
const param = loop.param;
|
|
1340
1360
|
const indexVar = loop.iterationShape === "keys" ? `$${param}` : loop.index ? `$${loop.index}` : "$_i";
|
|
1341
|
-
const loopBound = loop.iterationShape === "keys" ? [param] : supportableDestructure ? ["__bf_item", ...(loop.paramBindings ?? []).map((b) => b.name), loop.index ?? "_i"] : [param, loop.index ?? "_i"];
|
|
1361
|
+
const loopBound = loop.objectIteration === "entries" ? [param, loop.index ?? "_k"] : loop.objectIteration === "keys" || loop.objectIteration === "values" || loop.iterationShape === "keys" ? [param] : supportableDestructure ? ["__bf_item", ...(loop.paramBindings ?? []).map((b) => b.name), loop.index ?? "_i"] : [param, loop.index ?? "_i"];
|
|
1342
1362
|
for (const n of loopBound) {
|
|
1343
1363
|
this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
|
|
1344
1364
|
}
|
|
1345
1365
|
const prevInLoop = this.inLoop;
|
|
1346
1366
|
this.inLoop = true;
|
|
1367
|
+
const prevLoopKeyDepth = this.currentLoopKeyDepth;
|
|
1368
|
+
this.currentLoopKeyDepth = loop.depth;
|
|
1347
1369
|
const renderedChildren = this.renderChildren(loop.children);
|
|
1370
|
+
this.currentLoopKeyDepth = prevLoopKeyDepth;
|
|
1348
1371
|
this.inLoop = prevInLoop;
|
|
1349
1372
|
const children = loop.bodyIsItemConditional && loop.key ? `<%== bf->comment("loop-i:" . ${this.convertExpressionToPerl(loop.key)}) %>
|
|
1350
1373
|
${renderedChildren}` : renderedChildren;
|
|
@@ -1378,23 +1401,31 @@ ${renderedChildren}` : renderedChildren;
|
|
|
1378
1401
|
}
|
|
1379
1402
|
lines.push(`% my $${sortedHoist} = ${sorted};`);
|
|
1380
1403
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1404
|
+
if (loop.objectIteration) {
|
|
1405
|
+
const keyVar = loop.objectIteration === "values" ? "$__bf_k" : `$${loop.index ?? param}`;
|
|
1406
|
+
lines.push(`% for my ${keyVar} (sort keys %{${array}}) {`);
|
|
1407
|
+
if (loop.objectIteration === "entries" || loop.objectIteration === "values") {
|
|
1408
|
+
lines.push(`% my $${param} = ${array}->{${keyVar}};`);
|
|
1409
|
+
}
|
|
1410
|
+
} else {
|
|
1411
|
+
lines.push(`% for my ${indexVar} (0..$#{${array}}) {`);
|
|
1412
|
+
if (loop.iterationShape !== "keys") {
|
|
1413
|
+
if (supportableDestructure) {
|
|
1414
|
+
lines.push(`% my $__bf_item = ${array}->[${indexVar}];`);
|
|
1415
|
+
for (const b of loop.paramBindings ?? []) {
|
|
1416
|
+
const parent = perlSegmentAccessor("$__bf_item", b.segments ?? []);
|
|
1417
|
+
if (b.rest?.kind === "object") {
|
|
1418
|
+
const exclude = b.rest.exclude.map((k) => perlStringLiteral(k.key)).join(", ");
|
|
1419
|
+
lines.push(`% my $${b.name} = bf->omit(${parent}, [${exclude}]);`);
|
|
1420
|
+
} else if (b.rest?.kind === "array") {
|
|
1421
|
+
lines.push(`% my $${b.name} = bf->slice(${parent}, ${b.rest.from}, undef);`);
|
|
1422
|
+
} else {
|
|
1423
|
+
lines.push(`% my $${b.name} = ${perlSegmentAccessor("$__bf_item", b.segments ?? [])};`);
|
|
1424
|
+
}
|
|
1394
1425
|
}
|
|
1426
|
+
} else {
|
|
1427
|
+
lines.push(`% my $${param} = ${array}->[${indexVar}];`);
|
|
1395
1428
|
}
|
|
1396
|
-
} else {
|
|
1397
|
-
lines.push(`% my $${param} = ${array}->[${indexVar}];`);
|
|
1398
1429
|
}
|
|
1399
1430
|
}
|
|
1400
1431
|
if (loop.filterPredicate) {
|
|
@@ -1449,9 +1480,20 @@ ${renderedChildren}` : renderedChildren;
|
|
|
1449
1480
|
};
|
|
1450
1481
|
renderComponent(comp) {
|
|
1451
1482
|
const propParts = [];
|
|
1483
|
+
const namedSlotCaptures = [];
|
|
1452
1484
|
for (const p of comp.props) {
|
|
1453
1485
|
if ((p.name.match(/^on[A-Z]/) || p.name === "ref") && p.value.kind === "expression")
|
|
1454
1486
|
continue;
|
|
1487
|
+
if (p.value.kind === "jsx-children" && p.name !== "children") {
|
|
1488
|
+
const prevInLoop = this.inLoop;
|
|
1489
|
+
this.inLoop = false;
|
|
1490
|
+
const slotBody = this.renderChildren(p.value.children);
|
|
1491
|
+
this.inLoop = prevInLoop;
|
|
1492
|
+
const varName = `$bf_prop_${this.childrenCaptureCounter++}`;
|
|
1493
|
+
namedSlotCaptures.push(`<% my ${varName} = begin %>${slotBody}<% end %>`);
|
|
1494
|
+
propParts.push(`${perlHashKey(p.name)} => ${varName}`);
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1455
1497
|
const lowered = emitAttrValue(p.value, this.componentPropEmitter, p.name);
|
|
1456
1498
|
if (lowered)
|
|
1457
1499
|
propParts.push(lowered);
|
|
@@ -1468,9 +1510,9 @@ ${renderedChildren}` : renderedChildren;
|
|
|
1468
1510
|
const childrenBody = this.renderChildren(effectiveChildren);
|
|
1469
1511
|
this.inLoop = prevInLoop;
|
|
1470
1512
|
const varName = `$bf_children_${comp.slotId ?? "c" + this.childrenCaptureCounter++}`;
|
|
1471
|
-
return
|
|
1513
|
+
return `${namedSlotCaptures.join("")}<% my ${varName} = begin %>${childrenBody}<% end %><%== bf->render_child('${tplName}'${propsStr}, children => ${varName}) %>`;
|
|
1472
1514
|
}
|
|
1473
|
-
return
|
|
1515
|
+
return `${namedSlotCaptures.join("")}<%== bf->render_child('${tplName}'${propsStr}) %>`;
|
|
1474
1516
|
}
|
|
1475
1517
|
childrenCaptureCounter = 0;
|
|
1476
1518
|
presenceVarCounter = 0;
|
|
@@ -1515,7 +1557,7 @@ ${alternate}
|
|
|
1515
1557
|
${children}`;
|
|
1516
1558
|
}
|
|
1517
1559
|
elementAttrEmitter = {
|
|
1518
|
-
emitLiteral: (value, name) => `${name}="${value.value}"`,
|
|
1560
|
+
emitLiteral: (value, name) => `${name}="${escapeHtml(value.value)}"`,
|
|
1519
1561
|
emitExpression: (value, name) => {
|
|
1520
1562
|
if (name === "style") {
|
|
1521
1563
|
const css = this.tryLowerStyleObject(value.expr);
|
|
@@ -1609,9 +1651,10 @@ ${children}`;
|
|
|
1609
1651
|
let attrName;
|
|
1610
1652
|
if (attr.name === "className")
|
|
1611
1653
|
attrName = "class";
|
|
1612
|
-
else if (attr.name === "key")
|
|
1613
|
-
|
|
1614
|
-
|
|
1654
|
+
else if (attr.name === "key") {
|
|
1655
|
+
const depth = this.currentLoopKeyDepth;
|
|
1656
|
+
attrName = depth > 0 ? `data-key-${depth}` : "data-key";
|
|
1657
|
+
} else
|
|
1615
1658
|
attrName = attr.name;
|
|
1616
1659
|
const lowered = emitAttrValue(attr.value, this.elementAttrEmitter, attrName);
|
|
1617
1660
|
if (lowered)
|
|
@@ -1801,27 +1844,10 @@ var conformancePins = {
|
|
|
1801
1844
|
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2038" }
|
|
1802
1845
|
],
|
|
1803
1846
|
"array-map-function-reference": [{ code: "BF101", severity: "error" }],
|
|
1804
|
-
"dangerous-inner-html": [{ code: "BF101", severity: "error" }]
|
|
1805
|
-
"string-replaceall": [{ code: "BF101", severity: "error" }]
|
|
1847
|
+
"dangerous-inner-html": [{ code: "BF101", severity: "error" }]
|
|
1806
1848
|
};
|
|
1807
1849
|
// src/render-divergences.ts
|
|
1808
|
-
var renderDivergences = {
|
|
1809
|
-
"arithmetic-text": "`(count() + 2) * 3` renders 10 instead of 18 — the parenthesised sub-expression loses its grouping (silent wrong arithmetic)",
|
|
1810
|
-
"string-concat-plus": "`'Hello, ' + name` renders \"0\" — Perl's numeric `+` coerces the strings; JS string-concat `+` needs Perl's `.`",
|
|
1811
|
-
"string-length-text": "`.length` on a STRING prop diverges (array-length lowering misapplied to a scalar)",
|
|
1812
|
-
"number-tofixed": "the literal `¥` in template text reaches the output as U+FFFD — a UTF-8 encoding gap for non-ASCII literal text adjacent to a dynamic slot",
|
|
1813
|
-
"html-entity-text": "`©` in JSX literal text: Hono decodes to `©`, this adapter re-emits the raw entity — same DOM, different bytes",
|
|
1814
|
-
"math-methods": "Math.min/max/abs over a signal render empty (only Math.floor is in the template-primitive registry)",
|
|
1815
|
-
"boolean-attr-literals": 'camelCase boolean alias `readOnly`: Hono SSRs `readOnly="true"`, this adapter emits bare presence',
|
|
1816
|
-
"camelcase-attributes": "`htmlFor` is not lowered to `for` (Hono maps it)",
|
|
1817
|
-
"static-attr-escape": 'static attribute values are not HTML-escaped (`title="Fish & Chips"` emitted raw; Hono escapes)',
|
|
1818
|
-
"svg-icon": "SVG camelCase presentation attrs (`strokeWidth`, `strokeLinecap`) pass through unmapped; Hono lowers to kebab-case",
|
|
1819
|
-
"object-entries-map": "`Object.entries(prop).map(([k, v]) => …)` renders an EMPTY list — the object-shaped prop silently produces zero iterations",
|
|
1820
|
-
"nested-loop-outer-binding": "nested-loop inner items carry `data-key` where the reference emits the depth-suffixed `data-key-1`",
|
|
1821
|
-
"jsx-element-prop": "a JSX element passed as a NON-children prop renders an empty slot — the element value is silently dropped",
|
|
1822
|
-
"string-slice": "`.slice()` on a STRING misfires through the array slice helper",
|
|
1823
|
-
"string-trim-sided": "`.trimStart()` / `.trimEnd()` render empty (no lowering)"
|
|
1824
|
-
};
|
|
1850
|
+
var renderDivergences = {};
|
|
1825
1851
|
export {
|
|
1826
1852
|
renderDivergences,
|
|
1827
1853
|
mojoAdapter,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-divergences.d.ts","sourceRoot":"","sources":["../src/render-divergences.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD,eAAO,MAAM,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"render-divergences.d.ts","sourceRoot":"","sources":["../src/render-divergences.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD,eAAO,MAAM,iBAAiB,EAAE,iBAAsB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxE;AAkCD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,OAAO,iBAAiB,EAAE,eAAe,CAAA;IAClD,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxE;AAkCD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,OAAO,iBAAiB,EAAE,eAAe,CAAA;IAClD,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA6NjF;AAqWD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAuBT"}
|