@barefootjs/mojolicious 0.18.5 → 0.18.7

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.
@@ -19,7 +19,12 @@ import {
19
19
  queryHrefArgs,
20
20
  isValidHelperId,
21
21
  sortComparatorFromArrow as sortComparatorFromArrow2,
22
- isLowerableLoopDestructure
22
+ isLowerableLoopDestructure,
23
+ isDangerousInnerHtmlAttr,
24
+ resolveDangerousInnerHtml,
25
+ dangerousInnerHtmlMetacharViolation,
26
+ dangerousInnerHtmlDiagnostic,
27
+ resolveStaticLoopSource
23
28
  } from "@barefootjs/jsx";
24
29
 
25
30
  // src/adapter/boolean-result.ts
@@ -338,6 +343,42 @@ function renderFlatMethod(recv, depth, emit) {
338
343
  return `bf->flat(${recv}, ${d})`;
339
344
  }
340
345
 
346
+ // src/adapter/lib/static-value.ts
347
+ function escapePerlSingleQuote2(s) {
348
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
349
+ }
350
+ function staticValueToPerl(value) {
351
+ if (value === null || value === undefined)
352
+ return "undef";
353
+ if (typeof value === "boolean")
354
+ return null;
355
+ if (typeof value === "number")
356
+ return String(value);
357
+ if (typeof value === "string")
358
+ return `'${escapePerlSingleQuote2(value)}'`;
359
+ if (Array.isArray(value)) {
360
+ const items = [];
361
+ for (const el of value) {
362
+ const serialized = staticValueToPerl(el);
363
+ if (serialized === null)
364
+ return null;
365
+ items.push(serialized);
366
+ }
367
+ return `[${items.join(", ")}]`;
368
+ }
369
+ if (typeof value === "object") {
370
+ const entries = [];
371
+ for (const [key, val] of Object.entries(value)) {
372
+ const serialized = staticValueToPerl(val);
373
+ if (serialized === null)
374
+ return null;
375
+ entries.push(`${perlHashKey(key)} => ${serialized}`);
376
+ }
377
+ return `{ ${entries.join(", ")} }`;
378
+ }
379
+ return null;
380
+ }
381
+
341
382
  // src/adapter/expr/emitters.ts
342
383
  import {
343
384
  groupBinaryOperand,
@@ -934,6 +975,9 @@ function generateDerivedMemoSeed(ctx, ir) {
934
975
  ` : "";
935
976
  }
936
977
 
978
+ // src/adapter/props/prop-classes.ts
979
+ import { collectLoopBoundNames } from "@barefootjs/jsx";
980
+
937
981
  // src/adapter/value/parsed-literal.ts
938
982
  function isStringTypeInfo(type) {
939
983
  return type?.kind === "primitive" && type.primitive === "string";
@@ -970,6 +1014,12 @@ function collectStringValueNames(ir) {
970
1014
  if (isStringTypeInfo(p.type))
971
1015
  names.add(p.name);
972
1016
  }
1017
+ for (const c of ir.metadata.localConstants) {
1018
+ if (isStringTypeInfo(c.type ?? undefined) || isBareStringLiteral(c.value))
1019
+ names.add(c.name);
1020
+ }
1021
+ for (const bound of collectLoopBoundNames(ir))
1022
+ names.delete(bound);
973
1023
  return names;
974
1024
  }
975
1025
 
@@ -1200,7 +1250,8 @@ class MojoAdapter extends BaseAdapter {
1200
1250
  renderElement(element) {
1201
1251
  const tag = element.tag;
1202
1252
  const attrs = this.renderAttributes(element);
1203
- const children = this.renderChildren(element.children);
1253
+ const dangerousHtml = this.renderDangerousInnerHtml(element);
1254
+ const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
1204
1255
  let hydrationAttrs = "";
1205
1256
  if (element.needsScope) {
1206
1257
  hydrationAttrs += ` ${this.renderScopeMarker("")}`;
@@ -1235,6 +1286,22 @@ class MojoAdapter extends BaseAdapter {
1235
1286
  }
1236
1287
  return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
1237
1288
  }
1289
+ renderDangerousInnerHtml(element) {
1290
+ const resolution = resolveDangerousInnerHtml(element);
1291
+ if (!resolution)
1292
+ return null;
1293
+ if (resolution.kind === "dynamic") {
1294
+ this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
1295
+ return "";
1296
+ }
1297
+ const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
1298
+ if (violation) {
1299
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
1300
+ this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
1301
+ return "";
1302
+ }
1303
+ return resolution.html;
1304
+ }
1238
1305
  renderExpression(expr) {
1239
1306
  if (expr.clientOnly) {
1240
1307
  if (expr.slotId) {
@@ -1242,7 +1309,7 @@ class MojoAdapter extends BaseAdapter {
1242
1309
  }
1243
1310
  return "";
1244
1311
  }
1245
- const perlExpr = this.convertExpressionToPerl(expr.expr);
1312
+ const perlExpr = this.convertExpressionToPerl(expr.expr, expr.parsed);
1246
1313
  if (expr.slotId) {
1247
1314
  return `<%== bf->text_start("${expr.slotId}") %><%= ${perlExpr} %><%== bf->text_end %>`;
1248
1315
  }
@@ -1334,8 +1401,12 @@ ${whenTrue}
1334
1401
  }
1335
1402
  });
1336
1403
  }
1404
+ const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
1405
+ isNameShadowed: (name) => this.loopBoundNames.has(name)
1406
+ });
1407
+ const staticArray = staticItems !== null ? staticValueToPerl(staticItems) : null;
1337
1408
  const arrayName = loop.array.trim();
1338
- if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
1409
+ if (staticArray === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
1339
1410
  const arrayConst = (this.localConstants ?? []).find((c) => c.name === arrayName);
1340
1411
  if (arrayConst && !arrayConst.isModule && this.resolveLiteralConst(arrayName) === null) {
1341
1412
  this.errors.push({
@@ -1349,7 +1420,7 @@ ${whenTrue}
1349
1420
  });
1350
1421
  }
1351
1422
  }
1352
- const rawArray = this.convertExpressionToPerl(loop.array);
1423
+ const rawArray = staticArray ?? this.convertExpressionToPerl(loop.array);
1353
1424
  let sortedHoist = null;
1354
1425
  let array = rawArray;
1355
1426
  if (loop.sortComparator) {
@@ -1612,7 +1683,7 @@ ${children}`;
1612
1683
  if (ternaryHashref !== null) {
1613
1684
  return `<%== bf->spread_attrs(${ternaryHashref}) %>`;
1614
1685
  }
1615
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
1686
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.loopBoundNames.has(trimmed)) {
1616
1687
  const localConst = this.localConstants.find((c) => c.name === trimmed && !c.isModule);
1617
1688
  if (localConst?.value !== undefined) {
1618
1689
  const initTrimmed = localConst.value.trim();
@@ -1648,6 +1719,8 @@ ${children}`;
1648
1719
  for (const attr of element.attrs) {
1649
1720
  if (attr.clientOnly)
1650
1721
  continue;
1722
+ if (isDangerousInnerHtmlAttr(attr))
1723
+ continue;
1651
1724
  let attrName;
1652
1725
  if (attr.name === "className")
1653
1726
  attrName = "class";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Serialize a compile-time-evaluated JS value (`@barefootjs/jsx`'s
3
+ * `evaluateStaticLiteral`/`resolveStaticLoopSource`, #2208) into a native
4
+ * Perl literal. Used to inline a fully-static loop source (an inline array
5
+ * literal, or a function-scope local const with a static initializer)
6
+ * directly in the loop-bound expression, rather than requiring a bound
7
+ * template variable.
8
+ *
9
+ * Booleans deliberately return `null` (defer to the caller's BF101
10
+ * refusal) rather than baking `1`/`''` — Perl has no boolean literal, and
11
+ * that would diverge from JS's `String(true) === "true"` at render.
12
+ *
13
+ * Returns `null` for a value this adapter can't represent as a literal —
14
+ * the caller falls back to its existing BF101 refusal instead of guessing.
15
+ */
16
+ export declare function staticValueToPerl(value: unknown): string | null;
17
+ //# sourceMappingURL=static-value.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-value.d.ts","sourceRoot":"","sources":["../../../src/adapter/lib/static-value.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAwB/D"}
@@ -165,6 +165,14 @@ export declare class MojoAdapter extends BaseAdapter implements IRNodeEmitter<Mo
165
165
  * quoted string literal (`const totalPages = 5`, #1897 pagination) —
166
166
  * function-scope consts never reach the per-render stash, so a bare
167
167
  * `$totalPages` faults under strict mode.
168
+ *
169
+ * The `loopBoundNames` guard also covers the #2221 hazard (a loop
170
+ * callback's own param shadowing this outer const's name): unlike the
171
+ * Twig-family adapters' coarse, whole-component `collectLoopBoundNames(ir)`
172
+ * static set, this adapter's `loopBoundNames` is a LIVE ref-counted map
173
+ * `renderLoop` populates/depopulates as it descends/ascends into each
174
+ * loop body (#1749) — so it's already scope-precise for this call site;
175
+ * no separate `staticLoopSourceBoundNames`-style field is needed here.
168
176
  */
169
177
  private resolveLiteralConst;
170
178
  private resolveStaticRecordLiteral;
@@ -218,6 +226,13 @@ export declare class MojoAdapter extends BaseAdapter implements IRNodeEmitter<Mo
218
226
  private isClientOnlyContextIdentifier;
219
227
  emitAsync(node: IRAsync, _ctx: MojoRenderCtx, _emit: EmitIRNode<MojoRenderCtx>): string;
220
228
  renderElement(element: IRElement): string;
229
+ /**
230
+ * `dangerouslySetInnerHTML={{ __html: '...' }}` (#2207) — see the Blade
231
+ * adapter's identical helper for the full rationale. `null` means the
232
+ * attribute is absent (caller falls through to normal `renderChildren`);
233
+ * a non-`null` string (possibly `''`) replaces the children outright.
234
+ */
235
+ private renderDangerousInnerHtml;
221
236
  renderExpression(expr: IRExpression): string;
222
237
  renderConditional(cond: IRConditional): string;
223
238
  private renderNodeOrNull;
@@ -1 +1 @@
1
- {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@
6
6
  * prop/name sets the adapter consults during lowering. Mirror of the Go
7
7
  * adapter's `props/prop-types.ts`. No adapter instance state.
8
8
  */
9
- import type { ComponentIR } from '@barefootjs/jsx';
9
+ import { type ComponentIR } from '@barefootjs/jsx';
10
10
  /**
11
11
  * (#1971) SSR-resolvable context-value names: props, signal getters, memos.
12
12
  * A `<Ctx.Provider value>` member NOT in this set is a client-only function
@@ -38,11 +38,28 @@ export declare function collectBooleanTypedProps(ir: ComponentIR): Set<string>;
38
38
  */
39
39
  export declare function collectNullableOptionalProps(ir: ComponentIR): Set<string>;
40
40
  /**
41
- * String-typed signals and props, so equality comparisons against them lower
42
- * to `eq`/`ne` (#1672). A signal is string-typed when its inferred type is
43
- * `string` (the analyzer infers this from a string-literal initial value) or,
44
- * defensively, when its initial value is a bare string literal; a prop when
45
- * its annotated type is `string`.
41
+ * String-typed signals, props, and same-file local consts, so equality
42
+ * comparisons against them lower to `eq`/`ne` (#1672) and `+` concatenation
43
+ * against them lowers to Perl's `.` instead of numeric `+` (#2163, #2212 —
44
+ * `isStringConcatBinary`/`isStringTypedOperand` in `@barefootjs/jsx`, which
45
+ * now also recognizes a bare identifier operand, not just a prop/getter/
46
+ * literal). A signal is string-typed when its inferred type is `string`
47
+ * (the analyzer infers this from a string-literal initial value) or,
48
+ * defensively, when its initial value is a bare string literal; a prop or
49
+ * local const when its annotated (or inferred) type is `string`.
50
+ *
51
+ * Excludes any name bound as a `.map()`/`.filter()` loop callback's item
52
+ * or index parameter ANYWHERE in the component (Fable review, #2212): the
53
+ * lookup below is a flat, scope-blind `Set<string>` with no notion of a
54
+ * loop param shadowing an outer string-typed binding of the same name
55
+ * (`items.map((name) => 1 + name)` inside a component that also has a
56
+ * string `name` prop) — left unguarded, that shadowed `name` would be
57
+ * misdetected as string-typed and `1 + name` would silently lower to `.`
58
+ * instead of staying numeric `+`. Subtracting loop-bound names is coarse
59
+ * (it also suppresses a genuinely non-shadowed same-named string
60
+ * elsewhere in the component) but safe: the suppressed case just falls
61
+ * back to today's numeric `+` — the same, already-accepted residual as an
62
+ * unresolvable operand — never silently-wrong output.
46
63
  */
47
64
  export declare function collectStringValueNames(ir: ComponentIR): Set<string>;
48
65
  //# sourceMappingURL=prop-classes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prop-classes.d.ts","sourceRoot":"","sources":["../../../src/adapter/props/prop-classes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAGlD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAMrE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAMrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAWzE;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAWpE"}
1
+ {"version":3,"file":"prop-classes.d.ts","sourceRoot":"","sources":["../../../src/adapter/props/prop-classes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAyB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAGzE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAMrE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAMrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAWzE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAepE"}
package/dist/build.js CHANGED
@@ -19,7 +19,12 @@ import {
19
19
  queryHrefArgs,
20
20
  isValidHelperId,
21
21
  sortComparatorFromArrow as sortComparatorFromArrow2,
22
- isLowerableLoopDestructure
22
+ isLowerableLoopDestructure,
23
+ isDangerousInnerHtmlAttr,
24
+ resolveDangerousInnerHtml,
25
+ dangerousInnerHtmlMetacharViolation,
26
+ dangerousInnerHtmlDiagnostic,
27
+ resolveStaticLoopSource
23
28
  } from "@barefootjs/jsx";
24
29
 
25
30
  // src/adapter/boolean-result.ts
@@ -338,6 +343,42 @@ function renderFlatMethod(recv, depth, emit) {
338
343
  return `bf->flat(${recv}, ${d})`;
339
344
  }
340
345
 
346
+ // src/adapter/lib/static-value.ts
347
+ function escapePerlSingleQuote2(s) {
348
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
349
+ }
350
+ function staticValueToPerl(value) {
351
+ if (value === null || value === undefined)
352
+ return "undef";
353
+ if (typeof value === "boolean")
354
+ return null;
355
+ if (typeof value === "number")
356
+ return String(value);
357
+ if (typeof value === "string")
358
+ return `'${escapePerlSingleQuote2(value)}'`;
359
+ if (Array.isArray(value)) {
360
+ const items = [];
361
+ for (const el of value) {
362
+ const serialized = staticValueToPerl(el);
363
+ if (serialized === null)
364
+ return null;
365
+ items.push(serialized);
366
+ }
367
+ return `[${items.join(", ")}]`;
368
+ }
369
+ if (typeof value === "object") {
370
+ const entries = [];
371
+ for (const [key, val] of Object.entries(value)) {
372
+ const serialized = staticValueToPerl(val);
373
+ if (serialized === null)
374
+ return null;
375
+ entries.push(`${perlHashKey(key)} => ${serialized}`);
376
+ }
377
+ return `{ ${entries.join(", ")} }`;
378
+ }
379
+ return null;
380
+ }
381
+
341
382
  // src/adapter/expr/emitters.ts
342
383
  import {
343
384
  groupBinaryOperand,
@@ -934,6 +975,9 @@ function generateDerivedMemoSeed(ctx, ir) {
934
975
  ` : "";
935
976
  }
936
977
 
978
+ // src/adapter/props/prop-classes.ts
979
+ import { collectLoopBoundNames } from "@barefootjs/jsx";
980
+
937
981
  // src/adapter/value/parsed-literal.ts
938
982
  function isStringTypeInfo(type) {
939
983
  return type?.kind === "primitive" && type.primitive === "string";
@@ -970,6 +1014,12 @@ function collectStringValueNames(ir) {
970
1014
  if (isStringTypeInfo(p.type))
971
1015
  names.add(p.name);
972
1016
  }
1017
+ for (const c of ir.metadata.localConstants) {
1018
+ if (isStringTypeInfo(c.type ?? undefined) || isBareStringLiteral(c.value))
1019
+ names.add(c.name);
1020
+ }
1021
+ for (const bound of collectLoopBoundNames(ir))
1022
+ names.delete(bound);
973
1023
  return names;
974
1024
  }
975
1025
 
@@ -1200,7 +1250,8 @@ class MojoAdapter extends BaseAdapter {
1200
1250
  renderElement(element) {
1201
1251
  const tag = element.tag;
1202
1252
  const attrs = this.renderAttributes(element);
1203
- const children = this.renderChildren(element.children);
1253
+ const dangerousHtml = this.renderDangerousInnerHtml(element);
1254
+ const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
1204
1255
  let hydrationAttrs = "";
1205
1256
  if (element.needsScope) {
1206
1257
  hydrationAttrs += ` ${this.renderScopeMarker("")}`;
@@ -1235,6 +1286,22 @@ class MojoAdapter extends BaseAdapter {
1235
1286
  }
1236
1287
  return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
1237
1288
  }
1289
+ renderDangerousInnerHtml(element) {
1290
+ const resolution = resolveDangerousInnerHtml(element);
1291
+ if (!resolution)
1292
+ return null;
1293
+ if (resolution.kind === "dynamic") {
1294
+ this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
1295
+ return "";
1296
+ }
1297
+ const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
1298
+ if (violation) {
1299
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
1300
+ this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
1301
+ return "";
1302
+ }
1303
+ return resolution.html;
1304
+ }
1238
1305
  renderExpression(expr) {
1239
1306
  if (expr.clientOnly) {
1240
1307
  if (expr.slotId) {
@@ -1242,7 +1309,7 @@ class MojoAdapter extends BaseAdapter {
1242
1309
  }
1243
1310
  return "";
1244
1311
  }
1245
- const perlExpr = this.convertExpressionToPerl(expr.expr);
1312
+ const perlExpr = this.convertExpressionToPerl(expr.expr, expr.parsed);
1246
1313
  if (expr.slotId) {
1247
1314
  return `<%== bf->text_start("${expr.slotId}") %><%= ${perlExpr} %><%== bf->text_end %>`;
1248
1315
  }
@@ -1334,8 +1401,12 @@ ${whenTrue}
1334
1401
  }
1335
1402
  });
1336
1403
  }
1404
+ const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
1405
+ isNameShadowed: (name) => this.loopBoundNames.has(name)
1406
+ });
1407
+ const staticArray = staticItems !== null ? staticValueToPerl(staticItems) : null;
1337
1408
  const arrayName = loop.array.trim();
1338
- if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
1409
+ if (staticArray === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
1339
1410
  const arrayConst = (this.localConstants ?? []).find((c) => c.name === arrayName);
1340
1411
  if (arrayConst && !arrayConst.isModule && this.resolveLiteralConst(arrayName) === null) {
1341
1412
  this.errors.push({
@@ -1349,7 +1420,7 @@ ${whenTrue}
1349
1420
  });
1350
1421
  }
1351
1422
  }
1352
- const rawArray = this.convertExpressionToPerl(loop.array);
1423
+ const rawArray = staticArray ?? this.convertExpressionToPerl(loop.array);
1353
1424
  let sortedHoist = null;
1354
1425
  let array = rawArray;
1355
1426
  if (loop.sortComparator) {
@@ -1612,7 +1683,7 @@ ${children}`;
1612
1683
  if (ternaryHashref !== null) {
1613
1684
  return `<%== bf->spread_attrs(${ternaryHashref}) %>`;
1614
1685
  }
1615
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
1686
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.loopBoundNames.has(trimmed)) {
1616
1687
  const localConst = this.localConstants.find((c) => c.name === trimmed && !c.isModule);
1617
1688
  if (localConst?.value !== undefined) {
1618
1689
  const initTrimmed = localConst.value.trim();
@@ -1648,6 +1719,8 @@ ${children}`;
1648
1719
  for (const attr of element.attrs) {
1649
1720
  if (attr.clientOnly)
1650
1721
  continue;
1722
+ if (isDangerousInnerHtmlAttr(attr))
1723
+ continue;
1651
1724
  let attrName;
1652
1725
  if (attr.name === "className")
1653
1726
  attrName = "class";
@@ -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,eAqI7B,CAAA"}
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,eAmI7B,CAAA"}
package/dist/index.js CHANGED
@@ -19,7 +19,12 @@ import {
19
19
  queryHrefArgs,
20
20
  isValidHelperId,
21
21
  sortComparatorFromArrow as sortComparatorFromArrow2,
22
- isLowerableLoopDestructure
22
+ isLowerableLoopDestructure,
23
+ isDangerousInnerHtmlAttr,
24
+ resolveDangerousInnerHtml,
25
+ dangerousInnerHtmlMetacharViolation,
26
+ dangerousInnerHtmlDiagnostic,
27
+ resolveStaticLoopSource
23
28
  } from "@barefootjs/jsx";
24
29
 
25
30
  // src/adapter/boolean-result.ts
@@ -338,6 +343,42 @@ function renderFlatMethod(recv, depth, emit) {
338
343
  return `bf->flat(${recv}, ${d})`;
339
344
  }
340
345
 
346
+ // src/adapter/lib/static-value.ts
347
+ function escapePerlSingleQuote2(s) {
348
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
349
+ }
350
+ function staticValueToPerl(value) {
351
+ if (value === null || value === undefined)
352
+ return "undef";
353
+ if (typeof value === "boolean")
354
+ return null;
355
+ if (typeof value === "number")
356
+ return String(value);
357
+ if (typeof value === "string")
358
+ return `'${escapePerlSingleQuote2(value)}'`;
359
+ if (Array.isArray(value)) {
360
+ const items = [];
361
+ for (const el of value) {
362
+ const serialized = staticValueToPerl(el);
363
+ if (serialized === null)
364
+ return null;
365
+ items.push(serialized);
366
+ }
367
+ return `[${items.join(", ")}]`;
368
+ }
369
+ if (typeof value === "object") {
370
+ const entries = [];
371
+ for (const [key, val] of Object.entries(value)) {
372
+ const serialized = staticValueToPerl(val);
373
+ if (serialized === null)
374
+ return null;
375
+ entries.push(`${perlHashKey(key)} => ${serialized}`);
376
+ }
377
+ return `{ ${entries.join(", ")} }`;
378
+ }
379
+ return null;
380
+ }
381
+
341
382
  // src/adapter/expr/emitters.ts
342
383
  import {
343
384
  groupBinaryOperand,
@@ -934,6 +975,9 @@ function generateDerivedMemoSeed(ctx, ir) {
934
975
  ` : "";
935
976
  }
936
977
 
978
+ // src/adapter/props/prop-classes.ts
979
+ import { collectLoopBoundNames } from "@barefootjs/jsx";
980
+
937
981
  // src/adapter/value/parsed-literal.ts
938
982
  function isStringTypeInfo(type) {
939
983
  return type?.kind === "primitive" && type.primitive === "string";
@@ -970,6 +1014,12 @@ function collectStringValueNames(ir) {
970
1014
  if (isStringTypeInfo(p.type))
971
1015
  names.add(p.name);
972
1016
  }
1017
+ for (const c of ir.metadata.localConstants) {
1018
+ if (isStringTypeInfo(c.type ?? undefined) || isBareStringLiteral(c.value))
1019
+ names.add(c.name);
1020
+ }
1021
+ for (const bound of collectLoopBoundNames(ir))
1022
+ names.delete(bound);
973
1023
  return names;
974
1024
  }
975
1025
 
@@ -1200,7 +1250,8 @@ class MojoAdapter extends BaseAdapter {
1200
1250
  renderElement(element) {
1201
1251
  const tag = element.tag;
1202
1252
  const attrs = this.renderAttributes(element);
1203
- const children = this.renderChildren(element.children);
1253
+ const dangerousHtml = this.renderDangerousInnerHtml(element);
1254
+ const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
1204
1255
  let hydrationAttrs = "";
1205
1256
  if (element.needsScope) {
1206
1257
  hydrationAttrs += ` ${this.renderScopeMarker("")}`;
@@ -1235,6 +1286,22 @@ class MojoAdapter extends BaseAdapter {
1235
1286
  }
1236
1287
  return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
1237
1288
  }
1289
+ renderDangerousInnerHtml(element) {
1290
+ const resolution = resolveDangerousInnerHtml(element);
1291
+ if (!resolution)
1292
+ return null;
1293
+ if (resolution.kind === "dynamic") {
1294
+ this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
1295
+ return "";
1296
+ }
1297
+ const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
1298
+ if (violation) {
1299
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
1300
+ this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
1301
+ return "";
1302
+ }
1303
+ return resolution.html;
1304
+ }
1238
1305
  renderExpression(expr) {
1239
1306
  if (expr.clientOnly) {
1240
1307
  if (expr.slotId) {
@@ -1242,7 +1309,7 @@ class MojoAdapter extends BaseAdapter {
1242
1309
  }
1243
1310
  return "";
1244
1311
  }
1245
- const perlExpr = this.convertExpressionToPerl(expr.expr);
1312
+ const perlExpr = this.convertExpressionToPerl(expr.expr, expr.parsed);
1246
1313
  if (expr.slotId) {
1247
1314
  return `<%== bf->text_start("${expr.slotId}") %><%= ${perlExpr} %><%== bf->text_end %>`;
1248
1315
  }
@@ -1334,8 +1401,12 @@ ${whenTrue}
1334
1401
  }
1335
1402
  });
1336
1403
  }
1404
+ const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
1405
+ isNameShadowed: (name) => this.loopBoundNames.has(name)
1406
+ });
1407
+ const staticArray = staticItems !== null ? staticValueToPerl(staticItems) : null;
1337
1408
  const arrayName = loop.array.trim();
1338
- if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
1409
+ if (staticArray === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
1339
1410
  const arrayConst = (this.localConstants ?? []).find((c) => c.name === arrayName);
1340
1411
  if (arrayConst && !arrayConst.isModule && this.resolveLiteralConst(arrayName) === null) {
1341
1412
  this.errors.push({
@@ -1349,7 +1420,7 @@ ${whenTrue}
1349
1420
  });
1350
1421
  }
1351
1422
  }
1352
- const rawArray = this.convertExpressionToPerl(loop.array);
1423
+ const rawArray = staticArray ?? this.convertExpressionToPerl(loop.array);
1353
1424
  let sortedHoist = null;
1354
1425
  let array = rawArray;
1355
1426
  if (loop.sortComparator) {
@@ -1612,7 +1683,7 @@ ${children}`;
1612
1683
  if (ternaryHashref !== null) {
1613
1684
  return `<%== bf->spread_attrs(${ternaryHashref}) %>`;
1614
1685
  }
1615
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
1686
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed) && !this.loopBoundNames.has(trimmed)) {
1616
1687
  const localConst = this.localConstants.find((c) => c.name === trimmed && !c.isModule);
1617
1688
  if (localConst?.value !== undefined) {
1618
1689
  const initTrimmed = localConst.value.trim();
@@ -1648,6 +1719,8 @@ ${children}`;
1648
1719
  for (const attr of element.attrs) {
1649
1720
  if (attr.clientOnly)
1650
1721
  continue;
1722
+ if (isDangerousInnerHtmlAttr(attr))
1723
+ continue;
1651
1724
  let attrName;
1652
1725
  if (attr.name === "className")
1653
1726
  attrName = "class";
@@ -1832,19 +1905,12 @@ Options:
1832
1905
  var mojoAdapter = new MojoAdapter;
1833
1906
  // src/conformance-pins.ts
1834
1907
  var conformancePins = {
1835
- "static-array-children": [{ code: "BF103", severity: "error" }],
1836
- "todo-app": [{ code: "BF103", severity: "error" }],
1837
- "todo-app-ssr": [{ code: "BF103", severity: "error" }],
1838
1908
  "static-array-from-props": [{ code: "BF101", severity: "error" }],
1839
- "static-array-from-props-with-component": [
1840
- { code: "BF103", severity: "error" },
1841
- { code: "BF101", severity: "error" }
1842
- ],
1909
+ "static-array-from-props-with-component": [{ code: "BF101", severity: "error" }],
1843
1910
  "filter-nested-find-predicate": [
1844
1911
  { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2038" }
1845
1912
  ],
1846
- "array-map-function-reference": [{ code: "BF101", severity: "error" }],
1847
- "dangerous-inner-html": [{ code: "BF101", severity: "error" }]
1913
+ "dangerous-inner-html-dynamic": [{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2215" }]
1848
1914
  };
1849
1915
  // src/render-divergences.ts
1850
1916
  var renderDivergences = {};
@@ -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,iBAAsB,CAAA"}
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,iBAK/B,CAAA"}
@@ -42,9 +42,4 @@ export interface RenderOptions {
42
42
  componentName?: string;
43
43
  }
44
44
  export declare function renderMojoComponent(options: RenderOptions): Promise<string>;
45
- /**
46
- * Evaluate a signal initializer expression using provided props.
47
- * Handles patterns like: props.initial ?? 0, props.value, literal values.
48
- */
49
- export declare function evaluateSignalInit(expr: string, props?: Record<string, unknown>): unknown;
50
45
  //# sourceMappingURL=test-render.d.ts.map
@@ -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,CA6NjF;AAqWD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAuBT"}
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,CAoOjF"}
@@ -1,5 +1,5 @@
1
1
  package BarefootJS::Backend::Mojo;
2
- our $VERSION = "0.18.4";
2
+ our $VERSION = "0.18.5";
3
3
  use Mojo::Base -base, -signatures;
4
4
 
5
5
  use Mojo::ByteStream qw(b);