Revision: 3825
Author: [email protected]
Date: Thu Oct 22 14:07:39 2009
Log: Some extensions to Expression
http://codereview.appspot.com/135053
Added code to conservatively figure out what a typeof <the expression>
would yield, and conservative folding. These help with static
analysis and code optimization.
And added a static utility method
Operation.is(ParseTreeNode, Operator)
to replace a frequently appearing but hard to read idiom.
[email protected]
http://code.google.com/p/google-caja/source/detail?r=3825
Modified:
/trunk/src/com/google/caja/parser/js/AbstractExpression.java
/trunk/src/com/google/caja/parser/js/ArrayConstructor.java
/trunk/src/com/google/caja/parser/js/BooleanLiteral.java
/trunk/src/com/google/caja/parser/js/CajoledModuleExpression.java
/trunk/src/com/google/caja/parser/js/Declaration.java
/trunk/src/com/google/caja/parser/js/Expression.java
/trunk/src/com/google/caja/parser/js/FunctionConstructor.java
/trunk/src/com/google/caja/parser/js/NoChildren.java
/trunk/src/com/google/caja/parser/js/NullLiteral.java
/trunk/src/com/google/caja/parser/js/NumberLiteral.java
/trunk/src/com/google/caja/parser/js/ObjectConstructor.java
/trunk/src/com/google/caja/parser/js/Operation.java
/trunk/src/com/google/caja/parser/js/Parser.java
/trunk/src/com/google/caja/parser/js/QuotedExpression.java
/trunk/src/com/google/caja/parser/js/RealLiteral.java
/trunk/src/com/google/caja/parser/js/Reference.java
/trunk/src/com/google/caja/parser/js/RegexpLiteral.java
/trunk/src/com/google/caja/parser/js/StringLiteral.java
/trunk/src/com/google/caja/parser/quasiliteral/AlphaRenaming.java
/trunk/tests/com/google/caja/parser/js/ExpressionTest.java
/trunk/tests/com/google/caja/plugin/templates/JsConcatenatorTest.java
=======================================
--- /trunk/src/com/google/caja/parser/js/AbstractExpression.java Wed Aug 26
17:12:16 2009
+++ /trunk/src/com/google/caja/parser/js/AbstractExpression.java Thu Oct 22
14:07:39 2009
@@ -46,4 +46,6 @@
public Expression simplifyForSideEffect() { return this; }
public Boolean conditionResult() { return null; }
-}
+
+ public Expression fold() { return this; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/ArrayConstructor.java Wed Aug 26
17:12:16 2009
+++ /trunk/src/com/google/caja/parser/js/ArrayConstructor.java Thu Oct 22
14:07:39 2009
@@ -61,8 +61,7 @@
out.consume(",");
}
last = e;
- if (!(e instanceof Operation
- && Operator.COMMA == ((Operation) e).getOperator())) {
+ if (!Operation.is(e, Operator.COMMA)) {
e.render(rc);
} else {
out.consume("(");
@@ -73,4 +72,6 @@
out.mark(FilePosition.endOfOrNull(pos));
out.consume("]");
}
-}
+
+ public String typeOf() { return "object"; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/BooleanLiteral.java Wed Jul 15
11:24:34 2009
+++ /trunk/src/com/google/caja/parser/js/BooleanLiteral.java Thu Oct 22
14:07:39 2009
@@ -28,7 +28,7 @@
public final boolean value;
/** @param children unused. This ctor is provided for reflection. */
- @ReflectiveCtor
+ @ReflectiveCtor
public BooleanLiteral(
FilePosition pos, Boolean value, List<? extends ParseTreeNode>
children) {
this(pos, value);
@@ -48,4 +48,6 @@
public boolean getValueInBooleanContext() {
return value;
}
-}
+
+ public String typeOf() { return "boolean"; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/CajoledModuleExpression.java Wed
Jul 22 17:07:41 2009
+++ /trunk/src/com/google/caja/parser/js/CajoledModuleExpression.java Thu
Oct 22 14:07:39 2009
@@ -58,10 +58,12 @@
public void render(RenderContext r) {
ObjectConstructor oc = getCajoledModule().getModuleBody();
-
+
Expression e = (Expression) QuasiBuilder.substV(
- "___.prepareModule(@module);",
+ "___.prepareModule(@module)",
"module", oc);
e.render(r);
}
-}
+
+ public String typeOf() { return null; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/Declaration.java Wed Jul 15
11:24:34 2009
+++ /trunk/src/com/google/caja/parser/js/Declaration.java Thu Oct 22
14:07:39 2009
@@ -96,11 +96,10 @@
identifier.render(rc);
if (null != initializer) {
out.consume("=");
- boolean isComma = initializer instanceof Operation
- && Operator.COMMA == ((Operation) initializer).getOperator();
- if (isComma) out.consume("(");
+ boolean isComma = Operation.is(initializer, Operator.COMMA);
+ if (isComma) { out.consume("("); }
initializer.render(rc);
- if (isComma) out.consume(")");
+ if (isComma) { out.consume(")"); }
}
}
}
=======================================
--- /trunk/src/com/google/caja/parser/js/Expression.java Wed Aug 26
17:12:16 2009
+++ /trunk/src/com/google/caja/parser/js/Expression.java Thu Oct 22
14:07:39 2009
@@ -37,4 +37,23 @@
* an exception, then it may return any result.
*/
Boolean conditionResult();
-}
+
+ /**
+ * {...@code null} or the result of applying the {...@code typeof} operator
to
+ * the result of this expression.
+ *
+ * @return if the expression yields a result with the same {...@code typeof}
+ * in all environments in which it returns normally, then returns the
+ * result of applying the {...@code typeof} operator to the result.
+ * {...@code null} if the type cannot be determined.
+ * This method is conservative, so it may return null where it is
possible
+ * to prove a bound.
+ */
+ String typeOf();
+
+ /**
+ * This expression or a semantically equivalent simpler expression.
+ * This method does not recurse.
+ */
+ Expression fold();
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/FunctionConstructor.java Wed Aug
26 17:12:16 2009
+++ /trunk/src/com/google/caja/parser/js/FunctionConstructor.java Thu Oct
22 14:07:39 2009
@@ -108,4 +108,6 @@
out.consume(")");
body.renderBlock(rc, false);
}
-}
+
+ public String typeOf() { return "function"; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/NoChildren.java Fri Apr 17
11:17:36 2009
+++ /trunk/src/com/google/caja/parser/js/NoChildren.java Thu Oct 22
14:07:39 2009
@@ -28,4 +28,5 @@
@Override
public Object getValue() { throw new AssertionError(); }
public void render(RenderContext rc) { throw new AssertionError(); }
-}
+ public String typeOf() { throw new AssertionError(); }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/NullLiteral.java Wed Aug 26
17:12:16 2009
+++ /trunk/src/com/google/caja/parser/js/NullLiteral.java Thu Oct 22
14:07:39 2009
@@ -60,4 +60,6 @@
public boolean getValueInBooleanContext() {
return false;
}
-}
+
+ public String typeOf() { return "object"; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/NumberLiteral.java Wed Feb 11
20:17:12 2009
+++ /trunk/src/com/google/caja/parser/js/NumberLiteral.java Thu Oct 22
14:07:39 2009
@@ -179,4 +179,6 @@
+ ((n - 1) < 0 ? "e-" : "e+") + Math.abs(n - 1);
}
}
-}
+
+ public String typeOf() { return "number"; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/ObjectConstructor.java Wed Aug 26
17:12:16 2009
+++ /trunk/src/com/google/caja/parser/js/ObjectConstructor.java Thu Oct 22
14:07:39 2009
@@ -122,8 +122,7 @@
}
key.render(rc);
out.consume(":");
- if (!(value instanceof Operation
- && Operator.COMMA == ((Operation) value).getOperator())) {
+ if (!Operation.is(value, Operator.COMMA)) {
value.render(rc);
} else {
out.mark(value.getFilePosition());
@@ -135,4 +134,6 @@
out.mark(FilePosition.endOfOrNull(getFilePosition()));
out.consume("}");
}
-}
+
+ public String typeOf() { return "object"; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/Operation.java Wed Aug 26 17:12:16
2009
+++ /trunk/src/com/google/caja/parser/js/Operation.java Thu Oct 22 14:07:39
2009
@@ -38,6 +38,10 @@
if (null == op) { throw new NullPointerException(); }
createMutation().appendChildren(params).execute();
}
+
+ public static boolean is(ParseTreeNode n, Operator op) {
+ return n instanceof Operation && op == ((Operation) n).getOperator();
+ }
@Override
public List<? extends Expression> children() {
@@ -100,9 +104,15 @@
Expression sb = operands.get(1).simplifyForSideEffect();
return sb == null ? sa : sa == null ? sb : this;
}
- case VOID:
+ case TERNARY:
operands = children();
- return operands.get(0).simplifyForSideEffect();
+ if (operands.get(1).simplifyForSideEffect() == null
+ && operands.get(2).simplifyForSideEffect() == null) {
+ return operands.get(0).simplifyForSideEffect();
+ }
+ return this;
+ case NOT: case TYPEOF: return
children().get(0).simplifyForSideEffect();
+ case VOID: return children().get(0).simplifyForSideEffect();
default: break;
}
return this;
@@ -118,9 +128,7 @@
case CONSTRUCTOR:
return true;
case FUNCTION_CALL:
- return (operands.get(0) instanceof Operation
- && Operator.CONSTRUCTOR == operands.get(0).getValue())
- ? true : null;
+ return is(operands.get(0), Operator.CONSTRUCTOR) ? true : null;
case LOGICAL_AND:
opResult = operands.get(0).conditionResult();
if (opResult != null) {
@@ -336,7 +344,7 @@
// By inspection of the grammar, a slash after a function call
// or a member access is a division op, so no chance of
// lexical ambiguity here. These are also common enough that
- // unecessarily parenthesizing them things less readable.
+ // unnecessarily parenthesizing them things less readable.
if (childOp != Operator.FUNCTION_CALL
&& childOp != Operator.MEMBER_ACCESS) {
return true;
@@ -390,4 +398,233 @@
if (op == Operator.FUNCTION_CALL) { return Integer.MAX_VALUE; }
return op.getType().getArity();
}
-}
+
+ public String typeOf() {
+ List<? extends Expression> operands = children();
+ switch (getOperator()) {
+ case PRE_INCREMENT: case PRE_DECREMENT: case TO_NUMBER: case
NEGATION:
+ case INVERSE: case MULTIPLICATION: case DIVISION: case MODULUS:
+ case SUBTRACTION: case LSHIFT: case RSHIFT: case RUSHIFT:
+ case BITWISE_AND: case BITWISE_OR: case BITWISE_XOR:
+ case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_SUB:
+ case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_USH: case ASSIGN_AND:
+ case ASSIGN_XOR: case ASSIGN_OR:
+ return "number";
+ case DELETE: case IN: case NOT: case LESS_THAN: case GREATER_THAN:
+ case LESS_EQUALS: case GREATER_EQUALS: case INSTANCE_OF: case EQUAL:
+ case NOT_EQUAL: case STRICTLY_EQUAL: case STRICTLY_NOT_EQUAL:
+ return "boolean";
+ case VOID: return "undefined";
+ case LOGICAL_OR: case LOGICAL_AND: case TERNARY: {
+ String t1 = operands.get(operands.size() - 2).typeOf();
+ String t2 = operands.get(operands.size() - 1).typeOf();
+ return (t1 != null && t1.equals(t2)) ? t1 : null;
+ }
+ case ADDITION: {
+ String t1 = operands.get(operands.size() - 2).typeOf();
+ String t2 = operands.get(operands.size() - 1).typeOf();
+ if ("string".equals(t1) || "string".equals(t2)) { return "string";
}
+ if ("number".equals(t1) && "number".equals(t2)) { return "number";
}
+ return null;
+ }
+ case TYPEOF: return "string"; // Trippy
+ case COMMA:
+ return operands.get(1).typeOf();
+ default: return null;
+ }
+ }
+
+ @Override
+ public Expression fold() {
+ if (getOperator() == Operator.FUNCTION_CALL) { return foldCall(); }
+ switch (children().size()) {
+ case 1: return foldUnaryOp();
+ case 2: return foldBinaryOp();
+ case 3: return foldTernaryOp();
+ default: return this;
+ }
+ }
+
+ private Expression foldUnaryOp() {
+ Expression operand = children().get(0);
+ switch (op) {
+ case NOT:
+ Boolean b = operand.conditionResult();
+ if (b != null && operand.simplifyForSideEffect() == null) {
+ return new BooleanLiteral(getFilePosition(), !b);
+ }
+ return this;
+ case TYPEOF:
+ String type = operand.typeOf();
+ if (type != null && operand.simplifyForSideEffect() == null) {
+ return StringLiteral.valueOf(getFilePosition(), type);
+ }
+ return this;
+ default:
+ break;
+ }
+ Object v = toLiteralValue(operand);
+ if (v != null) {
+ FilePosition pos = getFilePosition();
+ switch (getOperator()) {
+ case NEGATION:
+ if (v instanceof Number) {
+ if (operand instanceof IntegerLiteral) {
+ long n = ((IntegerLiteral) operand).getValue().longValue();
+ if (n != Long.MIN_VALUE) {
+ return new IntegerLiteral(pos, -n);
+ }
+ }
+ return new RealLiteral(pos, -((Number) v).doubleValue());
+ }
+ break;
+ case INVERSE:
+ if (v instanceof Number) {
+ return new IntegerLiteral(pos, ~((Number) v).longValue());
+ }
+ break;
+ case TO_NUMBER:
+ if (v instanceof Number) { return children().get(0); }
+ break;
+ default: break;
+ }
+ }
+ return this;
+ }
+
+ private Expression foldTernaryOp() {
+ if (getOperator() == Operator.TERNARY) {
+ Expression cond = children().get(0);
+ Boolean condResult = cond.conditionResult();
+ if (condResult != null && cond.simplifyForSideEffect() == null) {
+ return children().get(condResult ? 1 : 2);
+ }
+ }
+ return this;
+ }
+
+ private Expression foldBinaryOp() {
+ List<? extends Expression> operands = children();
+ Expression left = operands.get(0), right = operands.get(1);
+ Operator op = getOperator();
+ if (op == Operator.LOGICAL_AND || op == Operator.LOGICAL_OR) {
+ Boolean bv = left.conditionResult();
+ if (bv != null) {
+ Expression sideEffect = left.simplifyForSideEffect();
+ if (bv == (op == Operator.LOGICAL_AND)) {
+ return sideEffect == null
+ ? right
+ : Operation.createInfix(Operator.COMMA, sideEffect, right);
+ } else {
+ return left;
+ }
+ }
+ } else if (op == Operator.MEMBER_ACCESS) {
+ if (left instanceof StringLiteral) {
+ Reference r = (Reference) right;
+ if ("length".equals(r.getIdentifierName())) {
+ return new IntegerLiteral(
+ getFilePosition(),
+ ((StringLiteral) left).getUnquotedValue().length());
+ }
+ }
+ }
+ Object lhs = toLiteralValue(left);
+ Object rhs = toLiteralValue(right);
+ if (lhs != null && rhs != null) {
+ FilePosition pos = getFilePosition();
+ switch (op) {
+ case EQUAL: case STRICTLY_EQUAL:
+ case NOT_EQUAL: case STRICTLY_NOT_EQUAL:
+ boolean isStrict = op == Operator.STRICTLY_EQUAL
+ || op == Operator.STRICTLY_NOT_EQUAL;
+ boolean isEqual = op == Operator.EQUAL
+ || op == Operator.STRICTLY_EQUAL;
+ boolean areEqual = lhs.equals(rhs);
+ if (isStrict || areEqual
+ || lhs.getClass().equals(rhs.getClass())) {
+ return new BooleanLiteral(pos, areEqual == isEqual);
+ }
+ break;
+ case ADDITION:
+ if ((lhs instanceof String) || (rhs instanceof String)) {
+ if (lhs instanceof Number) {
+ lhs = NumberLiteral.numberToString(
+ ((Number) lhs).doubleValue());
+ } else if (rhs instanceof Number) {
+ rhs = NumberLiteral.numberToString(
+ ((Number) rhs).doubleValue());
+ }
+ return StringLiteral.valueOf(pos, "" + lhs + rhs);
+ }
+ break;
+ default: break;
+ }
+ if (lhs instanceof Number && rhs instanceof Number) {
+ double a = ((Number) lhs).doubleValue();
+ double b = ((Number) rhs).doubleValue();
+ double result = Double.NaN;
+ switch (op) {
+ case ADDITION: result = a + b; break;
+ case SUBTRACTION: result = a - b; break;
+ case MULTIPLICATION: result = a * b; break;
+ case DIVISION: result = a / b; break;
+ case MODULUS: result = Math.IEEEremainder(a, b); break;
+ default: break;
+ }
+ if (!Double.isNaN(result)) {
+ return new RealLiteral(pos, result);
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Fold some common string operations like indexOf which show up
frequently
+ * in userAgent testing code. This helps when we've inlined the value of
+ * navigator.userAgent.
+ */
+ private Expression foldCall() {
+ List<? extends Expression> operands = children();
+ Expression fn = operands.get(0);
+ if (is(fn, Operator.MEMBER_ACCESS)
+ && fn.children().get(0) instanceof StringLiteral) {
+ StringLiteral sl = (StringLiteral) fn.children().get(0);
+ String methodName = ((Reference) fn.children().get(1))
+ .getIdentifierName();
+ if ("indexOf".equals(methodName)) {
+ if (operands.size() == 2) {
+ Expression target = operands.get(1);
+ if (target instanceof StringLiteral) {
+ int index = sl.getUnquotedValue().indexOf(
+ ((StringLiteral) target).getUnquotedValue());
+ return new IntegerLiteral(getFilePosition(), index);
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ private static final Object UNDEFINED = new Object() {
+ @Override public String toString() { return "undefined"; }
+ };
+ private static Object toLiteralValue(Expression e) {
+ if (is(e, Operator.VOID) && e.simplifyForSideEffect() == null) {
+ return UNDEFINED;
+ }
+ if (e instanceof Literal) {
+ if (e instanceof StringLiteral) {
+ return ((StringLiteral) e).getUnquotedValue();
+ }
+ if (e instanceof NumberLiteral) {
+ return ((NumberLiteral) e).doubleValue();
+ }
+ if (e instanceof BooleanLiteral || e instanceof NullLiteral) {
+ return e.getValue();
+ }
+ }
+ return null;
+ }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/Parser.java Thu Oct 15 15:42:13
2009
+++ /trunk/src/com/google/caja/parser/js/Parser.java Thu Oct 22 14:07:39
2009
@@ -46,7 +46,7 @@
*
* <p>
* The grammar below is a context-free representation of the grammar this
- * parser parses. It disagrees with EcmaScript 262 Edition 3 (ES3) where
+ * parser parses. It disagrees with EcmaScript 262 Edition 3 (ES3) where
* implementations disagree with ES3. The rules for semicolon insertion
and
* the possible backtracking in expressions needed to properly handle
* backtracking are commented thoroughly in code, since semicolon insertion
@@ -1418,6 +1418,8 @@
public void render(RenderContext rc) {
throw new UnsupportedOperationException();
}
+
+ public String typeOf() { return null; }
}
private static void issueLintWarningsForProblematicEscapes(
=======================================
--- /trunk/src/com/google/caja/parser/js/QuotedExpression.java Wed Jul 15
11:24:34 2009
+++ /trunk/src/com/google/caja/parser/js/QuotedExpression.java Thu Oct 22
14:07:39 2009
@@ -55,4 +55,6 @@
}
public Expression unquote() { return (Expression) children().get(0); }
-}
+
+ public String typeOf() { return null; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/RealLiteral.java Wed Jul 15
11:24:34 2009
+++ /trunk/src/com/google/caja/parser/js/RealLiteral.java Thu Oct 22
14:07:39 2009
@@ -60,7 +60,7 @@
out.consume(")");
} else if (Double.isInfinite(value)) {
out.consume("(");
- if (value >= 0) { out.consume("-"); }
+ if (value < 0) { out.consume("-"); }
out.consume("1");
out.consume("/");
out.consume("0");
=======================================
--- /trunk/src/com/google/caja/parser/js/Reference.java Wed Jul 15 11:24:34
2009
+++ /trunk/src/com/google/caja/parser/js/Reference.java Thu Oct 22 14:07:39
2009
@@ -78,4 +78,6 @@
}
ident.render(rc);
}
-}
+
+ public String typeOf() { return null; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/RegexpLiteral.java Mon Aug 31
13:09:32 2009
+++ /trunk/src/com/google/caja/parser/js/RegexpLiteral.java Thu Oct 22
14:07:39 2009
@@ -163,4 +163,7 @@
}
return true;
}
-}
+
+ // "function" on some interpreters, "object" on others.
+ public String typeOf() { return null; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/js/StringLiteral.java Wed Jul 15
11:24:34 2009
+++ /trunk/src/com/google/caja/parser/js/StringLiteral.java Thu Oct 22
14:07:39 2009
@@ -164,4 +164,6 @@
m.appendTail(sb);
return sb.toString();
}
-}
+
+ public String typeOf() { return "string"; }
+}
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/AlphaRenaming.java Thu
Sep 24 10:51:59 2009
+++ /trunk/src/com/google/caja/parser/quasiliteral/AlphaRenaming.java Thu
Oct 22 14:07:39 2009
@@ -197,12 +197,10 @@
} else if (n instanceof Reference) {
String name = ((Reference) n).getIdentifierName();
if (isOuter(name, s)) { outers.add(name); }
- } else if (n instanceof Operation) {
+ } else if (Operation.is(n, Operator.MEMBER_ACCESS)) {
Operation op = (Operation) n;
- if (op.getOperator() == Operator.MEMBER_ACCESS) {
- checkScope(op.children().get(0), s, outers);
- return;
- }
+ checkScope(op.children().get(0), s, outers);
+ return;
}
for (ParseTreeNode child : n.children()) {
checkScope(child, childScope, outers);
=======================================
--- /trunk/tests/com/google/caja/parser/js/ExpressionTest.java Wed Aug 26
17:12:16 2009
+++ /trunk/tests/com/google/caja/parser/js/ExpressionTest.java Thu Oct 22
14:07:39 2009
@@ -105,6 +105,93 @@
assertSimplified(null, "1 && 2");
assertSimplified("x + y", "x + y"); // coercion might be
side-effecting
}
+
+ public final void testTypeOf() throws Exception {
+ assertTypeOf("boolean", "true");
+ assertTypeOf("boolean", "false");
+ assertTypeOf("number", "0");
+ assertTypeOf("number", "1");
+ assertTypeOf("number", "-1.5");
+ assertTypeOf("number", "6.02e+23");
+ assertTypeOf("string", "''");
+ assertTypeOf("string", "'foo'");
+ assertTypeOf("object", "null");
+ assertTypeOf("object", "{}");
+ assertTypeOf("function", "function () {}");
+ assertTypeOf("boolean", "!x");
+ assertTypeOf("boolean", "!x || !y");
+ assertTypeOf(null, "!x || y");
+ assertTypeOf(null, "x || !y");
+ assertTypeOf("number", "+x");
+ assertTypeOf("number", "-x");
+ assertTypeOf("number", "x - y");
+ assertTypeOf(null, "y++"); // See browser-expectations.html
+ assertTypeOf(null, "z--");
+ assertTypeOf("number", "++y");
+ assertTypeOf("number", "--z");
+ assertTypeOf(null, "a + b");
+ assertTypeOf("number", "+a + 1");
+ assertTypeOf("string", "'' + b");
+ assertTypeOf("number", "'4' - 1");
+ assertTypeOf("string", "foo() ? 'bar' : 'baz'");
+ assertTypeOf(null, "foo() ? 'bar' : null");
+ assertTypeOf(null, "foo() ? bar : 'baz'");
+ assertTypeOf("string", "'bar' && ('' + baz)");
+ assertTypeOf(null, "'bar' && null");
+ assertTypeOf(null, "bar && 'baz'");
+ assertTypeOf("boolean", "foo(), !x");
+ assertTypeOf(null, "'' + foo(), x");
+ assertTypeOf(null, "/./");
+ }
+
+ public final void testFold() throws Exception {
+ assertFolded("4.0", "1 + 3");
+ assertFolded("'13'", "1 + '3'");
+ assertFolded("'13'", "'1' + 3");
+ assertFolded("'13'", "'1' + '3'");
+ assertFolded("'-1.5'", "'' + -1.5");
+ assertFolded("'undefined'", "'' + void 0");
+ assertFolded("'null'", "null + ''");
+ assertFolded("null + 0", "null + 0"); // 0 in JS
+ assertFolded("-2.0", "1 - 3");
+ assertFolded("4.0", "2 * 2");
+ assertFolded("true", "!''");
+ assertFolded("true", "!0");
+ assertFolded("false", "!'0'");
+ assertFolded("true", "!null");
+ assertFolded("true", "!(void 0)");
+ assertFolded("! (void foo())", "!(void foo())");
+ assertFolded("false", "!(4,true)");
+ assertFolded("! (foo() || true)", "!(foo()||true)");
+ assertFolded("true", "'foo' == 'foo'");
+ assertFolded("true", "'foo' === 'foo'");
+ assertFolded("false", "'foo' == 'bar'");
+ assertFolded("false", "'foo' === 'bar'");
+ assertFolded("false", "4 === '4'");
+ assertFolded("4 == '4'", "4 == '4'");
+ assertFolded("false", "'foo' != 'foo'");
+ assertFolded("false", "'foo' !== 'foo'");
+ assertFolded("true", "'foo' != 'bar'");
+ assertFolded("true", "'foo' !== 'bar'");
+ assertFolded("true", "4 !== '4'");
+ assertFolded("4 != '4'", "4 != '4'");
+ assertFolded("0.5", "1 / 2");
+ assertFolded("1 / '2'", "1 / '2'");
+ assertFolded("(1/0)", "1 / 0");
+ assertFolded("(-1/0)", "-1 / 0");
+ assertFolded("0 / 0", "0 / 0");
+ assertFolded("1.0", "1 % 3");
+ assertFolded("1.0", "1 % -3");
+ assertFolded("-1.0", "-1 % 3");
+ assertFolded("-1.0", "-1 % -3");
+ assertFolded("4.0", "4.0");
+ assertFolded("4.0", "+4.0");
+ assertFolded("-1", "~0");
+ assertFolded("-1", "-1");
+ assertFolded("3", "'foo'.length");
+ assertFolded("1", "'foo'.indexOf('o')");
+ assertFolded("-1", "'foo'.indexOf('bar')");
+ }
private void assertSimplified(String golden, String input)
throws ParseException {
@@ -115,4 +202,24 @@
}
assertEquals(input, render(jsExpr(fromString(golden))),
render(simple));
}
-}
+
+ private void assertTypeOf(String type, String expr) throws
ParseException {
+ assertEquals(type, jsExpr(fromString(expr)).typeOf());
+ }
+
+ private void assertFolded(String result, String expr) throws
ParseException {
+ Expression input = jsExpr(fromString(expr));
+ // Fold operands so we can test negative numbers.
+ if (input instanceof Operation) {
+ Operation op = (Operation) input;
+ for (Expression operand : op.children()) {
+ if (Operation.is(operand, Operator.NEGATION)
+ && operand.children().get(0) instanceof NumberLiteral) {
+ op.replaceChild(operand.fold(), operand);
+ }
+ }
+ }
+ Expression actual = input.fold();
+ assertEquals(result, actual != null ? render(actual) : null);
+ }
+}
=======================================
--- /trunk/tests/com/google/caja/plugin/templates/JsConcatenatorTest.java
Thu Aug 27 13:59:50 2009
+++ /trunk/tests/com/google/caja/plugin/templates/JsConcatenatorTest.java
Thu Oct 22 14:07:39 2009
@@ -106,7 +106,7 @@
JsConcatenator concat = new JsConcatenator();
for (String part : parts) {
Expression e = jsExpr(fromString(part));
- if (e instanceof Operation && Operator.VOID == e.getValue()) {
+ if (Operation.is(e, Operator.VOID)) {
concat.forSideEffect(((Operation) e).children().get(0));
} else {
concat.append(e);