svgedit
Version:
Powerful SVG-Editor for your browser
490 lines (487 loc) • 20.7 kB
JavaScript
import jsx from '@babel/plugin-syntax-jsx';
import { declare } from '@babel/helper-plugin-utils';
import { types, template } from '@babel/core';
import { isModule, addNamed, addNamespace } from '@babel/helper-module-imports';
import annotateAsPure from '@babel/helper-annotate-as-pure';
const DEFAULT = {
importSource: "react",
pragma: "React.createElement",
pragmaFrag: "React.Fragment"
};
const JSX_SOURCE_ANNOTATION_REGEX = /^\s*(?:\*\s*)?@jsxImportSource\s+(\S+)\s*$/m;
const JSX_RUNTIME_ANNOTATION_REGEX = /^\s*(?:\*\s*)?@jsxRuntime\s+(\S+)\s*$/m;
const JSX_ANNOTATION_REGEX = /^\s*(?:\*\s*)?@jsx\s+(\S+)\s*$/m;
const JSX_FRAG_ANNOTATION_REGEX = /^\s*(?:\*\s*)?@jsxFrag\s+(\S+)\s*$/m;
const get = (pass, name) => pass.get(`@babel/plugin-react-jsx/${name}`);
const set = (pass, name, v) => pass.set(`@babel/plugin-react-jsx/${name}`, v);
function hasProto(node) {
return node.properties.some(value => types.isObjectProperty(value, {
computed: false,
shorthand: false
}) && (types.isIdentifier(value.key, {
name: "__proto__"
}) || types.isStringLiteral(value.key, {
value: "__proto__"
})));
}
function createPlugin({
name,
development
}) {
return declare((_, options) => {
const {
pure: PURE_ANNOTATION,
throwIfNamespace = true,
filter,
runtime: RUNTIME_DEFAULT = "automatic",
importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource,
pragma: PRAGMA_DEFAULT = DEFAULT.pragma,
pragmaFrag: PRAGMA_FRAG_DEFAULT = DEFAULT.pragmaFrag
} = options;
const sourceSelf = development ? options.sourceSelf : undefined;
if ("useSpread" in options) {
throw new Error('@babel/plugin-transform-react-jsx: Since Babel 8, an inline object with spread elements is always used, and the "useSpread" option is no longer available. Please remove it from your config.');
}
if ("useBuiltIns" in options) {
const useBuiltInsFormatted = JSON.stringify(options.useBuiltIns);
throw new Error(`@babel/plugin-transform-react-jsx: Since "useBuiltIns" is removed in Babel 8, you can remove it from the config.
- Babel 8 now transforms JSX spread to object spread. If you need to transpile object spread with
\`useBuiltIns: ${useBuiltInsFormatted}\`, you can use the following config
{
"plugins": [
"@babel/plugin-transform-react-jsx"
["@babel/plugin-transform-object-rest-spread", { "loose": true, "useBuiltIns": ${useBuiltInsFormatted} }]
]
}`);
}
if (filter != null && RUNTIME_DEFAULT === "automatic") {
throw new Error('@babel/plugin-transform-react-jsx: "filter" option can not be used with automatic runtime. If you are upgrading from Babel 7, please specify `runtime: "classic"`.');
}
let commentsNode = null;
return {
name,
inherits: jsx,
visitor: {
JSXNamespacedName(path) {
if (throwIfNamespace) {
throw path.buildCodeFrameError(`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
You can set \`throwIfNamespace: false\` to bypass this warning.`);
}
},
JSXSpreadChild(path) {
throw path.buildCodeFrameError("Spread children are not supported in React.");
},
Program: {
enter(path, state) {
const {
file
} = state;
let runtime = RUNTIME_DEFAULT;
let source = IMPORT_SOURCE_DEFAULT;
let pragma = PRAGMA_DEFAULT;
let pragmaFrag = PRAGMA_FRAG_DEFAULT;
let sourceSet = !!options.importSource;
let pragmaSet = !!options.pragma;
let pragmaFragSet = !!options.pragmaFrag;
if (file.ast.comments) {
for (const comment of file.ast.comments) {
const sourceMatches = JSX_SOURCE_ANNOTATION_REGEX.exec(comment.value);
if (sourceMatches) {
source = sourceMatches[1];
sourceSet = true;
}
const runtimeMatches = JSX_RUNTIME_ANNOTATION_REGEX.exec(comment.value);
if (runtimeMatches) {
runtime = runtimeMatches[1];
}
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
if (jsxMatches) {
pragma = jsxMatches[1];
pragmaSet = true;
}
const jsxFragMatches = JSX_FRAG_ANNOTATION_REGEX.exec(comment.value);
if (jsxFragMatches) {
pragmaFrag = jsxFragMatches[1];
pragmaFragSet = true;
}
}
}
set(state, "runtime", runtime);
if (runtime === "classic") {
if (sourceSet) {
throw path.buildCodeFrameError(`importSource cannot be set when runtime is classic.`);
}
const createElement = toMemberExpression(pragma);
const fragment = toMemberExpression(pragmaFrag);
set(state, "id/createElement", () => types.cloneNode(createElement));
set(state, "id/fragment", () => types.cloneNode(fragment));
set(state, "defaultPure", pragma === DEFAULT.pragma);
} else if (runtime === "automatic") {
if (pragmaSet || pragmaFragSet) {
throw path.buildCodeFrameError(`pragma and pragmaFrag cannot be set when runtime is automatic.`);
}
const define = (name, id) => set(state, name, createImportLazily(state, path, id, source));
define("id/jsx", development ? "jsxDEV" : "jsx");
define("id/jsxs", development ? "jsxDEV" : "jsxs");
define("id/createElement", "createElement");
define("id/fragment", "Fragment");
set(state, "defaultPure", source === DEFAULT.importSource);
} else {
throw path.buildCodeFrameError(`Runtime must be either "classic" or "automatic".`);
}
if (development && sourceSelf) {
function isDerivedClass(classNode) {
return classNode.superClass !== null;
}
function isThisAllowed(parents) {
let i = parents.length - 1;
do {
const {
node
} = parents[i];
if (types.isFunctionParent(node) && !types.isArrowFunctionExpression(node)) {
if (!types.isMethod(node)) {
return true;
}
if (node.kind !== "constructor") {
return true;
}
return !isDerivedClass(parents[i - 2].node);
}
if (types.isTSModuleBlock(node)) {
return false;
}
} while (i-- > 0);
return true;
}
let fileNameIdentifier;
function makeSource(node) {
const location = node.loc;
if (!location) {
return types.buildUndefinedNode();
}
if (!fileNameIdentifier) {
fileNameIdentifier = path.scope.generateUidIdentifier("_jsxFileName");
}
return makeTrace(types.cloneNode(fileNameIdentifier), location.start.line, location.start.column);
}
function makeTrace(fileNameIdentifier, lineNumber, column0Based) {
const fileLineLiteral = lineNumber != null ? types.numericLiteral(lineNumber) : types.nullLiteral();
const fileColumnLiteral = column0Based != null ? types.numericLiteral(column0Based + 1) : types.nullLiteral();
return template.expression.ast`{
fileName: ${fileNameIdentifier},
lineNumber: ${fileLineLiteral},
columnNumber: ${fileColumnLiteral},
}`;
}
types.traverse(path.node, {
enter(node, parents) {
if (!types.isJSXOpeningElement(node)) {
return;
}
const attributes = node.attributes;
if (isThisAllowed(parents)) {
attributes.push(types.jsxAttribute(types.jsxIdentifier("__self"), types.jsxExpressionContainer(types.thisExpression())));
}
attributes.push(types.jsxAttribute(types.jsxIdentifier("__source"), types.jsxExpressionContainer(makeSource(node))));
}
});
if (fileNameIdentifier) {
const {
filename = ""
} = state;
path.scope.push({
id: fileNameIdentifier,
init: types.stringLiteral(filename)
});
}
}
}
},
JSXFragment: {
exit(path, file) {
let callExpr;
if (get(file, "runtime") === "classic") {
callExpr = buildCreateElementFragmentCall(path, file);
} else {
callExpr = buildJSXFragmentCall(path, file);
}
path.replaceWith(types.inherits(callExpr, path.node));
}
},
JSXElement: {
exit(path, file) {
let callExpr;
if (get(file, "runtime") === "classic" || shouldUseCreateElement(path)) {
callExpr = buildCreateElementCall(path, file);
} else {
callExpr = buildJSXElementCall(path, file);
}
path.replaceWith(types.inherits(callExpr, path.node));
}
},
JSXAttribute(path) {
if (types.isJSXElement(path.node.value)) {
path.node.value = types.jsxExpressionContainer(path.node.value);
}
}
}
};
function call(pass, name, args) {
const node = types.callExpression(get(pass, `id/${name}`)(), args);
if (PURE_ANNOTATION ?? get(pass, "defaultPure")) annotateAsPure(node);
return node;
}
function shouldUseCreateElement(path) {
const openingPath = path.get("openingElement");
const attributes = openingPath.node.attributes;
let seenPropsSpread = false;
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
if (seenPropsSpread && types.isJSXAttribute(attr) && attr.name.name === "key") {
return true;
} else if (types.isJSXSpreadAttribute(attr)) {
seenPropsSpread = true;
}
}
return false;
}
function convertJSXIdentifier(node, parent) {
if (types.isJSXIdentifier(node)) {
if (node.name === "this" && types.isReferenced(node, parent)) {
return types.thisExpression();
} else if (types.isValidIdentifier(node.name, false)) {
node.type = "Identifier";
return node;
} else {
return types.stringLiteral(node.name);
}
} else if (types.isJSXMemberExpression(node)) {
return types.memberExpression(convertJSXIdentifier(node.object, node), convertJSXIdentifier(node.property, node));
} else if (types.isJSXNamespacedName(node)) {
return types.stringLiteral(`${node.namespace.name}:${node.name.name}`);
}
return node;
}
function convertAttributeValue(node) {
if (types.isJSXExpressionContainer(node)) {
return node.expression;
} else {
return node;
}
}
function processComments(attribs) {
commentsNode = null;
if (attribs.length && attribs[0].isJSXSpreadAttribute()) {
const node = attribs[0].node.argument;
if (node.leadingComments || node.trailingComments) {
commentsNode = types.cloneNode(node);
}
}
}
function accumulateAttribute(array, attribute) {
if (types.isJSXSpreadAttribute(attribute.node)) {
const arg = attribute.node.argument;
if (types.isObjectExpression(arg) && !hasProto(arg)) {
array.push(...arg.properties);
} else {
array.push(types.spreadElement(arg));
}
return array;
}
const value = convertAttributeValue(attribute.node.name.name !== "key" ? attribute.node.value || types.booleanLiteral(true) : attribute.node.value);
if (attribute.node.name.name === "key" && value === null) {
throw attribute.buildCodeFrameError('Please provide an explicit key value. Using "key" as a shorthand for "key={true}" is not allowed.');
}
if (types.isStringLiteral(value) && !types.isJSXExpressionContainer(attribute.node.value)) {
value.value = value.value.replace(/\n\s+/g, " ");
delete value.extra?.raw;
}
if (types.isJSXNamespacedName(attribute.node.name)) {
attribute.node.name = types.stringLiteral(attribute.node.name.namespace.name + ":" + attribute.node.name.name.name);
} else if (types.isValidIdentifier(attribute.node.name.name, false)) {
attribute.node.name.type = "Identifier";
} else {
attribute.node.name = types.stringLiteral(attribute.node.name.name);
}
array.push(types.inherits(types.objectProperty(attribute.node.name, value), attribute.node));
return array;
}
function buildChildrenProperty(children) {
let childrenNode;
if (children.length === 1) {
childrenNode = children[0];
} else if (children.length > 1) {
childrenNode = types.arrayExpression(children);
} else {
return undefined;
}
return types.objectProperty(types.identifier("children"), childrenNode);
}
function buildJSXElementCall(path, file) {
const openingPath = path.get("openingElement");
const args = [getTag(openingPath)];
const attribsArray = [];
const extracted = Object.create(null);
for (const attr of openingPath.get("attributes")) {
if (attr.isJSXAttribute() && types.isJSXIdentifier(attr.node.name)) {
const {
name
} = attr.node.name;
switch (name) {
case "__source":
case "__self":
if (extracted[name]) throw sourceSelfError(path, name);
case "key":
{
const keyValue = convertAttributeValue(attr.node.value);
if (keyValue === null) {
throw attr.buildCodeFrameError('Please provide an explicit key value. Using "key" as a shorthand for "key={true}" is not allowed.');
}
extracted[name] = keyValue;
break;
}
default:
attribsArray.push(attr);
}
} else {
attribsArray.push(attr);
}
}
const children = types.react.buildChildren(path.node);
let attribs;
if (attribsArray.length || children.length) {
attribs = buildJSXOpeningElementAttributes(attribsArray, children);
if (commentsNode) {
types.inheritsComments(attribs, commentsNode);
}
} else {
attribs = types.objectExpression([]);
}
args.push(attribs);
if (development) {
args.push(extracted.key ?? types.buildUndefinedNode(), types.booleanLiteral(children.length > 1));
if (sourceSelf) {
if (extracted.__source) {
args.push(extracted.__source);
if (extracted.__self) args.push(extracted.__self);
} else if (extracted.__self) {
args.push(types.buildUndefinedNode(), extracted.__self);
}
}
} else if (extracted.key !== undefined) {
args.push(extracted.key);
}
return call(file, children.length > 1 ? "jsxs" : "jsx", args);
}
function buildJSXOpeningElementAttributes(attribs, children) {
processComments(attribs);
const props = attribs.reduce(accumulateAttribute, []);
if (children?.length > 0) {
props.push(buildChildrenProperty(children));
}
return types.objectExpression(props);
}
function buildJSXFragmentCall(path, file) {
const args = [get(file, "id/fragment")()];
const children = types.react.buildChildren(path.node);
args.push(types.objectExpression(children.length > 0 ? [buildChildrenProperty(children)] : []));
if (development) {
args.push(types.buildUndefinedNode(), types.booleanLiteral(children.length > 1));
}
return call(file, children.length > 1 ? "jsxs" : "jsx", args);
}
function buildCreateElementFragmentCall(path, file) {
if (filter && !filter(path.node, file)) return;
return call(file, "createElement", [get(file, "id/fragment")(), types.nullLiteral(), ...types.react.buildChildren(path.node)]);
}
function buildCreateElementCall(path, file) {
const openingPath = path.get("openingElement");
return call(file, "createElement", [getTag(openingPath), buildCreateElementOpeningElementAttributes(file, path, openingPath.get("attributes")), ...types.react.buildChildren(path.node)]);
}
function getTag(openingPath) {
const tagExpr = convertJSXIdentifier(openingPath.node.name, openingPath.node);
let tagName;
if (types.isIdentifier(tagExpr)) {
tagName = tagExpr.name;
} else if (types.isStringLiteral(tagExpr)) {
tagName = tagExpr.value;
}
if (types.react.isCompatTag(tagName)) {
return types.stringLiteral(tagName);
} else {
return tagExpr;
}
}
function buildCreateElementOpeningElementAttributes(file, path, attribs) {
const runtime = get(file, "runtime");
const props = [];
const found = Object.create(null);
processComments(attribs);
for (const attr of attribs) {
const {
node
} = attr;
const name = types.isJSXAttribute(node) && types.isJSXIdentifier(node.name) && node.name.name;
if (runtime === "automatic" && (name === "__source" || name === "__self")) {
if (found[name]) throw sourceSelfError(path, name);
found[name] = true;
}
accumulateAttribute(props, attr);
}
const ret = props.length === 1 && types.isSpreadElement(props[0]) && !types.isObjectExpression(props[0].argument) ? props[0].argument : props.length > 0 ? types.objectExpression(props) : types.nullLiteral();
if (commentsNode) {
types.inheritsComments(ret, commentsNode);
}
return ret;
}
});
function getSource(source, importName) {
switch (importName) {
case "Fragment":
return `${source}/${development ? "jsx-dev-runtime" : "jsx-runtime"}`;
case "jsxDEV":
return `${source}/jsx-dev-runtime`;
case "jsx":
case "jsxs":
return `${source}/jsx-runtime`;
case "createElement":
return source;
}
}
function createImportLazily(pass, path, importName, source) {
return () => {
const actualSource = getSource(source, importName);
if (isModule(path)) {
let reference = get(pass, `imports/${importName}`);
if (reference) return types.cloneNode(reference);
reference = addNamed(path, importName, actualSource, {
importedInterop: "uncompiled",
importPosition: "after"
});
set(pass, `imports/${importName}`, reference);
return reference;
} else {
let reference = get(pass, `requires/${actualSource}`);
if (reference) {
reference = types.cloneNode(reference);
} else {
reference = addNamespace(path, actualSource, {
importedInterop: "uncompiled"
});
set(pass, `requires/${actualSource}`, reference);
}
return types.memberExpression(reference, types.identifier(importName));
}
};
}
}
function toMemberExpression(id) {
return id.split(".").map(name => types.identifier(name)).reduce((object, property) => types.memberExpression(object, property));
}
function sourceSelfError(path, name) {
const pluginName = `transform-react-jsx-${name.slice(2)}`;
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.`);
}
export { createPlugin };
//# sourceMappingURL=create-plugin-shared.js.map