From 1d9a0ce7ff8d6439a76ae3e2d419e36c016a47f5 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 13 Nov 2013 14:53:21 -0500 Subject: [PATCH] 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 --- vendor/constants.js | 82 +++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/vendor/constants.js b/vendor/constants.js index 935b0561c6..ef9b0f3e3b 100644 --- a/vendor/constants.js +++ b/vendor/constants.js @@ -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;