256b27617ddc87e19d029cafab4b7d38e89a4fdc5f3a69ac8a95790ad95cd306acc5165c2291ec973767503a1d5cacc449136ce284111bb126c8c157136238 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import esutils from "esutils";
  2. /**
  3. * Converts JSX Spread arguments into Object Spread, avoiding Babel's helper or Object.assign injection.
  4. * Input:
  5. * <div a="1" {...b} />
  6. * Output:
  7. * <div {...{ a: "1", ...b }} />
  8. * ...which Babel converts to:
  9. * h("div", { a: "1", ...b })
  10. */
  11. export default ({ types: t }) => {
  12. // converts a set of JSXAttributes to an Object.assign() call
  13. function convertAttributesAssign(attributes) {
  14. const args = [];
  15. for (let i = 0, current; i < attributes.length; i++) {
  16. const node = attributes[i];
  17. if (t.isJSXSpreadAttribute(node)) {
  18. // the first attribute is a spread, avoid copying all other attributes onto it
  19. if (i === 0) {
  20. args.push(t.objectExpression([]));
  21. }
  22. current = null;
  23. args.push(node.argument);
  24. } else {
  25. const name = getAttributeName(node);
  26. const value = getAttributeValue(node);
  27. if (!current) {
  28. current = t.objectExpression([]);
  29. args.push(current);
  30. }
  31. current.properties.push(t.objectProperty(name, value));
  32. }
  33. }
  34. return t.callExpression(
  35. t.memberExpression(t.identifier("Object"), t.identifier("assign")),
  36. args
  37. );
  38. }
  39. // Converts a JSXAttribute to the equivalent ObjectExpression property
  40. function convertAttributeSpread(node) {
  41. if (t.isJSXSpreadAttribute(node)) {
  42. return t.spreadElement(node.argument);
  43. }
  44. const name = getAttributeName(node);
  45. const value = getAttributeValue(node);
  46. return t.inherits(t.objectProperty(name, value), node);
  47. }
  48. // Convert a JSX attribute name to an Object expression property name
  49. function getAttributeName(node) {
  50. if (t.isJSXNamespacedName(node.name)) {
  51. return t.stringLiteral(
  52. node.name.namespace.name + ":" + node.name.name.name
  53. );
  54. }
  55. if (esutils.keyword.isIdentifierNameES6(node.name.name)) {
  56. return t.identifier(node.name.name);
  57. }
  58. return t.stringLiteral(node.name.name);
  59. }
  60. // Convert a JSX attribute value to a JavaScript expression value
  61. function getAttributeValue(node) {
  62. let value = node.value || t.booleanLiteral(true);
  63. if (t.isJSXExpressionContainer(value)) {
  64. value = value.expression;
  65. } else if (t.isStringLiteral(value)) {
  66. value.value = value.value.replace(/\n\s+/g, " ");
  67. // "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
  68. if (value.extra && value.extra.raw) {
  69. delete value.extra.raw;
  70. }
  71. }
  72. return value;
  73. }
  74. return {
  75. name: "transform-jsx-spread",
  76. visitor: {
  77. JSXOpeningElement(path, state) {
  78. const useSpread = state.opts.useSpread === true;
  79. const hasSpread = path.node.attributes.some(attr =>
  80. t.isJSXSpreadAttribute(attr)
  81. );
  82. // ignore JSX Elements without spread or with lone spread:
  83. if (!hasSpread || path.node.attributes.length === 1) return;
  84. if (useSpread) {
  85. path.node.attributes = [
  86. t.jsxSpreadAttribute(
  87. t.objectExpression(
  88. path.node.attributes.map(convertAttributeSpread)
  89. )
  90. ),
  91. ];
  92. } else {
  93. path.node.attributes = [
  94. t.jsxSpreadAttribute(convertAttributesAssign(path.node.attributes)),
  95. ];
  96. }
  97. },
  98. },
  99. };
  100. };