rename TraversalPath to NodePath and further split up methods into separate files
This commit is contained in:
parent
ec74eb41cf
commit
7d88a1ca0b
@ -15,15 +15,15 @@ export function Func/*tion*/(node) {
|
||||
// but that's a small optimization. Starting here instead of at the
|
||||
// root of the AST is the key optimization, since huge async/generator
|
||||
// functions are relatively rare.
|
||||
regenerator.transform(convertTraversalPathToNodePath(this));
|
||||
regenerator.transform(convertNodePath(this));
|
||||
}
|
||||
}
|
||||
|
||||
// Given a TraversalPath, return a NodePath that includes full ancestry
|
||||
// information (up to and including the Program node). This is complicated
|
||||
// by having to include intermediate objects like blockStatement.body
|
||||
// Given a Babel NodePath, return an ast-types NodePath that includes full
|
||||
// ancestry information (up to and including the Program node). This is
|
||||
// complicated by having to include intermediate objects like blockStatement.body
|
||||
// arrays, in addition to Node objects.
|
||||
function convertTraversalPathToNodePath(path) {
|
||||
function convertNodePath(path) {
|
||||
var programNode;
|
||||
var keysAlongPath = [];
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import TraversalPath from "./path";
|
||||
import NodePath from "./path";
|
||||
import compact from "lodash/array/compact";
|
||||
import * as t from "../types";
|
||||
|
||||
@ -16,7 +16,7 @@ export default class TraversalContext {
|
||||
}
|
||||
|
||||
create(node, obj, key) {
|
||||
var path = TraversalPath.get(this.parentPath, node, obj, key);
|
||||
var path = NodePath.get(this.parentPath, node, obj, key);
|
||||
path.unshiftContext(this);
|
||||
return path;
|
||||
}
|
||||
|
||||
65
src/babel/traversal/path/ancestry.js
Normal file
65
src/babel/traversal/path/ancestry.js
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function findParent(callback) {
|
||||
var path = this;
|
||||
while (path) {
|
||||
if (callback(path.node, path)) return path;
|
||||
path = path.parentPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function getAncestry() {
|
||||
var ancestry = [];
|
||||
|
||||
var path = this.parentPath;
|
||||
while (path) {
|
||||
ancestry.push(path.node);
|
||||
path = path.parentPath;
|
||||
}
|
||||
|
||||
return ancestry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function inType(types) {
|
||||
if (!Array.isArray(types)) types = [types];
|
||||
|
||||
var path = this;
|
||||
while (path) {
|
||||
for (var type of (types: Array)) {
|
||||
if (path.node.type === type) return true;
|
||||
}
|
||||
path = path.parentPath;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function inShadow() {
|
||||
var path = this;
|
||||
while (path) {
|
||||
if (path.isFunction()) {
|
||||
if (path.node.shadow) {
|
||||
return path;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
path = path.parentPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint eqeqeq: 0 */
|
||||
|
||||
/**
|
||||
* Walk the input `node` and statically evaluate if it's truthy.
|
||||
*
|
||||
|
||||
@ -1,130 +0,0 @@
|
||||
import * as react from "../../transformation/helpers/react";
|
||||
import * as t from "../../types";
|
||||
|
||||
var referenceVisitor = {
|
||||
ReferencedIdentifier(node, parent, scope, state) {
|
||||
if (this.isJSXIdentifier() && react.isCompatTag(node.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// direct references that we need to track to hoist this to the highest scope we can
|
||||
var bindingInfo = scope.getBinding(node.name);
|
||||
if (!bindingInfo) return;
|
||||
|
||||
// this binding isn't accessible from the parent scope so we can safely ignore it
|
||||
// eg. it's in a closure etc
|
||||
if (bindingInfo !== state.scope.getBinding(node.name)) return;
|
||||
|
||||
if (bindingInfo.constant) {
|
||||
state.bindings[node.name] = bindingInfo;
|
||||
} else {
|
||||
for (var violationPath of (bindingInfo.constantViolations: Array)) {
|
||||
state.breakOnScopePaths.push(violationPath.scope.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default class PathHoister {
|
||||
constructor(path, scope) {
|
||||
this.breakOnScopePaths = [];
|
||||
this.bindings = {};
|
||||
this.scopes = [];
|
||||
this.scope = scope;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
isCompatibleScope(scope) {
|
||||
for (var key in this.bindings) {
|
||||
var binding = this.bindings[key];
|
||||
if (!scope.bindingIdentifierEquals(key, binding.identifier)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getCompatibleScopes() {
|
||||
var 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() {
|
||||
var scopes = this.scopes;
|
||||
|
||||
var scope = scopes.pop();
|
||||
if (!scope) return;
|
||||
|
||||
if (scope.path.isFunction()) {
|
||||
if (this.hasOwnParamBindings(scope)) {
|
||||
// should ignore this scope since it's ourselves
|
||||
if (this.scope.is(scope)) return;
|
||||
|
||||
// needs to be attached to the body
|
||||
return scope.path.get("body").get("body")[0];
|
||||
} else {
|
||||
// doesn't need to be be attached to this scope
|
||||
return this.getNextScopeStatementParent();
|
||||
}
|
||||
} else if (scope.path.isProgram()) {
|
||||
return this.getNextScopeStatementParent();
|
||||
}
|
||||
}
|
||||
|
||||
getNextScopeStatementParent() {
|
||||
var scope = this.scopes.pop();
|
||||
if (scope) return scope.path.getStatementParent();
|
||||
}
|
||||
|
||||
hasOwnParamBindings(scope) {
|
||||
for (var name in this.bindings) {
|
||||
if (!scope.hasOwnBinding(name)) continue
|
||||
|
||||
var binding = this.bindings[name];
|
||||
if (binding.kind === "param") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
run() {
|
||||
var node = this.path.node;
|
||||
if (node._hoisted) return;
|
||||
node._hoisted = true;
|
||||
|
||||
this.path.traverse(referenceVisitor, this);
|
||||
|
||||
this.getCompatibleScopes();
|
||||
|
||||
var path = this.getAttachmentPath();
|
||||
if (!path) return;
|
||||
|
||||
var uid = path.scope.generateUidIdentifier("ref");
|
||||
|
||||
path.insertBefore([
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(uid, this.path.node)
|
||||
])
|
||||
]);
|
||||
|
||||
var parent = this.path.parentPath;
|
||||
|
||||
if (parent.isJSXElement() && this.path.container === parent.node.children) {
|
||||
// turning the `span` in `<div><span /></div>` to an expression so we need to wrap it with
|
||||
// an expression container
|
||||
uid = t.jSXExpressionContainer(uid);
|
||||
}
|
||||
|
||||
this.path.replaceWith(uid);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import PathHoister from "./hoister";
|
||||
import * as virtualTypes from "./virtual-types";
|
||||
import PathHoister from "./lib/hoister";
|
||||
import * as virtualTypes from "./lib/virtual-types";
|
||||
import * as messages from "../../messages";
|
||||
import * as contextual from "./lib/contextual";
|
||||
import isBoolean from "lodash/lang/isBoolean";
|
||||
import isNumber from "lodash/lang/isNumber";
|
||||
import isRegExp from "lodash/lang/isRegExp";
|
||||
@ -15,35 +16,7 @@ import extend from "lodash/object/extend";
|
||||
import Scope from "../scope";
|
||||
import * as t from "../../types";
|
||||
|
||||
var hoistVariablesVisitor = explode({
|
||||
Function() {
|
||||
this.skip();
|
||||
},
|
||||
|
||||
VariableDeclaration(node, parent, scope) {
|
||||
if (node.kind !== "var") return;
|
||||
|
||||
var bindings = this.getBindingIdentifiers();
|
||||
for (var key in bindings) {
|
||||
scope.push({ id: bindings[key] });
|
||||
}
|
||||
|
||||
var exprs = [];
|
||||
|
||||
for (var declar of (node.declarations: Array)) {
|
||||
var declar = node.declarations[i];
|
||||
if (declar.init) {
|
||||
exprs.push(t.expressionStatement(
|
||||
t.assignmentExpression("=", declar.id, declar.init)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return exprs;
|
||||
}
|
||||
});
|
||||
|
||||
export default class TraversalPath {
|
||||
export default class NodePath {
|
||||
constructor(parent, container) {
|
||||
this.container = container;
|
||||
this.contexts = [];
|
||||
@ -55,7 +28,7 @@ export default class TraversalPath {
|
||||
* Description
|
||||
*/
|
||||
|
||||
static get(parentPath: TraversalPath, parent, container, key) {
|
||||
static get(parentPath: NodePath, parent, container, key) {
|
||||
var targetNode = container[key];
|
||||
var paths = container._paths = container._paths || [];
|
||||
var path;
|
||||
@ -69,7 +42,7 @@ export default class TraversalPath {
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
path = new TraversalPath(parent, container);
|
||||
path = new NodePath(parent, container);
|
||||
paths.push(path);
|
||||
}
|
||||
|
||||
@ -82,7 +55,7 @@ export default class TraversalPath {
|
||||
* Description
|
||||
*/
|
||||
|
||||
static getScope(path: TraversalPath, scope: Scope, file?: File) {
|
||||
static getScope(path: NodePath, scope: Scope, file?: File) {
|
||||
var ourScope = scope;
|
||||
|
||||
// we're entering a new scope so let's construct it!
|
||||
@ -93,59 +66,6 @@ export default class TraversalPath {
|
||||
return ourScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
getAncestry() {
|
||||
var ancestry = [];
|
||||
|
||||
var path = this.parentPath;
|
||||
while (path) {
|
||||
ancestry.push(path.node);
|
||||
path = path.parentPath;
|
||||
}
|
||||
|
||||
return ancestry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
inType(types) {
|
||||
if (!Array.isArray(types)) types = [types];
|
||||
|
||||
var path = this;
|
||||
while (path) {
|
||||
for (var type of (types: Array)) {
|
||||
if (path.node.type === type) return true;
|
||||
}
|
||||
path = path.parentPath;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
inShadow() {
|
||||
var path = this;
|
||||
while (path) {
|
||||
if (path.isFunction()) {
|
||||
if (path.node.shadow) {
|
||||
return path;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
path = path.parentPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this node was a part of the original AST.
|
||||
*/
|
||||
@ -162,19 +82,6 @@ export default class TraversalPath {
|
||||
return !this.isUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
findParent(callback) {
|
||||
var path = this;
|
||||
while (path) {
|
||||
if (callback(path.node, path)) return path;
|
||||
path = path.parentPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
@ -229,7 +136,7 @@ export default class TraversalPath {
|
||||
paths.push(path);
|
||||
this.queueNode(path);
|
||||
} else {
|
||||
paths.push(TraversalPath.get(this, node, this.container, to));
|
||||
paths.push(NodePath.get(this, node, this.container, to));
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,7 +263,7 @@ export default class TraversalPath {
|
||||
if (this.opts && this.opts.noScope) return;
|
||||
|
||||
var target = this.context || this.parentPath;
|
||||
this.scope = TraversalPath.getScope(this, target && target.scope, file);
|
||||
this.scope = NodePath.getScope(this, target && target.scope, file);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -492,38 +399,18 @@ export default class TraversalPath {
|
||||
*/
|
||||
|
||||
remove() {
|
||||
if (this._contextualCall("removers", "pre")) return;
|
||||
|
||||
this.shareCommentsWithSiblings();
|
||||
this._remove();
|
||||
this.removed = true;
|
||||
|
||||
var parentPath = this.parentPath;
|
||||
var parent = this.parent;
|
||||
if (!parentPath) return;
|
||||
this._contextualCall("removers", "post");
|
||||
}
|
||||
|
||||
// we've just removed the last declarator of a variable declaration so there's no point in
|
||||
// keeping it
|
||||
if (parentPath.isVariableDeclaration() && parent.declarations.length === 0) {
|
||||
return parentPath.remove();
|
||||
}
|
||||
|
||||
// we're the child of an expression statement so we should remove the parent
|
||||
if (parentPath.isExpressionStatement()) {
|
||||
return parentPath.remove();
|
||||
}
|
||||
|
||||
// we've just removed the second element of a sequence expression so let's turn that sequence
|
||||
// expression into a regular expression
|
||||
if (parentPath.isSequenceExpression() && parent.expressions.length === 1) {
|
||||
parentPath.replaceWith(parent.expressions[0]);
|
||||
}
|
||||
|
||||
// we're in a binary expression, better remove it and replace it with the last expression
|
||||
if (parentPath.isBinary()) {
|
||||
if (this.key === "left") {
|
||||
parentPath.replaceWith(parent.right);
|
||||
} else { // key === "right"
|
||||
parentPath.replaceWith(parent.left);
|
||||
}
|
||||
_contextualCall(type, position) {
|
||||
for (var fn of (contextual[type][position]: Array)) {
|
||||
if (fn(this, this.parentPath)) return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -565,24 +452,6 @@ export default class TraversalPath {
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
replaceInline(nodes) {
|
||||
if (Array.isArray(nodes)) {
|
||||
if (Array.isArray(this.container)) {
|
||||
nodes = this._verifyNodeList(nodes);
|
||||
this._containerInsertAfter(nodes);
|
||||
return this.remove();
|
||||
} else {
|
||||
return this.replaceWithMultiple(nodes);
|
||||
}
|
||||
} else {
|
||||
return this.replaceWith(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
@ -617,7 +486,7 @@ export default class TraversalPath {
|
||||
// doesn't matter, our nodes will be inserted anyway
|
||||
|
||||
var container = this.node[containerKey];
|
||||
var path = TraversalPath.get(this, this.node, container, 0);
|
||||
var path = NodePath.get(this, this.node, container, 0);
|
||||
|
||||
return path.insertBefore(nodes);
|
||||
}
|
||||
@ -634,105 +503,11 @@ export default class TraversalPath {
|
||||
|
||||
var container = this.node[containerKey];
|
||||
var i = container.length;
|
||||
var path = TraversalPath.get(this, this.node, container, i);
|
||||
var path = NodePath.get(this, this.node, container, i);
|
||||
|
||||
return path.replaceWith(nodes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
replaceWithMultiple(nodes: Array<Object>) {
|
||||
nodes = this._verifyNodeList(nodes);
|
||||
t.inheritsComments(nodes[0], this.node);
|
||||
this.node = this.container[this.key] = null;
|
||||
this.insertAfter(nodes);
|
||||
if (!this.node) this.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
replaceWithSourceString(replacement) {
|
||||
try {
|
||||
replacement = `(${replacement})`;
|
||||
replacement = parse(replacement);
|
||||
} catch (err) {
|
||||
var loc = err.loc;
|
||||
if (loc) {
|
||||
err.message += " - make sure this is an expression.";
|
||||
err.message += "\n" + codeFrame(replacement, loc.line, loc.column + 1);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
replacement = replacement.program.body[0].expression;
|
||||
traverse.removeProperties(replacement);
|
||||
return this.replaceWith(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
replaceWith(replacement, whateverAllowed) {
|
||||
if (this.removed) {
|
||||
throw new Error("You can't replace this node, we've already removed it");
|
||||
}
|
||||
|
||||
if (!replacement) {
|
||||
throw new Error("You passed `path.replaceWith()` a falsy node, use `path.remove()` instead");
|
||||
}
|
||||
|
||||
if (this.node === replacement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// normalise inserting an entire AST
|
||||
if (t.isProgram(replacement)) {
|
||||
replacement = replacement.body;
|
||||
whateverAllowed = true;
|
||||
}
|
||||
|
||||
if (Array.isArray(replacement)) {
|
||||
if (whateverAllowed) {
|
||||
return this.replaceWithMultiple(replacement);
|
||||
} else {
|
||||
throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`");
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof replacement === "string") {
|
||||
if (whateverAllowed) {
|
||||
return this.replaceWithSourceString(replacement);
|
||||
} else {
|
||||
throw new Error("Don't use `path.replaceWith()` with a string, use `path.replaceWithSourceString()`");
|
||||
}
|
||||
}
|
||||
|
||||
// replacing a statement with an expression so wrap it in an expression statement
|
||||
if (this.isPreviousType("Statement") && t.isExpression(replacement) && !this.canHaveVariableDeclarationOrExpression()) {
|
||||
replacement = t.expressionStatement(replacement);
|
||||
}
|
||||
|
||||
// replacing an expression with a statement so let's explode it
|
||||
if (this.isPreviousType("Expression") && t.isStatement(replacement)) {
|
||||
return this.replaceExpressionWithStatements([replacement]);
|
||||
}
|
||||
|
||||
var oldNode = this.node;
|
||||
if (oldNode) t.inheritsComments(replacement, oldNode);
|
||||
|
||||
// replace the node
|
||||
this.node = this.container[this.key] = replacement;
|
||||
this.type = replacement.type;
|
||||
|
||||
// potentially create new scope
|
||||
this.setScope();
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks whether or now we're in one of the following positions:
|
||||
*
|
||||
@ -751,7 +526,7 @@ export default class TraversalPath {
|
||||
* Description
|
||||
*/
|
||||
|
||||
getStatementParent(): ?TraversalPath {
|
||||
getStatementParent(): ?NodePath {
|
||||
var path = this;
|
||||
|
||||
do {
|
||||
@ -773,7 +548,7 @@ export default class TraversalPath {
|
||||
* Description
|
||||
*/
|
||||
|
||||
getCompletionRecords(): Array<TraversalPath> {
|
||||
getCompletionRecords(): Array<NodePath> {
|
||||
var paths = [];
|
||||
|
||||
var add = function (path) {
|
||||
@ -796,43 +571,6 @@ export default class TraversalPath {
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
replaceExpressionWithStatements(nodes: Array) {
|
||||
var toSequenceExpression = t.toSequenceExpression(nodes, this.scope);
|
||||
|
||||
if (toSequenceExpression) {
|
||||
return this.replaceWith(toSequenceExpression);
|
||||
} else {
|
||||
var container = t.functionExpression(null, [], t.blockStatement(nodes));
|
||||
container.shadow = true;
|
||||
|
||||
this.replaceWith(t.callExpression(container, []));
|
||||
this.traverse(hoistVariablesVisitor);
|
||||
|
||||
// add implicit returns to all ending expression statements
|
||||
var last = this.get("callee").getCompletionRecords();
|
||||
for (var i = 0; i < last.length; i++) {
|
||||
var lastNode = last[i];
|
||||
if (lastNode.isExpressionStatement()) {
|
||||
var loop = lastNode.findParent((node, path) => path.isLoop());
|
||||
if (loop) {
|
||||
var uid = this.get("callee").scope.generateDeclaredUidIdentifier("ret");
|
||||
this.get("callee.body").pushContainer("body", t.returnStatement(uid));
|
||||
lastNode.get("expression").replaceWith(
|
||||
t.assignmentExpression("=", uid, lastNode.node.expression)
|
||||
);
|
||||
} else {
|
||||
lastNode.replaceWith(t.returnStatement(lastNode.node.expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
@ -851,7 +589,7 @@ export default class TraversalPath {
|
||||
for (var fn of (fns: Array)) {
|
||||
if (!fn) continue;
|
||||
|
||||
var node = this.node;
|
||||
let node = this.node;
|
||||
if (!node) return;
|
||||
|
||||
var previousType = this.type;
|
||||
@ -919,14 +657,14 @@ export default class TraversalPath {
|
||||
*/
|
||||
|
||||
getSibling(key) {
|
||||
return TraversalPath.get(this.parentPath, this.parent, this.container, key, this.file);
|
||||
return NodePath.get(this.parentPath, this.parent, this.container, key, this.file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
get(key: string): TraversalPath {
|
||||
get(key: string): NodePath {
|
||||
var parts = key.split(".");
|
||||
if (parts.length === 1) { // "foo"
|
||||
return this._getKey(key);
|
||||
@ -946,10 +684,10 @@ export default class TraversalPath {
|
||||
if (Array.isArray(container)) {
|
||||
// requested a container so give them all the paths
|
||||
return container.map((_, i) => {
|
||||
return TraversalPath.get(this, node, container, i).setContext();
|
||||
return NodePath.get(this, node, container, i).setContext();
|
||||
});
|
||||
} else {
|
||||
return TraversalPath.get(this, node, node, key).setContext();
|
||||
return NodePath.get(this, node, node, key).setContext();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1010,134 +748,6 @@ export default class TraversalPath {
|
||||
return this.node[key] === value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
getTypeAnnotation(): {
|
||||
inferred: boolean;
|
||||
annotation: ?Object;
|
||||
} {
|
||||
if (this.typeInfo) {
|
||||
return this.typeInfo;
|
||||
}
|
||||
|
||||
var info = this.typeInfo = {
|
||||
inferred: false,
|
||||
annotation: null
|
||||
};
|
||||
|
||||
var type = this.node && this.node.typeAnnotation;
|
||||
|
||||
if (!type) {
|
||||
info.inferred = true;
|
||||
type = this.inferType(this);
|
||||
}
|
||||
|
||||
if (type) {
|
||||
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
|
||||
info.annotation = type;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
resolve(): ?TraversalPath {
|
||||
if (this.isVariableDeclarator()) {
|
||||
if (this.get("id").isIdentifier()) {
|
||||
return this.get("init").resolve();
|
||||
} else {
|
||||
// otherwise it's a request for a destructuring declarator and i'm not
|
||||
// ready to resolve those just yet
|
||||
}
|
||||
} else if (this.isIdentifier()) {
|
||||
var binding = this.scope.getBinding(this.node.name);
|
||||
if (!binding || !binding.constant) return;
|
||||
|
||||
// todo: take into consideration infinite recursion #1149
|
||||
return;
|
||||
|
||||
if (binding.path === this) {
|
||||
return this;
|
||||
} else {
|
||||
return binding.path.resolve();
|
||||
}
|
||||
} else if (this.isMemberExpression()) {
|
||||
// this is dangerous, as non-direct target assignments will mutate it's state
|
||||
// making this resolution inaccurate
|
||||
|
||||
var targetKey = this.toComputedKey();
|
||||
if (!t.isLiteral(targetKey)) return;
|
||||
var targetName = targetKey.value;
|
||||
|
||||
var target = this.get("object").resolve();
|
||||
if (!target || !target.isObjectExpression()) return;
|
||||
|
||||
var props = target.get("properties");
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var prop = props[i];
|
||||
if (!prop.isProperty()) continue;
|
||||
|
||||
var key = prop.get("key");
|
||||
|
||||
// { foo: obj }
|
||||
var match = prop.isnt("computed") && key.isIdentifier({ name: targetName });
|
||||
|
||||
// { "foo": "obj" } or { ["foo"]: "obj" }
|
||||
match = match || key.isLiteral({ value: targetName });
|
||||
|
||||
if (match) return prop.get("value");
|
||||
}
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
inferType(path: TraversalPath): ?Object {
|
||||
path = path.resolve();
|
||||
if (!path) return;
|
||||
|
||||
if (path.isPreviousType("RestElement") || path.parentPath.isPreviousType("RestElement") || path.isPreviousType("ArrayExpression")) {
|
||||
return t.genericTypeAnnotation(t.identifier("Array"));
|
||||
}
|
||||
|
||||
if (path.parentPath.isPreviousType("TypeCastExpression")) {
|
||||
return path.parentPath.node.typeAnnotation;
|
||||
}
|
||||
|
||||
if (path.isPreviousType("TypeCastExpression")) {
|
||||
return path.node.typeAnnotation;
|
||||
}
|
||||
|
||||
if (path.isPreviousType("ObjectExpression")) {
|
||||
return t.genericTypeAnnotation(t.identifier("Object"));
|
||||
}
|
||||
|
||||
if (path.isPreviousType("Function")) {
|
||||
return t.identifier("Function");
|
||||
}
|
||||
|
||||
if (path.isPreviousType("Literal")) {
|
||||
var value = path.node.value;
|
||||
if (isString(value)) return t.stringTypeAnnotation();
|
||||
if (isNumber(value)) return t.numberTypeAnnotation();
|
||||
if (isBoolean(value)) return t.booleanTypeAnnotation();
|
||||
}
|
||||
|
||||
if (path.isPreviousType("CallExpression")) {
|
||||
var callee = path.get("callee").resolve();
|
||||
if (callee && callee.isPreviousType("Function")) return callee.node.returnType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
@ -1146,30 +756,6 @@ export default class TraversalPath {
|
||||
return t.isType(this.type, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
isTypeGeneric(genericName: string, opts = {}): boolean {
|
||||
var typeInfo = this.getTypeAnnotation();
|
||||
var type = typeInfo.annotation;
|
||||
if (!type) return false;
|
||||
|
||||
if (typeInfo.inferred && opts.inference === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!t.isGenericTypeAnnotation(type) || !t.isIdentifier(type.id, { name: genericName })) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.requireTypeParameters && !type.typeParameters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
@ -1254,20 +840,23 @@ export default class TraversalPath {
|
||||
}
|
||||
}
|
||||
|
||||
assign(TraversalPath.prototype, require("./evaluation"));
|
||||
assign(TraversalPath.prototype, require("./conversion"));
|
||||
assign(NodePath.prototype, require("./ancestry"));
|
||||
assign(NodePath.prototype, require("./resolution"));
|
||||
assign(NodePath.prototype, require("./replacement"));
|
||||
assign(NodePath.prototype, require("./evaluation"));
|
||||
assign(NodePath.prototype, require("./conversion"));
|
||||
|
||||
for (let type in virtualTypes) {
|
||||
if (type[0] === "_") continue;
|
||||
|
||||
TraversalPath.prototype[`is${type}`] = function (opts) {
|
||||
NodePath.prototype[`is${type}`] = function (opts) {
|
||||
return virtualTypes[type].checkPath(this, opts);
|
||||
};
|
||||
}
|
||||
|
||||
for (let type of (t.TYPES: Array)) {
|
||||
let typeKey = `is${type}`;
|
||||
TraversalPath.prototype[typeKey] = function (opts) {
|
||||
NodePath.prototype[typeKey] = function (opts) {
|
||||
return t[typeKey](this.node, opts);
|
||||
};
|
||||
}
|
||||
|
||||
181
src/babel/traversal/path/replacement.js
Normal file
181
src/babel/traversal/path/replacement.js
Normal file
@ -0,0 +1,181 @@
|
||||
import codeFrame from "../../helpers/code-frame";
|
||||
import traverse from "../index";
|
||||
import * as t from "../../types";
|
||||
import parse from "../../helpers/parse";
|
||||
|
||||
var hoistVariablesVisitor = {
|
||||
Function() {
|
||||
this.skip();
|
||||
},
|
||||
|
||||
VariableDeclaration(node, parent, scope) {
|
||||
if (node.kind !== "var") return;
|
||||
|
||||
var bindings = this.getBindingIdentifiers();
|
||||
for (var key in bindings) {
|
||||
scope.push({ id: bindings[key] });
|
||||
}
|
||||
|
||||
var exprs = [];
|
||||
|
||||
for (var declar of (node.declarations: Array)) {
|
||||
if (declar.init) {
|
||||
exprs.push(t.expressionStatement(
|
||||
t.assignmentExpression("=", declar.id, declar.init)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return exprs;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function replaceWithMultiple(nodes: Array<Object>) {
|
||||
nodes = this._verifyNodeList(nodes);
|
||||
t.inheritsComments(nodes[0], this.node);
|
||||
this.node = this.container[this.key] = null;
|
||||
this.insertAfter(nodes);
|
||||
if (!this.node) this.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function replaceWithSourceString(replacement) {
|
||||
try {
|
||||
replacement = `(${replacement})`;
|
||||
replacement = parse(replacement);
|
||||
} catch (err) {
|
||||
var loc = err.loc;
|
||||
if (loc) {
|
||||
err.message += " - make sure this is an expression.";
|
||||
err.message += "\n" + codeFrame(replacement, loc.line, loc.column + 1);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
replacement = replacement.program.body[0].expression;
|
||||
traverse.removeProperties(replacement);
|
||||
return this.replaceWith(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function replaceWith(replacement, whateverAllowed) {
|
||||
if (this.removed) {
|
||||
throw new Error("You can't replace this node, we've already removed it");
|
||||
}
|
||||
|
||||
if (!replacement) {
|
||||
throw new Error("You passed `path.replaceWith()` a falsy node, use `path.remove()` instead");
|
||||
}
|
||||
|
||||
if (this.node === replacement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// normalise inserting an entire AST
|
||||
if (t.isProgram(replacement)) {
|
||||
replacement = replacement.body;
|
||||
whateverAllowed = true;
|
||||
}
|
||||
|
||||
if (Array.isArray(replacement)) {
|
||||
if (whateverAllowed) {
|
||||
return this.replaceWithMultiple(replacement);
|
||||
} else {
|
||||
throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`");
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof replacement === "string") {
|
||||
if (whateverAllowed) {
|
||||
return this.replaceWithSourceString(replacement);
|
||||
} else {
|
||||
throw new Error("Don't use `path.replaceWith()` with a string, use `path.replaceWithSourceString()`");
|
||||
}
|
||||
}
|
||||
|
||||
// replacing a statement with an expression so wrap it in an expression statement
|
||||
if (this.isPreviousType("Statement") && t.isExpression(replacement) && !this.canHaveVariableDeclarationOrExpression()) {
|
||||
replacement = t.expressionStatement(replacement);
|
||||
}
|
||||
|
||||
// replacing an expression with a statement so let's explode it
|
||||
if (this.isPreviousType("Expression") && t.isStatement(replacement)) {
|
||||
return this.replaceExpressionWithStatements([replacement]);
|
||||
}
|
||||
|
||||
var oldNode = this.node;
|
||||
if (oldNode) t.inheritsComments(replacement, oldNode);
|
||||
|
||||
// replace the node
|
||||
this.node = this.container[this.key] = replacement;
|
||||
this.type = replacement.type;
|
||||
|
||||
// potentially create new scope
|
||||
this.setScope();
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function replaceExpressionWithStatements(nodes: Array) {
|
||||
var toSequenceExpression = t.toSequenceExpression(nodes, this.scope);
|
||||
|
||||
if (toSequenceExpression) {
|
||||
return this.replaceWith(toSequenceExpression);
|
||||
} else {
|
||||
var container = t.functionExpression(null, [], t.blockStatement(nodes));
|
||||
container.shadow = true;
|
||||
|
||||
this.replaceWith(t.callExpression(container, []));
|
||||
this.traverse(hoistVariablesVisitor);
|
||||
|
||||
// add implicit returns to all ending expression statements
|
||||
var last = this.get("callee").getCompletionRecords();
|
||||
for (var i = 0; i < last.length; i++) {
|
||||
var lastNode = last[i];
|
||||
if (lastNode.isExpressionStatement()) {
|
||||
var loop = lastNode.findParent((node, path) => path.isLoop());
|
||||
if (loop) {
|
||||
var uid = this.get("callee").scope.generateDeclaredUidIdentifier("ret");
|
||||
this.get("callee.body").pushContainer("body", t.returnStatement(uid));
|
||||
lastNode.get("expression").replaceWith(
|
||||
t.assignmentExpression("=", uid, lastNode.node.expression)
|
||||
);
|
||||
} else {
|
||||
lastNode.replaceWith(t.returnStatement(lastNode.node.expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function replaceInline(nodes) {
|
||||
if (Array.isArray(nodes)) {
|
||||
if (Array.isArray(this.container)) {
|
||||
nodes = this._verifyNodeList(nodes);
|
||||
this._containerInsertAfter(nodes);
|
||||
return this.remove();
|
||||
} else {
|
||||
return this.replaceWithMultiple(nodes);
|
||||
}
|
||||
} else {
|
||||
return this.replaceWith(nodes);
|
||||
}
|
||||
}
|
||||
156
src/babel/traversal/path/resolution.js
Normal file
156
src/babel/traversal/path/resolution.js
Normal file
@ -0,0 +1,156 @@
|
||||
import isBoolean from "lodash/lang/isBoolean";
|
||||
import isNumber from "lodash/lang/isNumber";
|
||||
import isString from "lodash/lang/isString";
|
||||
import * as t from "../../types";
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function getTypeAnnotation(): {
|
||||
inferred: boolean;
|
||||
annotation: ?Object;
|
||||
} {
|
||||
if (this.typeInfo) {
|
||||
return this.typeInfo;
|
||||
}
|
||||
|
||||
var info = this.typeInfo = {
|
||||
inferred: false,
|
||||
annotation: null
|
||||
};
|
||||
|
||||
var type = this.node && this.node.typeAnnotation;
|
||||
|
||||
if (!type) {
|
||||
info.inferred = true;
|
||||
type = this.inferType(this);
|
||||
}
|
||||
|
||||
if (type) {
|
||||
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
|
||||
info.annotation = type;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function resolve(): ?NodePath {
|
||||
if (this.isVariableDeclarator()) {
|
||||
if (this.get("id").isIdentifier()) {
|
||||
return this.get("init").resolve();
|
||||
} else {
|
||||
// otherwise it's a request for a destructuring declarator and i'm not
|
||||
// ready to resolve those just yet
|
||||
}
|
||||
} else if (this.isIdentifier()) {
|
||||
var binding = this.scope.getBinding(this.node.name);
|
||||
if (!binding || !binding.constant) return;
|
||||
|
||||
// todo: take into consideration infinite recursion #1149
|
||||
return;
|
||||
|
||||
if (binding.path === this) {
|
||||
return this;
|
||||
} else {
|
||||
return binding.path.resolve();
|
||||
}
|
||||
} else if (this.isMemberExpression()) {
|
||||
// this is dangerous, as non-direct target assignments will mutate it's state
|
||||
// making this resolution inaccurate
|
||||
|
||||
var targetKey = this.toComputedKey();
|
||||
if (!t.isLiteral(targetKey)) return;
|
||||
var targetName = targetKey.value;
|
||||
|
||||
var target = this.get("object").resolve();
|
||||
if (!target || !target.isObjectExpression()) return;
|
||||
|
||||
var props = target.get("properties");
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var prop = props[i];
|
||||
if (!prop.isProperty()) continue;
|
||||
|
||||
var key = prop.get("key");
|
||||
|
||||
// { foo: obj }
|
||||
var match = prop.isnt("computed") && key.isIdentifier({ name: targetName });
|
||||
|
||||
// { "foo": "obj" } or { ["foo"]: "obj" }
|
||||
match = match || key.isLiteral({ value: targetName });
|
||||
|
||||
if (match) return prop.get("value");
|
||||
}
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function inferType(path: NodePath): ?Object {
|
||||
path = path.resolve();
|
||||
if (!path) return;
|
||||
|
||||
if (path.isPreviousType("RestElement") || path.parentPath.isPreviousType("RestElement") || path.isPreviousType("ArrayExpression")) {
|
||||
return t.genericTypeAnnotation(t.identifier("Array"));
|
||||
}
|
||||
|
||||
if (path.parentPath.isPreviousType("TypeCastExpression")) {
|
||||
return path.parentPath.node.typeAnnotation;
|
||||
}
|
||||
|
||||
if (path.isPreviousType("TypeCastExpression")) {
|
||||
return path.node.typeAnnotation;
|
||||
}
|
||||
|
||||
if (path.isPreviousType("ObjectExpression")) {
|
||||
return t.genericTypeAnnotation(t.identifier("Object"));
|
||||
}
|
||||
|
||||
if (path.isPreviousType("Function")) {
|
||||
return t.identifier("Function");
|
||||
}
|
||||
|
||||
if (path.isPreviousType("Literal")) {
|
||||
var value = path.node.value;
|
||||
if (isString(value)) return t.stringTypeAnnotation();
|
||||
if (isNumber(value)) return t.numberTypeAnnotation();
|
||||
if (isBoolean(value)) return t.booleanTypeAnnotation();
|
||||
}
|
||||
|
||||
if (path.isPreviousType("CallExpression")) {
|
||||
var callee = path.get("callee").resolve();
|
||||
if (callee && callee.isPreviousType("Function")) return callee.node.returnType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function isTypeGeneric(genericName: string, opts = {}): boolean {
|
||||
var typeInfo = this.getTypeAnnotation();
|
||||
var type = typeInfo.annotation;
|
||||
if (!type) return false;
|
||||
|
||||
if (typeInfo.inferred && opts.inference === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!t.isGenericTypeAnnotation(type) || !t.isIdentifier(type.id, { name: genericName })) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.requireTypeParameters && !type.typeParameters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
import * as t from "../../types";
|
||||
|
||||
export var ReferencedIdentifier = {
|
||||
types: ["Identifier", "JSXIdentifier"],
|
||||
checkPath(path, opts) {
|
||||
return t.isReferencedIdentifier(path.node, path.parent, opts);
|
||||
}
|
||||
};
|
||||
|
||||
export var Scope = {
|
||||
types: ["Scopable"],
|
||||
checkPath(path) {
|
||||
return t.isScope(path.node, path.parent);
|
||||
}
|
||||
};
|
||||
|
||||
export var Referenced = {
|
||||
checkPath(path) {
|
||||
return t.isReferenced(path.node, path.parent);
|
||||
}
|
||||
};
|
||||
|
||||
export var BlockScoped = {
|
||||
checkPath(path) {
|
||||
return t.isBlockScoped(path.node);
|
||||
}
|
||||
};
|
||||
|
||||
export var Var = {
|
||||
types: ["VariableDeclaration"],
|
||||
checkPath(path) {
|
||||
return t.isVar(path.node);
|
||||
}
|
||||
};
|
||||
@ -38,7 +38,7 @@ var functionVariableVisitor = {
|
||||
}
|
||||
};
|
||||
|
||||
var programReferenceVisitor = explode({
|
||||
var programReferenceVisitor = {
|
||||
ReferencedIdentifier(node, parent, scope, state) {
|
||||
var bindingInfo = scope.getBinding(node.name);
|
||||
if (bindingInfo) {
|
||||
@ -85,9 +85,9 @@ var programReferenceVisitor = explode({
|
||||
UnaryExpression(node, parent, scope, state) {
|
||||
if (node.operator === "delete") scope.registerConstantViolation(this.get("left"), null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var blockVariableVisitor = explode({
|
||||
var blockVariableVisitor = {
|
||||
Scope() {
|
||||
this.skip();
|
||||
},
|
||||
@ -97,9 +97,9 @@ var blockVariableVisitor = explode({
|
||||
state.registerDeclaration(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var renameVisitor = explode({
|
||||
var renameVisitor = {
|
||||
ReferencedIdentifier(node, parent, scope, state) {
|
||||
if (node.name === state.oldName) {
|
||||
node.name = state.newName;
|
||||
@ -107,7 +107,7 @@ var renameVisitor = explode({
|
||||
},
|
||||
|
||||
Declaration(node, parent, scope, state) {
|
||||
var ids = this.getBindingIdentifiers();;
|
||||
var ids = this.getBindingIdentifiers();
|
||||
|
||||
for (var name in ids) {
|
||||
if (name === state.oldName) ids[name].name = state.newName;
|
||||
@ -121,7 +121,7 @@ var renameVisitor = explode({
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default class Scope {
|
||||
|
||||
@ -130,7 +130,7 @@ export default class Scope {
|
||||
* within.
|
||||
*/
|
||||
|
||||
constructor(path: TraversalPath, parent?: Scope, file?: File) {
|
||||
constructor(path: NodePath, parent?: Scope, file?: File) {
|
||||
if (parent && parent.block === path.node) {
|
||||
return parent;
|
||||
}
|
||||
@ -419,7 +419,7 @@ export default class Scope {
|
||||
* Description
|
||||
*/
|
||||
|
||||
registerDeclaration(path: TraversalPath) {
|
||||
registerDeclaration(path: NodePath) {
|
||||
var node = path.node;
|
||||
if (t.isFunctionDeclaration(node)) {
|
||||
this.registerBinding("hoisted", path);
|
||||
@ -441,7 +441,7 @@ export default class Scope {
|
||||
* Description
|
||||
*/
|
||||
|
||||
registerConstantViolation(left: TraversalPath, right: TraversalPath) {
|
||||
registerConstantViolation(left: NodePath, right: NodePath) {
|
||||
var ids = left.getBindingIdentifiers();
|
||||
for (var name in ids) {
|
||||
var binding = this.getBinding(name);
|
||||
@ -460,7 +460,7 @@ export default class Scope {
|
||||
* Description
|
||||
*/
|
||||
|
||||
registerBinding(kind: string, path: TraversalPath) {
|
||||
registerBinding(kind: string, path: NodePath) {
|
||||
if (!kind) throw new ReferenceError("no `kind`");
|
||||
|
||||
if (path.isVariableDeclaration()) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user