babel-plugin-jsx-dom-expressions 0.40.10 → 0.40.14

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.
Files changed (2) hide show
  1. package/index.js +168 -30
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -321,6 +321,49 @@ const SVGElements = /*#__PURE__*/new Set([
321
321
  "vkern"]);
322
322
 
323
323
 
324
+ const MathMLElements = /*#__PURE__*/new Set([
325
+ "annotation",
326
+ "annotation-xml",
327
+ "maction",
328
+ "math",
329
+ "menclose",
330
+ "merror",
331
+ "mfenced",
332
+ "mfrac",
333
+ "mi",
334
+ "mmultiscripts",
335
+ "mn",
336
+ "mo",
337
+ "mover",
338
+ "mpadded",
339
+ "mphantom",
340
+ "mprescripts",
341
+ "mroot",
342
+ "mrow",
343
+ "ms",
344
+ "mspace",
345
+ "msqrt",
346
+ "mstyle",
347
+ "msub",
348
+ "msubsup",
349
+ "msup",
350
+ "mtable",
351
+ "mtd",
352
+ "mtext",
353
+ "mtr",
354
+ "munder",
355
+ "munderover",
356
+ "semantics"]);
357
+
358
+
359
+ const MathMLTextIntegrationPoints = /*#__PURE__*/new Set([
360
+ "mi",
361
+ "mo",
362
+ "mn",
363
+ "ms",
364
+ "mtext"]);
365
+
366
+
324
367
  const SVGNamespace = {
325
368
  xlink: "http://www.w3.org/1999/xlink",
326
369
  xml: "http://www.w3.org/XML/1998/namespace"
@@ -409,6 +452,58 @@ function isComponent(tagName) {
409
452
 
410
453
  }
411
454
 
455
+ // <annotation-xml> is a MathML HTML integration point only when its encoding
456
+ // is text/html or application/xhtml+xml
457
+ function isHTMLEncodingAnnotationXML(node) {
458
+ const encoding = node.openingElement.attributes.find(
459
+ (attr) => t__namespace.isJSXAttribute(attr) && t__namespace.isJSXIdentifier(attr.name) && attr.name.name === "encoding");
460
+
461
+ return (
462
+ t__namespace.isStringLiteral(encoding?.value) &&
463
+ /^(?:text\/html|application\/xhtml\+xml)$/i.test(encoding.value.value));
464
+
465
+ }
466
+
467
+ // <script> and <style> are only raw text in HTML. Inside SVG or MathML the
468
+ // parser decodes entities and treats `<` as markup, so their children still
469
+ // need escaping. Integration points (<foreignObject> in SVG; the MathML text
470
+ // integration points and <annotation-xml> with an HTML encoding in MathML)
471
+ // switch their content back to HTML parsing.
472
+ function isRawTextElement(path) {
473
+ const tagName = getTagName(path.node);
474
+ if (tagName !== "script" && tagName !== "style") return false;
475
+ const ancestors = [];
476
+ let parent = path.parentPath;
477
+ while (t__namespace.isJSXElement(parent.node)) {
478
+ if (isComponent(getTagName(parent.node))) break;
479
+ ancestors.push(parent.node);
480
+ parent = parent.parentPath;
481
+ }
482
+ const rootTag = ancestors.length ? getTagName(ancestors[ancestors.length - 1]) : undefined;
483
+ // a top-level SVG element is parsed inside an <svg> wrapper and a top-level
484
+ // MathML element inside a MathML template
485
+ let namespace = SVGElements.has(rootTag) ?
486
+ "svg" :
487
+ MathMLElements.has(rootTag) ?
488
+ "math" :
489
+ "html";
490
+ for (let i = ancestors.length - 1; i >= 0; i--) {
491
+ const parentTag = getTagName(ancestors[i]);
492
+ if (namespace === "html") {
493
+ if (parentTag === "svg") namespace = "svg";else
494
+ if (parentTag === "math") namespace = "math";
495
+ } else if (namespace === "svg") {
496
+ if (parentTag === "foreignObject") namespace = "html";
497
+ } else if (
498
+ MathMLTextIntegrationPoints.has(parentTag) ||
499
+ parentTag === "annotation-xml" && isHTMLEncodingAnnotationXML(ancestors[i]))
500
+ {
501
+ namespace = "html";
502
+ }
503
+ }
504
+ return namespace === "html";
505
+ }
506
+
412
507
  function hasStaticMarker(object, path) {
413
508
  if (!object) return false;
414
509
  if (
@@ -1901,6 +1996,7 @@ function findLastElement(children, hydratable) {
1901
1996
  function transformChildren$2(path, results, config) {
1902
1997
  let tempPath = results.id && results.id.name,
1903
1998
  tagName = getTagName(path.node),
1999
+ doNotEscape = isRawTextElement(path),
1904
2000
  nextPlaceholder,
1905
2001
  childPostExprs = [],
1906
2002
  i = 0;
@@ -1913,6 +2009,7 @@ function transformChildren$2(path, results, config) {
1913
2009
 
1914
2010
  }
1915
2011
  const transformed = transformNode(child, {
2012
+ doNotEscape,
1916
2013
  toBeClosed: results.toBeClosed,
1917
2014
  lastElement: index === lastElement,
1918
2015
  skipId: !results.id || !detectExpressions(filteredChildren, index, config)
@@ -2542,7 +2639,7 @@ function transformElement$2(path, info) {
2542
2639
 
2543
2640
  const config = getConfig(path);
2544
2641
  const tagName = getTagName(path.node);
2545
- if (tagName === "script" || tagName === "style") path.doNotEscape = true;
2642
+ if (isRawTextElement(path)) path.doNotEscape = true;
2546
2643
 
2547
2644
  // contains spread attributes
2548
2645
  if (path.node.openingElement.attributes.some((a) => t__namespace.isJSXSpreadAttribute(a)))
@@ -2625,26 +2722,38 @@ function setAttr$1(attribute, results, name, value, isSVG) {
2625
2722
  }
2626
2723
  }
2627
2724
 
2628
- function escapeExpression(path, expression, attr, escapeLiterals) {
2725
+ /**
2726
+ * How `escapeExpression` treats a string literal it reaches.
2727
+ * - `undefined`: the hole itself. The caller escapes a literal when it
2728
+ * inlines it into the template, so it stays as written here.
2729
+ * - `"escape"`: a position whose value the runtime writes as is.
2730
+ * - `"keep"`: inside a function value in an attribute. `ssrAttribute`
2731
+ * stringifies the function's source, so literals stay as written.
2732
+ */
2733
+ function escapeExpression(path, expression, attr, literals) {
2734
+ if (!expression) return expression;
2735
+ // A literal in a branch is the hole's value, which `ssr` and
2736
+ // `ssrAttribute` write as is, so branches escape literals.
2737
+ const branch = literals === "keep" ? "keep" : "escape";
2738
+ // A function's returns escape like branches, except for a function value in
2739
+ // an attribute: `ssrAttribute` stringifies its source, so its literals stay
2740
+ // as written at every depth.
2741
+ const returns = literals === undefined && attr ? "keep" : branch;
2629
2742
  if (
2630
2743
  t__namespace.isStringLiteral(expression) ||
2631
2744
  t__namespace.isNumericLiteral(expression) ||
2632
2745
  t__namespace.isTemplateLiteral(expression) && expression.expressions.length === 0)
2633
2746
  {
2634
- if (escapeLiterals) {
2747
+ if (literals === "escape") {
2635
2748
  if (t__namespace.isStringLiteral(expression)) return t__namespace.stringLiteral(escapeHTML(expression.value, attr));else
2636
2749
  if (t__namespace.isTemplateLiteral(expression))
2637
- return t__namespace.stringLiteral(escapeHTML(expression.quasis[0].value.raw, attr));
2750
+ return t__namespace.stringLiteral(
2751
+ escapeHTML(expression.quasis[0].value.cooked ?? expression.quasis[0].value.raw, attr));
2752
+
2638
2753
  }
2639
2754
  return expression;
2640
2755
  } else if (t__namespace.isFunction(expression)) {
2641
- if (t__namespace.isBlockStatement(expression.body)) {
2642
- expression.body.body = expression.body.body.map((e) => {
2643
- if (t__namespace.isReturnStatement(e))
2644
- e.argument = escapeExpression(path, e.argument, attr, escapeLiterals);
2645
- return e;
2646
- });
2647
- } else expression.body = escapeExpression(path, expression.body, attr, escapeLiterals);
2756
+ escapeFunctionResult(path, expression, attr, returns);
2648
2757
  return expression;
2649
2758
  } else if (t__namespace.isTemplateLiteral(expression)) {
2650
2759
  // Interpolations are escaped recursively. The static quasis are not —
@@ -2652,35 +2761,30 @@ function escapeExpression(path, expression, attr, escapeLiterals) {
2652
2761
  // attr="...", which HTML parses as attribute injection.
2653
2762
  if (attr) escapeTemplateQuasis(expression, true);
2654
2763
  expression.expressions = expression.expressions.map((e) =>
2655
- escapeExpression(path, e, attr, escapeLiterals));
2764
+ escapeExpression(path, e, attr, literals));
2656
2765
 
2657
2766
  return expression;
2658
2767
  } else if (t__namespace.isUnaryExpression(expression)) {
2659
2768
  return expression;
2660
2769
  } else if (t__namespace.isBinaryExpression(expression)) {
2661
- expression.left = escapeExpression(path, expression.left, attr, escapeLiterals);
2662
- expression.right = escapeExpression(path, expression.right, attr, escapeLiterals);
2770
+ expression.left = escapeExpression(path, expression.left, attr, literals);
2771
+ expression.right = escapeExpression(path, expression.right, attr, literals);
2663
2772
  return expression;
2664
2773
  } else if (t__namespace.isConditionalExpression(expression)) {
2665
- expression.consequent = escapeExpression(path, expression.consequent, attr, escapeLiterals);
2666
- expression.alternate = escapeExpression(path, expression.alternate, attr, escapeLiterals);
2774
+ expression.consequent = escapeExpression(path, expression.consequent, attr, branch);
2775
+ expression.alternate = escapeExpression(path, expression.alternate, attr, branch);
2667
2776
  return expression;
2668
2777
  } else if (t__namespace.isLogicalExpression(expression)) {
2669
2778
  // Preserve the old short-circuit shape for && while still escaping the
2670
2779
  // selected result of || and ?? as a whole.
2671
2780
  if (expression.operator === "&&") {
2672
- expression.right = escapeExpression(path, expression.right, attr, escapeLiterals);
2781
+ expression.right = escapeExpression(path, expression.right, attr, branch);
2673
2782
  return expression;
2674
2783
  }
2675
2784
  } else if (t__namespace.isCallExpression(expression) && t__namespace.isFunction(expression.callee)) {
2676
- if (t__namespace.isBlockStatement(expression.callee.body)) {
2677
- expression.callee.body.body = expression.callee.body.body.map((e) => {
2678
- if (t__namespace.isReturnStatement(e))
2679
- e.argument = escapeExpression(path, e.argument, attr, escapeLiterals);
2680
- return e;
2681
- });
2682
- } else
2683
- expression.callee.body = escapeExpression(path, expression.callee.body, attr, escapeLiterals);
2785
+ // The call's result is the hole's value, so its returns escape like
2786
+ // branches.
2787
+ escapeFunctionResult(path, expression.callee, attr, branch);
2684
2788
  return expression;
2685
2789
  } else if (t__namespace.isJSXElement(expression) && !isComponent(getTagName(expression))) {
2686
2790
  expression.wontEscape = true;
@@ -2693,6 +2797,37 @@ function escapeExpression(path, expression, attr, escapeLiterals) {
2693
2797
 
2694
2798
  }
2695
2799
 
2800
+ /** Escapes a function's expression body or its `return` arguments. */
2801
+ function escapeFunctionResult(path, fn, attr, literals) {
2802
+ if (t__namespace.isBlockStatement(fn.body)) escapeReturns(path, fn.body, attr, literals);else
2803
+ fn.body = escapeExpression(path, fn.body, attr, literals);
2804
+ }
2805
+
2806
+ /**
2807
+ * Escapes the `return` arguments in a statement at any depth, without
2808
+ * entering nested functions or classes.
2809
+ */
2810
+ function escapeReturns(path, statement, attr, literals) {
2811
+ if (!statement) return;
2812
+ if (t__namespace.isReturnStatement(statement)) {
2813
+ statement.argument = escapeExpression(path, statement.argument, attr, literals);
2814
+ } else if (t__namespace.isBlockStatement(statement)) {
2815
+ for (const child of statement.body) escapeReturns(path, child, attr, literals);
2816
+ } else if (t__namespace.isIfStatement(statement)) {
2817
+ escapeReturns(path, statement.consequent, attr, literals);
2818
+ escapeReturns(path, statement.alternate, attr, literals);
2819
+ } else if (t__namespace.isSwitchStatement(statement)) {
2820
+ for (const switchCase of statement.cases)
2821
+ for (const child of switchCase.consequent) escapeReturns(path, child, attr, literals);
2822
+ } else if (t__namespace.isTryStatement(statement)) {
2823
+ escapeReturns(path, statement.block, attr, literals);
2824
+ escapeReturns(path, statement.handler && statement.handler.body, attr, literals);
2825
+ escapeReturns(path, statement.finalizer, attr, literals);
2826
+ } else if (t__namespace.isLoop(statement) || t__namespace.isLabeledStatement(statement) || t__namespace.isWithStatement(statement)) {
2827
+ escapeReturns(path, statement.body, attr, literals);
2828
+ }
2829
+ }
2830
+
2696
2831
  function escapeTemplateQuasis(expression, attr) {
2697
2832
  for (const quasi of expression.quasis) {
2698
2833
  const src = quasi.value.cooked != null ? quasi.value.cooked : quasi.value.raw;
@@ -2841,8 +2976,11 @@ function transformAttributes$1(path, results, info) {
2841
2976
  let doEscape = true;
2842
2977
 
2843
2978
  if (key.startsWith("attr:")) key = key.replace("attr:", "");
2844
- if (BooleanAttributes.has(key) || key.startsWith("bool:")) {
2845
- key = key.replace("bool:", "");
2979
+ // preservesPitch is a boolean property, but not an HTML boolean attribute.
2980
+ const isBooleanAttribute =
2981
+ key !== "preservesPitch" && BooleanAttributes.has(toAttribute(key, isSVG));
2982
+ if (isBooleanAttribute || key.startsWith("bool:")) {
2983
+ key = toAttribute(key.replace("bool:", ""), isSVG);
2846
2984
  results.template.push("");
2847
2985
  const fn = t__namespace.callExpression(registerImportMethod(attribute, "ssrAttribute"), [
2848
2986
  t__namespace.stringLiteral(key),
@@ -2865,14 +3003,14 @@ function transformAttributes$1(path, results, info) {
2865
3003
  if (p.computed) {
2866
3004
  return t__namespace.callExpression(registerImportMethod(path, "ssrStyleProperty"), [
2867
3005
  t__namespace.binaryExpression("+", p.key, t__namespace.stringLiteral(":")),
2868
- escapeExpression(path, p.value, true, true)]);
3006
+ escapeExpression(path, p.value, true, "escape")]);
2869
3007
 
2870
3008
  }
2871
3009
  return t__namespace.callExpression(registerImportMethod(path, "ssrStyleProperty"), [
2872
3010
  t__namespace.stringLiteral(
2873
3011
  (i ? ";" : "") + (t__namespace.isIdentifier(p.key) ? p.key.name : p.key.value) + ":"),
2874
3012
 
2875
- escapeExpression(path, p.value, true, true)]);
3013
+ escapeExpression(path, p.value, true, "escape")]);
2876
3014
 
2877
3015
  });
2878
3016
 
@@ -4068,7 +4206,7 @@ function transformNode(path, info = {}) {
4068
4206
  info.doNotEscape ?
4069
4207
  staticValue.toString() :
4070
4208
  escapeHTML(staticValue.toString()) :
4071
- trimWhitespace(node.extra.raw);
4209
+ trimWhitespace(info.doNotEscape ? node.value : node.extra.raw);
4072
4210
  if (!text.length) return null;
4073
4211
  const results = {
4074
4212
  template: text,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "babel-plugin-jsx-dom-expressions",
3
3
  "description": "A JSX to DOM plugin that wraps expressions for fine grained change detection",
4
- "version": "0.40.10",
4
+ "version": "0.40.14",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -33,5 +33,5 @@
33
33
  "@rollup/plugin-babel": "6.0.3",
34
34
  "@types/babel__core": "^7.20.5"
35
35
  },
36
- "gitHead": "727298ff79a54ef5299d3649ab7f4105a326e9b3"
36
+ "gitHead": "f6fecdf9e455d03cfa90aaba0323ea3d4686ada6"
37
37
  }