diff --git a/packages/babel/src/traversal/path/evaluation.js b/packages/babel/src/traversal/path/evaluation.js index 6349dfa3b8..1aab7e32eb 100644 --- a/packages/babel/src/traversal/path/evaluation.js +++ b/packages/babel/src/traversal/path/evaluation.js @@ -141,12 +141,28 @@ export function evaluate(): { confident: boolean; value: any } { } if (path.isLogicalExpression()) { + // If we are confident that one side of an && is false, or one side of + // an || is true, we can be confident about the entire expression + let wasConfident = confident; let left = evaluate(path.get("left")); + let leftConfident = confident; + confident = wasConfident; let right = evaluate(path.get("right")); + let rightConfident = confident; + let uncertain = leftConfident !== rightConfident; + confident = leftConfident && rightConfident; switch (node.operator) { - case "||": return left || right; - case "&&": return left && right; + case "||": + if ((left || right) && uncertain) { + confident = true; + } + return left || right; + case "&&": + if ((!left && leftConfident) || (!right && rightConfident)) { + confident = true; + } + return left && right; } } diff --git a/packages/babel/test/evaluation.js b/packages/babel/test/evaluation.js new file mode 100644 index 0000000000..632691bb0d --- /dev/null +++ b/packages/babel/test/evaluation.js @@ -0,0 +1,164 @@ +var evaluation = require("../lib/traversal/path/evaluation"); +var traverse = require('../lib/traversal'); +var parse = require("../lib/helpers/parse"); +var assert = require("assert"); + +suite("evaluation", function () { + test("binary expression", function () { + traverse(parse("5 + 5"), { + enter: function (node) { + if (this.isBinaryExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: 10 }); + } + } + }); + + traverse(parse("'str' === 'str'"), { + enter: function(node) { + if (this.isBinaryExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + + traverse(parse("'four' === 4"), { + enter: function(node) { + if (this.isBinaryExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: false }); + } + } + }); + }); + + test("logical expression", function () { + traverse(parse("'abc' === 'abc' && 1 === 1"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + + traverse(parse("'abc' === 'abc' && 1 === 10"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: false }); + } + } + }); + + traverse(parse("'abc' === 'xyz' && 1 === 1"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: false }); + } + } + }); + + traverse(parse("'abc' === 'xyz' && 1 === 10"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: false }); + } + } + }); + + traverse(parse("'abc' === 'abc' || 1 === 1"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + + traverse(parse("'abc' === 'abc' || 1 === 10"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + + traverse(parse("'abc' === 'xyz' || 1 === 1"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + + traverse(parse("'abc' === 'xyz' || 1 === 10"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: false }); + } + } + }); + }); + + test("logical expression without certainty", function () { + traverse(parse("'abc' === 'abc' || config.flag === 1"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + + traverse(parse("obj.a === 'abc' || config.flag === 1"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: false, value: undefined }); + } + } + }); + + traverse(parse("'abc' !== 'abc' && config.flag === 1"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: false }); + } + } + }); + + traverse(parse("obj.a === 'abc' && 1 === 1"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: false, value: undefined }); + } + } + }); + + traverse(parse("'abc' === 'abc' && (1 === 1 || config.flag)"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + + traverse(parse("'abc' === 'xyz' || (1 === 1 && config.flag)"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: false, value: undefined }); + } + } + }); + + traverse(parse("'abc' === 'xyz' || (1 === 1 && 'four' === 'four')"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + + traverse(parse("'abc' === 'abc' && (1 === 1 && 'four' === 'four')"), { + enter: function(node) { + if (this.isLogicalExpression()) { + assert.deepEqual(this.evaluate(), { confident: true, value: true }); + } + } + }); + }); +});