add t.evaluate method

This commit is contained in:
Sebastian McKenzie 2015-03-01 17:01:46 +11:00
parent 82833a8901
commit 78434bb404
4 changed files with 138 additions and 11 deletions

View File

@ -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, {

View File

@ -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 {

View File

@ -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);

View File

@ -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")));
});
});