6.0.0
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:
@@ -1,6 +1,10 @@
|
||||
/* @flow */
|
||||
|
||||
import NodePath from "./path";
|
||||
import * as t from "babel-types";
|
||||
|
||||
let testing = process.env.NODE_ENV === "test";
|
||||
|
||||
export default class TraversalContext {
|
||||
constructor(scope, opts, state, parentPath) {
|
||||
this.parentPath = parentPath;
|
||||
@@ -9,57 +13,101 @@ export default class TraversalContext {
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
queue = null;
|
||||
parentPath: NodePath;
|
||||
scope;
|
||||
state;
|
||||
opts;
|
||||
queue: ?Array<NodePath> = null;
|
||||
|
||||
shouldVisit(node) {
|
||||
var opts = this.opts;
|
||||
/**
|
||||
* This method does a simple check to determine whether or not we really need to attempt
|
||||
* visit a node. This will prevent us from constructing a NodePath.
|
||||
*/
|
||||
|
||||
shouldVisit(node): boolean {
|
||||
let opts = this.opts;
|
||||
if (opts.enter || opts.exit) return true;
|
||||
|
||||
// check if we have a visitor for this node
|
||||
if (opts[node.type]) return true;
|
||||
|
||||
var keys = t.VISITOR_KEYS[node.type];
|
||||
// check if we're going to traverse into this node
|
||||
let keys: ?Array<string> = t.VISITOR_KEYS[node.type];
|
||||
if (!keys || !keys.length) return false;
|
||||
|
||||
for (var key of (keys: Array)) {
|
||||
// we need to traverse into this node so ensure that it has children to traverse into!
|
||||
for (let key of keys) {
|
||||
if (node[key]) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
create(node, obj, key, listKey) {
|
||||
var path = NodePath.get({
|
||||
create(node, obj, key, listKey): NodePath {
|
||||
return NodePath.get({
|
||||
parentPath: this.parentPath,
|
||||
parent: node,
|
||||
container: obj,
|
||||
key: key,
|
||||
listKey
|
||||
});
|
||||
path.unshiftContext(this);
|
||||
return path;
|
||||
}
|
||||
|
||||
maybeQueue(path) {
|
||||
if (this.trap) {
|
||||
throw new Error("Infinite cycle detected");
|
||||
}
|
||||
|
||||
if (this.queue) {
|
||||
this.priorityQueue.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
visitMultiple(container, parent, listKey) {
|
||||
// nothing to traverse!
|
||||
if (container.length === 0) return false;
|
||||
|
||||
var visited = [];
|
||||
|
||||
var queue = this.queue = [];
|
||||
var stop = false;
|
||||
let queue = [];
|
||||
|
||||
// build up initial queue
|
||||
for (let key = 0; key < container.length; key++) {
|
||||
var self = container[key];
|
||||
if (self && this.shouldVisit(self)) {
|
||||
let node = container[key];
|
||||
if (node && this.shouldVisit(node)) {
|
||||
queue.push(this.create(parent, container, key, listKey));
|
||||
}
|
||||
}
|
||||
|
||||
// visit the queue
|
||||
for (let path of (queue: Array)) {
|
||||
path.resync();
|
||||
return this.visitQueue(queue);
|
||||
}
|
||||
|
||||
visitSingle(node, key): boolean {
|
||||
if (this.shouldVisit(node[key])) {
|
||||
return this.visitQueue([
|
||||
this.create(node, node, key)
|
||||
]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
visitQueue(queue: Array<NodePath>) {
|
||||
// set queue
|
||||
this.queue = queue;
|
||||
this.priorityQueue = [];
|
||||
|
||||
let visited = [];
|
||||
let stop = false;
|
||||
|
||||
// visit the queue
|
||||
for (let path of queue) {
|
||||
path.resync();
|
||||
path.pushContext(this);
|
||||
|
||||
if (testing && queue.length >= 1000) {
|
||||
this.trap = true;
|
||||
}
|
||||
|
||||
// ensure we don't visit the same node twice
|
||||
if (visited.indexOf(path.node) >= 0) continue;
|
||||
visited.push(path.node);
|
||||
|
||||
@@ -67,28 +115,29 @@ export default class TraversalContext {
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.priorityQueue.length) {
|
||||
stop = this.visitQueue(this.priorityQueue);
|
||||
this.priorityQueue = [];
|
||||
this.queue = queue;
|
||||
if (stop) break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let path of (queue: Array)) {
|
||||
path.shiftContext();
|
||||
// clear queue
|
||||
for (let path of queue) {
|
||||
path.popContext();
|
||||
}
|
||||
|
||||
// clear queue
|
||||
this.queue = null;
|
||||
|
||||
return stop;
|
||||
}
|
||||
|
||||
visitSingle(node, key) {
|
||||
if (this.shouldVisit(node[key])) {
|
||||
var path = this.create(node, node, key);
|
||||
path.visit();
|
||||
path.shiftContext();
|
||||
}
|
||||
}
|
||||
|
||||
visit(node, key) {
|
||||
var nodes = node[key];
|
||||
if (!nodes) return;
|
||||
let nodes = node[key];
|
||||
if (!nodes) return false;
|
||||
|
||||
if (Array.isArray(nodes)) {
|
||||
return this.visitMultiple(nodes, node, key);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* [Please add a description.]
|
||||
*/
|
||||
/* @flow */
|
||||
|
||||
export default class Hub {
|
||||
constructor(file, options) {
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
/* @flow */
|
||||
|
||||
import TraversalContext from "./context";
|
||||
import * as visitors from "./visitors";
|
||||
import * as messages from "babel-messages";
|
||||
import includes from "lodash/collection/includes";
|
||||
import * as t from "babel-types";
|
||||
|
||||
export default function traverse(parent: Object, opts?: Object, scope?: Object, state: Object, parentPath: Object) {
|
||||
export { default as NodePath } from "./path";
|
||||
export { default as Scope } from "./scope";
|
||||
export { default as Hub } from "./hub";
|
||||
export { visitors };
|
||||
|
||||
export default function traverse(
|
||||
parent: Object | Array<Object>,
|
||||
opts?: Object,
|
||||
scope?: Object,
|
||||
state: Object,
|
||||
parentPath: Object,
|
||||
) {
|
||||
if (!parent) return;
|
||||
if (!opts) opts = {};
|
||||
|
||||
@@ -16,14 +29,7 @@ export default function traverse(parent: Object, opts?: Object, scope?: Object,
|
||||
|
||||
visitors.explode(opts);
|
||||
|
||||
// array of nodes
|
||||
if (Array.isArray(parent)) {
|
||||
for (var i = 0; i < parent.length; i++) {
|
||||
traverse.node(parent[i], opts, scope, state, parentPath);
|
||||
}
|
||||
} else {
|
||||
traverse.node(parent, opts, scope, state, parentPath);
|
||||
}
|
||||
traverse.node(parent, opts, scope, state, parentPath);
|
||||
}
|
||||
|
||||
traverse.visitors = visitors;
|
||||
@@ -35,22 +41,32 @@ traverse.Scope = require("./scope");
|
||||
traverse.Hub = require("./hub");
|
||||
|
||||
traverse.cheap = function (node, enter) {
|
||||
var keys = t.VISITOR_KEYS[node.type];
|
||||
if (!node) return;
|
||||
|
||||
let keys = t.VISITOR_KEYS[node.type];
|
||||
if (!keys) return;
|
||||
|
||||
enter(node);
|
||||
|
||||
for (var key of keys) {
|
||||
traverse.cheap(node[key], enter);
|
||||
for (let key of keys) {
|
||||
let subNode = node[key];
|
||||
|
||||
if (Array.isArray(subNode)) {
|
||||
for (let node of subNode) {
|
||||
traverse.cheap(node, enter);
|
||||
}
|
||||
} else {
|
||||
traverse.cheap(subNode, enter);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
traverse.node = function (node: Object, opts: Object, scope: Object, state: Object, parentPath: Object, skipKeys?) {
|
||||
var keys: Array = t.VISITOR_KEYS[node.type];
|
||||
let keys: Array = t.VISITOR_KEYS[node.type];
|
||||
if (!keys) return;
|
||||
|
||||
var context = new TraversalContext(scope, opts, state, parentPath);
|
||||
for (var key of keys) {
|
||||
let context = new TraversalContext(scope, opts, state, parentPath);
|
||||
for (let key of keys) {
|
||||
if (skipKeys && skipKeys[key]) continue;
|
||||
if (context.visit(node, key)) return;
|
||||
}
|
||||
@@ -64,7 +80,7 @@ const CLEAR_KEYS: Array = t.COMMENT_KEYS.concat([
|
||||
]);
|
||||
|
||||
traverse.clearNode = function (node) {
|
||||
for (var key of CLEAR_KEYS) {
|
||||
for (let key of CLEAR_KEYS) {
|
||||
if (node[key] != null) node[key] = undefined;
|
||||
}
|
||||
};
|
||||
@@ -88,7 +104,7 @@ traverse.hasType = function (tree: Object, scope: Object, type: Object, blacklis
|
||||
// the type we're looking for is the same as the passed node
|
||||
if (tree.type === type) return true;
|
||||
|
||||
var state = {
|
||||
let state = {
|
||||
has: false,
|
||||
type: type
|
||||
};
|
||||
|
||||
@@ -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:
|
||||
//
|
||||
|
||||
@@ -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);
|
||||
|
||||
1
packages/babel-traverse/src/path/constants.js
Normal file
1
packages/babel-traverse/src/path/constants.js
Normal file
@@ -0,0 +1 @@
|
||||
export const PATH_CACHE_KEY = "_paths"; //Symbol();
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/* @flow */
|
||||
|
||||
import type NodePath from "../path";
|
||||
|
||||
/**
|
||||
* This class is responsible for a binding inside of a scope.
|
||||
*
|
||||
@@ -11,21 +15,17 @@
|
||||
|
||||
export default class Binding {
|
||||
constructor({ existing, identifier, scope, path, kind }) {
|
||||
this.constantViolations = [];
|
||||
this.constant = true;
|
||||
|
||||
this.identifier = identifier;
|
||||
this.scope = scope;
|
||||
this.path = path;
|
||||
this.kind = kind;
|
||||
|
||||
this.constantViolations = [];
|
||||
this.constant = true;
|
||||
|
||||
this.referencePaths = [];
|
||||
this.references = 0;
|
||||
this.referenced = false;
|
||||
|
||||
this.scope = scope;
|
||||
this.path = path;
|
||||
this.kind = kind;
|
||||
|
||||
this.hasValue = false;
|
||||
this.hasDeoptedValue = false;
|
||||
this.value = null;
|
||||
this.references = 0;
|
||||
|
||||
this.clearValue();
|
||||
|
||||
@@ -38,6 +38,18 @@ export default class Binding {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
constantViolations: Array<NodePath>;
|
||||
constant: boolean;
|
||||
|
||||
referencePaths: Array<NodePath>;
|
||||
referenced: boolean;
|
||||
references: number;
|
||||
|
||||
hasDeoptedValue: boolean;
|
||||
hasValue: boolean;
|
||||
value: any;
|
||||
|
||||
deoptValue() {
|
||||
this.clearValue();
|
||||
this.hasDeoptedValue = true;
|
||||
@@ -68,7 +80,7 @@ export default class Binding {
|
||||
* Increment the amount of references to this binding.
|
||||
*/
|
||||
|
||||
reference(path) {
|
||||
reference(path: NodePath) {
|
||||
this.referenced = true;
|
||||
this.references++;
|
||||
this.referencePaths.push(path)
|
||||
|
||||
@@ -1,138 +1,113 @@
|
||||
/* @flow */
|
||||
|
||||
import includes from "lodash/collection/includes";
|
||||
import repeating from "repeating";
|
||||
import Renamer from "./lib/renamer";
|
||||
import type NodePath from "../path";
|
||||
import traverse from "../index";
|
||||
import defaults from "lodash/object/defaults";
|
||||
import * as messages from "babel-messages";
|
||||
import Binding from "./binding";
|
||||
import globals from "globals";
|
||||
import flatten from "lodash/array/flatten";
|
||||
import extend from "lodash/object/extend";
|
||||
import * as t from "babel-types";
|
||||
|
||||
var collectorVisitor = {
|
||||
const SCOPE_INFO_CACHE_KEY = "_scopeInfo"; //Symbol();
|
||||
|
||||
For(node, parent, scope) {
|
||||
for (var key of (t.FOR_INIT_KEYS: Array)) {
|
||||
var declar = this.get(key);
|
||||
if (declar.isVar()) scope.getFunctionParent().registerBinding("var", declar);
|
||||
let collectorVisitor = {
|
||||
For(path) {
|
||||
for (let key of (t.FOR_INIT_KEYS: Array)) {
|
||||
let declar = path.get(key);
|
||||
if (declar.isVar()) path.scope.getFunctionParent().registerBinding("var", declar);
|
||||
}
|
||||
},
|
||||
|
||||
Declaration(node, parent, scope) {
|
||||
Declaration(path) {
|
||||
// delegate block scope handling to the `blockVariableVisitor`
|
||||
if (this.isBlockScoped()) return;
|
||||
if (path.isBlockScoped()) return;
|
||||
|
||||
// this will be hit again once we traverse into it after this iteration
|
||||
if (this.isExportDeclaration() && this.get("declaration").isDeclaration()) return;
|
||||
if (path.isExportDeclaration() && path.get("declaration").isDeclaration()) return;
|
||||
|
||||
// we've ran into a declaration!
|
||||
scope.getFunctionParent().registerDeclaration(this);
|
||||
path.scope.getFunctionParent().registerDeclaration(path);
|
||||
},
|
||||
|
||||
ReferencedIdentifier(node) {
|
||||
var binding = this.scope.getBinding(node.name);
|
||||
if (binding) {
|
||||
binding.reference(this);
|
||||
} else {
|
||||
this.scope.getProgramParent().addGlobal(node);
|
||||
}
|
||||
ReferencedIdentifier(path, state) {
|
||||
state.references.push(path);
|
||||
},
|
||||
|
||||
ForXStatement() {
|
||||
var left = this.get("left");
|
||||
ForXStatement(path, state) {
|
||||
let left = path.get("left");
|
||||
if (left.isPattern() || left.isIdentifier()) {
|
||||
this.scope.registerConstantViolation(left, left);
|
||||
state.constantViolations.push(left);
|
||||
}
|
||||
},
|
||||
|
||||
ExportDeclaration: {
|
||||
exit(node) {
|
||||
var declar = node.declaration;
|
||||
exit({ node, scope }) {
|
||||
let declar = node.declaration;
|
||||
if (t.isClassDeclaration(declar) || t.isFunctionDeclaration(declar)) {
|
||||
this.scope.getBinding(declar.id.name).reference();
|
||||
let id = declar.id;
|
||||
if (!id) return;
|
||||
|
||||
let binding = scope.getBinding(id.name);
|
||||
if (binding) binding.reference();
|
||||
} else if (t.isVariableDeclaration(declar)) {
|
||||
for (var decl of (declar.declarations: Array)) {
|
||||
var ids = t.getBindingIdentifiers(decl);
|
||||
for (var name in ids) {
|
||||
this.scope.getBinding(name).reference();
|
||||
for (let decl of (declar.declarations: Array<Object>)) {
|
||||
let ids = t.getBindingIdentifiers(decl);
|
||||
for (let name in ids) {
|
||||
let binding = scope.getBinding(name);
|
||||
if (binding) binding.reference();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
LabeledStatement(node) {
|
||||
this.scope.getProgramParent().addGlobal(node);
|
||||
this.scope.getBlockParent().registerDeclaration(this);
|
||||
LabeledStatement(path) {
|
||||
path.scope.getProgramParent().addGlobal(path.node);
|
||||
path.scope.getBlockParent().registerDeclaration(path);
|
||||
},
|
||||
|
||||
AssignmentExpression() {
|
||||
// register undeclared bindings as globals
|
||||
var ids = this.getBindingIdentifiers();
|
||||
var programParent;
|
||||
for (var name in ids) {
|
||||
if (this.scope.getBinding(name)) continue;
|
||||
AssignmentExpression(path, state) {
|
||||
state.assignments.push(path);
|
||||
},
|
||||
|
||||
programParent = programParent || this.scope.getProgramParent();
|
||||
programParent.addGlobal(ids[name]);
|
||||
UpdateExpression(path, state) {
|
||||
state.constantViolations.push(path.get("argument"));
|
||||
},
|
||||
|
||||
UnaryExpression(path, state) {
|
||||
if (path.node.operator === "delete") {
|
||||
state.constantViolations.push(path.get("argument"));
|
||||
}
|
||||
|
||||
// register as constant violation
|
||||
this.scope.registerConstantViolation(this, this.get("left"), this.get("right"));
|
||||
},
|
||||
|
||||
UpdateExpression(node, parent, scope) {
|
||||
scope.registerConstantViolation(this, this.get("argument"), null);
|
||||
BlockScoped(path) {
|
||||
let scope = path.scope;
|
||||
if (scope.path === path) scope = scope.parent;
|
||||
scope.getBlockParent().registerDeclaration(path);
|
||||
},
|
||||
|
||||
UnaryExpression(node, parent, scope) {
|
||||
if (node.operator === "delete") scope.registerConstantViolation(this, this.get("left"), null);
|
||||
ClassDeclaration(path) {
|
||||
let id = path.node.id;
|
||||
if (!id) return;
|
||||
|
||||
let name = id.name;
|
||||
path.scope.bindings[name] = path.scope.getBinding(name);
|
||||
},
|
||||
|
||||
BlockScoped(node, parent, scope) {
|
||||
if (scope.path === this) scope = scope.parent;
|
||||
scope.getBlockParent().registerDeclaration(this);
|
||||
},
|
||||
|
||||
ClassDeclaration(node, parent, scope) {
|
||||
var name = node.id.name;
|
||||
scope.bindings[name] = scope.getBinding(name);
|
||||
},
|
||||
|
||||
Block(node, parent, scope) {
|
||||
var paths = this.get("body");
|
||||
for (var path of (paths: Array)) {
|
||||
if (path.isFunctionDeclaration()) {
|
||||
scope.getBlockParent().registerDeclaration(path);
|
||||
Block(path) {
|
||||
let paths = path.get("body");
|
||||
for (let bodyPath of (paths: Array)) {
|
||||
if (bodyPath.isFunctionDeclaration()) {
|
||||
path.scope.getBlockParent().registerDeclaration(bodyPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var renameVisitor = {
|
||||
|
||||
ReferencedIdentifier(node, parent, scope, state) {
|
||||
if (node.name === state.oldName) {
|
||||
node.name = state.newName;
|
||||
}
|
||||
},
|
||||
|
||||
Scope(node, parent, scope, state) {
|
||||
if (!scope.bindingIdentifierEquals(state.oldName, state.binding)) {
|
||||
this.skip();
|
||||
}
|
||||
},
|
||||
|
||||
"AssignmentExpression|Declaration"(node, parent, scope, state) {
|
||||
var ids = this.getBindingIdentifiers();
|
||||
|
||||
for (var name in ids) {
|
||||
if (name === state.oldName) ids[name].name = state.newName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default class Scope {
|
||||
|
||||
/**
|
||||
@@ -145,7 +120,7 @@ export default class Scope {
|
||||
return parent;
|
||||
}
|
||||
|
||||
var cached = path.getData("scope");
|
||||
let cached = path.getData("scope");
|
||||
if (cached && cached.parent === parent && cached.block === path.node) {
|
||||
return cached;
|
||||
} else {
|
||||
@@ -164,7 +139,7 @@ export default class Scope {
|
||||
* Globals.
|
||||
*/
|
||||
|
||||
static globals = flatten([globals.builtin, globals.browser, globals.node].map(Object.keys));
|
||||
static globals = Object.keys(globals.builtin);
|
||||
|
||||
/**
|
||||
* Variables available in current context.
|
||||
@@ -190,7 +165,7 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
generateDeclaredUidIdentifier(name: string = "temp") {
|
||||
var id = this.generateUidIdentifier(name);
|
||||
let id = this.generateUidIdentifier(name);
|
||||
this.push({ id });
|
||||
return id;
|
||||
}
|
||||
@@ -208,17 +183,16 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
generateUid(name: string) {
|
||||
name = t.toIdentifier(name).replace(/^_+/, "");
|
||||
name = t.toIdentifier(name).replace(/^_+/, "").replace(/[0-9]+$/g, "");
|
||||
|
||||
var uid;
|
||||
var i = 0;
|
||||
let uid;
|
||||
let i = 0;
|
||||
do {
|
||||
uid = this._generateUid(name, i);
|
||||
i++;
|
||||
} while (this.hasBinding(uid) || this.hasGlobal(uid) || this.hasReference(uid));
|
||||
|
||||
|
||||
var program = this.getProgramParent();
|
||||
let program = this.getProgramParent();
|
||||
program.references[uid] = true;
|
||||
program.uids[uid] = true;
|
||||
|
||||
@@ -230,7 +204,7 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
_generateUid(name, i) {
|
||||
var id = name;
|
||||
let id = name;
|
||||
if (i > 1) id += i;
|
||||
return `_${id}`;
|
||||
}
|
||||
@@ -240,24 +214,24 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
generateUidIdentifierBasedOnNode(parent: Object, defaultName?: String): Object {
|
||||
var node = parent;
|
||||
let node = parent;
|
||||
|
||||
if (t.isAssignmentExpression(parent)) {
|
||||
node = parent.left;
|
||||
} else if (t.isVariableDeclarator(parent)) {
|
||||
node = parent.id;
|
||||
} else if (t.isProperty(node)) {
|
||||
} else if (t.isObjectProperty(node) || t.isObjectMethod(node)) {
|
||||
node = node.key;
|
||||
}
|
||||
|
||||
var parts = [];
|
||||
let parts = [];
|
||||
|
||||
var add = function (node) {
|
||||
let add = function (node) {
|
||||
if (t.isModuleDeclaration(node)) {
|
||||
if (node.source) {
|
||||
add(node.source);
|
||||
} else if (node.specifiers && node.specifiers.length) {
|
||||
for (var specifier of (node.specifiers: Array)) {
|
||||
for (let specifier of (node.specifiers: Array)) {
|
||||
add(specifier);
|
||||
}
|
||||
} else if (node.declaration) {
|
||||
@@ -275,7 +249,7 @@ export default class Scope {
|
||||
} else if (t.isCallExpression(node)) {
|
||||
add(node.callee);
|
||||
} else if (t.isObjectExpression(node) || t.isObjectPattern(node)) {
|
||||
for (var prop of (node.properties: Array)) {
|
||||
for (let prop of (node.properties: Array)) {
|
||||
add(prop.key || prop.argument);
|
||||
}
|
||||
}
|
||||
@@ -283,7 +257,7 @@ export default class Scope {
|
||||
|
||||
add(node);
|
||||
|
||||
var id = parts.join("$");
|
||||
let id = parts.join("$");
|
||||
id = id.replace(/^_/, "") || defaultName || "ref";
|
||||
|
||||
return this.generateUidIdentifier(id.slice(0, 20));
|
||||
@@ -305,7 +279,7 @@ export default class Scope {
|
||||
}
|
||||
|
||||
if (t.isIdentifier(node)) {
|
||||
var binding = this.getBinding(node.name);
|
||||
let binding = this.getBinding(node.name);
|
||||
if (binding) {
|
||||
return binding.constant;
|
||||
} else {
|
||||
@@ -324,7 +298,7 @@ export default class Scope {
|
||||
if (this.isStatic(node)) {
|
||||
return null;
|
||||
} else {
|
||||
var id = this.generateUidIdentifierBasedOnNode(node);
|
||||
let id = this.generateUidIdentifierBasedOnNode(node);
|
||||
if (!dontPush) this.push({ id });
|
||||
return id;
|
||||
}
|
||||
@@ -337,7 +311,7 @@ export default class Scope {
|
||||
// ignore hoisted functions if there's also a local let
|
||||
if (kind === "hoisted" && local.kind === "let") return;
|
||||
|
||||
var duplicate = false;
|
||||
let duplicate = false;
|
||||
|
||||
// don't allow duplicate bindings to exist alongside
|
||||
if (!duplicate) duplicate = kind === "let" || local.kind === "let" || local.kind === "const" || local.kind === "module";
|
||||
@@ -346,7 +320,7 @@ export default class Scope {
|
||||
if (!duplicate) duplicate = local.kind === "param" && (kind === "let" || kind === "const");
|
||||
|
||||
if (duplicate) {
|
||||
throw this.hub.file.errorWithNode(id, messages.get("scopeDuplicateDeclaration", name), TypeError);
|
||||
throw this.hub.file.buildCodeFrameError(id, messages.get("scopeDuplicateDeclaration", name), TypeError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,16 +340,17 @@ export default class Scope {
|
||||
}
|
||||
|
||||
dump() {
|
||||
var sep = repeating("-", 60);
|
||||
let sep = repeating("-", 60);
|
||||
console.log(sep);
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
console.log("#", scope.block.type);
|
||||
for (var name in scope.bindings) {
|
||||
var binding = scope.bindings[name];
|
||||
for (let name in scope.bindings) {
|
||||
let binding = scope.bindings[name];
|
||||
console.log(" -", name, {
|
||||
constant: binding.constant,
|
||||
references: binding.references,
|
||||
violations: binding.constantViolations.length,
|
||||
kind: binding.kind
|
||||
});
|
||||
}
|
||||
@@ -384,10 +359,10 @@ export default class Scope {
|
||||
}
|
||||
|
||||
toArray(node: Object, i?: number) {
|
||||
var file = this.hub.file;
|
||||
let file = this.hub.file;
|
||||
|
||||
if (t.isIdentifier(node)) {
|
||||
var binding = this.getBinding(node.name);
|
||||
let binding = this.getBinding(node.name);
|
||||
if (binding && binding.constant && binding.path.isGenericType("Array")) return node;
|
||||
}
|
||||
|
||||
@@ -396,17 +371,29 @@ export default class Scope {
|
||||
}
|
||||
|
||||
if (t.isIdentifier(node, { name: "arguments" })) {
|
||||
return t.callExpression(t.memberExpression(file.addHelper("slice"), t.identifier("call")), [node]);
|
||||
return t.callExpression(
|
||||
t.memberExpression(
|
||||
t.memberExpression(
|
||||
t.memberExpression(
|
||||
t.identifier("Array"),
|
||||
t.identifier("prototype")
|
||||
),
|
||||
t.identifier("slice")
|
||||
),
|
||||
t.identifier("call")
|
||||
),
|
||||
[node]
|
||||
);
|
||||
}
|
||||
|
||||
var helperName = "to-array";
|
||||
var args = [node];
|
||||
let helperName = "toArray";
|
||||
let args = [node];
|
||||
if (i === true) {
|
||||
helperName = "to-consumable-array";
|
||||
helperName = "toConsumableArray";
|
||||
} else if (i) {
|
||||
args.push(t.literal(i));
|
||||
helperName = "sliced-to-array";
|
||||
if (this.hub.file.isLoose("es6.forOf")) helperName += "-loose";
|
||||
args.push(t.numberLiteral(i));
|
||||
helperName = "slicedToArray";
|
||||
// TODO if (this.hub.file.isLoose("es6.forOf")) helperName += "-loose";
|
||||
}
|
||||
return t.callExpression(file.addHelper(helperName), args);
|
||||
}
|
||||
@@ -415,17 +402,17 @@ export default class Scope {
|
||||
if (path.isLabeledStatement()) {
|
||||
this.registerBinding("label", path);
|
||||
} else if (path.isFunctionDeclaration()) {
|
||||
this.registerBinding("hoisted", path);
|
||||
this.registerBinding("hoisted", path.get("id"));
|
||||
} else if (path.isVariableDeclaration()) {
|
||||
var declarations = path.get("declarations");
|
||||
let declarations = path.get("declarations");
|
||||
for (let declar of (declarations: Array)) {
|
||||
this.registerBinding(path.node.kind, declar);
|
||||
}
|
||||
} else if (path.isClassDeclaration()) {
|
||||
this.registerBinding("let", path);
|
||||
} else if (path.isImportDeclaration()) {
|
||||
var specifiers = path.get("specifiers");
|
||||
for (var specifier of (specifiers: Array)) {
|
||||
let specifiers = path.get("specifiers");
|
||||
for (let specifier of (specifiers: Array)) {
|
||||
this.registerBinding("module", specifier);
|
||||
}
|
||||
} else if (path.isExportDeclaration()) {
|
||||
@@ -446,11 +433,11 @@ export default class Scope {
|
||||
}
|
||||
}
|
||||
|
||||
registerConstantViolation(root: NodePath, left: NodePath, right: NodePath) {
|
||||
var ids = left.getBindingIdentifiers();
|
||||
for (var name in ids) {
|
||||
var binding = this.getBinding(name);
|
||||
if (binding) binding.reassign(root, left, right);
|
||||
registerConstantViolation(path: NodePath) {
|
||||
let ids = path.getBindingIdentifiers();
|
||||
for (let name in ids) {
|
||||
let binding = this.getBinding(name);
|
||||
if (binding) binding.reassign(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,19 +445,19 @@ export default class Scope {
|
||||
if (!kind) throw new ReferenceError("no `kind`");
|
||||
|
||||
if (path.isVariableDeclaration()) {
|
||||
var declarators = path.get("declarations");
|
||||
for (var declar of (declarators: Array)) {
|
||||
let declarators: Array<NodePath> = path.get("declarations");
|
||||
for (let declar of declarators) {
|
||||
this.registerBinding(kind, declar);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = this.getProgramParent();
|
||||
var ids = path.getBindingIdentifiers(true);
|
||||
let parent = this.getProgramParent();
|
||||
let ids = path.getBindingIdentifiers(true);
|
||||
|
||||
for (var name in ids) {
|
||||
for (var id of (ids[name]: Array)) {
|
||||
var local = this.getOwnBinding(name);
|
||||
for (let name in ids) {
|
||||
for (let id of (ids[name]: Array<Object>)) {
|
||||
let local = this.getOwnBinding(name);
|
||||
if (local) {
|
||||
// same identifier so continue safely as we're likely trying to register it
|
||||
// multiple times
|
||||
@@ -497,7 +484,7 @@ export default class Scope {
|
||||
}
|
||||
|
||||
hasUid(name): boolean {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
|
||||
do {
|
||||
if (scope.uids[name]) return true;
|
||||
@@ -507,7 +494,7 @@ export default class Scope {
|
||||
}
|
||||
|
||||
hasGlobal(name: string): boolean {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
|
||||
do {
|
||||
if (scope.globals[name]) return true;
|
||||
@@ -517,7 +504,7 @@ export default class Scope {
|
||||
}
|
||||
|
||||
hasReference(name: string): boolean {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
|
||||
do {
|
||||
if (scope.references[name]) return true;
|
||||
@@ -528,29 +515,39 @@ export default class Scope {
|
||||
|
||||
isPure(node, constantsOnly?: boolean) {
|
||||
if (t.isIdentifier(node)) {
|
||||
var binding = this.getBinding(node.name);
|
||||
let binding = this.getBinding(node.name);
|
||||
if (!binding) return false;
|
||||
if (constantsOnly) return binding.constant;
|
||||
return true;
|
||||
} else if (t.isClass(node)) {
|
||||
return !node.superClass || this.isPure(node.superClass, constantsOnly);
|
||||
if (node.superClass && !this.isPure(node.superClass, constantsOnly)) return false;
|
||||
return this.isPure(node.body, constantsOnly);
|
||||
} else if (t.isClassBody(node)) {
|
||||
for (let method of node.body) {
|
||||
if (!this.isPure(method, constantsOnly)) return false;
|
||||
}
|
||||
return true;
|
||||
} else if (t.isBinary(node)) {
|
||||
return this.isPure(node.left, constantsOnly) && this.isPure(node.right, constantsOnly);
|
||||
} else if (t.isArrayExpression(node)) {
|
||||
for (var elem of (node.elements: Array)) {
|
||||
for (let elem of (node.elements: Array<Object>)) {
|
||||
if (!this.isPure(elem, constantsOnly)) return false;
|
||||
}
|
||||
return true;
|
||||
} else if (t.isObjectExpression(node)) {
|
||||
for (var prop of (node.properties: Array)) {
|
||||
for (let prop of (node.properties: Array<Object>)) {
|
||||
if (!this.isPure(prop, constantsOnly)) return false;
|
||||
}
|
||||
return true;
|
||||
} else if (t.isProperty(node)) {
|
||||
} else if (t.isClassMethod(node)) {
|
||||
if (node.computed && !this.isPure(node.key, constantsOnly)) return false;
|
||||
if (node.kind === "get" || node.kind === "set") return false;
|
||||
return true;
|
||||
} else if (t.isClassProperty(node)) {
|
||||
if (node.computed && !this.isPure(node.key, constantsOnly)) return false;
|
||||
return this.isPure(node.value, constantsOnly);
|
||||
} else {
|
||||
return t.isPure(node);
|
||||
return t.isPureish(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,9 +564,9 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
getData(key) {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
var data = scope.data[key];
|
||||
let data = scope.data[key];
|
||||
if (data != null) return data;
|
||||
} while(scope = scope.parent);
|
||||
}
|
||||
@@ -580,9 +577,9 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
removeData(key) {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
var data = scope.data[key];
|
||||
let data = scope.data[key];
|
||||
if (data != null) scope.data[key] = null;
|
||||
} while(scope = scope.parent);
|
||||
}
|
||||
@@ -592,14 +589,14 @@ export default class Scope {
|
||||
}
|
||||
|
||||
crawl() {
|
||||
var path = this.path;
|
||||
let path = this.path;
|
||||
|
||||
//
|
||||
|
||||
var info = this.block._scopeInfo;
|
||||
let info = this.block[SCOPE_INFO_CACHE_KEY];
|
||||
if (info) return extend(this, info);
|
||||
|
||||
info = this.block._scopeInfo = {
|
||||
info = this.block[SCOPE_INFO_CACHE_KEY] = {
|
||||
references: Object.create(null),
|
||||
bindings: Object.create(null),
|
||||
globals: Object.create(null),
|
||||
@@ -607,13 +604,13 @@ export default class Scope {
|
||||
data: Object.create(null),
|
||||
};
|
||||
|
||||
extend(this, info);
|
||||
Object.assign(this, info);
|
||||
|
||||
// ForStatement - left, init
|
||||
|
||||
if (path.isLoop()) {
|
||||
for (let key of (t.FOR_INIT_KEYS: Array)) {
|
||||
var node = path.get(key);
|
||||
for (let key of (t.FOR_INIT_KEYS: Array<string>)) {
|
||||
let node = path.get(key);
|
||||
if (node.isBlockScoped()) this.registerBinding(node.node.kind, node);
|
||||
}
|
||||
}
|
||||
@@ -621,22 +618,20 @@ export default class Scope {
|
||||
// FunctionExpression - id
|
||||
|
||||
if (path.isFunctionExpression() && path.has("id")) {
|
||||
if (!t.isProperty(path.parent, { method: true })) {
|
||||
this.registerBinding("var", path);
|
||||
}
|
||||
this.registerBinding("local", path);
|
||||
}
|
||||
|
||||
// Class
|
||||
|
||||
if (path.isClassExpression() && path.has("id")) {
|
||||
this.registerBinding("var", path);
|
||||
this.registerBinding("local", path);
|
||||
}
|
||||
|
||||
// Function - params, rest
|
||||
|
||||
if (path.isFunction()) {
|
||||
var params = path.get("params");
|
||||
for (let param of (params: Array)) {
|
||||
let params: Array<NodePath> = path.get("params");
|
||||
for (let param of params) {
|
||||
this.registerBinding("param", param);
|
||||
}
|
||||
}
|
||||
@@ -647,24 +642,61 @@ export default class Scope {
|
||||
this.registerBinding("let", path);
|
||||
}
|
||||
|
||||
// ComprehensionExpression - blocks
|
||||
|
||||
if (path.isComprehensionExpression()) {
|
||||
this.registerBinding("let", path);
|
||||
}
|
||||
|
||||
// Program
|
||||
|
||||
var parent = this.getProgramParent();
|
||||
let parent = this.getProgramParent();
|
||||
if (parent.crawling) return;
|
||||
|
||||
let state = {
|
||||
references: [],
|
||||
constantViolations: [],
|
||||
assignments: [],
|
||||
};
|
||||
|
||||
this.crawling = true;
|
||||
path.traverse(collectorVisitor);
|
||||
path.traverse(collectorVisitor, state);
|
||||
this.crawling = false;
|
||||
|
||||
// register assignments
|
||||
for (let path of state.assignments) {
|
||||
// register undeclared bindings as globals
|
||||
let ids = path.getBindingIdentifiers();
|
||||
let programParent;
|
||||
for (let name in ids) {
|
||||
if (path.scope.getBinding(name)) continue;
|
||||
|
||||
programParent = programParent || path.scope.getProgramParent();
|
||||
programParent.addGlobal(ids[name]);
|
||||
}
|
||||
|
||||
// register as constant violation
|
||||
path.scope.registerConstantViolation(path);
|
||||
}
|
||||
|
||||
// register references
|
||||
for (let ref of state.references) {
|
||||
let binding = ref.scope.getBinding(ref.node.name);
|
||||
if (binding) {
|
||||
binding.reference(ref);
|
||||
} else {
|
||||
ref.scope.getProgramParent().addGlobal(ref.node);
|
||||
}
|
||||
}
|
||||
|
||||
// register constant violations
|
||||
for (let path of state.constantViolations) {
|
||||
path.scope.registerConstantViolation(path);
|
||||
}
|
||||
}
|
||||
|
||||
push(opts: Object) {
|
||||
var path = this.path;
|
||||
push(opts: {
|
||||
id: Object;
|
||||
init: ?Object;
|
||||
unique: ?boolean;
|
||||
_blockHoist: ?number;
|
||||
kind: "var" | "let";
|
||||
}) {
|
||||
let path = this.path;
|
||||
|
||||
if (path.isSwitchStatement()) {
|
||||
path = this.getFunctionParent().path;
|
||||
@@ -679,25 +711,23 @@ export default class Scope {
|
||||
path = this.getBlockParent().path;
|
||||
}
|
||||
|
||||
var unique = opts.unique;
|
||||
var kind = opts.kind || "var";
|
||||
var blockHoist = opts._blockHoist == null ? 2 : opts._blockHoist;
|
||||
let unique = opts.unique;
|
||||
let kind = opts.kind || "var";
|
||||
let blockHoist = opts._blockHoist == null ? 2 : opts._blockHoist;
|
||||
|
||||
var dataKey = `declaration:${kind}:${blockHoist}`;
|
||||
var declarPath = !unique && path.getData(dataKey);
|
||||
let dataKey = `declaration:${kind}:${blockHoist}`;
|
||||
let declarPath = !unique && path.getData(dataKey);
|
||||
|
||||
if (!declarPath) {
|
||||
var declar = t.variableDeclaration(kind, []);
|
||||
let declar = t.variableDeclaration(kind, []);
|
||||
declar._generated = true;
|
||||
declar._blockHoist = blockHoist;
|
||||
|
||||
this.hub.file.attachAuxiliaryComment(declar);
|
||||
|
||||
[declarPath] = path.unshiftContainer("body", [declar]);
|
||||
if (!unique) path.setData(dataKey, declarPath);
|
||||
}
|
||||
|
||||
var declarator = t.variableDeclarator(opts.id, opts.init);
|
||||
let declarator = t.variableDeclarator(opts.id, opts.init);
|
||||
declarPath.node.declarations.push(declarator);
|
||||
this.registerBinding(kind, declarPath.get("declarations").pop());
|
||||
}
|
||||
@@ -707,7 +737,7 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
getProgramParent() {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
if (scope.path.isProgram()) {
|
||||
return scope;
|
||||
@@ -722,7 +752,7 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
getFunctionParent() {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
if (scope.path.isFunctionParent()) {
|
||||
return scope;
|
||||
@@ -737,7 +767,7 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
getBlockParent() {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
if (scope.path.isBlockParent()) {
|
||||
return scope;
|
||||
@@ -751,9 +781,9 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
getAllBindings(): Object {
|
||||
var ids = Object.create(null);
|
||||
let ids = Object.create(null);
|
||||
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
defaults(ids, scope.bindings);
|
||||
scope = scope.parent;
|
||||
@@ -767,13 +797,13 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
getAllBindingsOfKind(): Object {
|
||||
var ids = Object.create(null);
|
||||
let ids = Object.create(null);
|
||||
|
||||
for (let kind of (arguments: Array)) {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
for (var name in scope.bindings) {
|
||||
var binding = scope.bindings[name];
|
||||
for (let name in scope.bindings) {
|
||||
let binding = scope.bindings[name];
|
||||
if (binding.kind === kind) ids[name] = binding;
|
||||
}
|
||||
scope = scope.parent;
|
||||
@@ -788,10 +818,10 @@ export default class Scope {
|
||||
}
|
||||
|
||||
getBinding(name: string) {
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
|
||||
do {
|
||||
var binding = scope.getOwnBinding(name);
|
||||
let binding = scope.getOwnBinding(name);
|
||||
if (binding) return binding;
|
||||
} while (scope = scope.parent);
|
||||
}
|
||||
@@ -801,12 +831,12 @@ export default class Scope {
|
||||
}
|
||||
|
||||
getBindingIdentifier(name: string) {
|
||||
var info = this.getBinding(name);
|
||||
let info = this.getBinding(name);
|
||||
return info && info.identifier;
|
||||
}
|
||||
|
||||
getOwnBindingIdentifier(name: string) {
|
||||
var binding = this.bindings[name];
|
||||
let binding = this.bindings[name];
|
||||
return binding && binding.identifier;
|
||||
}
|
||||
|
||||
@@ -833,7 +863,7 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
moveBindingTo(name, scope) {
|
||||
var info = this.getBinding(name);
|
||||
let info = this.getBinding(name);
|
||||
if (info) {
|
||||
info.scope.removeOwnBinding(name);
|
||||
info.scope = scope;
|
||||
@@ -847,13 +877,13 @@ export default class Scope {
|
||||
|
||||
removeBinding(name: string) {
|
||||
// clear literal binding
|
||||
var info = this.getBinding(name);
|
||||
let info = this.getBinding(name);
|
||||
if (info) {
|
||||
info.scope.removeOwnBinding(name);
|
||||
}
|
||||
|
||||
// clear uids with this name - https://github.com/babel/babel/issues/2101
|
||||
var scope = this;
|
||||
let scope = this;
|
||||
do {
|
||||
if (scope.uids[name]) {
|
||||
scope.uids[name] = false;
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class Renamer {
|
||||
binding: Binding;
|
||||
|
||||
maybeConvertFromExportDeclaration(parentDeclar) {
|
||||
let exportDeclar = parentDeclar && parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath;
|
||||
let exportDeclar = parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath;
|
||||
if (!exportDeclar) return;
|
||||
|
||||
// build specifiers that point back to this export declaration
|
||||
@@ -63,6 +63,8 @@ export default class Renamer {
|
||||
}
|
||||
|
||||
maybeConvertFromClassFunctionDeclaration(path) {
|
||||
return; // TODO
|
||||
|
||||
// retain the `name` of a class/function declaration
|
||||
|
||||
if (!path.isFunctionDeclaration() && !path.isClassDeclaration()) return;
|
||||
@@ -77,6 +79,8 @@ export default class Renamer {
|
||||
}
|
||||
|
||||
maybeConvertFromClassFunctionExpression(path) {
|
||||
return; // TODO
|
||||
|
||||
// retain the `name` of a class/function expression
|
||||
|
||||
if (!path.isFunctionExpression() && !path.isClassExpression()) return;
|
||||
@@ -96,7 +100,9 @@ export default class Renamer {
|
||||
let { scope, path } = binding;
|
||||
|
||||
let parentDeclar = path.find((path) => path.isDeclaration() || path.isFunctionExpression());
|
||||
this.maybeConvertFromExportDeclaration(parentDeclar);
|
||||
if (parentDeclar) {
|
||||
this.maybeConvertFromExportDeclaration(parentDeclar);
|
||||
}
|
||||
|
||||
scope.traverse(block || scope.block, renameVisitor, this);
|
||||
|
||||
@@ -111,7 +117,9 @@ export default class Renamer {
|
||||
// todo: hoist and convert function to a let
|
||||
}
|
||||
|
||||
this.maybeConvertFromClassFunctionDeclaration(parentDeclar);
|
||||
this.maybeConvertFromClassFunctionExpression(parentDeclar);
|
||||
if (parentDeclar) {
|
||||
this.maybeConvertFromClassFunctionDeclaration(parentDeclar);
|
||||
this.maybeConvertFromClassFunctionExpression(parentDeclar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* @flow */
|
||||
|
||||
import * as virtualTypes from "./path/lib/virtual-types";
|
||||
import * as messages from "babel-messages";
|
||||
import * as t from "babel-types";
|
||||
@@ -22,22 +24,6 @@ export function explode(visitor) {
|
||||
}
|
||||
}
|
||||
|
||||
// normalise colons
|
||||
for (let nodeType in visitor) {
|
||||
if (shouldIgnoreKey(nodeType)) continue;
|
||||
|
||||
let parts = nodeType.split(":");
|
||||
if (parts.length === 1) continue;
|
||||
|
||||
let fns = visitor[nodeType];
|
||||
delete visitor[nodeType];
|
||||
|
||||
nodeType = parts[0];
|
||||
|
||||
visitor[nodeType] = visitor[nodeType] || {};
|
||||
visitor[nodeType][parts[1]] = fns;
|
||||
}
|
||||
|
||||
// verify data structure
|
||||
verify(visitor);
|
||||
|
||||
@@ -55,7 +41,7 @@ export function explode(visitor) {
|
||||
for (let nodeType of (Object.keys(visitor): Array)) {
|
||||
if (shouldIgnoreKey(nodeType)) continue;
|
||||
|
||||
var wrapper = virtualTypes[nodeType];
|
||||
let wrapper = virtualTypes[nodeType];
|
||||
if (!wrapper) continue;
|
||||
|
||||
// wrap all the functions
|
||||
@@ -87,14 +73,14 @@ export function explode(visitor) {
|
||||
|
||||
let fns = visitor[nodeType];
|
||||
|
||||
var aliases = t.FLIPPED_ALIAS_KEYS[nodeType];
|
||||
let aliases = t.FLIPPED_ALIAS_KEYS[nodeType];
|
||||
if (!aliases) continue;
|
||||
|
||||
// clear it from the visitor
|
||||
delete visitor[nodeType];
|
||||
|
||||
for (var alias of (aliases: Array)) {
|
||||
var existing = visitor[alias];
|
||||
for (let alias of (aliases: Array)) {
|
||||
let existing = visitor[alias];
|
||||
if (existing) {
|
||||
mergePair(existing, fns);
|
||||
} else {
|
||||
@@ -119,16 +105,16 @@ export function verify(visitor) {
|
||||
throw new Error(messages.get("traverseVerifyRootFunction"));
|
||||
}
|
||||
|
||||
for (var nodeType in visitor) {
|
||||
for (let nodeType in visitor) {
|
||||
if (shouldIgnoreKey(nodeType)) continue;
|
||||
|
||||
if (t.TYPES.indexOf(nodeType) < 0) {
|
||||
throw new Error(messages.get("traverseVerifyNodeType", nodeType));
|
||||
}
|
||||
|
||||
var visitors = visitor[nodeType];
|
||||
let visitors = visitor[nodeType];
|
||||
if (typeof visitors === "object") {
|
||||
for (var visitorKey in visitors) {
|
||||
for (let visitorKey in visitors) {
|
||||
if (visitorKey === "enter" || visitorKey === "exit") continue;
|
||||
throw new Error(messages.get("traverseVerifyVisitorProperty", nodeType, visitorKey));
|
||||
}
|
||||
@@ -138,15 +124,47 @@ export function verify(visitor) {
|
||||
visitor._verified = true;
|
||||
}
|
||||
|
||||
export function merge(visitors) {
|
||||
var rootVisitor = {};
|
||||
export function merge(visitors: Array, states: Array = []) {
|
||||
let rootVisitor = {};
|
||||
|
||||
for (let i = 0; i < visitors.length; i++) {
|
||||
let visitor = visitors[i];
|
||||
let state = states[i];
|
||||
|
||||
for (var visitor of (visitors: Array)) {
|
||||
explode(visitor);
|
||||
|
||||
for (var type in visitor) {
|
||||
var nodeVisitor = rootVisitor[type] = rootVisitor[type] || {};
|
||||
mergePair(nodeVisitor, visitor[type]);
|
||||
for (let type in visitor) {
|
||||
let visitorType = visitor[type];
|
||||
|
||||
// if we have state then overload the callbacks to take it
|
||||
if (state) {
|
||||
let oldVisitorType = visitorType;
|
||||
visitorType = {};
|
||||
|
||||
for (let key in oldVisitorType) {
|
||||
let fns = oldVisitorType[key];
|
||||
|
||||
// not an enter/exit array of callbacks
|
||||
if (!Array.isArray(fns)) continue;
|
||||
|
||||
fns = fns.map(function (fn) {
|
||||
if (typeof fn === "function") {
|
||||
let newFn = function (path) {
|
||||
return fn.call(state, path, state);
|
||||
};
|
||||
newFn.toString = () => fn.toString();
|
||||
return newFn;
|
||||
} else {
|
||||
return fn;
|
||||
}
|
||||
});
|
||||
|
||||
visitorType[key] = fns;
|
||||
}
|
||||
}
|
||||
|
||||
let nodeVisitor = rootVisitor[type] = rootVisitor[type] || {};
|
||||
mergePair(nodeVisitor, visitorType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +175,7 @@ function ensureEntranceObjects(obj) {
|
||||
for (let key in obj) {
|
||||
if (shouldIgnoreKey(key)) continue;
|
||||
|
||||
var fns = obj[key];
|
||||
let fns = obj[key];
|
||||
if (typeof fns === "function") {
|
||||
obj[key] = { enter: fns };
|
||||
}
|
||||
@@ -170,11 +188,13 @@ function ensureCallbackArrays(obj){
|
||||
}
|
||||
|
||||
function wrapCheck(wrapper, fn) {
|
||||
return function () {
|
||||
if (wrapper.checkPath(this)) {
|
||||
let newFn = function (path) {
|
||||
if (wrapper.checkPath(path)) {
|
||||
return fn.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
newFn.toString = () => fn.toString();
|
||||
return newFn;
|
||||
}
|
||||
|
||||
function shouldIgnoreKey(key) {
|
||||
@@ -191,7 +211,7 @@ function shouldIgnoreKey(key) {
|
||||
}
|
||||
|
||||
function mergePair(dest, src) {
|
||||
for (var key in src) {
|
||||
for (let key in src) {
|
||||
dest[key] = [].concat(dest[key] || [], src[key]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user