You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
5.4 KiB
211 lines
5.4 KiB
"use strict"; |
|
|
|
Object.defineProperty(exports, "__esModule", { |
|
value: true |
|
}); |
|
exports.default = void 0; |
|
|
|
var _helperPluginUtils = require("@babel/helper-plugin-utils"); |
|
|
|
var _core = require("@babel/core"); |
|
|
|
var _default = (0, _helperPluginUtils.declare)((api, options) => { |
|
api.assertVersion(7); |
|
const { |
|
allowMutablePropsOnTags |
|
} = options; |
|
|
|
if (allowMutablePropsOnTags != null && !Array.isArray(allowMutablePropsOnTags)) { |
|
throw new Error(".allowMutablePropsOnTags must be an array, null, or undefined."); |
|
} |
|
|
|
const HOISTED = new WeakMap(); |
|
|
|
function declares(node, scope) { |
|
if (_core.types.isJSXIdentifier(node, { |
|
name: "this" |
|
}) || _core.types.isJSXIdentifier(node, { |
|
name: "arguments" |
|
}) || _core.types.isJSXIdentifier(node, { |
|
name: "super" |
|
}) || _core.types.isJSXIdentifier(node, { |
|
name: "new" |
|
})) { |
|
const { |
|
path |
|
} = scope; |
|
return path.isFunctionParent() && !path.isArrowFunctionExpression(); |
|
} |
|
|
|
return scope.hasOwnBinding(node.name); |
|
} |
|
|
|
function isHoistingScope({ |
|
path |
|
}) { |
|
return path.isFunctionParent() || path.isLoop() || path.isProgram(); |
|
} |
|
|
|
function getHoistingScope(scope) { |
|
while (!isHoistingScope(scope)) scope = scope.parent; |
|
|
|
return scope; |
|
} |
|
|
|
const immutabilityVisitor = { |
|
enter(path, state) { |
|
const stop = () => { |
|
state.isImmutable = false; |
|
path.stop(); |
|
}; |
|
|
|
const skip = () => { |
|
path.skip(); |
|
}; |
|
|
|
if (path.isJSXClosingElement()) return skip(); |
|
|
|
if (path.isJSXIdentifier({ |
|
name: "ref" |
|
}) && path.parentPath.isJSXAttribute({ |
|
name: path.node |
|
})) { |
|
return stop(); |
|
} |
|
|
|
if (path.isJSXIdentifier() || path.isJSXMemberExpression() || path.isJSXNamespacedName() || path.isImmutable()) { |
|
return; |
|
} |
|
|
|
if (path.isIdentifier()) { |
|
const binding = path.scope.getBinding(path.node.name); |
|
if (binding && binding.constant) return; |
|
} |
|
|
|
const { |
|
mutablePropsAllowed |
|
} = state; |
|
|
|
if (mutablePropsAllowed && path.isFunction()) { |
|
path.traverse(targetScopeVisitor, state); |
|
return skip(); |
|
} |
|
|
|
if (!path.isPure()) return stop(); |
|
const expressionResult = path.evaluate(); |
|
|
|
if (expressionResult.confident) { |
|
const { |
|
value |
|
} = expressionResult; |
|
|
|
if (mutablePropsAllowed || value === null || typeof value !== "object" && typeof value !== "function") { |
|
return skip(); |
|
} |
|
} else if (_core.types.isIdentifier(expressionResult.deopt)) { |
|
return; |
|
} |
|
|
|
stop(); |
|
} |
|
|
|
}; |
|
const targetScopeVisitor = { |
|
ReferencedIdentifier(path, state) { |
|
const { |
|
node |
|
} = path; |
|
let { |
|
scope |
|
} = path; |
|
|
|
while (scope !== state.jsxScope) { |
|
if (declares(node, scope)) return; |
|
scope = scope.parent; |
|
} |
|
|
|
while (scope) { |
|
if (scope === state.targetScope) return; |
|
if (declares(node, scope)) break; |
|
scope = scope.parent; |
|
} |
|
|
|
state.targetScope = getHoistingScope(scope); |
|
} |
|
|
|
}; |
|
const hoistingVisitor = Object.assign({}, immutabilityVisitor, targetScopeVisitor); |
|
return { |
|
name: "transform-react-constant-elements", |
|
visitor: { |
|
JSXElement(path) { |
|
var _jsxScope; |
|
|
|
if (HOISTED.has(path.node)) return; |
|
HOISTED.set(path.node, path.scope); |
|
const name = path.node.openingElement.name; |
|
let mutablePropsAllowed = false; |
|
|
|
if (allowMutablePropsOnTags != null) { |
|
let lastSegment = name; |
|
|
|
while (_core.types.isJSXMemberExpression(lastSegment)) { |
|
lastSegment = lastSegment.property; |
|
} |
|
|
|
const elementName = lastSegment.name; |
|
mutablePropsAllowed = allowMutablePropsOnTags.includes(elementName); |
|
} |
|
|
|
let jsxScope; |
|
let current = path; |
|
|
|
while (!jsxScope && current.parentPath.isJSX()) { |
|
current = current.parentPath; |
|
jsxScope = HOISTED.get(current.node); |
|
} |
|
|
|
(_jsxScope = jsxScope) != null ? _jsxScope : jsxScope = path.scope; |
|
const visitorState = { |
|
isImmutable: true, |
|
mutablePropsAllowed, |
|
jsxScope, |
|
targetScope: path.scope.getProgramParent() |
|
}; |
|
path.traverse(hoistingVisitor, visitorState); |
|
if (!visitorState.isImmutable) return; |
|
const { |
|
targetScope |
|
} = visitorState; |
|
HOISTED.set(path.node, targetScope); |
|
|
|
for (let currentScope = jsxScope;;) { |
|
if (targetScope === currentScope) return; |
|
if (isHoistingScope(currentScope)) break; |
|
currentScope = currentScope.parent; |
|
|
|
if (!currentScope) { |
|
throw new Error("Internal @babel/plugin-transform-react-constant-elements error: " + "targetScope must be an ancestor of jsxScope. " + "This is a Babel bug, please report it."); |
|
} |
|
} |
|
|
|
const id = path.scope.generateUidBasedOnNode(name); |
|
targetScope.push({ |
|
id: _core.types.identifier(id) |
|
}); |
|
let replacement = _core.template.expression.ast` |
|
${_core.types.identifier(id)} || (${_core.types.identifier(id)} = ${path.node}) |
|
`; |
|
|
|
if (path.parentPath.isJSXElement() || path.parentPath.isJSXAttribute()) { |
|
replacement = _core.types.jsxExpressionContainer(replacement); |
|
} |
|
|
|
path.replaceWith(replacement); |
|
} |
|
|
|
} |
|
}; |
|
}); |
|
|
|
exports.default = _default; |