@samitouri / QOS-React-2 / commits / 2b763fc452

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.

Ben Newman committed Nov 13, 2013 at 14:53 UTC 2b763fc4526e531a9d3856ff8356ac78101240bd
2 files changed +42 -40
package.json
+1 -1
@@ -49,7 +49,7 @@
49 "grunt-contrib-jshint": "~0.6.0",
50 "optimist": "~0.6.0",
51 "phantomjs": "~1.9",
52 - "recast": "~0.4.16",
52 + "recast": "~0.4.24",
53 "semver": "~2.1.0",
54 "uglify-js": "~2.4.0",
55 "grunt-contrib-clean": "~0.5.0",
vendor/constants.js
+41 -39
@@ -16,58 +16,60 @@
16 'use strict';
17
18 var recast = require('recast');
19 +var types = recast.types;
20 +var namedTypes = types.namedTypes;
21 +var builders = types.builders;
22 +var hasOwn = Object.prototype.hasOwnProperty;
23
20 -exports.propagate = function(constants, source) {
21 - var ast = recast.parse(source);
22 - ast = new ConstantVisitor(constants).visit(ast);
23 - return recast.print(ast);
24 -};
24 +function propagate(constants, source) {
25 + return recast.print(transform(recast.parse(source), constants));
26 +}
27
26 -var ConstantVisitor = recast.Visitor.extend({
27 - init: function(constants) {
28 - this.constants = constants || {};
29 - },
28 +function transform(ast, constants) {
29 + constants = constants || {};
30
31 - visitIdentifier: function(ident) {
32 - if (this.constants.hasOwnProperty(ident.name)) {
33 - return recast.builder.literal(this.constants[ident.name]);
34 - }
35 - },
31 + return types.traverse(ast, function(node, traverse) {
32 + if (namedTypes.Identifier.check(node)) {
33 + if (namedTypes.MemberExpression.check(this.parent.node) &&
34 + this.name === 'property' &&
35 + !this.parent.node.computed) {
36 + return false;
37 + }
38
37 - visitCallExpression: function(call) {
38 - if (!this.constants.__DEV__) {
39 - if (call.callee.type === 'Identifier' && call.callee.name === 'invariant') {
40 - call.arguments.length = 1;
39 + if (hasOwn.call(constants, node.name)) {
40 + this.replace(builders.literal(constants[node.name]));
41 + return false;
42 }
42 - }
43 - this.genericVisit(call);
44 - },
43
46 - visitIfStatement: function(stmt) {
47 - // Replaces all identifiers in this.constants with literal values.
48 - this.genericVisit(stmt);
44 + } else if (namedTypes.CallExpression.check(node)) {
45 + if (!constants.__DEV__) {
46 + if (namedTypes.Identifier.check(node.callee) &&
47 + node.callee.name === 'invariant') {
48 + node.arguments.length = 1;
49 + }
50 + }
51
50 - if (stmt.test.type === recast.Syntax.Literal) {
51 - if (stmt.test.value) {
52 - stmt.alternate = null;
53 - } else if (stmt.alternate) {
54 - return stmt.alternate;
52 + } else if (namedTypes.IfStatement.check(node) &&
53 + namedTypes.Literal.check(node.test)) {
54 + if (node.test.value) {
55 + node.alternate = null;
56 + } else if (node.alternate) {
57 + this.replace(node.alternate);
58 + return false;
59 } else {
56 - // In case this if statement is an alternate clause for another
57 - // if-statement, replacing that alternate with null will have the
58 - // effect of pruning the unnecessary clause. If this is just a
59 - // free-floating if statement, replacing it with null will have
60 - // the effect of removing it from the enclosing list of
61 - // statements.
62 - return null;
60 + this.replace(); // Remove the if-statement.
61 + return false;
62 }
63 }
65 - }
66 -});
64 + });
65 +}
66
67 if (!module.parent) {
68 var constants = JSON.parse(process.argv[3]);
69 recast.run(function(ast, callback) {
71 - callback(new ConstantVisitor(constants).visit(ast));
70 + callback(transform(ast, constants));
71 });
72 }
73 +
74 +exports.propagate = propagate;
75 +exports.transform = transform;