Refactor traversal to avoid closures

This commit is contained in:
Dan Abramov 2015-01-09 18:37:32 +03:00
parent 508b3531e5
commit 421906bcc1

View File

@ -4,46 +4,34 @@ var Scope = require("./scope");
var t = require("../types"); var t = require("../types");
var _ = require("lodash"); var _ = require("lodash");
function traverse(parent, opts, scope) { function TraversalContext(previousContext) {
// falsy node this.didSkip = false;
if (!parent) return; this.didRemove = false;
this.didStop = false;
var i, j; this.didFlatten = previousContext ? previousContext.didFlatten : false;
// array of nodes
if (_.isArray(parent)) {
for (i = 0; i < parent.length; i++) {
traverse(parent[i], opts, scope);
}
return;
} }
// unknown node type to traverse TraversalContext.prototype.flatten = function () {
var keys = t.VISITOR_KEYS[parent.type]; this.didFlatten = true;
if (!keys) return; };
opts = opts || {}; TraversalContext.prototype.remove = function () {
this.didRemove = true;
this.skip();
};
var stopped = false; TraversalContext.prototype.skip = function () {
this.didSkip = true;
};
for (i = 0; i < keys.length; i++) { TraversalContext.prototype.stop = function () {
var key = keys[i]; this.didStop = true;
var nodes = parent[key]; this.skip();
if (!nodes) continue; };
var flatten = false; TraversalContext.prototype.maybeReplace = function (result, obj, key, node) {
if (result === false) return node;
var handle = function (obj, key) { if (result == null) return node;
var node = obj[key];
if (!node) return;
// type is blacklisted
if (opts.blacklist && opts.blacklist.indexOf(node.type) > -1) return;
// replace node
var maybeReplace = function (result) {
if (result === false) return;
if (result == null) return;
var isArray = _.isArray(result); var isArray = _.isArray(result);
@ -55,49 +43,42 @@ function traverse(parent, opts, scope) {
// replace the node // replace the node
node = obj[key] = result; node = obj[key] = result;
if (isArray) flatten = true;
// we're replacing a statement or block node with an array of statements so we better // we're replacing a statement or block node with an array of statements so we better
// ensure that it's a block // ensure that it's a block
if (isArray && _.contains(t.STATEMENT_OR_BLOCK_KEYS, key) && !t.isBlockStatement(obj)) { if (isArray && _.contains(t.STATEMENT_OR_BLOCK_KEYS, key) && !t.isBlockStatement(obj)) {
t.ensureBlock(obj, key); t.ensureBlock(obj, key);
} }
};
var skipped = false; if (isArray) {
var removed = false; this.flatten();
var context = {
stop: function () {
skipped = stopped = true;
},
skip: function () {
skipped = true;
},
remove: function () {
this.skip();
removed = true;
} }
return node;
}; };
// TraversalContext.prototype.visit = function (obj, key, opts, scope, parent) {
var node = obj[key];
if (!node) return;
// type is blacklisted
if (opts.blacklist && opts.blacklist.indexOf(node.type) > -1) return;
var result;
var ourScope = scope; var ourScope = scope;
if (t.isScope(node)) ourScope = new Scope(node, scope); if (t.isScope(node)) ourScope = new Scope(node, scope);
// enter // enter
if (opts.enter) { if (opts.enter) {
var result = opts.enter.call(context, node, parent, ourScope); result = opts.enter.call(this, node, parent, ourScope);
maybeReplace(result); node = this.maybeReplace(result, obj, key, node);
if (removed) { if (this.didRemove) {
obj[key] = null; obj[key] = null;
flatten = true; this.flatten();
} }
// stop iteration // stop traversal
if (skipped) return; if (this.didSkip) return;
} }
// traverse node // traverse node
@ -105,17 +86,42 @@ function traverse(parent, opts, scope) {
// exit // exit
if (opts.exit) { if (opts.exit) {
maybeReplace(opts.exit.call(context, node, parent, ourScope)); result = opts.exit.call(this, node, parent, ourScope);
node = this.maybeReplace(result, obj, key, node);
} }
}; };
if (_.isArray(nodes)) { function traverse(parent, opts, scope) {
for (j = 0; j < nodes.length; j++) { // falsy node
handle(nodes, j); if (!parent) return;
if (stopped) return;
// array of nodes
if (_.isArray(parent)) {
for (var i = 0; i < parent.length; i++)
traverse(parent[i], opts, scope);
return;
} }
if (flatten) { // unknown node type to traverse
var keys = t.VISITOR_KEYS[parent.type];
if (!keys) return;
opts = opts || {};
var context = null;
for (var j = 0; j < keys.length; j++) {
var key = keys[j];
var nodes = parent[key];
if (!nodes) continue;
if (_.isArray(nodes)) {
for (var k = 0; k < nodes.length; k++) {
context = new TraversalContext(context);
context.visit(nodes, k, opts, scope, parent);
if (context.didStop) return;
}
if (context && context.didFlatten) {
parent[key] = _.flatten(parent[key]); parent[key] = _.flatten(parent[key]);
if (key === "body") { if (key === "body") {
@ -124,8 +130,9 @@ function traverse(parent, opts, scope) {
} }
} }
} else { } else {
handle(parent, key); context = new TraversalContext(context);
if (stopped) return; context.visit(parent, key, opts, scope, parent);
if (context.didStop) return;
} }
} }
} }