UNPKG

21 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = createPlugin;
7
8var _pluginSyntaxJsx = require("@babel/plugin-syntax-jsx");
9
10var _helperPluginUtils = require("@babel/helper-plugin-utils");
11
12var _core = require("@babel/core");
13
14var _helperModuleImports = require("@babel/helper-module-imports");
15
16var _helperAnnotateAsPure = require("@babel/helper-annotate-as-pure");
17
18const DEFAULT = {
19 importSource: "react",
20 runtime: "automatic",
21 pragma: "React.createElement",
22 pragmaFrag: "React.Fragment"
23};
24const JSX_SOURCE_ANNOTATION_REGEX = /^\s*\*?\s*@jsxImportSource\s+([^\s]+)\s*$/m;
25const JSX_RUNTIME_ANNOTATION_REGEX = /^\s*\*?\s*@jsxRuntime\s+([^\s]+)\s*$/m;
26const JSX_ANNOTATION_REGEX = /^\s*\*?\s*@jsx\s+([^\s]+)\s*$/m;
27const JSX_FRAG_ANNOTATION_REGEX = /^\s*\*?\s*@jsxFrag\s+([^\s]+)\s*$/m;
28
29const get = (pass, name) => pass.get(`@babel/plugin-react-jsx/${name}`);
30
31const set = (pass, name, v) => pass.set(`@babel/plugin-react-jsx/${name}`, v);
32
33function createPlugin({
34 name,
35 development
36}) {
37 return (0, _helperPluginUtils.declare)((_, options) => {
38 const {
39 pure: PURE_ANNOTATION,
40 throwIfNamespace = true,
41 filter,
42 runtime: RUNTIME_DEFAULT = development ? "automatic" : "classic",
43 importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource,
44 pragma: PRAGMA_DEFAULT = DEFAULT.pragma,
45 pragmaFrag: PRAGMA_FRAG_DEFAULT = DEFAULT.pragmaFrag
46 } = options;
47 {
48 var {
49 useSpread = false,
50 useBuiltIns = false
51 } = options;
52
53 if (RUNTIME_DEFAULT === "classic") {
54 if (typeof useSpread !== "boolean") {
55 throw new Error("transform-react-jsx currently only accepts a boolean option for " + "useSpread (defaults to false)");
56 }
57
58 if (typeof useBuiltIns !== "boolean") {
59 throw new Error("transform-react-jsx currently only accepts a boolean option for " + "useBuiltIns (defaults to false)");
60 }
61
62 if (useSpread && useBuiltIns) {
63 throw new Error("transform-react-jsx currently only accepts useBuiltIns or useSpread " + "but not both");
64 }
65 }
66 }
67 const injectMetaPropertiesVisitor = {
68 JSXOpeningElement(path, state) {
69 const attributes = [];
70
71 if (isThisAllowed(path.scope)) {
72 attributes.push(_core.types.jsxAttribute(_core.types.jsxIdentifier("__self"), _core.types.jsxExpressionContainer(_core.types.thisExpression())));
73 }
74
75 attributes.push(_core.types.jsxAttribute(_core.types.jsxIdentifier("__source"), _core.types.jsxExpressionContainer(makeSource(path, state))));
76 path.pushContainer("attributes", attributes);
77 }
78
79 };
80 return {
81 name,
82 inherits: _pluginSyntaxJsx.default,
83 visitor: {
84 JSXNamespacedName(path) {
85 if (throwIfNamespace) {
86 throw path.buildCodeFrameError(`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
87You can set \`throwIfNamespace: false\` to bypass this warning.`);
88 }
89 },
90
91 JSXSpreadChild(path) {
92 throw path.buildCodeFrameError("Spread children are not supported in React.");
93 },
94
95 Program: {
96 enter(path, state) {
97 const {
98 file
99 } = state;
100 let runtime = RUNTIME_DEFAULT;
101 let source = IMPORT_SOURCE_DEFAULT;
102 let pragma = PRAGMA_DEFAULT;
103 let pragmaFrag = PRAGMA_FRAG_DEFAULT;
104 let sourceSet = !!options.importSource;
105 let pragmaSet = !!options.pragma;
106 let pragmaFragSet = !!options.pragmaFrag;
107
108 if (file.ast.comments) {
109 for (const comment of file.ast.comments) {
110 const sourceMatches = JSX_SOURCE_ANNOTATION_REGEX.exec(comment.value);
111
112 if (sourceMatches) {
113 source = sourceMatches[1];
114 sourceSet = true;
115 }
116
117 const runtimeMatches = JSX_RUNTIME_ANNOTATION_REGEX.exec(comment.value);
118
119 if (runtimeMatches) {
120 runtime = runtimeMatches[1];
121 }
122
123 const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
124
125 if (jsxMatches) {
126 pragma = jsxMatches[1];
127 pragmaSet = true;
128 }
129
130 const jsxFragMatches = JSX_FRAG_ANNOTATION_REGEX.exec(comment.value);
131
132 if (jsxFragMatches) {
133 pragmaFrag = jsxFragMatches[1];
134 pragmaFragSet = true;
135 }
136 }
137 }
138
139 set(state, "runtime", runtime);
140
141 if (runtime === "classic") {
142 if (sourceSet) {
143 throw path.buildCodeFrameError(`importSource cannot be set when runtime is classic.`);
144 }
145
146 const createElement = toMemberExpression(pragma);
147 const fragment = toMemberExpression(pragmaFrag);
148 set(state, "id/createElement", () => _core.types.cloneNode(createElement));
149 set(state, "id/fragment", () => _core.types.cloneNode(fragment));
150 set(state, "defaultPure", pragma === DEFAULT.pragma);
151 } else if (runtime === "automatic") {
152 if (pragmaSet || pragmaFragSet) {
153 throw path.buildCodeFrameError(`pragma and pragmaFrag cannot be set when runtime is automatic.`);
154 }
155
156 const define = (name, id) => set(state, name, createImportLazily(state, path, id, source));
157
158 define("id/jsx", development ? "jsxDEV" : "jsx");
159 define("id/jsxs", development ? "jsxDEV" : "jsxs");
160 define("id/createElement", "createElement");
161 define("id/fragment", "Fragment");
162 set(state, "defaultPure", source === DEFAULT.importSource);
163 } else {
164 throw path.buildCodeFrameError(`Runtime must be either "classic" or "automatic".`);
165 }
166
167 if (development) {
168 path.traverse(injectMetaPropertiesVisitor, state);
169 }
170 }
171
172 },
173 JSXElement: {
174 exit(path, file) {
175 let callExpr;
176
177 if (get(file, "runtime") === "classic" || shouldUseCreateElement(path)) {
178 callExpr = buildCreateElementCall(path, file);
179 } else {
180 callExpr = buildJSXElementCall(path, file);
181 }
182
183 path.replaceWith(_core.types.inherits(callExpr, path.node));
184 }
185
186 },
187 JSXFragment: {
188 exit(path, file) {
189 let callExpr;
190
191 if (get(file, "runtime") === "classic") {
192 callExpr = buildCreateElementFragmentCall(path, file);
193 } else {
194 callExpr = buildJSXFragmentCall(path, file);
195 }
196
197 path.replaceWith(_core.types.inherits(callExpr, path.node));
198 }
199
200 },
201
202 JSXAttribute(path) {
203 if (_core.types.isJSXElement(path.node.value)) {
204 path.node.value = _core.types.jsxExpressionContainer(path.node.value);
205 }
206 }
207
208 }
209 };
210
211 function isDerivedClass(classPath) {
212 return classPath.node.superClass !== null;
213 }
214
215 function isThisAllowed(scope) {
216 do {
217 const {
218 path
219 } = scope;
220
221 if (path.isFunctionParent() && !path.isArrowFunctionExpression()) {
222 if (!path.isMethod()) {
223 return true;
224 }
225
226 if (path.node.kind !== "constructor") {
227 return true;
228 }
229
230 return !isDerivedClass(path.parentPath.parentPath);
231 }
232
233 if (path.isTSModuleBlock()) {
234 return false;
235 }
236 } while (scope = scope.parent);
237
238 return true;
239 }
240
241 function call(pass, name, args) {
242 const node = _core.types.callExpression(get(pass, `id/${name}`)(), args);
243
244 if (PURE_ANNOTATION != null ? PURE_ANNOTATION : get(pass, "defaultPure")) (0, _helperAnnotateAsPure.default)(node);
245 return node;
246 }
247
248 function shouldUseCreateElement(path) {
249 const openingPath = path.get("openingElement");
250 const attributes = openingPath.node.attributes;
251 let seenPropsSpread = false;
252
253 for (let i = 0; i < attributes.length; i++) {
254 const attr = attributes[i];
255
256 if (seenPropsSpread && _core.types.isJSXAttribute(attr) && attr.name.name === "key") {
257 return true;
258 } else if (_core.types.isJSXSpreadAttribute(attr)) {
259 seenPropsSpread = true;
260 }
261 }
262
263 return false;
264 }
265
266 function convertJSXIdentifier(node, parent) {
267 if (_core.types.isJSXIdentifier(node)) {
268 if (node.name === "this" && _core.types.isReferenced(node, parent)) {
269 return _core.types.thisExpression();
270 } else if (_core.types.isValidIdentifier(node.name, false)) {
271 node.type = "Identifier";
272 } else {
273 return _core.types.stringLiteral(node.name);
274 }
275 } else if (_core.types.isJSXMemberExpression(node)) {
276 return _core.types.memberExpression(convertJSXIdentifier(node.object, node), convertJSXIdentifier(node.property, node));
277 } else if (_core.types.isJSXNamespacedName(node)) {
278 return _core.types.stringLiteral(`${node.namespace.name}:${node.name.name}`);
279 }
280
281 return node;
282 }
283
284 function convertAttributeValue(node) {
285 if (_core.types.isJSXExpressionContainer(node)) {
286 return node.expression;
287 } else {
288 return node;
289 }
290 }
291
292 function accumulateAttribute(array, attribute) {
293 if (_core.types.isJSXSpreadAttribute(attribute.node)) {
294 const arg = attribute.node.argument;
295
296 if (_core.types.isObjectExpression(arg)) {
297 array.push(...arg.properties);
298 } else {
299 array.push(_core.types.spreadElement(arg));
300 }
301
302 return array;
303 }
304
305 const value = convertAttributeValue(attribute.node.name.name !== "key" ? attribute.node.value || _core.types.booleanLiteral(true) : attribute.node.value);
306
307 if (attribute.node.name.name === "key" && value === null) {
308 throw attribute.buildCodeFrameError('Please provide an explicit key value. Using "key" as a shorthand for "key={true}" is not allowed.');
309 }
310
311 if (_core.types.isStringLiteral(value) && !_core.types.isJSXExpressionContainer(attribute.node.value)) {
312 var _value$extra;
313
314 value.value = value.value.replace(/\n\s+/g, " ");
315 (_value$extra = value.extra) == null ? true : delete _value$extra.raw;
316 }
317
318 if (_core.types.isJSXNamespacedName(attribute.node.name)) {
319 attribute.node.name = _core.types.stringLiteral(attribute.node.name.namespace.name + ":" + attribute.node.name.name.name);
320 } else if (_core.types.isValidIdentifier(attribute.node.name.name, false)) {
321 attribute.node.name.type = "Identifier";
322 } else {
323 attribute.node.name = _core.types.stringLiteral(attribute.node.name.name);
324 }
325
326 array.push(_core.types.inherits(_core.types.objectProperty(attribute.node.name, value), attribute.node));
327 return array;
328 }
329
330 function buildChildrenProperty(children) {
331 let childrenNode;
332
333 if (children.length === 1) {
334 childrenNode = children[0];
335 } else if (children.length > 1) {
336 childrenNode = _core.types.arrayExpression(children);
337 } else {
338 return undefined;
339 }
340
341 return _core.types.objectProperty(_core.types.identifier("children"), childrenNode);
342 }
343
344 function buildJSXElementCall(path, file) {
345 const openingPath = path.get("openingElement");
346 const args = [getTag(openingPath)];
347 const attribsArray = [];
348 const extracted = Object.create(null);
349
350 for (const attr of openingPath.get("attributes")) {
351 if (attr.isJSXAttribute() && _core.types.isJSXIdentifier(attr.node.name)) {
352 const {
353 name
354 } = attr.node.name;
355
356 switch (name) {
357 case "__source":
358 case "__self":
359 if (extracted[name]) throw sourceSelfError(path, name);
360
361 case "key":
362 {
363 const keyValue = convertAttributeValue(attr.node.value);
364
365 if (keyValue === null) {
366 throw attr.buildCodeFrameError('Please provide an explicit key value. Using "key" as a shorthand for "key={true}" is not allowed.');
367 }
368
369 extracted[name] = keyValue;
370 break;
371 }
372
373 default:
374 attribsArray.push(attr);
375 }
376 } else {
377 attribsArray.push(attr);
378 }
379 }
380
381 const children = _core.types.react.buildChildren(path.node);
382
383 let attribs;
384
385 if (attribsArray.length || children.length) {
386 attribs = buildJSXOpeningElementAttributes(attribsArray, children);
387 } else {
388 attribs = _core.types.objectExpression([]);
389 }
390
391 args.push(attribs);
392
393 if (development) {
394 var _extracted$key, _extracted$__source, _extracted$__self;
395
396 args.push((_extracted$key = extracted.key) != null ? _extracted$key : path.scope.buildUndefinedNode(), _core.types.booleanLiteral(children.length > 1), (_extracted$__source = extracted.__source) != null ? _extracted$__source : path.scope.buildUndefinedNode(), (_extracted$__self = extracted.__self) != null ? _extracted$__self : path.scope.buildUndefinedNode());
397 } else if (extracted.key !== undefined) {
398 args.push(extracted.key);
399 }
400
401 return call(file, children.length > 1 ? "jsxs" : "jsx", args);
402 }
403
404 function buildJSXOpeningElementAttributes(attribs, children) {
405 const props = attribs.reduce(accumulateAttribute, []);
406
407 if ((children == null ? void 0 : children.length) > 0) {
408 props.push(buildChildrenProperty(children));
409 }
410
411 return _core.types.objectExpression(props);
412 }
413
414 function buildJSXFragmentCall(path, file) {
415 const args = [get(file, "id/fragment")()];
416
417 const children = _core.types.react.buildChildren(path.node);
418
419 args.push(_core.types.objectExpression(children.length > 0 ? [buildChildrenProperty(children)] : []));
420
421 if (development) {
422 args.push(path.scope.buildUndefinedNode(), _core.types.booleanLiteral(children.length > 1));
423 }
424
425 return call(file, children.length > 1 ? "jsxs" : "jsx", args);
426 }
427
428 function buildCreateElementFragmentCall(path, file) {
429 if (filter && !filter(path.node, file)) return;
430 return call(file, "createElement", [get(file, "id/fragment")(), _core.types.nullLiteral(), ..._core.types.react.buildChildren(path.node)]);
431 }
432
433 function buildCreateElementCall(path, file) {
434 const openingPath = path.get("openingElement");
435 return call(file, "createElement", [getTag(openingPath), buildCreateElementOpeningElementAttributes(file, path, openingPath.get("attributes")), ..._core.types.react.buildChildren(path.node)]);
436 }
437
438 function getTag(openingPath) {
439 const tagExpr = convertJSXIdentifier(openingPath.node.name, openingPath.node);
440 let tagName;
441
442 if (_core.types.isIdentifier(tagExpr)) {
443 tagName = tagExpr.name;
444 } else if (_core.types.isStringLiteral(tagExpr)) {
445 tagName = tagExpr.value;
446 }
447
448 if (_core.types.react.isCompatTag(tagName)) {
449 return _core.types.stringLiteral(tagName);
450 } else {
451 return tagExpr;
452 }
453 }
454
455 function buildCreateElementOpeningElementAttributes(file, path, attribs) {
456 const runtime = get(file, "runtime");
457 {
458 if (runtime !== "automatic") {
459 const objs = [];
460 const props = attribs.reduce(accumulateAttribute, []);
461
462 if (!useSpread) {
463 let start = 0;
464 props.forEach((prop, i) => {
465 if (_core.types.isSpreadElement(prop)) {
466 if (i > start) {
467 objs.push(_core.types.objectExpression(props.slice(start, i)));
468 }
469
470 objs.push(prop.argument);
471 start = i + 1;
472 }
473 });
474
475 if (props.length > start) {
476 objs.push(_core.types.objectExpression(props.slice(start)));
477 }
478 } else if (props.length) {
479 objs.push(_core.types.objectExpression(props));
480 }
481
482 if (!objs.length) {
483 return _core.types.nullLiteral();
484 }
485
486 if (objs.length === 1) {
487 return objs[0];
488 }
489
490 if (!_core.types.isObjectExpression(objs[0])) {
491 objs.unshift(_core.types.objectExpression([]));
492 }
493
494 const helper = useBuiltIns ? _core.types.memberExpression(_core.types.identifier("Object"), _core.types.identifier("assign")) : file.addHelper("extends");
495 return _core.types.callExpression(helper, objs);
496 }
497 }
498 const props = [];
499 const found = Object.create(null);
500
501 for (const attr of attribs) {
502 const name = _core.types.isJSXAttribute(attr) && _core.types.isJSXIdentifier(attr.name) && attr.name.name;
503
504 if (runtime === "automatic" && (name === "__source" || name === "__self")) {
505 if (found[name]) throw sourceSelfError(path, name);
506 found[name] = true;
507 }
508
509 accumulateAttribute(props, attr);
510 }
511
512 return props.length === 1 && _core.types.isSpreadElement(props[0]) ? props[0].argument : props.length > 0 ? _core.types.objectExpression(props) : _core.types.nullLiteral();
513 }
514 });
515
516 function getSource(source, importName) {
517 switch (importName) {
518 case "Fragment":
519 return `${source}/${development ? "jsx-dev-runtime" : "jsx-runtime"}`;
520
521 case "jsxDEV":
522 return `${source}/jsx-dev-runtime`;
523
524 case "jsx":
525 case "jsxs":
526 return `${source}/jsx-runtime`;
527
528 case "createElement":
529 return source;
530 }
531 }
532
533 function createImportLazily(pass, path, importName, source) {
534 return () => {
535 const actualSource = getSource(source, importName);
536
537 if ((0, _helperModuleImports.isModule)(path)) {
538 let reference = get(pass, `imports/${importName}`);
539 if (reference) return _core.types.cloneNode(reference);
540 reference = (0, _helperModuleImports.addNamed)(path, importName, actualSource, {
541 importedInterop: "uncompiled",
542 importPosition: "after"
543 });
544 set(pass, `imports/${importName}`, reference);
545 return reference;
546 } else {
547 let reference = get(pass, `requires/${actualSource}`);
548
549 if (reference) {
550 reference = _core.types.cloneNode(reference);
551 } else {
552 reference = (0, _helperModuleImports.addNamespace)(path, actualSource, {
553 importedInterop: "uncompiled"
554 });
555 set(pass, `requires/${actualSource}`, reference);
556 }
557
558 return _core.types.memberExpression(reference, _core.types.identifier(importName));
559 }
560 };
561 }
562}
563
564function toMemberExpression(id) {
565 return id.split(".").map(name => _core.types.identifier(name)).reduce((object, property) => _core.types.memberExpression(object, property));
566}
567
568function makeSource(path, state) {
569 const location = path.node.loc;
570
571 if (!location) {
572 return path.scope.buildUndefinedNode();
573 }
574
575 if (!state.fileNameIdentifier) {
576 const {
577 filename = ""
578 } = state;
579 const fileNameIdentifier = path.scope.generateUidIdentifier("_jsxFileName");
580 const scope = path.hub.getScope();
581
582 if (scope) {
583 scope.push({
584 id: fileNameIdentifier,
585 init: _core.types.stringLiteral(filename)
586 });
587 }
588
589 state.fileNameIdentifier = fileNameIdentifier;
590 }
591
592 return makeTrace(_core.types.cloneNode(state.fileNameIdentifier), location.start.line, location.start.column);
593}
594
595function makeTrace(fileNameIdentifier, lineNumber, column0Based) {
596 const fileLineLiteral = lineNumber != null ? _core.types.numericLiteral(lineNumber) : _core.types.nullLiteral();
597 const fileColumnLiteral = column0Based != null ? _core.types.numericLiteral(column0Based + 1) : _core.types.nullLiteral();
598
599 const fileNameProperty = _core.types.objectProperty(_core.types.identifier("fileName"), fileNameIdentifier);
600
601 const lineNumberProperty = _core.types.objectProperty(_core.types.identifier("lineNumber"), fileLineLiteral);
602
603 const columnNumberProperty = _core.types.objectProperty(_core.types.identifier("columnNumber"), fileColumnLiteral);
604
605 return _core.types.objectExpression([fileNameProperty, lineNumberProperty, columnNumberProperty]);
606}
607
608function sourceSelfError(path, name) {
609 const pluginName = `transform-react-jsx-${name.slice(2)}`;
610 return path.buildCodeFrameError(`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.`);
611}
\No newline at end of file