I'm extremely stupid and didn't commit as I go. To anyone reading this
I'm extremely sorry. A lot of these changes are very broad and I plan on
releasing Babel 6.0.0 today live on stage at Ember Camp London so I'm
afraid I couldn't wait. If you're ever in London I'll buy you a beer
(or assorted beverage!) to make up for it, also I'll kiss your feet and
give you a back massage, maybe.
This commit is contained in:
Sebastian McKenzie
2015-10-29 17:51:24 +00:00
parent 3974dd762d
commit ae7d5367f1
1501 changed files with 16477 additions and 19786 deletions

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains that retrieve or validate anything related to the current paths ancestry.
import * as t from "babel-types";
@@ -9,13 +11,25 @@ import NodePath from "./index";
*/
export function findParent(callback) {
var path = this;
let path = this;
while (path = path.parentPath) {
if (callback(path)) return path;
}
return null;
}
/**
* Description
*/
export function find(callback) {
let path = this;
do {
if (callback(path)) return path;
} while (path = path.parentPath);
return null;
}
/**
* Get the parent function of the current path.
*/
@@ -29,7 +43,7 @@ export function getFunctionParent() {
*/
export function getStatementParent() {
var path = this;
let path = this;
do {
if (Array.isArray(path.container)) {
return path;
@@ -47,11 +61,11 @@ export function getStatementParent() {
export function getEarliestCommonAncestorFrom(paths: Array<NodePath>): NodePath {
return this.getDeepestCommonAncestorFrom(paths, function (deepest, i, ancestries) {
var earliest;
var keys = t.VISITOR_KEYS[deepest.type];
let earliest;
let keys = t.VISITOR_KEYS[deepest.type];
for (var ancestry of (ancestries: Array)) {
var path = ancestry[i + 1];
for (let ancestry of (ancestries: Array)) {
let path = ancestry[i + 1];
// first path
if (!earliest) {
@@ -69,8 +83,8 @@ export function getEarliestCommonAncestorFrom(paths: Array<NodePath>): NodePath
}
// handle keys
var earliestKeyIndex = keys.indexOf(earliest.parentKey);
var currentKeyIndex = keys.indexOf(path.parentKey);
let earliestKeyIndex = keys.indexOf(earliest.parentKey);
let currentKeyIndex = keys.indexOf(path.parentKey);
if (earliestKeyIndex > currentKeyIndex) {
// key appears before so it's earlier
earliest = path;
@@ -97,14 +111,14 @@ export function getDeepestCommonAncestorFrom(paths: Array<NodePath>, filter?: Fu
}
// minimum depth of the tree so we know the highest node
var minDepth = Infinity;
let minDepth = Infinity;
// last common ancestor
var lastCommonIndex, lastCommon;
let lastCommonIndex, lastCommon;
// get the ancestors of the path, breaking when the parent exceeds ourselves
var ancestries = paths.map((path) => {
var ancestry = [];
let ancestries = paths.map((path) => {
let ancestry = [];
do {
ancestry.unshift(path);
@@ -119,13 +133,13 @@ export function getDeepestCommonAncestorFrom(paths: Array<NodePath>, filter?: Fu
});
// get the first ancestry so we have a seed to assess all other ancestries with
var first = ancestries[0];
let first = ancestries[0];
// check ancestor equality
depthLoop: for (var i = 0; i < minDepth; i++) {
var shouldMatch = first[i];
depthLoop: for (let i = 0; i < minDepth; i++) {
let shouldMatch = first[i];
for (var ancestry of (ancestries: Array)) {
for (let ancestry of (ancestries: Array)) {
if (ancestry[i] !== shouldMatch) {
// we've hit a snag
break depthLoop;
@@ -155,8 +169,8 @@ export function getDeepestCommonAncestorFrom(paths: Array<NodePath>, filter?: Fu
*/
export function getAncestry() {
var path = this;
var paths = [];
let path = this;
let paths = [];
do {
paths.push(path);
} while(path = path.parentPath);
@@ -164,9 +178,9 @@ export function getAncestry() {
}
export function inType() {
var path = this;
let path = this;
while (path) {
for (var type of (arguments: Array)) {
for (let type of (arguments: Array)) {
if (path.node.type === type) return true;
}
path = path.parentPath;
@@ -180,10 +194,10 @@ export function inType() {
*/
export function inShadow(key?) {
var path = this;
let path = this;
do {
if (path.isFunction()) {
var shadow = path.node.shadow;
let shadow = path.node.shadow;
if (shadow) {
// this is because sometimes we may have a `shadow` value of:
//

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains methods responsible for dealing with comments.
/**
@@ -5,15 +7,15 @@
*/
export function shareCommentsWithSiblings() {
var node = this.node;
let node = this.node;
if (!node) return;
var trailing = node.trailingComments;
var leading = node.leadingComments;
let trailing = node.trailingComments;
let leading = node.leadingComments;
if (!trailing && !leading) return;
var prev = this.getSibling(this.key - 1);
var next = this.getSibling(this.key + 1);
let prev = this.getSibling(this.key - 1);
let next = this.getSibling(this.key + 1);
if (!prev.node) prev = next;
if (!next.node) next = prev;
@@ -36,10 +38,10 @@ export function addComment(type, content, line?) {
export function addComments(type: string, comments: Array) {
if (!comments) return;
var node = this.node;
let node = this.node;
if (!node) return;
var key = `${type}Comments`;
let key = `${type}Comments`;
if (node[key]) {
node[key] = node[key].concat(comments);

View File

@@ -0,0 +1 @@
export const PATH_CACHE_KEY = "_paths"; //Symbol();

View File

@@ -1,71 +1,69 @@
/* @flow */
// This file contains methods responsible for maintaining a TraversalContext.
import traverse from "../index";
export function call(key) {
var node = this.node;
if (!node) return;
export function call(key): boolean {
let opts = this.opts;
var opts = this.opts;
for (var fns of [opts[key], opts[node.type] && opts[node.type][key]]){
if (!fns) continue;
for (var fn of (fns: Array)) {
if (!fn) continue;
let node = this.node;
if (!node) return;
var previousType = this.type;
// call the function with the params (node, parent, scope, state)
var replacement = fn.call(this, this, this.state);
if (replacement) {
throw new Error("Unexpected return value from visitor method");
}
if (this.shouldStop || this.shouldSkip || this.removed) return;
if (previousType !== this.type) {
this.queueNode(this);
return;
}
}
if (this.node) {
if (this._call(opts[key])) return true;
}
if (this.node) {
return this._call(opts[this.node.type] && opts[this.node.type][key]);
}
return false;
}
export function _call(fns?: Array<Function>): boolean {
if (!fns) return false;
for (let fn of fns) {
if (!fn) continue;
let node = this.node;
if (!node) return true;
let ret = fn.call(this.state, this, this.state);
if (ret) throw new Error("Unexpected return value from visitor method " + fn);
// node has been replaced, it will have been requeued
if (this.node !== node) return true;
if (this.shouldStop || this.shouldSkip || this.removed) return true;
}
return false;
}
export function isBlacklisted(): boolean {
var blacklist = this.opts.blacklist;
let blacklist = this.opts.blacklist;
return blacklist && blacklist.indexOf(this.node.type) > -1;
}
export function visit(): boolean {
if (this.isBlacklisted()) return false;
if (this.opts.shouldSkip && this.opts.shouldSkip(this)) return false;
if (!this.node) {
return false;
}
this.call("enter");
if (this.isBlacklisted()) {
return false;
}
if (this.shouldSkip) {
if (this.opts.shouldSkip && this.opts.shouldSkip(this)) {
return false;
}
if (this.call("enter") || this.shouldSkip) {
return this.shouldStop;
}
var node = this.node;
var opts = this.opts;
traverse.node(this.node, this.opts, this.scope, this.state, this, this.skipKeys);
if (node) {
if (Array.isArray(node)) {
// traverse over these replacement nodes we purposely don't call exitNode
// as the original node has been destroyed
for (var i = 0; i < node.length; i++) {
traverse.node(node[i], opts, this.scope, this.state, this, this.skipKeys);
}
} else {
traverse.node(node, opts, this.scope, this.state, this, this.skipKeys);
this.call("exit");
}
}
this.call("exit");
return this.shouldStop;
}
@@ -86,8 +84,19 @@ export function stop() {
export function setScope() {
if (this.opts && this.opts.noScope) return;
var target = this.context || this.parentPath;
this.scope = this.getScope(target && target.scope);
let target = this.context && this.context.scope;
if (!target) {
let path = this.parentPath;
while (path && !target) {
if (path.opts && path.opts.noScope) return;
target = path.scope;
path = path.parentPath;
}
}
this.scope = this.getScope(target);
if (this.scope) this.scope.init();
}
@@ -138,37 +147,31 @@ export function _resyncKey() {
// not done through our path APIs
if (Array.isArray(this.container)) {
for (var i = 0; i < this.container.length; i++) {
for (let i = 0; i < this.container.length; i++) {
if (this.container[i] === this.node) {
return this.setKey(i);
}
}
} else {
for (var key in this.container) {
for (let key in this.container) {
if (this.container[key] === this.node) {
return this.setKey(key);
}
}
}
// ¯\_(ツ)_/¯ who knows where it's gone lol
this.key = null;
}
export function _resyncList() {
var listKey = this.listKey;
var parentPath = this.parentPath;
if (!listKey || !parentPath || !parentPath.node) return;
if (!this.parent || !this.inList) return;
var newContainer = parentPath.node[listKey];
let newContainer = this.parent[this.listKey];
if (this.container === newContainer) return;
// container is out of sync. this is likely the result of it being reassigned
if (newContainer) {
this.container = newContainer;
} else {
this.container = null;
}
this.container = newContainer || null;
}
export function _resyncRemoved() {
@@ -177,13 +180,13 @@ export function _resyncRemoved() {
}
}
export function shiftContext() {
this.contexts.shift();
this.setContext(this.contexts[0]);
export function popContext() {
this.contexts.pop();
this.setContext(this.contexts[this.contexts.length - 1]);
}
export function unshiftContext(context) {
this.contexts.unshift(context);
export function pushContext(context) {
this.contexts.push(context);
this.setContext(context);
}
@@ -203,10 +206,10 @@ export function setKey(key) {
this.type = this.node && this.node.type;
}
export function queueNode(path) {
for (var context of (this.contexts: Array)) {
if (context.queue) {
context.queue.push(path);
}
export function requeue(path = this) {
if (path.removed) return;
for (let context of this.contexts) {
context.maybeQueue(path);
}
}

View File

@@ -1,21 +1,23 @@
/* @flow */
// This file contains methods that convert the path node into another node or some other type of data.
import * as t from "babel-types";
export function toComputedKey(): Object {
var node = this.node;
let node = this.node;
var key;
let key;
if (this.isMemberExpression()) {
key = node.property;
} else if (this.isProperty()) {
} else if (this.isProperty() || this.isMethod()) {
key = node.key;
} else {
throw new ReferenceError("todo");
}
if (!node.computed) {
if (t.isIdentifier(key)) key = t.literal(key.name);
if (t.isIdentifier(key)) key = t.stringLiteral(key.name);
}
return key;
@@ -24,3 +26,15 @@ export function toComputedKey(): Object {
export function ensureBlock() {
return t.ensureBlock(this.node);
}
export function arrowFunctionToShadowed() {
// todo: maybe error
if (!this.isArrowFunctionExpression()) return;
this.ensureBlock();
let { node } = this;
node.expression = false;
node.type = "FunctionExpression";
node.shadow = node.shadow || true;
}

View File

@@ -1,3 +1,7 @@
/* @flow */
import type NodePath from "./index";
// This file contains Babels metainterpreter that can evaluate static code.
/* eslint eqeqeq: 0 */
@@ -24,7 +28,7 @@ const INVALID_METHODS = ["random"];
*/
export function evaluateTruthy(): boolean {
var res = this.evaluate();
let res = this.evaluate();
if (res.confident) return !!res.value;
}
@@ -44,40 +48,48 @@ export function evaluateTruthy(): boolean {
*/
export function evaluate(): { confident: boolean; value: any } {
var confident = true;
let confident = true;
let deoptPath: ?NodePath;
var value = evaluate(this);
function deopt(path) {
if (!confident) return;
deoptPath = path;
confident = false;
}
let value = evaluate(this);
if (!confident) value = undefined;
return {
confident: confident,
deopt: deoptPath,
value: value
};
function evaluate(path) {
if (!confident) return;
var node = path.node;
let { node } = path;
if (path.isSequenceExpression()) {
let exprs = path.get("expressions");
return evaluate(exprs[exprs.length - 1]);
}
if (path.isLiteral()) {
if (node.regex) {
// we have a regex and we can't represent it natively
} else {
return node.value;
}
if (path.isStringLiteral() || path.isNumberLiteral() || path.isBooleanLiteral()) {
return node.value;
}
if (path.isNullLiteral()) {
return null;
}
if (path.isTemplateLiteral()) {
var str = "";
let str = "";
var i = 0;
let i = 0;
let exprs = path.get("expressions");
for (let elem of (node.quasis: Array)) {
for (let elem of (node.quasis: Array<Object>)) {
// not confident, evaluated an expression we don't like
if (!confident) break;
@@ -85,7 +97,7 @@ export function evaluate(): { confident: boolean; value: any } {
str += elem.value.cooked;
// add on interpolated expression if it's present
var expr = exprs[i++];
let expr = exprs[i++];
if (expr) str += String(evaluate(expr));
}
@@ -100,20 +112,10 @@ export function evaluate(): { confident: boolean; value: any } {
}
}
if (path.isTypeCastExpression()) {
if (path.isExpressionWrapper()) { // TypeCastExpression, ExpressionStatement etc
return evaluate(path.get("expression"));
}
if (path.isIdentifier() && !path.scope.hasBinding(node.name, true)) {
if (node.name === "undefined") {
return undefined;
} else if (node.name === "Infinity") {
return Infinity;
} else if (node.name === "NaN") {
return NaN;
}
}
// "foo".length
if (path.isMemberExpression() && !path.parentPath.isCallExpression({ callee: node })) {
let property = path.get("property");
@@ -129,13 +131,21 @@ export function evaluate(): { confident: boolean; value: any } {
}
if (path.isReferencedIdentifier()) {
var binding = path.scope.getBinding(node.name);
let binding = path.scope.getBinding(node.name);
if (binding && binding.hasValue) {
return binding.value;
} else {
var resolved = path.resolve();
if (node.name === "undefined") {
return undefined;
} else if (node.name === "Infinity") {
return Infinity;
} else if (node.name === "NaN") {
return NaN;
}
let resolved = path.resolve();
if (resolved === path) {
return confident = false;
return deopt(path);
} else {
return evaluate(resolved);
}
@@ -143,27 +153,68 @@ export function evaluate(): { confident: boolean; value: any } {
}
if (path.isUnaryExpression({ prefix: true })) {
var arg = evaluate(path.get("argument"));
if (node.operator === "void") {
// we don't need to evaluate the argument to know what this will return
return undefined;
}
let argument = path.get("argument");
if (node.operator === "typeof" && (argument.isFunction() || argument.isClass())) {
return "function";
}
let arg = evaluate(argument);
switch (node.operator) {
case "void": return undefined;
case "!": return !arg;
case "+": return +arg;
case "-": return -arg;
case "~": return ~arg;
case "typeof": return typeof arg;
}
}
if (path.isArrayExpression() || path.isObjectExpression()) {
// we could evaluate these but it's probably impractical and not very useful
if (path.isArrayExpression()) {
let arr = [];
let elems: Array<NodePath> = path.get("elements");
for (let elem of elems) {
elem = elem.evaluate();
if (elem.confident) {
arr.push(elem.value);
} else {
return deopt(elem);
}
}
return arr;
}
if (path.isObjectExpression()) {
// todo
}
if (path.isLogicalExpression()) {
// If we are confident that one side of an && is false, or one side of
// an || is true, we can be confident about the entire expression
let wasConfident = confident;
let left = evaluate(path.get("left"));
let leftConfident = confident;
confident = wasConfident;
let right = evaluate(path.get("right"));
let rightConfident = confident;
let uncertain = leftConfident !== rightConfident;
confident = leftConfident && rightConfident;
switch (node.operator) {
case "||": return left || right;
case "&&": return left && right;
case "||":
if ((left || right) && uncertain) {
confident = true;
}
return left || right;
case "&&":
if ((!left && leftConfident) || (!right && rightConfident)) {
confident = true;
}
return left && right;
}
}
@@ -186,13 +237,19 @@ export function evaluate(): { confident: boolean; value: any } {
case "!=": return left != right;
case "===": return left === right;
case "!==": return left !== right;
case "|": return left | right;
case "&": return left & right;
case "^": return left ^ right;
case "<<": return left << right;
case ">>": return left >> right;
case ">>>": return left >>> right;
}
}
if (path.isCallExpression()) {
var callee = path.get("callee");
var context;
var func;
let callee = path.get("callee");
let context;
let func;
// Number(1);
if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name, true) && VALID_CALLEES.indexOf(callee.node.name) >= 0) {
@@ -201,7 +258,7 @@ export function evaluate(): { confident: boolean; value: any } {
if (callee.isMemberExpression()) {
let object = callee.get("object");
var property = callee.get("property");
let property = callee.get("property");
// Math.min(1, 2)
if (object.isIdentifier() && property.isIdentifier() && VALID_CALLEES.indexOf(object.node.name) >= 0 && INVALID_METHODS.indexOf(property.node.name) < 0) {
@@ -220,13 +277,13 @@ export function evaluate(): { confident: boolean; value: any } {
}
if (func) {
var args = path.get("arguments").map(evaluate);
let args = path.get("arguments").map(evaluate);
if (!confident) return;
return func.apply(context, args);
}
}
confident = false;
deopt(path);
}
}

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains methods responsible for dealing with/retrieving children or siblings.
import type TraversalContext from "../index";
@@ -5,7 +7,7 @@ import NodePath from "./index";
import * as t from "babel-types";
export function getStatementParent(): ?NodePath {
var path = this;
let path = this;
do {
if (!path.parentPath || (Array.isArray(path.container) && path.isStatement())) {
@@ -31,9 +33,9 @@ export function getOpposite() {
}
export function getCompletionRecords(): Array {
var paths = [];
let paths = [];
var add = function (path) {
let add = function (path) {
if (path) paths = paths.concat(path.getCompletionRecords());
};
@@ -69,7 +71,7 @@ export function getSibling(key) {
export function get(key: string, context?: boolean | TraversalContext): NodePath {
if (context === true) context = this.context;
var parts = key.split(".");
let parts = key.split(".");
if (parts.length === 1) { // "foo"
return this._getKey(key, context);
} else { // "foo.bar"
@@ -78,8 +80,8 @@ export function get(key: string, context?: boolean | TraversalContext): NodePath
}
export function _getKey(key, context?) {
var node = this.node;
var container = node[key];
let node = this.node;
let container = node[key];
if (Array.isArray(container)) {
// requested a container so give them all the paths
@@ -103,8 +105,8 @@ export function _getKey(key, context?) {
}
export function _getPattern(parts, context) {
var path = this;
for (var part of (parts: Array)) {
let path = this;
for (let part of (parts: Array)) {
if (part === ".") {
path = path.parentPath;
} else {

View File

@@ -1,4 +1,9 @@
/* @flow */
import type Hub from "../hub";
import type TraversalContext from "../context";
import * as virtualTypes from "./lib/virtual-types";
import { PATH_CACHE_KEY } from "./constants";
import invariant from "invariant";
import traverse from "../index";
import assign from "lodash/object/assign";
@@ -6,45 +11,66 @@ import Scope from "../scope";
import * as t from "babel-types";
export default class NodePath {
constructor(hub, parent) {
constructor(hub: Hub, parent: Object) {
this.parent = parent;
this.hub = hub;
this.contexts = [];
this.parent = parent;
this.data = {};
this.hub = hub;
this.shouldSkip = false;
this.shouldStop = false;
this.removed = false;
this.state = null;
this.opts = null;
this.skipKeys = null;
this.parentPath = null;
this.context = null;
this.container = null;
this.listKey = null;
this.inList = false;
this.parentKey = null;
this.key = null;
this.node = null;
this.scope = null;
this.type = null;
this.typeAnnotation = null;
this.data = {};
this.shouldSkip = false;
this.shouldStop = false;
this.removed = false;
this.state = null;
this.opts = null;
this.skipKeys = null;
this.parentPath = null;
this.context = null;
this.container = null;
this.listKey = null;
this.inList = false;
this.parentKey = null;
this.key = null;
this.node = null;
this.scope = null;
this.type = null;
this.typeAnnotation = null;
}
static get({ hub, parentPath, parent, container, listKey, key }) {
parent: Object;
hub: Hub;
contexts: Array<TraversalContext>;
data: Object;
shouldSkip: boolean;
shouldStop: boolean;
removed: boolean;
state: any;
opts: ?Object;
skipKeys: ?Object;
parentPath: ?NodePath;
context: TraversalContext;
container: ?Object | Array<Object>;
listKey: ?string;
inList: boolean;
parentKey: ?string;
key: ?string;
node: ?Object;
scope: Scope;
type: ?string;
typeAnnotation: ?Object;
static get({ hub, parentPath, parent, container, listKey, key }): NodePath {
if (!hub && parentPath) {
hub = parentPath.hub;
}
invariant(parent, "To get a node path the parent needs to exist");
var targetNode = container[key];
var paths = parent._paths = parent._paths || [];
var path;
let targetNode = container[key];
for (var i = 0; i < paths.length; i++) {
var pathCheck = paths[i];
let paths = parent[PATH_CACHE_KEY] = parent[PATH_CACHE_KEY] || [];
let path;
for (let i = 0; i < paths.length; i++) {
let pathCheck = paths[i];
if (pathCheck.node === targetNode) {
path = pathCheck;
break;
@@ -56,13 +82,17 @@ export default class NodePath {
paths.push(path);
}
if (!(path instanceof NodePath)) {
throw new Error("We found a path that isn't a NodePath instance");
}
path.setup(parentPath, container, listKey, key);
return path;
}
getScope(scope: Scope) {
var ourScope = scope;
let ourScope = scope;
// we're entering a new scope so let's construct it!
if (this.isScope()) {
@@ -72,25 +102,25 @@ export default class NodePath {
return ourScope;
}
setData(key, val) {
setData(key: string, val: any): any {
return this.data[key] = val;
}
getData(key, def) {
var val = this.data[key];
getData(key: string, def?: any): any {
let val = this.data[key];
if (!val && def) val = this.data[key] = def;
return val;
}
errorWithNode(msg, Error = SyntaxError) {
return this.hub.file.errorWithNode(this.node, msg, Error);
buildCodeFrameError(msg: string, Error: typeof Error = SyntaxError): Error {
return this.hub.file.buildCodeFrameError(this.node, msg, Error);
}
traverse(visitor, state) {
traverse(visitor: Object, state?: any) {
traverse(this.node, visitor, this.scope, state, this);
}
mark(type, message) {
mark(type: string, message: string) {
this.hub.file.metadata.marked.push({
type,
message,
@@ -98,14 +128,21 @@ export default class NodePath {
});
}
/**
* Description
*/
set(key, node) {
t.validate(key, this.node, node);
set(key: string, node: Object) {
t.validate(this.node, key, node);
this.node[key] = node;
}
dump() {
let parts = [];
let path = this;
do {
let key = path.key;
if (path.inList) key = `${path.listKey}[${key}]`;
parts.unshift(key);
} while(path = path.parentPath);
console.log(parts.join("."));
}
}
assign(NodePath.prototype, require("./ancestry"));
@@ -120,18 +157,26 @@ assign(NodePath.prototype, require("./modification"));
assign(NodePath.prototype, require("./family"));
assign(NodePath.prototype, require("./comments"));
for (let type of (t.TYPES: Array)) {
for (let type of (t.TYPES: Array<string>)) {
let typeKey = `is${type}`;
NodePath.prototype[typeKey] = function (opts) {
return t[typeKey](this.node, opts);
};
NodePath.prototype[`assert${type}`] = function (opts) {
if (!this[typeKey](opts)) {
throw new TypeError(`Expected node path of type ${type}`);
}
};
}
for (let type in virtualTypes) {
if (type[0] === "_") continue;
if (t.TYPES.indexOf(type) < 0) t.TYPES.push(type);
let virtualType = virtualTypes[type];
NodePath.prototype[`is${type}`] = function (opts) {
return virtualTypes[type].checkPath(this, opts);
return virtualType.checkPath(this, opts);
};
}

View File

@@ -1,3 +1,5 @@
/* @flow */
import type NodePath from "./index";
import * as inferers from "./inferers";
import * as t from "babel-types";
@@ -6,10 +8,10 @@ import * as t from "babel-types";
* Infer the type of the current `NodePath`.
*/
export function getTypeAnnotation() {
export function getTypeAnnotation(): Object {
if (this.typeAnnotation) return this.typeAnnotation;
var type = this._getTypeAnnotation() || t.anyTypeAnnotation();
let type = this._getTypeAnnotation() || t.anyTypeAnnotation();
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
return this.typeAnnotation = type;
}
@@ -19,20 +21,20 @@ export function getTypeAnnotation() {
*/
export function _getTypeAnnotation(): ?Object {
var node = this.node;
let node = this.node;
if (!node) {
// handle initializerless variables, add in checks for loop initializers too
if (this.key === "init" && this.parentPath.isVariableDeclarator()) {
var declar = this.parentPath.parentPath;
var declarParent = declar.parentPath;
let declar = this.parentPath.parentPath;
let declarParent = declar.parentPath;
// for (var NODE in bar) {}
// for (let NODE in bar) {}
if (declar.key === "left" && declarParent.isForInStatement()) {
return t.stringTypeAnnotation();
}
// for (var NODE of bar) {}
// for (let NODE of bar) {}
if (declar.key === "left" && declarParent.isForOfStatement()) {
return t.anyTypeAnnotation();
}
@@ -47,7 +49,7 @@ export function _getTypeAnnotation(): ?Object {
return node.typeAnnotation;
}
var inferer = inferers[node.type];
let inferer = inferers[node.type];
if (inferer) {
return inferer.call(this, node);
}
@@ -58,7 +60,7 @@ export function _getTypeAnnotation(): ?Object {
}
}
export function isBaseType(baseName: string, soft?): boolean {
export function isBaseType(baseName: string, soft?: boolean): boolean {
return _isBaseType(baseName, this.getTypeAnnotation(), soft);
}
@@ -85,11 +87,11 @@ function _isBaseType(baseName: string, type?, soft?): boolean {
}
export function couldBeBaseType(name: string): boolean {
var type = this.getTypeAnnotation();
let type = this.getTypeAnnotation();
if (t.isAnyTypeAnnotation(type)) return true;
if (t.isUnionTypeAnnotation(type)) {
for (var type2 of (type.types: Array)) {
for (let type2 of (type.types: Array<Object>)) {
if (t.isAnyTypeAnnotation(type2) || _isBaseType(name, type2, true)) {
return true;
}
@@ -101,7 +103,7 @@ export function couldBeBaseType(name: string): boolean {
}
export function baseTypeStrictlyMatches(right: NodePath) {
var left = this.getTypeAnnotation();
let left = this.getTypeAnnotation();
right = right.getTypeAnnotation();
if (!t.isAnyTypeAnnotation(left) && t.isFlowBaseAnnotation(left)) {
@@ -110,6 +112,6 @@ export function baseTypeStrictlyMatches(right: NodePath) {
}
export function isGenericType(genericName: string): boolean {
var type = this.getTypeAnnotation();
let type = this.getTypeAnnotation();
return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName });
}

View File

@@ -1,11 +1,14 @@
/* @flow */
import type NodePath from "../index";
import * as t from "babel-types";
export default function (node) {
export default function (node: Object) {
if (!this.isReferenced()) return;
// check if a binding exists of this value and if so then return a union type of all
// possible types that the binding could be
var binding = this.scope.getBinding(node.name);
let binding = this.scope.getBinding(node.name);
if (binding) {
if (binding.identifier.typeAnnotation) {
return binding.identifier.typeAnnotation;
@@ -25,17 +28,17 @@ export default function (node) {
}
function getTypeAnnotationBindingConstantViolations(path, name) {
var binding = path.scope.getBinding(name);
let binding = path.scope.getBinding(name);
var types = [];
let types = [];
path.typeAnnotation = t.unionTypeAnnotation(types);
var functionConstantViolations = [];
var constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations);
let functionConstantViolations = [];
let constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations);
var testType = getConditionalAnnotation(path, name);
let testType = getConditionalAnnotation(path, name);
if (testType) {
var testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement);
let testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement);
// remove constant violations observed before the IfStatement
constantViolations = constantViolations.filter((path) => testConstantViolations.indexOf(path) < 0);
@@ -47,11 +50,11 @@ function getTypeAnnotationBindingConstantViolations(path, name) {
if (constantViolations.length) {
// pick one constant from each scope which will represent the last possible
// control flow path that it could've taken/been
var rawConstantViolations = constantViolations.reverse();
var visitedScopes = [];
let rawConstantViolations = constantViolations.reverse();
let visitedScopes = [];
constantViolations = [];
for (let violation of (rawConstantViolations: Array)) {
var violationScope = violation.scope;
for (let violation of (rawConstantViolations: Array<NodePath>)) {
let violationScope = violation.scope;
if (visitedScopes.indexOf(violationScope) >= 0) continue;
visitedScopes.push(violationScope);
@@ -67,7 +70,7 @@ function getTypeAnnotationBindingConstantViolations(path, name) {
constantViolations = constantViolations.concat(functionConstantViolations);
// push on inferred types of violated paths
for (let violation of (constantViolations: Array)) {
for (let violation of (constantViolations: Array<NodePath>)) {
types.push(violation.getTypeAnnotation());
}
}
@@ -78,23 +81,23 @@ function getTypeAnnotationBindingConstantViolations(path, name) {
}
function getConstantViolationsBefore(binding, path, functions) {
var violations = binding.constantViolations.slice();
let violations = binding.constantViolations.slice();
violations.unshift(binding.path);
return violations.filter((violation) => {
violation = violation.resolve();
var status = violation._guessExecutionStatusRelativeTo(path);
let status = violation._guessExecutionStatusRelativeTo(path);
if (functions && status === "function") functions.push(violation);
return status === "before";
});
}
function inferAnnotationFromBinaryExpression(name, path) {
var operator = path.node.operator;
let operator = path.node.operator;
var right = path.get("right").resolve();
var left = path.get("left").resolve();
let right = path.get("right").resolve();
let left = path.get("left").resolve();
var target;
let target;
if (left.isIdentifier({ name })) {
target = right;
} else if (right.isIdentifier({ name })) {
@@ -113,8 +116,8 @@ function inferAnnotationFromBinaryExpression(name, path) {
}
//
var typeofPath;
var typePath;
let typeofPath;
let typePath;
if (left.isUnaryExpression({ operator: "typeof" })) {
typeofPath = left;
typePath = right;
@@ -129,7 +132,7 @@ function inferAnnotationFromBinaryExpression(name, path) {
if (!typePath.isLiteral()) return;
// and that it's a string so we can infer it
var typeValue = typePath.node.value;
let typeValue = typePath.node.value;
if (typeof typeValue !== "string") return;
// and that the argument of the typeof path references us!
@@ -140,7 +143,7 @@ function inferAnnotationFromBinaryExpression(name, path) {
}
function getParentConditionalPath(path) {
var parentPath;
let parentPath;
while (parentPath = path.parentPath) {
if (parentPath.isIfStatement() || parentPath.isConditionalExpression()) {
if (path.key === "test") {
@@ -155,12 +158,12 @@ function getParentConditionalPath(path) {
}
function getConditionalAnnotation(path, name) {
var ifStatement = getParentConditionalPath(path);
let ifStatement = getParentConditionalPath(path);
if (!ifStatement) return;
var test = ifStatement.get("test");
var paths = [test];
var types = [];
let test = ifStatement.get("test");
let paths = [test];
let types = [];
do {
let path = paths.shift().resolve();
@@ -171,7 +174,7 @@ function getConditionalAnnotation(path, name) {
}
if (path.isBinaryExpression()) {
var type = inferAnnotationFromBinaryExpression(name, path);
let type = inferAnnotationFromBinaryExpression(name, path);
if (type) types.push(type);
}
} while(paths.length);

View File

@@ -1,9 +1,11 @@
/* @flow */
import * as t from "babel-types";
export { default as Identifier } from "./inferer-reference";
export function VariableDeclarator() {
var id = this.get("id");
let id = this.get("id");
if (id.isIdentifier()) {
return this.get("init").getTypeAnnotation();
@@ -51,8 +53,8 @@ export function BinaryExpression(node) {
} else if (t.BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) {
return t.booleanTypeAnnotation();
} else if (operator === "+") {
var right = this.get("right");
var left = this.get("left");
let right = this.get("right");
let left = this.get("left");
if (left.isBaseType("number") && right.isBaseType("number")) {
// both numbers so this will be a number
@@ -99,13 +101,24 @@ export function UpdateExpression(node) {
}
}
export function Literal(node) {
var value = node.value;
if (typeof value === "string") return t.stringTypeAnnotation();
if (typeof value === "number") return t.numberTypeAnnotation();
if (typeof value === "boolean") return t.booleanTypeAnnotation();
if (value === null) return t.voidTypeAnnotation();
if (node.regex) return t.genericTypeAnnotation(t.identifier("RegExp"));
export function StringLiteral() {
return t.stringTypeAnnotation();
}
export function NumberLiteral() {
return t.numberTypeAnnotation();
}
export function BooleanLiteral() {
return t.booleanTypeAnnotation();
}
export function NullLiteral() {
return t.voidTypeAnnotation();
}
export function RegexLiteral() {
return t.genericTypeAnnotation(t.identifier("RegExp"));
}
export function ObjectExpression() {

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains methods responsible for introspecting the current path for certain values.
import type NodePath from "./index";
@@ -15,17 +17,17 @@ export function matchesPattern(pattern: string, allowPartial?: boolean): boolean
// not a member expression
if (!this.isMemberExpression()) return false;
var parts = pattern.split(".");
var search = [this.node];
var i = 0;
let parts = pattern.split(".");
let search = [this.node];
let i = 0;
function matches(name) {
var part = parts[i];
let part = parts[i];
return part === "*" || name === part;
}
while (search.length) {
var node = search.shift();
let node = search.shift();
if (allowPartial && i === parts.length) {
return true;
@@ -68,7 +70,7 @@ export function matchesPattern(pattern: string, allowPartial?: boolean): boolean
*/
export function has(key): boolean {
var val = this.node[key];
let val = this.node[key];
if (val && Array.isArray(val)) {
return !!val.length;
} else {
@@ -88,7 +90,7 @@ export function isStatic() {
* Alias of `has`.
*/
export var is = has;
export let is = has;
/**
* Opposite of `has`.
@@ -134,11 +136,11 @@ export function isNodeType(type: string): boolean {
*/
export function isCompletionRecord(allowInsideFunction?) {
var path = this;
var first = true;
let path = this;
let first = true;
do {
var container = path.container;
let container = path.container;
// we're in a function so can't be a completion record
if (path.isFunction() && !first) {
@@ -177,11 +179,11 @@ export function isStatementOrBlock() {
export function referencesImport(moduleSource, importName) {
if (!this.isReferencedIdentifier()) return false;
var binding = this.scope.getBinding(this.node.name);
let binding = this.scope.getBinding(this.node.name);
if (!binding || binding.kind !== "module") return false;
var path = binding.path;
var parent = path.parentPath;
let path = binding.path;
let parent = path.parentPath;
if (!parent.isImportDeclaration()) return false;
// check moduleSource
@@ -211,7 +213,7 @@ export function referencesImport(moduleSource, importName) {
*/
export function getSource() {
var node = this.node;
let node = this.node;
if (node.end) {
return this.hub.file.code.slice(node.start, node.end);
} else {
@@ -232,57 +234,29 @@ export function willIMaybeExecuteBefore(target) {
export function _guessExecutionStatusRelativeTo(target) {
// check if the two paths are in different functions, we can't track execution of these
var targetFuncParent = target.scope.getFunctionParent();
var selfFuncParent = this.scope.getFunctionParent();
let targetFuncParent = target.scope.getFunctionParent();
let selfFuncParent = this.scope.getFunctionParent();
if (targetFuncParent !== selfFuncParent) {
var targetFuncPath = targetFuncParent.path;
if (!targetFuncPath.isFunctionDeclaration()) return "function";
// so we're in a completely different function, if this is a function declaration
// then we can be a bit smarter and handle cases where the function is either
// a. not called at all (part of an export)
// b. called directly
var binding = targetFuncPath.scope.getBinding(targetFuncPath.node.id.name);
// no references!
if (!binding.references) return "before";
var referencePaths: Array = binding.referencePaths;
// verify that all of the references are calls
for (let path of referencePaths) {
if (path.key !== "callee" || !path.parentPath.isCallExpression()) {
return "function";
}
let status = this._guessExecutionStatusRelativeToDifferentFunctions(targetFuncParent);
if (status) {
return status;
} else {
target = targetFuncParent.path;
}
var allStatus;
// verify that all the calls have the same execution status
for (let path of referencePaths) {
var status = this._guessExecutionStatusRelativeTo(path);
if (allStatus) {
if (allStatus !== status) return "function";
} else {
allStatus = status;
}
}
return allStatus || "function";
}
var targetPaths = target.getAncestry();
let targetPaths = target.getAncestry();
if (targetPaths.indexOf(this) >= 0) return "after";
var selfPaths = this.getAncestry();
let selfPaths = this.getAncestry();
// get ancestor where the branches intersect
var commonPath;
var targetIndex;
var selfIndex;
let commonPath;
let targetIndex;
let selfIndex;
for (selfIndex = 0; selfIndex < selfPaths.length; selfIndex++) {
var selfPath = selfPaths[selfIndex];
let selfPath = selfPaths[selfIndex];
targetIndex = targetPaths.indexOf(selfPath);
if (targetIndex >= 0) {
commonPath = selfPath;
@@ -294,8 +268,8 @@ export function _guessExecutionStatusRelativeTo(target) {
}
// get the relationship paths that associate these nodes to their common ancestor
var targetRelationship = targetPaths[targetIndex - 1];
var selfRelationship = selfPaths[selfIndex - 1];
let targetRelationship = targetPaths[targetIndex - 1];
let selfRelationship = selfPaths[selfIndex - 1];
if (!targetRelationship || !selfRelationship) {
return "before";
}
@@ -306,11 +280,48 @@ export function _guessExecutionStatusRelativeTo(target) {
}
// otherwise we're associated by a parent node, check which key comes before the other
var targetKeyPosition = t.VISITOR_KEYS[targetRelationship.type].indexOf(targetRelationship.key);
var selfKeyPosition = t.VISITOR_KEYS[selfRelationship.type].indexOf(selfRelationship.key);
let targetKeyPosition = t.VISITOR_KEYS[targetRelationship.type].indexOf(targetRelationship.key);
let selfKeyPosition = t.VISITOR_KEYS[selfRelationship.type].indexOf(selfRelationship.key);
return targetKeyPosition > selfKeyPosition ? "before" : "after";
}
export function _guessExecutionStatusRelativeToDifferentFunctions(targetFuncParent) {
let targetFuncPath = targetFuncParent.path;
if (!targetFuncPath.isFunctionDeclaration()) return;
// so we're in a completely different function, if this is a function declaration
// then we can be a bit smarter and handle cases where the function is either
// a. not called at all (part of an export)
// b. called directly
let binding = targetFuncPath.scope.getBinding(targetFuncPath.node.id.name);
// no references!
if (!binding.references) return "before";
let referencePaths: Array = binding.referencePaths;
// verify that all of the references are calls
for (let path of referencePaths) {
if (path.key !== "callee" || !path.parentPath.isCallExpression()) {
return;
}
}
let allStatus;
// verify that all the calls have the same execution status
for (let path of referencePaths) {
let status = this._guessExecutionStatusRelativeTo(path);
if (allStatus) {
if (allStatus !== status) return;
} else {
allStatus = status;
}
}
return allStatus;
}
/**
* Resolve a "pointer" `NodePath` to it's absolute path.
*/
@@ -335,7 +346,7 @@ export function _resolve(dangerous?, resolved?): ?NodePath {
// otherwise it's a request for a pattern and that's a bit more tricky
}
} else if (this.isReferencedIdentifier()) {
var binding = this.scope.getBinding(this.node.name);
let binding = this.scope.getBinding(this.node.name);
if (!binding) return;
// reassigned so we can't really resolve it
@@ -353,22 +364,22 @@ export function _resolve(dangerous?, resolved?): ?NodePath {
// this is dangerous, as non-direct target assignments will mutate it's state
// making this resolution inaccurate
var targetKey = this.toComputedKey();
let targetKey = this.toComputedKey();
if (!t.isLiteral(targetKey)) return;
var targetName = targetKey.value;
let targetName = targetKey.value;
var target = this.get("object").resolve(dangerous, resolved);
let target = this.get("object").resolve(dangerous, resolved);
if (target.isObjectExpression()) {
var props = target.get("properties");
for (var prop of (props: Array)) {
let props = target.get("properties");
for (let prop of (props: Array)) {
if (!prop.isProperty()) continue;
var key = prop.get("key");
let key = prop.get("key");
// { foo: obj }
var match = prop.isnt("computed") && key.isIdentifier({ name: targetName });
let match = prop.isnt("computed") && key.isIdentifier({ name: targetName });
// { "foo": "obj" } or { ["foo"]: "obj" }
match = match || key.isLiteral({ value: targetName });
@@ -376,8 +387,8 @@ export function _resolve(dangerous?, resolved?): ?NodePath {
if (match) return prop.get("value").resolve(dangerous, resolved);
}
} else if (target.isArrayExpression() && !isNaN(+targetName)) {
var elems = target.get("elements");
var elem = elems[targetName];
let elems = target.get("elements");
let elem = elems[targetName];
if (elem) return elem.resolve(dangerous, resolved);
}
}

View File

@@ -1,25 +1,26 @@
/* @flow */
import { react } from "babel-types";
import * as t from "babel-types";
var referenceVisitor = {
ReferencedIdentifier(node, parent, scope, state) {
if (this.isJSXIdentifier() && react.isCompatTag(node.name)) {
let referenceVisitor = {
ReferencedIdentifier(path, state) {
if (path.isJSXIdentifier() && react.isCompatTag(path.node.name)) {
return;
}
// direct references that we need to track to hoist this to the highest scope we can
var binding = scope.getBinding(node.name);
let binding = path.scope.getBinding(path.node.name);
if (!binding) return;
// this binding isn't accessible from the parent scope so we can safely ignore it
// eg. it's in a closure etc
if (binding !== state.scope.getBinding(node.name)) return;
if (binding !== state.scope.getBinding(path.node.name)) return;
if (binding.constant) {
state.bindings[node.name] = binding;
state.bindings[path.node.name] = binding;
} else {
for (var violationPath of (binding.constantViolations: Array)) {
for (let violationPath of (binding.constantViolations: Array)) {
state.breakOnScopePaths = state.breakOnScopePaths.concat(violationPath.getAncestry());
}
}
@@ -36,8 +37,8 @@ export default class PathHoister {
}
isCompatibleScope(scope) {
for (var key in this.bindings) {
var binding = this.bindings[key];
for (let key in this.bindings) {
let binding = this.bindings[key];
if (!scope.bindingIdentifierEquals(key, binding.identifier)) {
return false;
}
@@ -47,7 +48,7 @@ export default class PathHoister {
}
getCompatibleScopes() {
var scope = this.path.scope;
let scope = this.path.scope;
do {
if (this.isCompatibleScope(scope)) {
this.scopes.push(scope);
@@ -62,9 +63,9 @@ export default class PathHoister {
}
getAttachmentPath() {
var scopes = this.scopes;
let scopes = this.scopes;
var scope = scopes.pop();
let scope = scopes.pop();
if (!scope) return;
if (scope.path.isFunction()) {
@@ -84,22 +85,22 @@ export default class PathHoister {
}
getNextScopeStatementParent() {
var scope = this.scopes.pop();
let scope = this.scopes.pop();
if (scope) return scope.path.getStatementParent();
}
hasOwnParamBindings(scope) {
for (var name in this.bindings) {
for (let name in this.bindings) {
if (!scope.hasOwnBinding(name)) continue;
var binding = this.bindings[name];
let binding = this.bindings[name];
if (binding.kind === "param") return true;
}
return false;
}
run() {
var node = this.path.node;
let node = this.path.node;
if (node._hoisted) return;
node._hoisted = true;
@@ -107,13 +108,13 @@ export default class PathHoister {
this.getCompatibleScopes();
var attachTo = this.getAttachmentPath();
let attachTo = this.getAttachmentPath();
if (!attachTo) return;
// don't bother hoisting to the same function as this will cause multiple branches to be evaluated more than once leading to a bad optimisation
if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return;
var uid = attachTo.scope.generateUidIdentifier("ref");
let uid = attachTo.scope.generateUidIdentifier("ref");
attachTo.insertBefore([
t.variableDeclaration("var", [
@@ -121,7 +122,7 @@ export default class PathHoister {
])
]);
var parent = this.path.parentPath;
let 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

View File

@@ -1,50 +1,21 @@
// this file contains hooks that handle ancestry cleanup of parent nodes when removing children
/* @flow */
import * as t from "babel-types";
// this file contains hooks that handle ancestry cleanup of parent nodes when removing children
/**
* Pre hooks should be used for either rejecting removal or delegating removal
*/
export var pre = [
function (self) {
if (self.key === "body" && (self.isBlockStatement() || self.isClassBody())) {
// function () NODE
// class NODE
// attempting to remove a block statement that's someones body so let's just clear all the inner
// statements instead
self.node.body = [];
export let hooks = [
function (self, parent) {
if (self.key === "body" && parent.isArrowFunctionExpression()) {
self.replaceWith(self.scope.buildUndefinedNode());
return true;
}
},
function (self, parent) {
var replace = false;
// () => NODE;
// removing the body of an arrow function
replace = replace || (self.key === "body" && parent.isArrowFunctionExpression());
// throw NODE;
// removing a throw statement argument
replace = replace || (self.key === "argument" && parent.isThrowStatement());
if (replace) {
self.replaceWith(t.identifier("undefined"));
return true;
}
}
];
/**
* Post hooks should be used for cleaning up parents
*/
export var post = [
function (self, parent) {
var removeParent = false;
let removeParent = false;
// while (NODE);
// removing the test of a while/switch, we can either just remove it entirely *or* turn the `test` into `true`
@@ -59,21 +30,16 @@ export var post = [
// stray labeled statement with no body
removeParent = removeParent || (self.key === "body" && parent.isLabeledStatement());
// var NODE;
// let NODE;
// remove an entire declaration if there are no declarators left
removeParent = removeParent || (self.listKey === "declarations" && parent.isVariableDeclaration() && parent.node.declarations.length === 0);
removeParent = removeParent || (self.listKey === "declarations" && parent.isVariableDeclaration() && parent.node.declarations.length === 1);
// NODE;
// remove the entire expression statement if there's no expression
removeParent = removeParent || (self.key === "expression" && parent.isExpressionStatement());
// if (NODE);
// remove the entire if since the consequent is never going to be hit, if there's an alternate then it's already been
// handled with the `pre` hook
removeParent = removeParent || (self.key === "test" && parent.isIfStatement());
if (removeParent) {
parent.dangerouslyRemove();
parent.remove();
return true;
}
},

View File

@@ -1,9 +1,12 @@
/* @flow */
import type NodePath from "../index";
import { react } from "babel-types";
import * as t from "babel-types";
export var ReferencedIdentifier = {
export let ReferencedIdentifier = {
types: ["Identifier", "JSXIdentifier"],
checkPath({ node, parent }, opts) {
checkPath({ node, parent }: NodePath, opts?: Object): boolean {
if (!t.isIdentifier(node, opts)) {
if (t.isJSXIdentifier(node, opts)) {
if (react.isCompatTag(node.name)) return false;
@@ -18,16 +21,23 @@ export var ReferencedIdentifier = {
}
};
export var BindingIdentifier = {
types: ["Identifier"],
export let ReferencedMemberExpression = {
types: ["MemberExpression"],
checkPath({ node, parent }) {
return t.isBinding(node, parent);
return t.isMemberExpression(node) && t.isReferenced(node, parent);
}
};
export var Statement = {
export let BindingIdentifier = {
types: ["Identifier"],
checkPath({ node, parent }: NodePath): boolean {
return t.isIdentifier(node) && t.isBinding(node, parent);
}
};
export let Statement = {
types: ["Statement"],
checkPath({ node, parent }) {
checkPath({ node, parent }: NodePath): boolean {
if (t.isStatement(node)) {
if (t.isVariableDeclaration(node)) {
if (t.isForXStatement(parent, { left: node })) return false;
@@ -41,9 +51,9 @@ export var Statement = {
}
};
export var Expression = {
export let Expression = {
types: ["Expression"],
checkPath(path) {
checkPath(path: NodePath): boolean {
if (path.isIdentifier()) {
return path.isReferencedIdentifier();
} else {
@@ -52,75 +62,53 @@ export var Expression = {
}
};
export var Scope = {
export let Scope = {
types: ["Scopable"],
checkPath(path) {
return t.isScope(path.node, path.parent);
}
};
export var Referenced = {
checkPath(path) {
export let Referenced = {
checkPath(path: NodePath): boolean {
return t.isReferenced(path.node, path.parent);
}
};
export var BlockScoped = {
checkPath(path) {
export let BlockScoped = {
checkPath(path: NodePath): boolean {
return t.isBlockScoped(path.node);
}
};
export var Var = {
export let Var = {
types: ["VariableDeclaration"],
checkPath(path) {
checkPath(path: NodePath): boolean {
return t.isVar(path.node);
}
};
export var DirectiveLiteral = {
types: ["Literal"],
checkPath(path) {
return path.parentPath.isDirective();
}
};
export var Directive = {
types: ["ExpressionStatement"],
checkPath({ inList, container, key }) {
// needs to be in a statement list
if (!inList) return false;
// get the last directive node in this list
var lastDirective = -1;
for (var i = 0; i < container.length; i++) {
var node = container[i];
if (t.isExpressionStatement(node) && t.isLiteral(node.expression)) {
lastDirective = i;
} else {
break;
}
}
return key <= lastDirective;
}
};
export var User = {
checkPath(path) {
export let User = {
checkPath(path: NodePath): boolean {
return path.node && !!path.node.loc;
}
};
export var Generated = {
checkPath(path) {
export let Generated = {
checkPath(path: NodePath): boolean {
return !path.isUser();
}
};
export var Flow = {
export let Pure = {
checkPath(path: NodePath, opts?): boolean {
return path.scope.isPure(path.node, opts);
}
};
export let Flow = {
types: ["Flow", "ImportDeclaration", "ExportDeclaration"],
checkPath({ node }) {
checkPath({ node }: NodePath): boolean {
if (t.isFlow(node)) {
return true;
} else if (t.isImportDeclaration(node)) {

View File

@@ -1,5 +1,8 @@
/* @flow */
// This file contains methods that modify the path/node in some ways.
import { PATH_CACHE_KEY } from "./constants";
import PathHoister from "./lib/hoister";
import NodePath from "./index";
import * as t from "babel-types";
@@ -36,17 +39,16 @@ export function insertBefore(nodes) {
export function _containerInsert(from, nodes) {
this.updateSiblingKeys(from, nodes.length);
var paths = [];
let paths = [];
for (var i = 0; i < nodes.length; i++) {
var to = from + i;
var node = nodes[i];
for (let i = 0; i < nodes.length; i++) {
let to = from + i;
let node = nodes[i];
this.container.splice(to, 0, node);
if (this.context) {
var path = this.context.create(this.parent, this.container, to, this.listKey);
let path = this.context.create(this.parent, this.container, to, this.listKey);
paths.push(path);
this.queueNode(path);
} else {
paths.push(NodePath.get({
parentPath: this,
@@ -58,6 +60,21 @@ export function _containerInsert(from, nodes) {
}
}
let contexts = this.contexts;
let path = this;
while (!contexts.length) {
path = path.parentPath;
contexts = path.contexts;
}
for (let path of paths) {
path.setScope();
for (let context of contexts) {
context.maybeQueue(path);
}
}
return paths;
}
@@ -70,8 +87,10 @@ export function _containerInsertAfter(nodes) {
}
export function _maybePopFromStatements(nodes) {
var last = nodes[nodes.length - 1];
if (t.isExpressionStatement(last) && t.isIdentifier(last.expression) && !this.isCompletionRecord()) {
let last = nodes[nodes.length - 1];
let isIdentifier = t.isIdentifier(last) || (t.isExpressionStatement(last) && t.isIdentifier(last.expression));
if (isIdentifier && !this.isCompletionRecord()) {
nodes.pop();
}
}
@@ -90,7 +109,7 @@ export function insertAfter(nodes) {
return this.parentPath.insertAfter(nodes);
} else if (this.isNodeType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
if (this.node) {
var temp = this.scope.generateDeclaredUidIdentifier();
let temp = this.scope.generateDeclaredUidIdentifier();
nodes.unshift(t.expressionStatement(t.assignmentExpression("=", temp, this.node)));
nodes.push(t.expressionStatement(temp));
}
@@ -117,8 +136,8 @@ export function insertAfter(nodes) {
export function updateSiblingKeys(fromIndex, incrementBy) {
if (!this.parent) return;
var paths = this.parent._paths;
for (var i = 0; i < paths.length; i++) {
let paths = this.parent[PATH_CACHE_KEY];
for (let i = 0; i < paths.length; i++) {
let path = paths[i];
if (path.key >= fromIndex) {
path.key += incrementBy;
@@ -127,12 +146,16 @@ export function updateSiblingKeys(fromIndex, incrementBy) {
}
export function _verifyNodeList(nodes) {
if (!nodes) {
return [];
}
if (nodes.constructor !== Array) {
nodes = [nodes];
}
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (!node) {
throw new Error(`Node list has falsy node with the index of ${i}`);
} else if (typeof node !== "object") {
@@ -154,12 +177,10 @@ export function unshiftContainer(listKey, nodes) {
// get the first path and insert our nodes before it, if it doesn't exist then it
// doesn't matter, our nodes will be inserted anyway
var container = this.node[listKey];
var path = NodePath.get({
let path = NodePath.get({
parentPath: this,
parent: this.node,
container: container,
container: this.node[listKey],
listKey,
key: 0
});
@@ -175,17 +196,16 @@ export function pushContainer(listKey, nodes) {
// get an invisible path that represents the last node + 1 and replace it with our
// nodes, effectively inlining it
var container = this.node[listKey];
var i = container.length;
var path = NodePath.get({
let container = this.node[listKey];
let path = NodePath.get({
parentPath: this,
parent: this.node,
container: container,
listKey,
key: i
key: container.length
});
return path.replaceWith(nodes, true);
return path.replaceWithMultiple(nodes);
}
/**
@@ -194,6 +214,6 @@ export function pushContainer(listKey, nodes) {
*/
export function hoist(scope = this.scope) {
var hoister = new PathHoister(this, scope);
let hoister = new PathHoister(this, scope);
return hoister.run();
}

View File

@@ -1,24 +1,15 @@
/* @flow */
// This file contains methods responsible for removing a node.
import * as removalHooks from "./lib/removal-hooks";
/**
* This is now safe.
*/
export var dangerouslyRemove = remove;
/**
* Dangerously remove the current node. This may sometimes result in a tainted
* invalid AST so use with caution.
*/
import { hooks } from "./lib/removal-hooks";
export function remove() {
this._assertUnremoved();
this.resync();
if (this._callRemovalHooks("pre")) {
if (this._callRemovalHooks()) {
this._markRemoved();
return;
}
@@ -26,12 +17,10 @@ export function remove() {
this.shareCommentsWithSiblings();
this._remove();
this._markRemoved();
this._callRemovalHooks("post");
}
export function _callRemovalHooks(position) {
for (var fn of (removalHooks[position]: Array)) {
export function _callRemovalHooks() {
for (let fn of (hooks: Array<Function>)) {
if (fn(this, this.parentPath)) return true;
}
}
@@ -53,6 +42,6 @@ export function _markRemoved() {
export function _assertUnremoved() {
if (this.removed) {
throw this.errorWithNode("NodePath has been removed so is read-only.");
throw this.buildCodeFrameError("NodePath has been removed so is read-only.");
}
}

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains methods responsible for replacing a node with another.
import codeFrame from "babel-code-frame";
@@ -6,23 +8,22 @@ import NodePath from "./index";
import { parse } from "babylon";
import * as t from "babel-types";
var hoistVariablesVisitor = {
Function() {
this.skip();
let hoistVariablesVisitor = {
Function(path) {
path.skip();
},
VariableDeclaration(node, parent, scope) {
if (node.kind !== "var") return;
VariableDeclaration(path) {
if (path.node.kind !== "var") return;
var bindings = this.getBindingIdentifiers();
for (var key in bindings) {
scope.push({ id: bindings[key] });
let bindings = path.getBindingIdentifiers();
for (let key in bindings) {
path.scope.push({ id: bindings[key] });
}
var exprs = [];
let exprs = [];
for (var declar of (node.declarations: Array)) {
for (let declar of (path.node.declarations: Array<Object>)) {
if (declar.init) {
exprs.push(t.expressionStatement(
t.assignmentExpression("=", declar.id, declar.init)
@@ -30,7 +31,7 @@ var hoistVariablesVisitor = {
}
}
return exprs;
path.replaceWithMultiple(exprs);
}
};
@@ -50,7 +51,12 @@ export function replaceWithMultiple(nodes: Array<Object>) {
t.inheritTrailingComments(nodes[nodes.length - 1], this.node);
this.node = this.container[this.key] = null;
this.insertAfter(nodes);
if (!this.node) this.dangerouslyRemove();
if (this.node) {
this.requeue();
} else {
this.remove();
}
}
/**
@@ -68,7 +74,7 @@ export function replaceWithSourceString(replacement) {
replacement = `(${replacement})`;
replacement = parse(replacement);
} catch (err) {
var loc = err.loc;
let loc = err.loc;
if (loc) {
err.message += " - make sure this is an expression.";
err.message += "\n" + codeFrame(replacement, loc.line, loc.column + 1);
@@ -85,7 +91,7 @@ export function replaceWithSourceString(replacement) {
* Replace the current node with another.
*/
export function replaceWith(replacement, whateverAllowed) {
export function replaceWith(replacement) {
this.resync();
if (this.removed) {
@@ -97,7 +103,7 @@ export function replaceWith(replacement, whateverAllowed) {
}
if (!replacement) {
throw new Error("You passed `path.replaceWith()` a falsy node, use `path.dangerouslyRemove()` instead");
throw new Error("You passed `path.replaceWith()` a falsy node, use `path.remove()` instead");
}
if (this.node === replacement) {
@@ -108,23 +114,12 @@ export function replaceWith(replacement, whateverAllowed) {
throw new Error("You can only replace a Program root node with another Program node");
}
// normalise inserting an entire AST
if (t.isProgram(replacement) && !this.isProgram()) {
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()`");
}
throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`");
}
if (typeof replacement === "string") {
// triggers an error
return this.replaceWithSourceString();
throw new Error("Don't use `path.replaceWith()` with a source string, use `path.replaceWithSourceString()`");
}
// replacing a statement with an expression so wrap it in an expression statement
@@ -149,6 +144,9 @@ export function replaceWith(replacement, whateverAllowed) {
// potentially create new scope
this.setScope();
// requeue for visiting
this.requeue();
}
/**
@@ -156,10 +154,14 @@ export function replaceWith(replacement, whateverAllowed) {
*/
export function _replaceWith(node) {
if (!this.container) {
throw new ReferenceError("Container is falsy");
}
if (this.inList) {
t.validate(this.key, this.parent, [node]);
t.validate(this.parent, this.key, [node]);
} else {
t.validate(this.key, this.parent, node);
t.validate(this.parent, this.key, node);
}
this.node = this.container[this.key] = node;
@@ -171,34 +173,50 @@ export function _replaceWith(node) {
* extremely important to retain original semantics.
*/
export function replaceExpressionWithStatements(nodes: Array) {
export function replaceExpressionWithStatements(nodes: Array<Object>) {
this.resync();
var toSequenceExpression = t.toSequenceExpression(nodes, this.scope);
let toSequenceExpression = t.toSequenceExpression(nodes, this.scope);
if (toSequenceExpression) {
return this.replaceWith(toSequenceExpression);
if (t.isSequenceExpression(toSequenceExpression)) {
let exprs = toSequenceExpression.expressions;
if (exprs.length >= 2 && this.parentPath.isExpressionStatement()) {
this._maybePopFromStatements(exprs);
}
// could be just one element due to the previous maybe popping
if (exprs.length === 1) {
this.replaceWith(exprs[0]);
} else {
this.replaceWith(toSequenceExpression);
}
} else if (toSequenceExpression) {
this.replaceWith(toSequenceExpression);
} else {
var container = t.functionExpression(null, [], t.blockStatement(nodes));
let 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 lastNode of (last: Array)) {
if (!lastNode.isExpressionStatement()) continue;
let completionRecords: Array<NodePath> = this.get("callee").getCompletionRecords();
for (let path of completionRecords) {
if (!path.isExpressionStatement()) continue;
var loop = lastNode.findParent((path) => path.isLoop());
let loop = path.findParent((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)
let callee = this.get("callee");
let uid = callee.scope.generateDeclaredUidIdentifier("ret");
callee.get("body").pushContainer("body", t.returnStatement(uid));
path.get("expression").replaceWith(
t.assignmentExpression("=", uid, path.node.expression)
);
} else {
lastNode.replaceWith(t.returnStatement(lastNode.node.expression));
path.replaceWith(t.returnStatement(path.node.expression));
}
}
@@ -206,14 +224,14 @@ export function replaceExpressionWithStatements(nodes: Array) {
}
}
export function replaceInline(nodes) {
export function replaceInline(nodes: Object | Array<Object>) {
this.resync();
if (Array.isArray(nodes)) {
if (Array.isArray(this.container)) {
nodes = this._verifyNodeList(nodes);
this._containerInsertAfter(nodes);
return this.dangerouslyRemove();
return this.remove();
} else {
return this.replaceWithMultiple(nodes);
}