index.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. import * as t from "@babel/types";
  2. import { addNamed, addNamespace, isModule } from "@babel/helper-module-imports";
  3. import annotateAsPure from "@babel/helper-annotate-as-pure";
  4. const DEFAULT = {
  5. importSource: "react",
  6. runtime: "automatic",
  7. pragma: "React.createElement",
  8. pragmaFrag: "React.Fragment",
  9. };
  10. export function helper(babel, options) {
  11. const FILE_NAME_VAR = "_jsxFileName";
  12. const JSX_SOURCE_ANNOTATION_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/;
  13. const JSX_RUNTIME_ANNOTATION_REGEX = /\*?\s*@jsxRuntime\s+([^\s]+)/;
  14. const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
  15. const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;
  16. // This is the number of possible import names
  17. // development: jsxDEV, Fragment, createElement
  18. // production: jsx, jsxs, Fragment, createElement
  19. const IMPORT_NAME_SIZE = options.development ? 3 : 4;
  20. const {
  21. importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource,
  22. runtime: RUNTIME_DEFAULT = DEFAULT.runtime,
  23. pragma: PRAGMA_DEFAULT = DEFAULT.pragma,
  24. pragmaFrag: PRAGMA_FRAG_DEFAULT = DEFAULT.pragmaFrag,
  25. } = options;
  26. const injectMetaPropertiesVisitor = {
  27. JSXOpeningElement(path, state) {
  28. for (const attr of path.get("attributes")) {
  29. if (!attr.isJSXElement()) continue;
  30. const { name } = attr.node.name;
  31. if (name === "__source" || name === "__self") {
  32. throw path.buildCodeFrameError(
  33. `__source and __self should not be defined in props and are reserved for internal usage.`,
  34. );
  35. }
  36. }
  37. const source = t.jsxAttribute(
  38. t.jsxIdentifier("__source"),
  39. t.jsxExpressionContainer(makeSource(path, state)),
  40. );
  41. const self = t.jsxAttribute(
  42. t.jsxIdentifier("__self"),
  43. t.jsxExpressionContainer(t.thisExpression()),
  44. );
  45. path.pushContainer("attributes", [source, self]);
  46. },
  47. };
  48. return {
  49. JSXNamespacedName(path, state) {
  50. const throwIfNamespace =
  51. state.opts.throwIfNamespace === undefined
  52. ? true
  53. : !!state.opts.throwIfNamespace;
  54. if (throwIfNamespace) {
  55. throw path.buildCodeFrameError(
  56. `Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
  57. You can set \`throwIfNamespace: false\` to bypass this warning.`,
  58. );
  59. }
  60. },
  61. JSXSpreadChild(path) {
  62. throw path.buildCodeFrameError(
  63. "Spread children are not supported in React.",
  64. );
  65. },
  66. JSXElement: {
  67. exit(path, file) {
  68. let callExpr;
  69. if (
  70. file.get("@babel/plugin-react-jsx/runtime") === "classic" ||
  71. shouldUseCreateElement(path)
  72. ) {
  73. callExpr = buildCreateElementCall(path, file);
  74. } else {
  75. callExpr = buildJSXElementCall(path, file);
  76. }
  77. path.replaceWith(t.inherits(callExpr, path.node));
  78. },
  79. },
  80. JSXFragment: {
  81. exit(path, file) {
  82. let callExpr;
  83. if (file.get("@babel/plugin-react-jsx/runtime") === "classic") {
  84. callExpr = buildCreateElementFragmentCall(path, file);
  85. } else {
  86. callExpr = buildJSXFragmentCall(path, file);
  87. }
  88. path.replaceWith(t.inherits(callExpr, path.node));
  89. },
  90. },
  91. JSXAttribute(path) {
  92. if (t.isJSXElement(path.node.value)) {
  93. path.node.value = t.jsxExpressionContainer(path.node.value);
  94. }
  95. },
  96. Program: {
  97. enter(path, state) {
  98. if (hasJSX(path)) {
  99. const { file } = state;
  100. let runtime = RUNTIME_DEFAULT;
  101. // For jsx mode
  102. let source = IMPORT_SOURCE_DEFAULT;
  103. let sourceSet = !!options.importSource;
  104. // For createElement mode
  105. let pragma = PRAGMA_DEFAULT;
  106. let pragmaFrag = PRAGMA_FRAG_DEFAULT;
  107. let pragmaSet = !!options.pragma;
  108. let pragmaFragSet = !!options.pragmaFrag;
  109. if (file.ast.comments) {
  110. for (const comment of (file.ast.comments: Array<Object>)) {
  111. const sourceMatches = JSX_SOURCE_ANNOTATION_REGEX.exec(
  112. comment.value,
  113. );
  114. if (sourceMatches) {
  115. source = sourceMatches[1];
  116. sourceSet = true;
  117. }
  118. const runtimeMatches = JSX_RUNTIME_ANNOTATION_REGEX.exec(
  119. comment.value,
  120. );
  121. if (runtimeMatches) {
  122. runtime = runtimeMatches[1];
  123. }
  124. const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
  125. if (jsxMatches) {
  126. pragma = jsxMatches[1];
  127. pragmaSet = true;
  128. }
  129. const jsxFragMatches = JSX_FRAG_ANNOTATION_REGEX.exec(
  130. comment.value,
  131. );
  132. if (jsxFragMatches) {
  133. pragmaFrag = jsxFragMatches[1];
  134. pragmaFragSet = true;
  135. }
  136. }
  137. }
  138. state.set("@babel/plugin-react-jsx/runtime", runtime);
  139. if (runtime === "classic") {
  140. if (sourceSet) {
  141. throw path.buildCodeFrameError(
  142. `importSource cannot be set when runtime is classic.`,
  143. );
  144. }
  145. state.set(
  146. "@babel/plugin-react-jsx/createElementIdentifier",
  147. createIdentifierParser(pragma),
  148. );
  149. state.set(
  150. "@babel/plugin-react-jsx/jsxFragIdentifier",
  151. createIdentifierParser(pragmaFrag),
  152. );
  153. state.set("@babel/plugin-react-jsx/usedFragment", false);
  154. state.set(
  155. "@babel/plugin-react-jsx/pragmaSet",
  156. pragma !== DEFAULT.pragma,
  157. );
  158. state.set(
  159. "@babel/plugin-react-jsx/pragmaFragSet",
  160. pragmaFrag !== DEFAULT.pragmaFrag,
  161. );
  162. } else if (runtime === "automatic") {
  163. if (pragmaSet || pragmaFragSet) {
  164. throw path.buildCodeFrameError(
  165. `pragma and pragmaFrag cannot be set when runtime is automatic.`,
  166. );
  167. }
  168. const importName = addAutoImports(path, {
  169. ...state.opts,
  170. source,
  171. });
  172. state.set(
  173. "@babel/plugin-react-jsx/jsxIdentifier",
  174. createIdentifierParser(
  175. createIdentifierName(
  176. path,
  177. options.development ? "jsxDEV" : "jsx",
  178. importName,
  179. ),
  180. ),
  181. );
  182. state.set(
  183. "@babel/plugin-react-jsx/jsxStaticIdentifier",
  184. createIdentifierParser(
  185. createIdentifierName(
  186. path,
  187. options.development ? "jsxDEV" : "jsxs",
  188. importName,
  189. ),
  190. ),
  191. );
  192. state.set(
  193. "@babel/plugin-react-jsx/createElementIdentifier",
  194. createIdentifierParser(
  195. createIdentifierName(path, "createElement", importName),
  196. ),
  197. );
  198. state.set(
  199. "@babel/plugin-react-jsx/jsxFragIdentifier",
  200. createIdentifierParser(
  201. createIdentifierName(path, "Fragment", importName),
  202. ),
  203. );
  204. state.set(
  205. "@babel/plugin-react-jsx/importSourceSet",
  206. source !== DEFAULT.importSource,
  207. );
  208. } else {
  209. throw path.buildCodeFrameError(
  210. `Runtime must be either "classic" or "automatic".`,
  211. );
  212. }
  213. if (options.development) {
  214. path.traverse(injectMetaPropertiesVisitor, state);
  215. }
  216. }
  217. },
  218. exit(path, state) {
  219. if (
  220. state.get("@babel/plugin-react-jsx/runtime") === "classic" &&
  221. state.get("@babel/plugin-react-jsx/pragmaSet") &&
  222. state.get("@babel/plugin-react-jsx/usedFragment") &&
  223. !state.get("@babel/plugin-react-jsx/pragmaFragSet")
  224. ) {
  225. throw new Error(
  226. "transform-react-jsx: pragma has been set but " +
  227. "pragmaFrag has not been set",
  228. );
  229. }
  230. },
  231. },
  232. };
  233. // We want to use React.createElement, even in the case of
  234. // jsx, for <div {...props} key={key} /> to distinguish it
  235. // from <div key={key} {...props} />. This is an intermediary
  236. // step while we deprecate key spread from props. Afterwards,
  237. // we will stop using createElement in the transform.
  238. function shouldUseCreateElement(path) {
  239. const openingPath = path.get("openingElement");
  240. const attributes = openingPath.node.attributes;
  241. let seenPropsSpread = false;
  242. for (let i = 0; i < attributes.length; i++) {
  243. const attr = attributes[i];
  244. if (
  245. seenPropsSpread &&
  246. t.isJSXAttribute(attr) &&
  247. attr.name.name === "key"
  248. ) {
  249. return true;
  250. } else if (t.isJSXSpreadAttribute(attr)) {
  251. seenPropsSpread = true;
  252. }
  253. }
  254. return false;
  255. }
  256. function createIdentifierName(path, name, importName) {
  257. if (isModule(path)) {
  258. const identifierName = `${importName[name]}`;
  259. return identifierName;
  260. } else {
  261. return `${importName[name]}.${name}`;
  262. }
  263. }
  264. function getImportNames(parentPath) {
  265. const imports = new Set();
  266. parentPath.traverse({
  267. "JSXElement|JSXFragment"(path) {
  268. if (path.type === "JSXFragment") imports.add("Fragment");
  269. const openingPath = path.get("openingElement");
  270. const validChildren = t.react.buildChildren(openingPath.parent);
  271. let importName;
  272. if (path.type === "JSXElement" && shouldUseCreateElement(path)) {
  273. importName = "createElement";
  274. } else if (options.development) {
  275. importName = "jsxDEV";
  276. } else if (validChildren.length > 1) {
  277. importName = "jsxs";
  278. } else {
  279. importName = "jsx";
  280. }
  281. imports.add(importName);
  282. if (imports.size === IMPORT_NAME_SIZE) {
  283. path.stop();
  284. }
  285. },
  286. });
  287. return imports;
  288. }
  289. function hasJSX(parentPath) {
  290. let fileHasJSX = false;
  291. parentPath.traverse({
  292. "JSXElement|JSXFragment"(path) {
  293. fileHasJSX = true;
  294. path.stop();
  295. },
  296. });
  297. return fileHasJSX;
  298. }
  299. function getSource(source, importName) {
  300. switch (importName) {
  301. case "Fragment":
  302. return `${source}/${
  303. options.development ? "jsx-dev-runtime" : "jsx-runtime"
  304. }`;
  305. case "jsxDEV":
  306. return `${source}/jsx-dev-runtime`;
  307. case "jsx":
  308. case "jsxs":
  309. return `${source}/jsx-runtime`;
  310. case "createElement":
  311. return source;
  312. }
  313. }
  314. function addAutoImports(path, state) {
  315. const imports = getImportNames(path, state);
  316. if (isModule(path)) {
  317. // import {jsx} from "react";
  318. // import {createElement} from "react";
  319. const importMap = {};
  320. imports.forEach(importName => {
  321. if (!importMap[importName]) {
  322. importMap[importName] = addNamed(
  323. path,
  324. importName,
  325. getSource(state.source, importName),
  326. {
  327. importedInterop: "uncompiled",
  328. ensureLiveReference: true,
  329. },
  330. ).name;
  331. }
  332. });
  333. return importMap;
  334. } else {
  335. const importMap = {};
  336. const sourceMap = {};
  337. imports.forEach(importName => {
  338. const source = getSource(state.source, importName);
  339. if (!importMap[importName]) {
  340. if (!sourceMap[source]) {
  341. // var _react = require("react")
  342. sourceMap[source] = addNamespace(path, source, {
  343. importedInterop: "uncompiled",
  344. ensureLiveReference: true,
  345. }).name;
  346. }
  347. importMap[importName] = sourceMap[source];
  348. }
  349. });
  350. return importMap;
  351. }
  352. }
  353. function createIdentifierParser(id) {
  354. return () => {
  355. return id
  356. .split(".")
  357. .map(name => t.identifier(name))
  358. .reduce((object, property) => t.memberExpression(object, property));
  359. };
  360. }
  361. function makeTrace(fileNameIdentifier, lineNumber, column0Based) {
  362. const fileLineLiteral =
  363. lineNumber != null ? t.numericLiteral(lineNumber) : t.nullLiteral();
  364. const fileColumnLiteral =
  365. column0Based != null
  366. ? t.numericLiteral(column0Based + 1)
  367. : t.nullLiteral();
  368. const fileNameProperty = t.objectProperty(
  369. t.identifier("fileName"),
  370. fileNameIdentifier,
  371. );
  372. const lineNumberProperty = t.objectProperty(
  373. t.identifier("lineNumber"),
  374. fileLineLiteral,
  375. );
  376. const columnNumberProperty = t.objectProperty(
  377. t.identifier("columnNumber"),
  378. fileColumnLiteral,
  379. );
  380. return t.objectExpression([
  381. fileNameProperty,
  382. lineNumberProperty,
  383. columnNumberProperty,
  384. ]);
  385. }
  386. function makeSource(path, state) {
  387. const location = path.node.loc;
  388. if (!location) {
  389. // the element was generated and doesn't have location information
  390. return;
  391. }
  392. if (!state.fileNameIdentifier) {
  393. const { filename = "" } = state;
  394. const fileNameIdentifier = path.scope.generateUidIdentifier(
  395. FILE_NAME_VAR,
  396. );
  397. const scope = path.hub.getScope();
  398. if (scope) {
  399. scope.push({
  400. id: fileNameIdentifier,
  401. init: t.stringLiteral(filename),
  402. });
  403. }
  404. state.fileNameIdentifier = fileNameIdentifier;
  405. }
  406. return makeTrace(
  407. t.cloneNode(state.fileNameIdentifier),
  408. location.start.line,
  409. location.start.column,
  410. );
  411. }
  412. function convertJSXIdentifier(node, parent) {
  413. if (t.isJSXIdentifier(node)) {
  414. if (node.name === "this" && t.isReferenced(node, parent)) {
  415. return t.thisExpression();
  416. } else if (t.isValidIdentifier(node.name, false)) {
  417. node.type = "Identifier";
  418. } else {
  419. return t.stringLiteral(node.name);
  420. }
  421. } else if (t.isJSXMemberExpression(node)) {
  422. return t.memberExpression(
  423. convertJSXIdentifier(node.object, node),
  424. convertJSXIdentifier(node.property, node),
  425. );
  426. } else if (t.isJSXNamespacedName(node)) {
  427. /**
  428. * If the flag "throwIfNamespace" is false
  429. * print XMLNamespace like string literal
  430. */
  431. return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
  432. }
  433. return node;
  434. }
  435. function convertAttributeValue(node) {
  436. if (t.isJSXExpressionContainer(node)) {
  437. return node.expression;
  438. } else {
  439. return node;
  440. }
  441. }
  442. function convertAttribute(node) {
  443. const value = convertAttributeValue(node.value || t.booleanLiteral(true));
  444. if (t.isJSXSpreadAttribute(node)) {
  445. return t.spreadElement(node.argument);
  446. }
  447. if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
  448. value.value = value.value.replace(/\n\s+/g, " ");
  449. // "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
  450. if (value.extra && value.extra.raw) {
  451. delete value.extra.raw;
  452. }
  453. }
  454. if (t.isJSXNamespacedName(node.name)) {
  455. node.name = t.stringLiteral(
  456. node.name.namespace.name + ":" + node.name.name.name,
  457. );
  458. } else if (t.isValidIdentifier(node.name.name, false)) {
  459. node.name.type = "Identifier";
  460. } else {
  461. node.name = t.stringLiteral(node.name.name);
  462. }
  463. return t.inherits(t.objectProperty(node.name, value), node);
  464. }
  465. // Builds JSX into:
  466. // Production: React.jsx(type, arguments, key)
  467. // Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
  468. function buildJSXElementCall(path, file) {
  469. const openingPath = path.get("openingElement");
  470. openingPath.parent.children = t.react.buildChildren(openingPath.parent);
  471. const tagExpr = convertJSXIdentifier(
  472. openingPath.node.name,
  473. openingPath.node,
  474. );
  475. const args = [];
  476. let tagName;
  477. if (t.isIdentifier(tagExpr)) {
  478. tagName = tagExpr.name;
  479. } else if (t.isLiteral(tagExpr)) {
  480. tagName = tagExpr.value;
  481. }
  482. const state = {
  483. tagExpr: tagExpr,
  484. tagName: tagName,
  485. args: args,
  486. pure: false,
  487. };
  488. if (options.pre) {
  489. options.pre(state, file);
  490. }
  491. let attribs = [];
  492. const extracted = Object.create(null);
  493. // for React.jsx, key, __source (dev), and __self (dev) is passed in as
  494. // a separate argument rather than in the args object. We go through the
  495. // props and filter out these three keywords so we can pass them in
  496. // as separate arguments later
  497. for (const attr of openingPath.get("attributes")) {
  498. if (attr.isJSXAttribute() && t.isJSXIdentifier(attr.node.name)) {
  499. const { name } = attr.node.name;
  500. switch (name) {
  501. case "__source":
  502. case "__self":
  503. if (extracted[name]) throw sourceSelfError(path, name);
  504. /* falls through */
  505. case "key":
  506. extracted[name] = convertAttributeValue(attr.node.value);
  507. break;
  508. default:
  509. attribs.push(attr.node);
  510. }
  511. } else {
  512. attribs.push(attr.node);
  513. }
  514. }
  515. if (attribs.length || path.node.children.length) {
  516. attribs = buildJSXOpeningElementAttributes(
  517. attribs,
  518. file,
  519. path.node.children,
  520. );
  521. } else {
  522. // attributes should never be null
  523. attribs = t.objectExpression([]);
  524. }
  525. args.push(attribs);
  526. if (!options.development) {
  527. if (extracted.key !== undefined) {
  528. args.push(extracted.key);
  529. }
  530. } else {
  531. // isStaticChildren, __source, and __self are only used in development
  532. // automatically include __source and __self in this plugin
  533. // so we can eliminate the need for separate Babel plugins in Babel 8
  534. args.push(
  535. extracted.key ?? path.scope.buildUndefinedNode(),
  536. t.booleanLiteral(path.node.children.length > 1),
  537. extracted.__source ?? path.scope.buildUndefinedNode(),
  538. extracted.__self ?? t.thisExpression(),
  539. );
  540. }
  541. if (options.post) {
  542. options.post(state, file);
  543. }
  544. const call =
  545. state.call ||
  546. t.callExpression(
  547. path.node.children.length > 1 ? state.jsxStaticCallee : state.jsxCallee,
  548. args,
  549. );
  550. if (state.pure) annotateAsPure(call);
  551. return call;
  552. }
  553. // Builds props for React.jsx. This function adds children into the props
  554. // and ensures that props is always an object
  555. function buildJSXOpeningElementAttributes(attribs, file, children) {
  556. const props = attribs.map(convertAttribute);
  557. // In React.jsx, children is no longer a separate argument, but passed in
  558. // through the argument object
  559. if (children && children.length > 0) {
  560. if (children.length === 1) {
  561. props.push(t.objectProperty(t.identifier("children"), children[0]));
  562. } else {
  563. props.push(
  564. t.objectProperty(
  565. t.identifier("children"),
  566. t.arrayExpression(children),
  567. ),
  568. );
  569. }
  570. }
  571. return t.objectExpression(props);
  572. }
  573. // Builds JSX Fragment <></> into
  574. // Production: React.jsx(type, arguments)
  575. // Development: React.jsxDEV(type, { children})
  576. function buildJSXFragmentCall(path, file) {
  577. const openingPath = path.get("openingElement");
  578. openingPath.parent.children = t.react.buildChildren(openingPath.parent);
  579. const args = [];
  580. const tagName = null;
  581. const tagExpr = file.get("@babel/plugin-react-jsx/jsxFragIdentifier")();
  582. const state = {
  583. tagExpr: tagExpr,
  584. tagName: tagName,
  585. args: args,
  586. pure: false,
  587. };
  588. if (options.pre) {
  589. options.pre(state, file);
  590. }
  591. let childrenNode;
  592. if (path.node.children.length > 0) {
  593. if (path.node.children.length === 1) {
  594. childrenNode = path.node.children[0];
  595. } else {
  596. childrenNode = t.arrayExpression(path.node.children);
  597. }
  598. }
  599. args.push(
  600. t.objectExpression(
  601. childrenNode !== undefined
  602. ? [t.objectProperty(t.identifier("children"), childrenNode)]
  603. : [],
  604. ),
  605. );
  606. if (options.development) {
  607. args.push(
  608. path.scope.buildUndefinedNode(),
  609. t.booleanLiteral(path.node.children.length > 1),
  610. );
  611. }
  612. if (options.post) {
  613. options.post(state, file);
  614. }
  615. const call =
  616. state.call ||
  617. t.callExpression(
  618. path.node.children.length > 1 ? state.jsxStaticCallee : state.jsxCallee,
  619. args,
  620. );
  621. if (state.pure) annotateAsPure(call);
  622. return call;
  623. }
  624. function buildCreateElementFragmentCall(path, file) {
  625. if (options.filter && !options.filter(path.node, file)) {
  626. return;
  627. }
  628. const openingPath = path.get("openingElement");
  629. openingPath.parent.children = t.react.buildChildren(openingPath.parent);
  630. const args = [];
  631. const tagName = null;
  632. const tagExpr = file.get("@babel/plugin-react-jsx/jsxFragIdentifier")();
  633. const state = {
  634. tagExpr: tagExpr,
  635. tagName: tagName,
  636. args: args,
  637. pure: false,
  638. };
  639. if (options.pre) {
  640. options.pre(state, file);
  641. }
  642. // no attributes are allowed with <> syntax
  643. args.push(t.nullLiteral(), ...path.node.children);
  644. if (options.post) {
  645. options.post(state, file);
  646. }
  647. file.set("@babel/plugin-react-jsx/usedFragment", true);
  648. const call =
  649. state.call || t.callExpression(state.createElementCallee, args);
  650. if (state.pure) annotateAsPure(call);
  651. return call;
  652. }
  653. // Builds JSX into:
  654. // Production: React.createElement(type, arguments, children)
  655. // Development: React.createElement(type, arguments, children, source, self)
  656. function buildCreateElementCall(path, file) {
  657. const openingPath = path.get("openingElement");
  658. openingPath.parent.children = t.react.buildChildren(openingPath.parent);
  659. const tagExpr = convertJSXIdentifier(
  660. openingPath.node.name,
  661. openingPath.node,
  662. );
  663. const args = [];
  664. let tagName;
  665. if (t.isIdentifier(tagExpr)) {
  666. tagName = tagExpr.name;
  667. } else if (t.isLiteral(tagExpr)) {
  668. tagName = tagExpr.value;
  669. }
  670. const state = {
  671. tagExpr: tagExpr,
  672. tagName: tagName,
  673. args: args,
  674. pure: false,
  675. };
  676. if (options.pre) {
  677. options.pre(state, file);
  678. }
  679. const attribs = buildCreateElementOpeningElementAttributes(
  680. path,
  681. openingPath.node.attributes,
  682. );
  683. args.push(attribs, ...path.node.children);
  684. if (options.post) {
  685. options.post(state, file);
  686. }
  687. const call =
  688. state.call || t.callExpression(state.createElementCallee, args);
  689. if (state.pure) annotateAsPure(call);
  690. return call;
  691. }
  692. /**
  693. * The logic for this is quite terse. It's because we need to
  694. * support spread elements. We loop over all attributes,
  695. * breaking on spreads, we then push a new object containing
  696. * all prior attributes to an array for later processing.
  697. */
  698. function buildCreateElementOpeningElementAttributes(path, attribs) {
  699. const props = [];
  700. const found = Object.create(null);
  701. for (const attr of attribs) {
  702. const name =
  703. t.isJSXAttribute(attr) &&
  704. t.isJSXIdentifier(attr.name) &&
  705. attr.name.name;
  706. if (name === "__source" || name === "__self") {
  707. if (found[name]) throw sourceSelfError(path, name);
  708. found[name] = true;
  709. if (!options.development) continue;
  710. }
  711. props.push(convertAttribute(attr));
  712. }
  713. return props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
  714. }
  715. function sourceSelfError(path, name) {
  716. const pluginName = `transform-react-jsx-${name.slice(2)}`;
  717. return path.buildCodeFrameError(
  718. `Duplicate ${name} prop found. You are most likely using the deprecated ${pluginName} Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.`,
  719. );
  720. }
  721. }