abstract out scope binding rename and handle function/class cases where we can retain the name with some ~magic~ - fixes #2435
This commit is contained in:
commit
fa88b1c00d
@ -351,31 +351,10 @@ export default class Scope {
|
||||
}
|
||||
|
||||
rename(oldName: string, newName: string, block?) {
|
||||
newName = newName || this.generateUidIdentifier(oldName).name;
|
||||
|
||||
var info = this.getBinding(oldName);
|
||||
if (!info) return;
|
||||
|
||||
var state = {
|
||||
newName: newName,
|
||||
oldName: oldName,
|
||||
binding: info.identifier,
|
||||
info: info
|
||||
};
|
||||
|
||||
var scope = info.scope;
|
||||
scope.traverse(block || scope.block, renameVisitor, state);
|
||||
|
||||
if (!block) {
|
||||
scope.removeOwnBinding(oldName);
|
||||
scope.bindings[newName] = info;
|
||||
state.binding.name = newName;
|
||||
}
|
||||
|
||||
var file = this.hub.file;
|
||||
if (file) {
|
||||
this._renameFromMap(file.moduleFormatter.localImports, oldName, newName, state.binding);
|
||||
//this._renameFromMap(file.moduleFormatter.localExports, oldName, newName);
|
||||
let binding = this.getBinding(oldName);
|
||||
if (binding) {
|
||||
newName = newName || this.generateUidIdentifier(oldName).name;
|
||||
return new Renamer(binding, oldName, newName).rename(block);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
117
packages/babel-traverse/src/scope/lib/renamer.js
Normal file
117
packages/babel-traverse/src/scope/lib/renamer.js
Normal file
@ -0,0 +1,117 @@
|
||||
/* @flow */
|
||||
|
||||
import Binding from "../binding";
|
||||
import * as t from "babel-types";
|
||||
|
||||
let renameVisitor = {
|
||||
ReferencedIdentifier({ node }, state) {
|
||||
if (node.name === state.oldName) {
|
||||
node.name = state.newName;
|
||||
}
|
||||
},
|
||||
|
||||
Scope(path, state) {
|
||||
if (!path.scope.bindingIdentifierEquals(state.oldName, state.binding.identifier)) {
|
||||
path.skip();
|
||||
}
|
||||
},
|
||||
|
||||
"AssignmentExpression|Declaration"(path, state) {
|
||||
let ids = path.getBindingIdentifiers();
|
||||
|
||||
for (let name in ids) {
|
||||
if (name === state.oldName) ids[name].name = state.newName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default class Renamer {
|
||||
constructor(binding: Binding, oldName: string, newName: string) {
|
||||
this.newName = newName;
|
||||
this.oldName = oldName;
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
oldName: string;
|
||||
newName: string;
|
||||
binding: Binding;
|
||||
|
||||
maybeConvertFromExportDeclaration(parentDeclar) {
|
||||
let exportDeclar = parentDeclar && parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath;
|
||||
if (!exportDeclar) return;
|
||||
|
||||
// build specifiers that point back to this export declaration
|
||||
let isDefault = exportDeclar.isExportDefaultDeclaration();
|
||||
let bindingIdentifiers = parentDeclar.getBindingIdentifiers();
|
||||
let specifiers = [];
|
||||
|
||||
for (let name in bindingIdentifiers) {
|
||||
let localName = name === this.oldName ? this.newName : name;
|
||||
let exportedName = isDefault ? "default" : name;
|
||||
specifiers.push(t.exportSpecifier(t.identifier(localName), t.identifier(exportedName)));
|
||||
}
|
||||
|
||||
let aliasDeclar = t.exportNamedDeclaration(null, specifiers);
|
||||
|
||||
// hoist to the top if it's a function
|
||||
if (parentDeclar.isFunctionDeclaration()) {
|
||||
aliasDeclar._blockHoist = 3;
|
||||
}
|
||||
|
||||
exportDeclar.insertAfter(aliasDeclar);
|
||||
exportDeclar.replaceWith(parentDeclar.node);
|
||||
}
|
||||
|
||||
maybeConvertFromClassFunctionDeclaration(path) {
|
||||
// retain the `name` of a class/function declaration
|
||||
|
||||
if (!path.isFunctionDeclaration() && !path.isClassDeclaration()) return;
|
||||
if (this.binding.kind !== "hoisted") return;
|
||||
|
||||
path.node.id = t.identifier(this.oldName);
|
||||
path.node._blockHoist = 3;
|
||||
|
||||
path.replaceWith(t.variableDeclaration("let", [
|
||||
t.variableDeclarator(t.identifier(this.newName), t.toExpression(path.node))
|
||||
]));
|
||||
}
|
||||
|
||||
maybeConvertFromClassFunctionExpression(path) {
|
||||
// retain the `name` of a class/function expression
|
||||
|
||||
if (!path.isFunctionExpression() && !path.isClassExpression()) return;
|
||||
if (this.binding.kind !== "local") return;
|
||||
|
||||
path.node.id = t.identifier(this.oldName);
|
||||
|
||||
this.binding.scope.parent.push({
|
||||
id: t.identifier(this.newName)
|
||||
});
|
||||
|
||||
path.replaceWith(t.assignmentExpression("=", t.identifier(this.newName), path.node));
|
||||
}
|
||||
|
||||
rename(block?) {
|
||||
let { binding, oldName, newName } = this;
|
||||
let { scope, path } = binding;
|
||||
|
||||
let parentDeclar = path.find((path) => path.isDeclaration() || path.isFunctionExpression());
|
||||
this.maybeConvertFromExportDeclaration(parentDeclar);
|
||||
|
||||
scope.traverse(block || scope.block, renameVisitor, this);
|
||||
|
||||
if (!block) {
|
||||
scope.removeOwnBinding(oldName);
|
||||
scope.bindings[newName] = binding;
|
||||
this.binding.identifier.name = newName;
|
||||
}
|
||||
|
||||
if (binding.type === "hoisted") {
|
||||
// https://github.com/babel/babel/issues/2435
|
||||
// todo: hoist and convert function to a let
|
||||
}
|
||||
|
||||
this.maybeConvertFromClassFunctionDeclaration(parentDeclar);
|
||||
this.maybeConvertFromClassFunctionExpression(parentDeclar);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user