@barefootjs/erb 0.18.4 → 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.
package/dist/build.js CHANGED
@@ -187308,7 +187308,12 @@ import {
187308
187308
  prepareLoweringMatchers,
187309
187309
  queryHrefArgs,
187310
187310
  isValidHelperId,
187311
- sortComparatorFromArrow as sortComparatorFromArrow2
187311
+ sortComparatorFromArrow as sortComparatorFromArrow2,
187312
+ isDangerousInnerHtmlAttr,
187313
+ resolveDangerousInnerHtml,
187314
+ dangerousInnerHtmlMetacharViolation,
187315
+ dangerousInnerHtmlDiagnostic,
187316
+ resolveStaticLoopSource
187312
187317
  } from "@barefootjs/jsx";
187313
187318
 
187314
187319
  // src/adapter/boolean-result.ts
@@ -187371,7 +187376,7 @@ function isExplicitStringCall(expr) {
187371
187376
  }
187372
187377
 
187373
187378
  // src/adapter/erb-adapter.ts
187374
- import { BF_SLOT, BF_COND, BF_REGION } from "@barefootjs/shared";
187379
+ import { BF_SLOT, BF_COND, BF_REGION, escapeHtml } from "@barefootjs/shared";
187375
187380
 
187376
187381
  // src/adapter/lib/constants.ts
187377
187382
  var ERB_TEMPLATE_PRIMITIVES = {
@@ -187380,7 +187385,10 @@ var ERB_TEMPLATE_PRIMITIVES = {
187380
187385
  Number: { arity: 1, emit: (args) => `bf.number(${args[0]})` },
187381
187386
  "Math.floor": { arity: 1, emit: (args) => `bf.floor(${args[0]})` },
187382
187387
  "Math.ceil": { arity: 1, emit: (args) => `bf.ceil(${args[0]})` },
187383
- "Math.round": { arity: 1, emit: (args) => `bf.round(${args[0]})` }
187388
+ "Math.round": { arity: 1, emit: (args) => `bf.round(${args[0]})` },
187389
+ "Math.min": { arity: 2, emit: (args) => `bf.min(${args[0]}, ${args[1]})` },
187390
+ "Math.max": { arity: 2, emit: (args) => `bf.max(${args[0]}, ${args[1]})` },
187391
+ "Math.abs": { arity: 1, emit: (args) => `bf.abs(${args[0]})` }
187384
187392
  };
187385
187393
  var ERB_PRIMITIVE_EMIT_MAP = Object.fromEntries(Object.entries(ERB_TEMPLATE_PRIMITIVES).map(([k, v]) => [k, v.emit]));
187386
187394
 
@@ -187548,6 +187556,12 @@ function renderArrayMethod(method, object, args, emit) {
187548
187556
  const recv = emit(object);
187549
187557
  return `bf.trim(${recv})`;
187550
187558
  }
187559
+ case "trimStart":
187560
+ case "trimEnd": {
187561
+ const fn = method === "trimStart" ? "trim_start" : "trim_end";
187562
+ const recv = emit(object);
187563
+ return `bf.${fn}(${recv})`;
187564
+ }
187551
187565
  case "toFixed": {
187552
187566
  const recv = emit(object);
187553
187567
  const digits = args.length >= 1 ? emit(args[0]) : "0";
@@ -187581,6 +187595,12 @@ function renderArrayMethod(method, object, args, emit) {
187581
187595
  const newS = emit(args[1]);
187582
187596
  return `bf.replace(${recv}, ${oldS}, ${newS})`;
187583
187597
  }
187598
+ case "replaceAll": {
187599
+ const recv = emit(object);
187600
+ const oldS = emit(args[0]);
187601
+ const newS = emit(args[1]);
187602
+ return `bf.replace_all(${recv}, ${oldS}, ${newS})`;
187603
+ }
187584
187604
  case "repeat": {
187585
187605
  const recv = emit(object);
187586
187606
  const count = args.length === 0 ? "0" : emit(args[0]);
@@ -187678,6 +187698,39 @@ function renderFlatMethod(recv, depth, emit) {
187678
187698
  return `bf.flat(${recv}, ${d})`;
187679
187699
  }
187680
187700
 
187701
+ // src/adapter/lib/static-value.ts
187702
+ function staticValueToRuby(value) {
187703
+ if (value === null || value === undefined)
187704
+ return "nil";
187705
+ if (typeof value === "boolean")
187706
+ return value ? "true" : "false";
187707
+ if (typeof value === "number")
187708
+ return String(value);
187709
+ if (typeof value === "string")
187710
+ return rubyStringLiteral(value);
187711
+ if (Array.isArray(value)) {
187712
+ const items = [];
187713
+ for (const el of value) {
187714
+ const serialized = staticValueToRuby(el);
187715
+ if (serialized === null)
187716
+ return null;
187717
+ items.push(serialized);
187718
+ }
187719
+ return `[${items.join(", ")}]`;
187720
+ }
187721
+ if (typeof value === "object") {
187722
+ const entries = [];
187723
+ for (const [key, val] of Object.entries(value)) {
187724
+ const serialized = staticValueToRuby(val);
187725
+ if (serialized === null)
187726
+ return null;
187727
+ entries.push(`${rubySymbolKey(key)} ${serialized}`);
187728
+ }
187729
+ return `{ ${entries.join(", ")} }`;
187730
+ }
187731
+ return null;
187732
+ }
187733
+
187681
187734
  // src/adapter/expr/emitters.ts
187682
187735
  import {
187683
187736
  emitParsedExpr,
@@ -187723,16 +187776,18 @@ class ErbFilterEmitter {
187723
187776
  isLoopBoundOuter;
187724
187777
  isStringName;
187725
187778
  onUnsupported;
187726
- constructor(param, localVarMap, isLoopBoundOuter = () => false, isStringName = unusedIsStringName, onUnsupported) {
187779
+ renderParamAs;
187780
+ constructor(param, localVarMap, isLoopBoundOuter = () => false, isStringName = unusedIsStringName, onUnsupported, renderParamAs = rubyLocal(param)) {
187727
187781
  this.param = param;
187728
187782
  this.localVarMap = localVarMap;
187729
187783
  this.isLoopBoundOuter = isLoopBoundOuter;
187730
187784
  this.isStringName = isStringName;
187731
187785
  this.onUnsupported = onUnsupported;
187786
+ this.renderParamAs = renderParamAs;
187732
187787
  }
187733
187788
  identifier(name) {
187734
187789
  if (name === this.param)
187735
- return rubyLocal(this.param);
187790
+ return this.renderParamAs;
187736
187791
  const signal = this.localVarMap.get(name);
187737
187792
  if (signal)
187738
187793
  return `v[${rubySymbolLiteral(signal)}]`;
@@ -187749,9 +187804,11 @@ class ErbFilterEmitter {
187749
187804
  return "nil";
187750
187805
  return String(value);
187751
187806
  }
187752
- member(object, property, _computed, emit) {
187807
+ member(object, property, _computed, optional, emit) {
187753
187808
  if (property === "length")
187754
187809
  return `${emit(object)}.length`;
187810
+ if (optional)
187811
+ return `${emit(object)}&.[](${rubySymbolLiteral(property)})`;
187755
187812
  return `${emit(object)}[${rubySymbolLiteral(property)}]`;
187756
187813
  }
187757
187814
  indexAccess(object, index, emit) {
@@ -187873,7 +187930,7 @@ class ErbTopLevelEmitter {
187873
187930
  return "nil";
187874
187931
  return String(value);
187875
187932
  }
187876
- member(object, property, _computed, emit) {
187933
+ member(object, property, _computed, optional, emit) {
187877
187934
  if (object.kind === "identifier" && object.name === "props") {
187878
187935
  return `v[${rubySymbolLiteral(property)}]`;
187879
187936
  }
@@ -187885,6 +187942,8 @@ class ErbTopLevelEmitter {
187885
187942
  const obj = emit(object);
187886
187943
  if (property === "length")
187887
187944
  return `${obj}.length`;
187945
+ if (optional)
187946
+ return `${obj}&.[](${rubySymbolLiteral(property)})`;
187888
187947
  return `${obj}[${rubySymbolLiteral(property)}]`;
187889
187948
  }
187890
187949
  indexAccess(object, index, emit) {
@@ -188316,6 +188375,7 @@ class ErbAdapter extends BaseAdapter {
188316
188375
  options;
188317
188376
  errors = [];
188318
188377
  inLoop = false;
188378
+ currentLoopKeyDepth = 0;
188319
188379
  propsObjectName = null;
188320
188380
  propsParams = [];
188321
188381
  booleanTypedProps = new Set;
@@ -188456,7 +188516,7 @@ class ErbAdapter extends BaseAdapter {
188456
188516
  return this.renderElement(node);
188457
188517
  }
188458
188518
  emitText(node) {
188459
- return node.value;
188519
+ return escapeHtml(node.value);
188460
188520
  }
188461
188521
  emitExpression(node) {
188462
188522
  return this.renderExpression(node);
@@ -188527,7 +188587,8 @@ class ErbAdapter extends BaseAdapter {
188527
188587
  renderElement(element) {
188528
188588
  const tag = element.tag;
188529
188589
  const attrs = this.renderAttributes(element);
188530
- const children = this.renderChildren(element.children);
188590
+ const dangerousHtml = this.renderDangerousInnerHtml(element);
188591
+ const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
188531
188592
  let hydrationAttrs = "";
188532
188593
  if (element.needsScope) {
188533
188594
  hydrationAttrs += ` ${this.renderScopeMarker("")}`;
@@ -188562,6 +188623,22 @@ class ErbAdapter extends BaseAdapter {
188562
188623
  }
188563
188624
  return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
188564
188625
  }
188626
+ renderDangerousInnerHtml(element) {
188627
+ const resolution = resolveDangerousInnerHtml(element);
188628
+ if (!resolution)
188629
+ return null;
188630
+ if (resolution.kind === "dynamic") {
188631
+ this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
188632
+ return "";
188633
+ }
188634
+ const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
188635
+ if (violation) {
188636
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
188637
+ this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
188638
+ return "";
188639
+ }
188640
+ return resolution.html;
188641
+ }
188565
188642
  renderExpression(expr) {
188566
188643
  if (expr.clientOnly) {
188567
188644
  if (expr.slotId) {
@@ -188569,7 +188646,7 @@ class ErbAdapter extends BaseAdapter {
188569
188646
  }
188570
188647
  return "";
188571
188648
  }
188572
- const rubyExpr = this.convertExpressionToRuby(expr.expr);
188649
+ const rubyExpr = this.convertExpressionToRuby(expr.expr, expr.parsed);
188573
188650
  const wrapped = this.isChildrenValueExpr(expr) ? rubyExpr : `bf.h(${rubyExpr})`;
188574
188651
  if (expr.slotId) {
188575
188652
  return `<%= bf.text_start("${expr.slotId}") %><%= ${wrapped} %><%= bf.text_end %>`;
@@ -188678,7 +188755,11 @@ ${whenTrue}
188678
188755
  }
188679
188756
  });
188680
188757
  }
188681
- if (loop.arrayParsed?.kind === "identifier") {
188758
+ const staticItems = resolveStaticLoopSource(loop.arrayParsed, this.localConstants, {
188759
+ isNameShadowed: (name) => this.loopBoundNames.has(name)
188760
+ });
188761
+ const staticArray = staticItems !== null ? staticValueToRuby(staticItems) : null;
188762
+ if (staticArray === null && loop.arrayParsed?.kind === "identifier") {
188682
188763
  const arrayName = loop.arrayParsed.name;
188683
188764
  const isUnresolvableLocalConst = !this.loopBoundNames.has(arrayName) && this.resolveModuleStringConst(arrayName) === null && this.resolveLiteralConst(arrayName) === null && this.localConstants.some((c) => c.name === arrayName && !c.isModule);
188684
188765
  if (isUnresolvableLocalConst) {
@@ -188688,7 +188769,7 @@ ${whenTrue}
188688
188769
  3. Precompute the value server-side and pass it in as a prop.`);
188689
188770
  }
188690
188771
  }
188691
- const rawArray = this.convertExpressionToRuby(loop.array);
188772
+ const rawArray = staticArray ?? this.convertExpressionToRuby(loop.array);
188692
188773
  let sortedHoist = null;
188693
188774
  let array = rawArray;
188694
188775
  if (loop.sortComparator) {
@@ -188697,13 +188778,16 @@ ${whenTrue}
188697
188778
  }
188698
188779
  const param = loop.param;
188699
188780
  const indexVar = loop.iterationShape === "keys" ? rubyLocal(param) : rubyLocal(loop.index ?? "_i");
188700
- const loopBound = loop.iterationShape === "keys" ? [param] : supportableDestructure ? ["__bf_item", ...(loop.paramBindings ?? []).map((b) => b.name), loop.index ?? "_i"] : [param, loop.index ?? "_i"];
188781
+ 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"];
188701
188782
  for (const n of loopBound) {
188702
188783
  this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
188703
188784
  }
188704
188785
  const prevInLoop = this.inLoop;
188705
188786
  this.inLoop = true;
188787
+ const prevLoopKeyDepth = this.currentLoopKeyDepth;
188788
+ this.currentLoopKeyDepth = loop.depth;
188706
188789
  const renderedChildren = this.renderChildren(loop.children);
188790
+ this.currentLoopKeyDepth = prevLoopKeyDepth;
188707
188791
  this.inLoop = prevInLoop;
188708
188792
  const children = loop.bodyIsItemConditional && loop.key ? `<%= bf.comment("loop-i:" + bf.string(${this.convertExpressionToRuby(loop.key)})) %>
188709
188793
  ${renderedChildren}` : renderedChildren;
@@ -188737,29 +188821,37 @@ ${renderedChildren}` : renderedChildren;
188737
188821
  }
188738
188822
  lines.push(`<%- ${sortedHoist} = ${sorted} -%>`);
188739
188823
  }
188740
- lines.push(`<%- (0...${array}.length).each do |${indexVar}| -%>`);
188741
- if (loop.iterationShape !== "keys") {
188742
- if (supportableDestructure) {
188743
- lines.push(`<%- __bf_item = ${array}[${indexVar}] -%>`);
188744
- for (const b of loop.paramBindings ?? []) {
188745
- const accessor = rubyAccessorFromSegments("__bf_item", b.segments ?? []);
188746
- if (b.rest?.kind === "array") {
188747
- lines.push(`<%- ${rubyLocal(b.name)} = bf.slice(${accessor}, ${b.rest.from}) -%>`);
188748
- } else if (b.rest?.kind === "object") {
188749
- const excludeSyms = b.rest.exclude.map((k) => rubySymbolLiteral(k.key)).join(", ");
188750
- lines.push(`<%- ${rubyLocal(b.name)} = ${accessor}.except(${excludeSyms}) -%>`);
188751
- } else {
188752
- lines.push(`<%- ${rubyLocal(b.name)} = ${accessor} -%>`);
188824
+ if (loop.objectIteration) {
188825
+ const method = loop.objectIteration === "entries" ? "each_pair" : loop.objectIteration === "keys" ? "each_key" : "each_value";
188826
+ const blockParams = loop.objectIteration === "entries" ? `${rubyLocal(loop.index ?? param)}, ${rubyLocal(param)}` : rubyLocal(param);
188827
+ lines.push(`<%- ${array}.${method} do |${blockParams}| -%>`);
188828
+ } else {
188829
+ lines.push(`<%- (0...${array}.length).each do |${indexVar}| -%>`);
188830
+ if (loop.iterationShape !== "keys") {
188831
+ if (supportableDestructure) {
188832
+ lines.push(`<%- __bf_item = ${array}[${indexVar}] -%>`);
188833
+ for (const b of loop.paramBindings ?? []) {
188834
+ const accessor = rubyAccessorFromSegments("__bf_item", b.segments ?? []);
188835
+ if (b.rest?.kind === "array") {
188836
+ lines.push(`<%- ${rubyLocal(b.name)} = bf.slice(${accessor}, ${b.rest.from}) -%>`);
188837
+ } else if (b.rest?.kind === "object") {
188838
+ const excludeSyms = b.rest.exclude.map((k) => rubySymbolLiteral(k.key)).join(", ");
188839
+ lines.push(`<%- ${rubyLocal(b.name)} = ${accessor}.except(${excludeSyms}) -%>`);
188840
+ } else {
188841
+ lines.push(`<%- ${rubyLocal(b.name)} = ${accessor} -%>`);
188842
+ }
188753
188843
  }
188844
+ } else {
188845
+ lines.push(`<%- ${rubyLocal(param)} = ${array}[${indexVar}] -%>`);
188754
188846
  }
188755
- } else {
188756
- lines.push(`<%- ${rubyLocal(param)} = ${array}[${indexVar}] -%>`);
188757
188847
  }
188758
188848
  }
188759
188849
  if (loop.filterPredicate) {
188760
188850
  let filterCond;
188761
188851
  if (loop.filterPredicate.predicate) {
188762
- filterCond = this.renderRubyFilterExpr(loop.filterPredicate.predicate, param);
188852
+ const filterOwnParam = loop.filterPredicate.param;
188853
+ const matchParam = filterOwnParam && !filterOwnParam.startsWith("[") && !filterOwnParam.startsWith("{") ? filterOwnParam : param;
188854
+ filterCond = this.renderRubyFilterExpr(loop.filterPredicate.predicate, matchParam, undefined, rubyLocal(param));
188763
188855
  } else {
188764
188856
  filterCond = "true";
188765
188857
  }
@@ -188805,9 +188897,23 @@ ${renderedChildren}` : renderedChildren;
188805
188897
  };
188806
188898
  renderComponent(comp) {
188807
188899
  const propParts = [];
188900
+ const namedSlotCaptures = [];
188808
188901
  for (const p of comp.props) {
188809
188902
  if ((p.name.match(/^on[A-Z]/) || p.name === "ref") && p.value.kind === "expression")
188810
188903
  continue;
188904
+ if (p.value.kind === "jsx-children" && p.name !== "children") {
188905
+ const prevInLoop = this.inLoop;
188906
+ this.inLoop = false;
188907
+ const slotBody = this.renderChildren(p.value.children);
188908
+ this.inLoop = prevInLoop;
188909
+ const suffix = `${this.childrenCaptureCounter++}`;
188910
+ const lenVar = `__bf_len_${suffix}`;
188911
+ const rawVar = `__bf_praw_${suffix}`;
188912
+ const capVar = `__bf_prop_${suffix}`;
188913
+ namedSlotCaptures.push(`<% ${lenVar} = _erbout.length %>${slotBody}<% ${rawVar} = _erbout.slice!(${lenVar}..); ${capVar} = bf.backend.mark_raw(${rawVar}) %>`);
188914
+ propParts.push(`${rubySymbolKey(p.name)} ${capVar}`);
188915
+ continue;
188916
+ }
188811
188917
  const lowered = emitAttrValue(p.value, this.componentPropEmitter, p.name);
188812
188918
  if (lowered)
188813
188919
  propParts.push(lowered);
@@ -188826,10 +188932,10 @@ ${renderedChildren}` : renderedChildren;
188826
188932
  const lenVar = `__bf_len_${suffix}`;
188827
188933
  const capVar = `__bf_children_${suffix}`;
188828
188934
  const propsHash2 = `{ ${[...propParts, `children: ${capVar}`].join(", ")} }`;
188829
- return `<% ${lenVar} = _erbout.length %>${childrenBody}<% ${capVar} = _erbout.slice!(${lenVar}..) %><%= bf.render_child('${tplName}', ${propsHash2}) %>`;
188935
+ return `${namedSlotCaptures.join("")}<% ${lenVar} = _erbout.length %>${childrenBody}<% ${capVar} = _erbout.slice!(${lenVar}..) %><%= bf.render_child('${tplName}', ${propsHash2}) %>`;
188830
188936
  }
188831
188937
  const propsHash = propParts.length > 0 ? `{ ${propParts.join(", ")} }` : "{}";
188832
- return `<%= bf.render_child('${tplName}', ${propsHash}) %>`;
188938
+ return `${namedSlotCaptures.join("")}<%= bf.render_child('${tplName}', ${propsHash}) %>`;
188833
188939
  }
188834
188940
  childrenCaptureCounter = 0;
188835
188941
  toTemplateName(componentName) {
@@ -188874,7 +188980,7 @@ ${alternate}
188874
188980
  ${children}`;
188875
188981
  }
188876
188982
  elementAttrEmitter = {
188877
- emitLiteral: (value, name) => `${name}="${value.value}"`,
188983
+ emitLiteral: (value, name) => `${name}="${escapeHtml(value.value)}"`,
188878
188984
  emitExpression: (value, name) => {
188879
188985
  if (name === "style") {
188880
188986
  const css = this.tryLowerStyleObject(value.expr);
@@ -188966,12 +189072,15 @@ ${children}`;
188966
189072
  for (const attr of element.attrs) {
188967
189073
  if (attr.clientOnly)
188968
189074
  continue;
189075
+ if (isDangerousInnerHtmlAttr(attr))
189076
+ continue;
188969
189077
  let attrName;
188970
189078
  if (attr.name === "className")
188971
189079
  attrName = "class";
188972
- else if (attr.name === "key")
188973
- attrName = "data-key";
188974
- else
189080
+ else if (attr.name === "key") {
189081
+ const depth = this.currentLoopKeyDepth;
189082
+ attrName = depth > 0 ? `data-key-${depth}` : "data-key";
189083
+ } else
188975
189084
  attrName = attr.name;
188976
189085
  const lowered = emitAttrValue(attr.value, this.elementAttrEmitter, attrName);
188977
189086
  if (lowered)
@@ -188988,8 +189097,8 @@ ${children}`;
188988
189097
  renderCondMarker(condId) {
188989
189098
  return `${BF_COND}="${condId}"`;
188990
189099
  }
188991
- renderRubyFilterExpr(expr, param, localVarMap = new Map) {
188992
- return emitParsedExpr2(expr, new ErbFilterEmitter(param, localVarMap, (n) => this.isLoopBoundName(n), (n) => this._isStringValueName(n), (message, reason) => this._recordExprBF101(message, reason)));
189100
+ renderRubyFilterExpr(expr, param, localVarMap = new Map, renderParamAs) {
189101
+ return emitParsedExpr2(expr, new ErbFilterEmitter(param, localVarMap, (n) => this.isLoopBoundName(n), (n) => this._isStringValueName(n), (message, reason) => this._recordExprBF101(message, reason), renderParamAs));
188993
189102
  }
188994
189103
  convertTemplateLiteralPartsToRuby(literalParts) {
188995
189104
  const parts = [];
@@ -1 +1 @@
1
- {"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,eAiG7B,CAAA"}
1
+ {"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,eA4F7B,CAAA"}