diff --git a/packages/babel-traverse/src/scope/index.js b/packages/babel-traverse/src/scope/index.js index 834c04eb8f..5a3669da5f 100644 --- a/packages/babel-traverse/src/scope/index.js +++ b/packages/babel-traverse/src/scope/index.js @@ -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); } } diff --git a/packages/babel-traverse/src/scope/lib/renamer.js b/packages/babel-traverse/src/scope/lib/renamer.js new file mode 100644 index 0000000000..c5abc0d738 --- /dev/null +++ b/packages/babel-traverse/src/scope/lib/renamer.js @@ -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); + } +}