Rewrite vendor/constants.js to use require("ast-types").traverse.

Most notably, this new style of transformation gives us access to
this.parent.node, which allows us to avoid replacing identifiers that are
not actually free variables, such as member expression properties.

Closes #496.

Conflicts:
	package.json
This commit is contained in:
Ben Newman
2013-11-13 14:53:21 -05:00
committed by Paul O’Shannessy
parent a426f1f216
commit 1d9a0ce7ff
+42 -40
View File
@@ -16,58 +16,60 @@
'use strict';
var recast = require('recast');
var types = recast.types;
var namedTypes = types.namedTypes;
var builders = types.builders;
var hasOwn = Object.prototype.hasOwnProperty;
exports.propagate = function(constants, source) {
var ast = recast.parse(source);
ast = new ConstantVisitor(constants).visit(ast);
return recast.print(ast);
};
function propagate(constants, source) {
return recast.print(transform(recast.parse(source), constants));
}
var ConstantVisitor = recast.Visitor.extend({
init: function(constants) {
this.constants = constants || {};
},
function transform(ast, constants) {
constants = constants || {};
visitIdentifier: function(ident) {
if (this.constants.hasOwnProperty(ident.name)) {
return recast.builder.literal(this.constants[ident.name]);
}
},
visitCallExpression: function(call) {
if (!this.constants.__DEV__) {
if (call.callee.type === 'Identifier' && call.callee.name === 'invariant') {
call.arguments.length = 1;
return types.traverse(ast, function(node, traverse) {
if (namedTypes.Identifier.check(node)) {
if (namedTypes.MemberExpression.check(this.parent.node) &&
this.name === 'property' &&
!this.parent.node.computed) {
return false;
}
}
this.genericVisit(call);
},
visitIfStatement: function(stmt) {
// Replaces all identifiers in this.constants with literal values.
this.genericVisit(stmt);
if (hasOwn.call(constants, node.name)) {
this.replace(builders.literal(constants[node.name]));
return false;
}
if (stmt.test.type === recast.Syntax.Literal) {
if (stmt.test.value) {
stmt.alternate = null;
} else if (stmt.alternate) {
return stmt.alternate;
} else if (namedTypes.CallExpression.check(node)) {
if (!constants.__DEV__) {
if (namedTypes.Identifier.check(node.callee) &&
node.callee.name === 'invariant') {
node.arguments.length = 1;
}
}
} else if (namedTypes.IfStatement.check(node) &&
namedTypes.Literal.check(node.test)) {
if (node.test.value) {
node.alternate = null;
} else if (node.alternate) {
this.replace(node.alternate);
return false;
} else {
// In case this if statement is an alternate clause for another
// if-statement, replacing that alternate with null will have the
// effect of pruning the unnecessary clause. If this is just a
// free-floating if statement, replacing it with null will have
// the effect of removing it from the enclosing list of
// statements.
return null;
this.replace(); // Remove the if-statement.
return false;
}
}
}
});
});
}
if (!module.parent) {
var constants = JSON.parse(process.argv[3]);
recast.run(function(ast, callback) {
callback(new ConstantVisitor(constants).visit(ast));
callback(transform(ast, constants));
});
}
exports.propagate = propagate;
exports.transform = transform;