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.
206 lines
5.3 KiB
206 lines
5.3 KiB
"use strict"; |
|
|
|
Object.defineProperty(exports, "__esModule", { |
|
value: true |
|
}); |
|
exports.default = void 0; |
|
|
|
var _t = require("@babel/types"); |
|
|
|
var _t2 = _t; |
|
const { |
|
react |
|
} = _t; |
|
const { |
|
cloneNode, |
|
jsxExpressionContainer, |
|
variableDeclaration, |
|
variableDeclarator |
|
} = _t2; |
|
const referenceVisitor = { |
|
ReferencedIdentifier(path, state) { |
|
if (path.isJSXIdentifier() && react.isCompatTag(path.node.name) && !path.parentPath.isJSXMemberExpression()) { |
|
return; |
|
} |
|
|
|
if (path.node.name === "this") { |
|
let scope = path.scope; |
|
|
|
do { |
|
if (scope.path.isFunction() && !scope.path.isArrowFunctionExpression()) { |
|
break; |
|
} |
|
} while (scope = scope.parent); |
|
|
|
if (scope) state.breakOnScopePaths.push(scope.path); |
|
} |
|
|
|
const binding = path.scope.getBinding(path.node.name); |
|
if (!binding) return; |
|
|
|
for (const violation of binding.constantViolations) { |
|
if (violation.scope !== binding.path.scope) { |
|
state.mutableBinding = true; |
|
path.stop(); |
|
return; |
|
} |
|
} |
|
|
|
if (binding !== state.scope.getBinding(path.node.name)) return; |
|
state.bindings[path.node.name] = binding; |
|
} |
|
|
|
}; |
|
|
|
class PathHoister { |
|
constructor(path, scope) { |
|
this.breakOnScopePaths = void 0; |
|
this.bindings = void 0; |
|
this.mutableBinding = void 0; |
|
this.scopes = void 0; |
|
this.scope = void 0; |
|
this.path = void 0; |
|
this.attachAfter = void 0; |
|
this.breakOnScopePaths = []; |
|
this.bindings = {}; |
|
this.mutableBinding = false; |
|
this.scopes = []; |
|
this.scope = scope; |
|
this.path = path; |
|
this.attachAfter = false; |
|
} |
|
|
|
isCompatibleScope(scope) { |
|
for (const key of Object.keys(this.bindings)) { |
|
const binding = this.bindings[key]; |
|
|
|
if (!scope.bindingIdentifierEquals(key, binding.identifier)) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
getCompatibleScopes() { |
|
let scope = this.path.scope; |
|
|
|
do { |
|
if (this.isCompatibleScope(scope)) { |
|
this.scopes.push(scope); |
|
} else { |
|
break; |
|
} |
|
|
|
if (this.breakOnScopePaths.indexOf(scope.path) >= 0) { |
|
break; |
|
} |
|
} while (scope = scope.parent); |
|
} |
|
|
|
getAttachmentPath() { |
|
let path = this._getAttachmentPath(); |
|
|
|
if (!path) return; |
|
let targetScope = path.scope; |
|
|
|
if (targetScope.path === path) { |
|
targetScope = path.scope.parent; |
|
} |
|
|
|
if (targetScope.path.isProgram() || targetScope.path.isFunction()) { |
|
for (const name of Object.keys(this.bindings)) { |
|
if (!targetScope.hasOwnBinding(name)) continue; |
|
const binding = this.bindings[name]; |
|
|
|
if (binding.kind === "param" || binding.path.parentKey === "params") { |
|
continue; |
|
} |
|
|
|
const bindingParentPath = this.getAttachmentParentForPath(binding.path); |
|
|
|
if (bindingParentPath.key >= path.key) { |
|
this.attachAfter = true; |
|
path = binding.path; |
|
|
|
for (const violationPath of binding.constantViolations) { |
|
if (this.getAttachmentParentForPath(violationPath).key > path.key) { |
|
path = violationPath; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return path; |
|
} |
|
|
|
_getAttachmentPath() { |
|
const scopes = this.scopes; |
|
const scope = scopes.pop(); |
|
if (!scope) return; |
|
|
|
if (scope.path.isFunction()) { |
|
if (this.hasOwnParamBindings(scope)) { |
|
if (this.scope === scope) return; |
|
const bodies = scope.path.get("body").get("body"); |
|
|
|
for (let i = 0; i < bodies.length; i++) { |
|
if (bodies[i].node._blockHoist) continue; |
|
return bodies[i]; |
|
} |
|
} else { |
|
return this.getNextScopeAttachmentParent(); |
|
} |
|
} else if (scope.path.isProgram()) { |
|
return this.getNextScopeAttachmentParent(); |
|
} |
|
} |
|
|
|
getNextScopeAttachmentParent() { |
|
const scope = this.scopes.pop(); |
|
if (scope) return this.getAttachmentParentForPath(scope.path); |
|
} |
|
|
|
getAttachmentParentForPath(path) { |
|
do { |
|
if (!path.parentPath || Array.isArray(path.container) && path.isStatement()) { |
|
return path; |
|
} |
|
} while (path = path.parentPath); |
|
} |
|
|
|
hasOwnParamBindings(scope) { |
|
for (const name of Object.keys(this.bindings)) { |
|
if (!scope.hasOwnBinding(name)) continue; |
|
const binding = this.bindings[name]; |
|
if (binding.kind === "param" && binding.constant) return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
run() { |
|
this.path.traverse(referenceVisitor, this); |
|
if (this.mutableBinding) return; |
|
this.getCompatibleScopes(); |
|
const attachTo = this.getAttachmentPath(); |
|
if (!attachTo) return; |
|
if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return; |
|
let uid = attachTo.scope.generateUidIdentifier("ref"); |
|
const declarator = variableDeclarator(uid, this.path.node); |
|
const insertFn = this.attachAfter ? "insertAfter" : "insertBefore"; |
|
const [attached] = attachTo[insertFn]([attachTo.isVariableDeclarator() ? declarator : variableDeclaration("var", [declarator])]); |
|
const parent = this.path.parentPath; |
|
|
|
if (parent.isJSXElement() && this.path.container === parent.node.children) { |
|
uid = jsxExpressionContainer(uid); |
|
} |
|
|
|
this.path.replaceWith(cloneNode(uid)); |
|
return attachTo.isVariableDeclarator() ? attached.get("init") : attached.get("declarations.0.init"); |
|
} |
|
|
|
} |
|
|
|
exports.default = PathHoister; |