Extract traversers outside methods

This commit is contained in:
Dan Abramov 2015-01-21 14:34:47 +03:00
parent cfd028288f
commit 3d9d842a0d
14 changed files with 505 additions and 461 deletions

View File

@ -3,19 +3,21 @@
var traverse = require("../../traverse");
var t = require("../../types");
var traverser = {
enter: function (node, parent, scope, context) {
if (t.isFunction(node)) context.skip();
if (t.isAwaitExpression(node)) {
node.type = "YieldExpression";
}
}
};
module.exports = function (node, callId) {
node.async = false;
node.generator = true;
traverse(node, {
enter: function (node, parent, scope, context) {
if (t.isFunction(node)) context.skip();
if (t.isAwaitExpression(node)) {
node.type = "YieldExpression";
}
}
});
traverse(node, traverser);
var call = t.callExpression(callId, [node]);

View File

@ -103,6 +103,34 @@ ReplaceSupers.prototype.replace = function () {
this.traverseLevel(this.methodNode.value, true);
};
var traverser = {
enter: function (node, parent, scope, context, state) {
var topLevel = state.topLevel;
var self = state.self;
if (t.isFunction(node) && !t.isArrowFunctionExpression(node)) {
// we need to call traverseLevel again so we're context aware
self.traverseLevel(node, false);
return context.skip();
}
if (t.isProperty(node, { method: true }) || t.isMethodDefinition(node)) {
// break on object methods
return context.skip();
}
var getThisReference = topLevel ?
// top level so `this` is the instance
t.thisExpression :
// not in the top level so we need to create a reference
self.getThisReference;
var callback = self.specHandle;
if (self.isLoose) callback = self.looseHandle;
return callback.call(self, getThisReference, node, parent);
}
};
/**
* Description
*
@ -111,36 +139,8 @@ ReplaceSupers.prototype.replace = function () {
*/
ReplaceSupers.prototype.traverseLevel = function (node, topLevel) {
var self = this;
traverse(node, {
enter: function (node, parent, scope, context) {
if (t.isFunction(node) && !t.isArrowFunctionExpression(node)) {
// we need to call traverseLevel again so we're context aware
self.traverseLevel(node, false);
return context.skip();
}
if (t.isProperty(node, { method: true }) || t.isMethodDefinition(node)) {
// break on object methods
return context.skip();
}
var getThisReference = function () {
if (topLevel) {
// top level so `this` is the instance
return t.thisExpression();
} else {
// not in the top level so we need to create a reference
return self.getThisReference();
}
};
var callback = self.specHandle;
if (self.isLoose) callback = self.looseHandle;
return callback.call(self, getThisReference, node, parent);
}
});
var state = { self: this, topLevel: topLevel };
traverse(node, traverser, null, state);
};
/**

View File

@ -47,8 +47,21 @@ DefaultFormatter.prototype.getLocalImports = function () {
return localImports;
};
DefaultFormatter.prototype.isLocalReference = function (node) {
var localImports = this.localImports;
return t.isIdentifier(node) && _.has(localImports, node.name) && localImports[node.name] !== node;
};
DefaultFormatter.prototype.checkLocalReference = function (node) {
var file = this.file;
if (this.isLocalReference(node)) {
throw file.errorWithNode(node, "Illegal assignment of module import");
}
};
var collissionsTraverser = {
enter: function (node, parent, scope, context, check) {
enter: function (node, parent, scope, context, state) {
var self = state.self;
if (t.isAssignmentExpression(node)) {
var left = node.left;
@ -56,30 +69,17 @@ var collissionsTraverser = {
while (left.object) left = left.object;
}
check(left);
self.checkLocalReference(left);
} else if (t.isDeclaration(node)) {
_.each(t.getIds(node, true), check);
_.each(t.getIds(node, true), self.checkLocalReference);
}
}
};
DefaultFormatter.prototype.checkCollisions = function () {
// todo: all check export collissions
var localImports = this.localImports;
var file = this.file;
var isLocalReference = function (node) {
return t.isIdentifier(node) && _.has(localImports, node.name) && localImports[node.name] !== node;
};
var check = function (node) {
if (isLocalReference(node)) {
throw file.errorWithNode(node, "Illegal assignment of module import");
}
};
traverse(file.ast, collissionsTraverser, null, check);
var state = { self: this };
traverse(this.file.ast, collissionsTraverser, null, state);
};
DefaultFormatter.prototype.remapExportAssignment = function (node) {
@ -94,51 +94,53 @@ DefaultFormatter.prototype.remapExportAssignment = function (node) {
);
};
DefaultFormatter.prototype.remapAssignments = function () {
DefaultFormatter.prototype.isLocalReference = function (node, scope) {
var localExports = this.localExports;
var self = this;
var name = node.name;
return t.isIdentifier(node) && localExports[name] && localExports[name] === scope.get(name, true);
};
var isLocalReference = function (node, scope) {
var name = node.name;
return t.isIdentifier(node) && localExports[name] && localExports[name] === scope.get(name, true);
};
var remapTraverser = {
enter: function (node, parent, scope, context, state) {
var self = state.self;
if (t.isUpdateExpression(node) && self.isLocalReference(node.argument, scope)) {
context.skip();
traverse(this.file.ast, {
enter: function (node, parent, scope, context, isLocalReference) {
if (t.isUpdateExpression(node) && isLocalReference(node.argument, scope)) {
context.skip();
// expand to long file assignment expression
var assign = t.assignmentExpression(node.operator[0] + "=", node.argument, t.literal(1));
// expand to long file assignment expression
var assign = t.assignmentExpression(node.operator[0] + "=", node.argument, t.literal(1));
// remap this assignment expression
var remapped = self.remapExportAssignment(assign);
// remap this assignment expression
var remapped = self.remapExportAssignment(assign);
// we don't need to change the result
if (t.isExpressionStatement(parent) || node.prefix) {
return remapped;
}
var nodes = [];
nodes.push(remapped);
var operator;
if (node.operator === "--") {
operator = "+";
} else { // "++"
operator = "-";
}
nodes.push(t.binaryExpression(operator, node.argument, t.literal(1)));
return t.sequenceExpression(nodes);
// we don't need to change the result
if (t.isExpressionStatement(parent) || node.prefix) {
return remapped;
}
if (t.isAssignmentExpression(node) && isLocalReference(node.left, scope)) {
context.skip();
return self.remapExportAssignment(node);
var nodes = [];
nodes.push(remapped);
var operator;
if (node.operator === "--") {
operator = "+";
} else { // "++"
operator = "-";
}
nodes.push(t.binaryExpression(operator, node.argument, t.literal(1)));
return t.sequenceExpression(nodes);
}
}, null, isLocalReference);
if (t.isAssignmentExpression(node) && self.isLocalReference(node.left, scope)) {
context.skip();
return self.remapExportAssignment(node);
}
}
};
DefaultFormatter.prototype.remapAssignments = function () {
var state = { self: this };
traverse(this.file.ast, remapTraverser, null, state);
};
DefaultFormatter.prototype.getModuleName = function () {

View File

@ -8,16 +8,19 @@ var util = require("../../util");
var t = require("../../types");
var _ = require("lodash");
var traverser = {
enter: function (node, parent, scope, context, state) {
if (t.isExportDeclaration(node) && !node.default) state.hasNonDefaultExports = true;
}
};
function CommonJSFormatter(file) {
DefaultFormatter.apply(this, arguments);
var hasNonDefaultExports = false;
traverse(file.ast, {
enter: function (node) {
if (t.isExportDeclaration(node) && !node.default) hasNonDefaultExports = true;
}
});
this.hasNonDefaultExports = hasNonDefaultExports;
var state = { hasNonDefaultExports: false };
traverse(file.ast, traverser, null, state);
this.hasNonDefaultExports = state.hasNonDefaultExports;
}
util.inherits(CommonJSFormatter, DefaultFormatter);

View File

@ -63,36 +63,91 @@ SystemFormatter.prototype.importSpecifier = function (specifier, node, nodes) {
this._addImportSource(_.last(nodes), node);
};
var runnerSettersTraverser = {
enter: function (node, parent, scope, context, state) {
if (node._importSource === state.source) {
if (t.isVariableDeclaration(node)) {
_.each(node.declarations, function (declar) {
state.hoistDeclarators.push(t.variableDeclarator(declar.id));
state.nodes.push(t.expressionStatement(
t.assignmentExpression("=", declar.id, declar.init)
));
});
} else {
state.nodes.push(node);
}
context.remove();
}
}
};
SystemFormatter.prototype.buildRunnerSetters = function (block, hoistDeclarators) {
return t.arrayExpression(_.map(this.ids, function (uid, source) {
var state = {
nodes: [],
source: source,
nodes: [],
hoistDeclarators: hoistDeclarators
};
traverse(block, {
enter: function (node, parent, scope, context, state) {
if (node._importSource === source) {
if (t.isVariableDeclaration(node)) {
_.each(node.declarations, function (declar) {
state.hoistDeclarators.push(t.variableDeclarator(declar.id));
state.nodes.push(t.expressionStatement(
t.assignmentExpression("=", declar.id, declar.init)
));
});
} else {
state.nodes.push(node);
}
context.remove();
}
}
}, null, state);
traverse(block, runnerSettersTraverser, null, state);
return t.functionExpression(null, [uid], t.blockStatement(state.nodes));
}));
};
var hoistVariablesTraverser = {
enter: function (node, parent, scope, context, hoistDeclarators) {
if (t.isFunction(node)) {
// nothing inside is accessible
return context.skip();
}
if (t.isVariableDeclaration(node)) {
if (node.kind !== "var" && !t.isProgram(parent)) { // let, const
// can't be accessed
return;
}
var nodes = [];
_.each(node.declarations, function (declar) {
hoistDeclarators.push(t.variableDeclarator(declar.id));
if (declar.init) {
// no initializer so we can just hoist it as-is
var assign = t.expressionStatement(t.assignmentExpression("=", declar.id, declar.init));
nodes.push(assign);
}
});
// for (var i in test)
// for (var i = 0;;)
if (t.isFor(parent)) {
if (parent.left === node) {
return node.declarations[0].id;
}
if (parent.init === node) {
return t.toSequenceExpression(nodes, scope);
}
}
return nodes;
}
}
};
var hoistFunctionsTraverser = {
enter: function (node, parent, scope, context, handlerBody) {
if (t.isFunction(node)) context.skip();
if (t.isFunctionDeclaration(node) || node._blockHoist) {
handlerBody.push(node);
context.remove();
}
}
};
SystemFormatter.prototype.transform = function (ast) {
var program = ast.program;
@ -116,46 +171,7 @@ SystemFormatter.prototype.transform = function (ast) {
var returnStatement = handlerBody.pop();
// hoist up all variable declarations
traverse(block, {
enter: function (node, parent, scope, context, hoistDeclarators) {
if (t.isFunction(node)) {
// nothing inside is accessible
return context.skip();
}
if (t.isVariableDeclaration(node)) {
if (node.kind !== "var" && !t.isProgram(parent)) { // let, const
// can't be accessed
return;
}
var nodes = [];
_.each(node.declarations, function (declar) {
hoistDeclarators.push(t.variableDeclarator(declar.id));
if (declar.init) {
// no initializer so we can just hoist it as-is
var assign = t.expressionStatement(t.assignmentExpression("=", declar.id, declar.init));
nodes.push(assign);
}
});
// for (var i in test)
// for (var i = 0;;)
if (t.isFor(parent)) {
if (parent.left === node) {
return node.declarations[0].id;
}
if (parent.init === node) {
return t.toSequenceExpression(nodes, scope);
}
}
return nodes;
}
}
}, null, hoistDeclarators);
traverse(block, hoistVariablesTraverser, null, hoistDeclarators);
if (hoistDeclarators.length) {
var hoistDeclar = t.variableDeclaration("var", hoistDeclarators);
@ -164,16 +180,7 @@ SystemFormatter.prototype.transform = function (ast) {
}
// hoist up function declarations for circular references
traverse(block, {
enter: function (node, parent, scope, context, handlerBody) {
if (t.isFunction(node)) context.skip();
if (t.isFunctionDeclaration(node) || node._blockHoist) {
handlerBody.push(node);
context.remove();
}
}
}, null, handlerBody);
traverse(block, hoistFunctionsTraverser, null, handlerBody);
handlerBody.push(returnStatement);

View File

@ -6,25 +6,6 @@ var traverse = require("../traverse");
var t = require("../types");
var _ = require("lodash");
function noop() { }
function enter(node, parent, scope, context, args) {
var fns = args[1][node.type];
if (!fns) return;
return fns.enter(node, parent, scope, context, args[0]);
}
function exit(node, parent, scope, context, args) {
var fns = args[1][node.type];
if (!fns) return;
return fns.exit(node, parent, scope, context, args[0]);
}
var traverseOpts = {
enter: enter,
exit: exit
};
function Transformer(key, transformer, opts) {
this.manipulateOptions = transformer.manipulateOptions;
this.experimental = !!transformer.experimental;
@ -53,8 +34,8 @@ Transformer.prototype.normalise = function (transformer) {
if (!_.isObject(fns)) return;
if (!fns.enter) fns.enter = noop;
if (!fns.exit) fns.exit = noop;
if (!fns.enter) fns.enter = _.noop;
if (!fns.exit) fns.exit = _.noop;
transformer[type] = fns;
@ -77,9 +58,26 @@ Transformer.prototype.astRun = function (file, key) {
}
};
var transformTraverser = {
enter: function (node, parent, scope, context, state) {
var fns = state.transformer[node.type];
if (!fns) return;
return fns.enter(node, parent, scope, context, state.file);
},
exit: function (node, parent, scope, context, state) {
var fns = state.transformer[node.type];
if (!fns) return;
return fns.exit(node, parent, scope, context, state.file);
}
};
Transformer.prototype.transform = function (file) {
this.astRun(file, "before");
traverse(file.ast, traverseOpts, null, [file, this.transformer]);
var state = { file: file, transformer: this.transformer };
traverse(file.ast, transformTraverser, null, state);
this.astRun(file, "after");
};

View File

@ -4,6 +4,17 @@ var traverse = require("../../traverse");
var t = require("../../types");
var _ = require("lodash");
var traverser = {
enter: function (child, parent, scope, context, state) {
if (child._ignoreConstant) return;
if (t.isVariableDeclaration(child)) return;
if (t.isVariableDeclarator(child) || t.isDeclaration(child) || t.isAssignmentExpression(child)) {
state.check(parent, state.getIds(child), scope);
}
}
};
exports.Program =
exports.BlockStatement =
exports.ForInStatement =
@ -71,19 +82,6 @@ exports.ForStatement = function (node, parent, scope, context, file) {
if (!hasConstants) return;
var state = {
check: check,
getIds: getIds
};
traverse(node, {
enter: function (child, parent, scope, context, state) {
if (child._ignoreConstant) return;
if (t.isVariableDeclaration(child)) return;
if (t.isVariableDeclarator(child) || t.isDeclaration(child) || t.isAssignmentExpression(child)) {
state.check(parent, state.getIds(child), scope);
}
}
}, null, state);
var state = { check: check, getIds: getIds };
traverse(node, traverser, null, state);
};

View File

@ -4,6 +4,22 @@ var traverse = require("../../traverse");
var util = require("../../util");
var t = require("../../types");
function checkTDZ(node, parent, scope, context, state) {
if (!t.isReferencedIdentifier(node, parent)) return;
if (state.ids.indexOf(node.name) >= 0) {
throw state.file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized");
}
if (scope.has(node.name, true)) {
state.iife = true;
}
}
var checkTDZTraverser = {
enter: checkTDZ
};
exports.Function = function (node, parent, scope, context, file) {
if (!node.defaults || !node.defaults.length) return;
t.ensureBlock(node);
@ -12,27 +28,10 @@ exports.Function = function (node, parent, scope, context, file) {
return t.getIds(param);
});
var iife = false;
var def;
var checkTDZ = function (param, def, ids) {
var check = function (node, parent) {
if (!t.isReferencedIdentifier(node, parent)) return;
if (ids.indexOf(node.name) >= 0) {
throw file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized");
}
if (scope.has(node.name, true)) {
iife = true;
}
};
check(def, node);
if (!t.isPattern(param)) {
traverse(def, { enter: check });
}
var state = {
file: file,
iife: false
};
for (var i = 0; i < node.defaults.length; i++) {
@ -44,14 +43,21 @@ exports.Function = function (node, parent, scope, context, file) {
// temporal dead zone check - here we prevent accessing of params that
// are to the right - ie. uninitialized parameters
var rightIds = ids.slice(i);
for (var i2 = 0; i2 < rightIds.length; i2++) {
checkTDZ(param, def, rightIds[i2]);
state.ids = rightIds[i2];
checkTDZ(def, node, scope, context, state);
if (!t.isPattern(param)) {
traverse(def, checkTDZTraverser, scope, state);
}
}
// we're accessing a variable that's already defined within this function
var has = scope.get(param.name, true);
if (has && node.params.indexOf(has) < 0) {
iife = true;
state.iife = true;
}
}
@ -82,7 +88,7 @@ exports.Function = function (node, parent, scope, context, file) {
// we need to cut off all trailing default parameters
node.params = node.params.slice(0, lastNonDefaultParam);
if (iife) {
if (state.iife) {
var container = t.functionExpression(null, [], node.body, node.generator);
container._aliasFunction = true;

View File

@ -112,6 +112,31 @@ LetScoping.prototype.run = function () {
}
};
function replace(node, parent, scope, context, remaps) {
if (!t.isReferencedIdentifier(node, parent)) return;
var remap = remaps[node.name];
if (!remap) return;
var own = scope.get(node.name, true);
if (own === remap.node) {
node.name = remap.uid;
} else {
// scope already has it's own declaration that doesn't
// match the one we have a stored replacement for
if (context) context.skip();
}
}
var replaceTraverser = {
enter: replace
};
function traverseReplace(node, parent, scope, remaps) {
replace(node, parent, scope, null, remaps);
traverse(node, replaceTraverser, scope, remaps);
}
/**
* Description
*/
@ -149,35 +174,14 @@ LetScoping.prototype.remap = function () {
//
var replace = function (node, parent, scope, context, remaps) {
if (!t.isReferencedIdentifier(node, parent)) return;
var remap = remaps[node.name];
if (!remap) return;
var own = scope.get(node.name, true);
if (own === remap.node) {
node.name = remap.uid;
} else {
// scope already has it's own declaration that doesn't
// match the one we have a stored replacement for
if (context) context.skip();
}
};
var traverseReplace = function (node, parent) {
replace(node, parent, scope, null, remaps);
traverse(node, { enter: replace }, scope, remaps);
};
var loopParent = this.loopParent;
if (loopParent) {
traverseReplace(loopParent.right, loopParent);
traverseReplace(loopParent.test, loopParent);
traverseReplace(loopParent.update, loopParent);
traverseReplace(loopParent.right, loopParent, scope, remaps);
traverseReplace(loopParent.test, loopParent, scope, remaps);
traverseReplace(loopParent.update, loopParent, scope, remaps);
}
traverse(this.block, { enter: replace }, scope, remaps);
traverse(this.block, replaceTraverser, scope, remaps);
};
/**
@ -217,6 +221,31 @@ LetScoping.prototype.needsClosure = function () {
this.build(ret, call);
};
var letReferenceFunctionTraverser = {
enter: function (node, parent, scope, context, state) {
// not a direct reference
if (!t.isReferencedIdentifier(node, parent)) return;
// this scope has a variable with the same name so it couldn't belong
// to our let scope
if (scope.hasOwn(node.name, true)) return;
// not a part of our scope
if (!state.letReferences[node.name]) return;
state.closurify = true;
}
};
var letReferenceBlockTraverser = {
enter: function (node, parent, scope, context, state) {
if (t.isFunction(node)) {
traverse(node, letReferenceFunctionTraverser, scope, state);
return context.skip();
}
}
};
/**
* Description
*/
@ -264,33 +293,42 @@ LetScoping.prototype.getLetReferences = function () {
// traverse through this block, stopping on functions and checking if they
// contain any local let references
traverse(this.block, {
enter: function (node, parent, scope, context, state) {
if (t.isFunction(node)) {
traverse(node, {
enter: function (node, parent) {
// not a direct reference
if (!t.isReferencedIdentifier(node, parent)) return;
// this scope has a variable with the same name so it couldn't belong
// to our let scope
if (scope.hasOwn(node.name, true)) return;
// not a part of our scope
if (!state.letReferences[node.name]) return;
state.closurify = true;
}
}, scope, state);
return context.skip();
}
}
}, this.scope, state);
traverse(this.block, letReferenceBlockTraverser, this.scope, state);
return state.closurify;
};
var loopTraverser = {
enter: function (node, parent, scope, context, state) {
var replace;
if (t.isFunction(node) || t.isLoop(node)) {
return context.skip();
}
if (node && !node.label) {
if (t.isBreakStatement(node)) {
if (t.isSwitchCase(parent)) return;
state.hasBreak = true;
replace = t.returnStatement(t.literal("break"));
} else if (t.isContinueStatement(node)) {
state.hasContinue = true;
replace = t.returnStatement(t.literal("continue"));
}
}
if (t.isReturnStatement(node)) {
state.hasReturn = true;
replace = t.returnStatement(t.objectExpression([
t.property("init", t.identifier("v"), node.argument || t.identifier("undefined"))
]));
}
if (replace) return t.inherits(replace, node);
}
};
/**
* If we're inside of a loop then traverse it and check if it has one of
* the following node types `ReturnStatement`, `BreakStatement`,
@ -301,44 +339,32 @@ LetScoping.prototype.getLetReferences = function () {
*/
LetScoping.prototype.checkLoop = function () {
var has = {
var state = {
hasContinue: false,
hasReturn: false,
hasBreak: false,
};
traverse(this.block, {
enter: function (node, parent, scope, context) {
var replace;
traverse(this.block, loopTraverser, this.scope, state);
return state;
};
if (t.isFunction(node) || t.isLoop(node)) {
return context.skip();
var hoistVarDeclarationsTraverser = {
enter: function (node, parent, scope, context, self) {
if (t.isForStatement(node)) {
if (isVar(node.init, node)) {
node.init = t.sequenceExpression(self.pushDeclar(node.init));
}
if (node && !node.label) {
if (t.isBreakStatement(node)) {
if (t.isSwitchCase(parent)) return;
has.hasBreak = true;
replace = t.returnStatement(t.literal("break"));
} else if (t.isContinueStatement(node)) {
has.hasContinue = true;
replace = t.returnStatement(t.literal("continue"));
}
} else if (t.isFor(node)) {
if (isVar(node.left, node)) {
node.left = node.left.declarations[0].id;
}
if (t.isReturnStatement(node)) {
has.hasReturn = true;
replace = t.returnStatement(t.objectExpression([
t.property("init", t.identifier("v"), node.argument || t.identifier("undefined"))
]));
}
if (replace) return t.inherits(replace, node);
} else if (isVar(node, parent)) {
return self.pushDeclar(node).map(t.expressionStatement);
} else if (t.isFunction(node)) {
return context.skip();
}
}, this.scope, has);
return has;
}
};
/**
@ -347,23 +373,7 @@ LetScoping.prototype.checkLoop = function () {
*/
LetScoping.prototype.hoistVarDeclarations = function () {
traverse(this.block, {
enter: function (node, parent, scope, context, self) {
if (t.isForStatement(node)) {
if (isVar(node.init, node)) {
node.init = t.sequenceExpression(self.pushDeclar(node.init));
}
} else if (t.isFor(node)) {
if (isVar(node.left, node)) {
node.left = node.left.declarations[0].id;
}
} else if (isVar(node, parent)) {
return self.pushDeclar(node).map(t.expressionStatement);
} else if (t.isFunction(node)) {
return context.skip();
}
}
}, this.scope, this);
traverse(this.block, hoistVarDeclarationsTraverser, this.scope, this);
};
/**

View File

@ -1,6 +1,36 @@
var traverse = require("../../traverse");
var t = require("../../types");
var traverser = {
enter: function (node, parent, scope, context, state) {
if (!t.isReferencedIdentifier(node, parent)) return;
var declared = state.letRefs[node.name];
if (!declared) return;
// declared node is different in this scope
if (scope.get(node.name, true) !== declared) return;
var declaredLoc = declared.loc;
var referenceLoc = node.loc;
if (!declaredLoc || !referenceLoc) return;
// does this reference appear on a line before the declaration?
var before = referenceLoc.start.line < declaredLoc.start.line;
if (referenceLoc.start.line === declaredLoc.start.line) {
// this reference appears on the same line
// check it appears before the declaration
before = referenceLoc.start.col < declaredLoc.start.col;
}
if (before) {
throw state.file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized");
}
}
};
exports.optional = true;
exports.Loop =
@ -11,36 +41,8 @@ exports.BlockStatement = function (node, parent, scope, context, file) {
var state = {
letRefs: letRefs,
file: file
file: file
};
traverse(node, {
enter: function (node, parent, scope, context, state) {
if (!t.isReferencedIdentifier(node, parent)) return;
var declared = state.letRefs[node.name];
if (!declared) return;
// declared node is different in this scope
if (scope.get(node.name, true) !== declared) return;
var declaredLoc = declared.loc;
var referenceLoc = node.loc;
if (!declaredLoc || !referenceLoc) return;
// does this reference appear on a line before the declaration?
var before = referenceLoc.start.line < declaredLoc.start.line;
if (referenceLoc.start.line === declaredLoc.start.line) {
// this reference appears on the same line
// check it appears before the declaration
before = referenceLoc.start.col < declaredLoc.start.col;
}
if (before) {
throw state.file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized");
}
}
}, scope, state);
traverse(node, traverser, scope, state);
};

View File

@ -19,6 +19,45 @@ var ALIASABLE_CONSTRUCTORS = [
"WeakSet"
];
var astTraverser = {
enter: function (node, parent, scope, context, file) {
var prop;
if (t.isMemberExpression(node) && t.isReferenced(node, parent)) {
// Array.from -> _core.Array.from
var obj = node.object;
prop = node.property;
if (!t.isReferenced(obj, node)) return;
if (!node.computed && coreHas(obj) && _.has(core[obj.name], prop.name)) {
context.skip();
return t.prependToMemberExpression(node, file.get("coreIdentifier"));
}
} else if (t.isReferencedIdentifier(node, parent) && !t.isMemberExpression(parent) && _.contains(ALIASABLE_CONSTRUCTORS, node.name)) {
// Symbol() -> _core.Symbol(); new Promise -> new _core.Promise
return t.memberExpression(file.get("coreIdentifier"), node);
} else if (t.isCallExpression(node)) {
// arr[Symbol.iterator]() -> _core.$for.getIterator(arr)
if (node.arguments.length) return;
var callee = node.callee;
if (!t.isMemberExpression(callee)) return;
if (!callee.computed) return;
prop = callee.property;
if (!t.isIdentifier(prop.object, { name: "Symbol" })) return;
if (!t.isIdentifier(prop.property, { name: "iterator" })) return;
return util.template("corejs-iterator", {
CORE_ID: file.get("coreIdentifier"),
VALUE: callee.object
});
}
}
};
exports.optional = true;
exports.ast = {
@ -29,44 +68,7 @@ exports.ast = {
},
exit: function (ast, file) {
traverse(ast, {
enter: function (node, parent, scope, context) {
var prop;
if (t.isMemberExpression(node) && t.isReferenced(node, parent)) {
// Array.from -> _core.Array.from
var obj = node.object;
prop = node.property;
if (!t.isReferenced(obj, node)) return;
if (!node.computed && coreHas(obj) && _.has(core[obj.name], prop.name)) {
context.skip();
return t.prependToMemberExpression(node, file.get("coreIdentifier"));
}
} else if (t.isReferencedIdentifier(node, parent) && !t.isMemberExpression(parent) && _.contains(ALIASABLE_CONSTRUCTORS, node.name)) {
// Symbol() -> _core.Symbol(); new Promise -> new _core.Promise
return t.memberExpression(file.get("coreIdentifier"), node);
} else if (t.isCallExpression(node)) {
// arr[Symbol.iterator]() -> _core.$for.getIterator(arr)
if (node.arguments.length) return;
var callee = node.callee;
if (!t.isMemberExpression(callee)) return;
if (!callee.computed) return;
prop = callee.property;
if (!t.isIdentifier(prop.object, { name: "Symbol" })) return;
if (!t.isIdentifier(prop.property, { name: "iterator" })) return;
return util.template("corejs-iterator", {
CORE_ID: file.get("coreIdentifier"),
VALUE: callee.object
});
}
}
});
traverse(ast, astTraverser, null, file);
}
};

View File

@ -3,6 +3,20 @@
var traverse = require("../../traverse");
var t = require("../../types");
var traverser = {
enter: function (node, parent, scope, context, state) {
if (t.isFunction(node)) return;
if (t.isReturnStatement(node) && node.argument) {
node.argument = t.memberExpression(t.callExpression(state.file.addHelper("define-property"), [
t.thisExpression(),
state.key,
node.argument
]), state.key, true);
}
}
};
exports.Property =
exports.MethodDefinition = function (node, parent, scope, context, file) {
if (node.kind !== "memo") return;
@ -17,17 +31,10 @@ exports.MethodDefinition = function (node, parent, scope, context, file) {
key = t.literal(key.name);
}
traverse(value, {
enter: function (node) {
if (t.isFunction(node)) return;
var state = {
key: key,
file: file
};
if (t.isReturnStatement(node) && node.argument) {
node.argument = t.memberExpression(t.callExpression(file.addHelper("define-property"), [
t.thisExpression(),
key,
node.argument
]), key, true);
}
}
});
traverse(value, traverser, null, state);
};

View File

@ -8,8 +8,6 @@ var Scope = require("./scope");
var t = require("../types");
var _ = require("lodash");
function noop() { }
function TraversalContext() {
this.shouldFlatten = false;
this.shouldRemove = false;
@ -167,8 +165,8 @@ function traverse(parent, opts, scope, state) {
if (!parent) return;
if (!opts) opts = {};
if (!opts.enter) opts.enter = noop;
if (!opts.exit) opts.exit = noop;
if (!opts.enter) opts.enter = _.noop;
if (!opts.exit) opts.exit = _.noop;
// array of nodes
if (Array.isArray(parent)) {
@ -180,35 +178,46 @@ function traverse(parent, opts, scope, state) {
}
}
traverse.removeProperties = function (tree) {
var clear = function (node) {
node._declarations = null;
node.extendedRange = null;
node._scopeInfo = null;
node.tokens = null;
node.range = null;
node.start = null;
node.end = null;
node.loc = null;
node.raw = null;
function clearNode(node) {
node._declarations = null;
node.extendedRange = null;
node._scopeInfo = null;
node.tokens = null;
node.range = null;
node.start = null;
node.end = null;
node.loc = null;
node.raw = null;
if (Array.isArray(node.trailingComments))
clearComments(node.trailingComments);
if (Array.isArray(node.leadingComments))
clearComments(node.leadingComments);
};
}
var clearComments = function (comments) {
_.each(comments, clear);
};
var clearTraverser = { enter: clearNode };
clear(tree);
traverse(tree, { enter: clear });
function clearComments(comments) {
for (var i = 0; i < comments.length; i++)
clearNode(comments[i]);
}
traverse.removeProperties = function (tree) {
clearNode(tree);
traverse(tree, clearTraverser);
return tree;
};
traverse.hasType = function (tree, type, blacklistTypes) {
blacklistTypes = [].concat(blacklistTypes || []);
function hasBlacklistedType(node, parent, scope, context, state) {
if (node.type === state.type) {
state.has = true;
context.skip();
}
}
traverse.hasType = function (tree, type, blacklistTypes) {
// the node we're searching in is blacklisted
if (_.contains(blacklistTypes, tree.type)) return false;
@ -216,17 +225,13 @@ traverse.hasType = function (tree, type, blacklistTypes) {
if (tree.type === type) return true;
var state = {
has: false
has: false,
type: type
};
traverse(tree, {
blacklist: blacklistTypes,
enter: function (node, parent, scope, context, state) {
if (node.type === type) {
state.has = true;
context.skip();
}
}
enter: hasBlacklistedType
}, null, state);
return state.has;

View File

@ -119,6 +119,34 @@ Scope.prototype.generateTempBasedOnNode = function (node, file) {
return id;
};
var functionVariableTraverser = {
enter: function (node, parent, scope, context, state) {
if (t.isFor(node)) {
_.each(FOR_KEYS, function (key) {
var declar = node[key];
if (t.isVar(declar)) state.add(declar);
});
}
// this block is a function so we'll stop since none of the variables
// declared within are accessible
if (t.isFunction(node)) return context.skip();
// function identifier doesn't belong to this scope
if (state.blockId && node === state.blockId) return;
if (t.isIdentifier(node) && t.isReferenced(node, parent) && !scope.has(node.name)) {
state.add(node, true);
}
// we've ran into a declaration!
// we'll let the BlockStatement scope deal with `let` declarations unless
if (t.isDeclaration(node) && !t.isBlockScoped(node)) {
state.add(node);
}
}
};
Scope.prototype.getInfo = function () {
var parent = this.parent;
var block = this.block;
@ -176,36 +204,10 @@ Scope.prototype.getInfo = function () {
if (t.isProgram(block) || t.isFunction(block)) {
var state = {
blockId: block.id,
add: add
add: add
};
traverse(block, {
enter: function (node, parent, scope, context, state) {
if (t.isFor(node)) {
_.each(FOR_KEYS, function (key) {
var declar = node[key];
if (t.isVar(declar)) add(declar);
});
}
// this block is a function so we'll stop since none of the variables
// declared within are accessible
if (t.isFunction(node)) return context.skip();
// function identifier doesn't belong to this scope
if (state.blockId && node === state.blockId) return;
if (t.isIdentifier(node) && t.isReferenced(node, parent) && !scope.has(node.name)) {
state.add(node, true);
}
// we've ran into a declaration!
// we'll let the BlockStatement scope deal with `let` declarations unless
if (t.isDeclaration(node) && !t.isBlockScoped(node)) {
state.add(node);
}
}
}, this, state);
traverse(block, functionVariableTraverser, this, state);
}
// Function - params, rest