diff --git a/src/babel/transformation/transformers/es6/classes.js b/src/babel/transformation/transformers/es6/classes.js index 36224c45f0..e77857fa53 100644 --- a/src/babel/transformation/transformers/es6/classes.js +++ b/src/babel/transformation/transformers/es6/classes.js @@ -182,7 +182,7 @@ class ClassTransformer { } // we have no constructor, we have a super, and the super doesn't appear to be falsy - if (!this.hasConstructor && this.hasSuper && !t.isFalsyExpression(superName)) { + if (!this.hasConstructor && this.hasSuper && t.evaluateTruthy(superName) !== false) { var helperName = "class-super-constructor-call"; if (this.isLoose) helperName += "-loose"; constructor.body.body.push(util.template(helperName, { diff --git a/src/babel/transformation/transformers/minification/dead-code-elimination.js b/src/babel/transformation/transformers/minification/dead-code-elimination.js index 3fd279ef15..2759f87200 100644 --- a/src/babel/transformation/transformers/minification/dead-code-elimination.js +++ b/src/babel/transformation/transformers/minification/dead-code-elimination.js @@ -26,6 +26,8 @@ export var IfStatement = { var alternate = node.alternate; var test = node.test; + var evaluateTest = t.evaluateTruthy(test); + // we can check if a test will be truthy 100% and if so then we can inline // the consequent and completely ignore the alternate // @@ -33,7 +35,7 @@ export var IfStatement = { // if ("foo") { foo; } -> { foo; } // - if (t.isLiteral(test) && test.value) { + if (evaluateTest === true) { return consequent; } @@ -44,7 +46,7 @@ export var IfStatement = { // if ("") { bar; } -> // - if (t.isFalsyExpression(test)) { + if (evaluateTest === false) { if (alternate) { return alternate; } else { diff --git a/src/babel/types/index.js b/src/babel/types/index.js index 7cca6db416..956b739d81 100644 --- a/src/babel/types/index.js +++ b/src/babel/types/index.js @@ -804,5 +804,133 @@ t.isScope = function (node, parent) { return t.isScopable(node); }; +/** + * Walk the input `node` and statically evaluate if it's truthy. + * + * Returning `true` when we're sure that the expression will evaluate to a + * truthy value, `false` if we're sure that it will evaluate to a falsy + * value and `undefined` if we aren't sure. Because of this please do not + * rely on coercion when using this method and check with === if it's false. + * + * For example do: + * + * if (t.evaluateTruthy(node) === false) falsyLogic(); + * + * **AND NOT** + * + * if (!t.evaluateTruthy(node)) falsyLogic(); + * + * @param {Node} node + * @returns {Boolean} + */ + +t.evaluateTruthy = function (node) { + var res = t.evaluate(node); + if (!res.broke) return !!res.value; +}; + +/** + * Walk the input `node` and statically evaluate it. + * + * Returns an pbject in the form `{ broke, value }`. `broke` indicates whether + * or not we had to drop out of evaluating the expression because of hitting + * an unknown node that we couldn't confidently find the value of. + * + * Example: + * + * t.evaluate(parse("5 + 5")) // { broke: false, value: 10 } + * t.evaluate(parse("!true")) // { broke: false, value: false } + * + * t.evaluate(parse("foo + foo")) // { broke: true, value: undefined } + * + * @param {Node} node + * @returns {Object} + */ + +t.evaluate = function (node) { + var BREAK = false; + + var value = evaluate(node); + if (BREAK) value = undefined; + return { + value: value, + broke: BREAK + }; + + function evaluate(node) { + if (BREAK) return; + + if (t.isSequenceExpression(node)) { + return evaluate(node.expressions[node.expressions.length - 1]); + } + + if (t.isLiteral(node)) { + if (node.regex && node.value === null) { + // we have a regex and we can't represent it natively + } else { + return node.value; + } + } + + if (t.isIdentifier(node, { name: "undefined" })) { + return undefined; + } + + if (t.isUnaryExpression(node, { prefix: true })) { + switch (node.operator) { + case "void": return undefined; + case "!": return !evaluate(node); + } + } + + if (t.isArrayExpression(node)) { + // possible perf issues - could deopt on X elements + var values = []; + for (var i = 0; i < node.elements.length; i++) { + values.push(evaluate(node.elements[i])); + } + return values; + } + + if (t.isObjectExpression(node)) { + // todo: deopt on mutable computed property keys etc + } + + if (t.isLogicalExpression(node)) { + var left = evaluate(node.left); + var right = evaluate(node.right); + + switch (node.operator) { + case "||": return left || right; + case "&&": return left && right; + } + } + + if (t.isBinaryExpression(node)) { + var left = evaluate(node.left); + var right = evaluate(node.right); + + switch (node.operator) { + case "-": return left - right; + case "+": return left + right; + case "/": return left / right; + case "*": return left * right; + case "%": return left % right; + case "<": return left < right; + case ">": return left > right; + case "<=": return left <= right; + case ">=": return left >= right; + case "==": return left == right; + case "!=": return left != right; + case "===": return left === right; + case "!==": return left !== right; + } + } + + // we can't deal with this node + BREAK = true; + } +}; + toFastProperties(t); toFastProperties(t.VISITOR_KEYS); diff --git a/test/types.js b/test/types.js index c5c15db2b1..f6fd11acbf 100644 --- a/test/types.js +++ b/test/types.js @@ -2,14 +2,11 @@ var assert = require("assert"); var t = require("../lib/babel/types"); suite("types", function () { - test("isFalsyExpression", function () { - assert.ok(t.isFalsyExpression(t.literal(""))); - assert.ok(t.isFalsyExpression(t.literal(null))); - assert.ok(t.isFalsyExpression(t.literal(0))); - assert.ok(t.isFalsyExpression(t.identifier("undefined"))); + test("evaluate", function () { + + }); + + test("evaluateTruthy", function () { - assert.ok(!t.isFalsyExpression(t.literal("foobar"))); - assert.ok(!t.isFalsyExpression(t.literal(5))); - assert.ok(!t.isFalsyExpression(t.identifier("foobar"))); }); });