Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"grunt-contrib-jshint": "~0.6.0",
"optimist": "~0.6.0",
"phantomjs": "~1.9",
"recast": "~0.4.16",
"recast": "~0.4.24",
"semver": "~2.1.0",
"uglify-js": "~2.4.0",
"grunt-contrib-clean": "~0.5.0",
Expand Down
96 changes: 57 additions & 39 deletions vendor/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,58 +16,76 @@
'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]);
}
},
return types.traverse(ast, function(node, traverse) {
if (namedTypes.Identifier.check(node)) {
// If the identifier is the property of a member expression
// (e.g. object.property), then it definitely is not a constant
// expression that we want to replace.
if (namedTypes.MemberExpression.check(this.parent.node) &&
this.name === 'property' &&
!this.parent.node.computed) {
return false;
}

visitCallExpression: function(call) {
if (!this.constants.__DEV__) {
if (call.callee.type === 'Identifier' && call.callee.name === 'invariant') {
call.arguments.length = 1;
// There could in principle be a constant called "hasOwnProperty",
// so be careful always to use Object.prototype.hasOwnProperty.
if (hasOwn.call(constants, node.name)) {
this.replace(builders.literal(constants[node.name]));
return false;
}

} else if (namedTypes.CallExpression.check(node)) {
if (!constants.__DEV__) {
if (namedTypes.Identifier.check(node.callee) &&
node.callee.name === 'invariant') {
// Truncate the arguments of invariant(condition, ...)
// statements to just the condition.
node.arguments.length = 1;
}
}
}
this.genericVisit(call);
},

visitIfStatement: function(stmt) {
// Replaces all identifiers in this.constants with literal values.
this.genericVisit(stmt);
} else if (namedTypes.IfStatement.check(node) &&
namedTypes.Literal.check(node.test)) {
if (node.test.value) {
// If the alternate (then) branch is dead code, remove it.
this.get("alternate").replace();

// This is what happens when you replace a node with nothing and
// it can't be removed from a list of statements.
assert.strictEqual(node.alternate, null);

} else if (node.alternate) {
// Replace the whole if-statement with just the alternate clause.
this.replace(node.alternate);
return false;

if (stmt.test.type === recast.Syntax.Literal) {
if (stmt.test.value) {
stmt.alternate = null;
} else if (stmt.alternate) {
return stmt.alternate;
} 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;
// Remove the entire if-statement.
this.replace();
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;