1717 lines
64 KiB
Java
1717 lines
64 KiB
Java
/*
|
|
* Copyright 2004 The Closure Compiler Authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.google.javascript.jscomp;
|
|
|
|
import com.google.common.base.Preconditions;
|
|
import com.google.common.base.Predicates;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
|
|
import com.google.javascript.rhino.IR;
|
|
import com.google.javascript.rhino.Node;
|
|
import com.google.javascript.rhino.Token;
|
|
import com.google.javascript.rhino.jstype.TernaryValue;
|
|
|
|
import junit.framework.TestCase;
|
|
|
|
import java.util.Collection;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* Tests for NodeUtil
|
|
*/
|
|
public class NodeUtilTest extends TestCase {
|
|
|
|
private static Node parse(String js) {
|
|
Compiler compiler = new Compiler();
|
|
compiler.initCompilerOptionsIfTesting();
|
|
compiler.getOptions().setLanguageIn(LanguageMode.ECMASCRIPT5);
|
|
Node n = compiler.parseTestCode(js);
|
|
assertEquals(0, compiler.getErrorCount());
|
|
return n;
|
|
}
|
|
|
|
static Node getNode(String js) {
|
|
Node root = parse("var a=(" + js + ");");
|
|
Node expr = root.getFirstChild();
|
|
Node var = expr.getFirstChild();
|
|
return var.getFirstChild();
|
|
}
|
|
|
|
public void testIsLiteralOrConstValue() {
|
|
assertLiteralAndImmutable(getNode("10"));
|
|
assertLiteralAndImmutable(getNode("-10"));
|
|
assertLiteralButNotImmutable(getNode("[10, 20]"));
|
|
assertLiteralButNotImmutable(getNode("{'a': 20}"));
|
|
assertLiteralButNotImmutable(getNode("[10, , 1.0, [undefined], 'a']"));
|
|
assertLiteralButNotImmutable(getNode("/abc/"));
|
|
assertLiteralAndImmutable(getNode("\"string\""));
|
|
assertLiteralAndImmutable(getNode("'aaa'"));
|
|
assertLiteralAndImmutable(getNode("null"));
|
|
assertLiteralAndImmutable(getNode("undefined"));
|
|
assertLiteralAndImmutable(getNode("void 0"));
|
|
assertNotLiteral(getNode("abc"));
|
|
assertNotLiteral(getNode("[10, foo(), 20]"));
|
|
assertNotLiteral(getNode("foo()"));
|
|
assertNotLiteral(getNode("c + d"));
|
|
assertNotLiteral(getNode("{'a': foo()}"));
|
|
assertNotLiteral(getNode("void foo()"));
|
|
}
|
|
|
|
public void assertLiteralAndImmutable(Node n) {
|
|
assertTrue(NodeUtil.isLiteralValue(n, true));
|
|
assertTrue(NodeUtil.isLiteralValue(n, false));
|
|
assertTrue(NodeUtil.isImmutableValue(n));
|
|
}
|
|
|
|
public void assertLiteralButNotImmutable(Node n) {
|
|
assertTrue(NodeUtil.isLiteralValue(n, true));
|
|
assertTrue(NodeUtil.isLiteralValue(n, false));
|
|
assertFalse(NodeUtil.isImmutableValue(n));
|
|
}
|
|
|
|
public void assertNotLiteral(Node n) {
|
|
assertFalse(NodeUtil.isLiteralValue(n, true));
|
|
assertFalse(NodeUtil.isLiteralValue(n, false));
|
|
assertFalse(NodeUtil.isImmutableValue(n));
|
|
}
|
|
|
|
public void testGetBooleanValue() {
|
|
assertPureBooleanTrue("true");
|
|
assertPureBooleanTrue("10");
|
|
assertPureBooleanTrue("'0'");
|
|
assertPureBooleanTrue("/a/");
|
|
assertPureBooleanTrue("{}");
|
|
assertPureBooleanTrue("[]");
|
|
assertPureBooleanFalse("false");
|
|
assertPureBooleanFalse("null");
|
|
assertPureBooleanFalse("0");
|
|
assertPureBooleanFalse("''");
|
|
assertPureBooleanFalse("undefined");
|
|
assertPureBooleanFalse("void 0");
|
|
assertPureBooleanUnknown("void foo()");
|
|
assertPureBooleanUnknown("b");
|
|
assertPureBooleanUnknown("-'0.0'");
|
|
|
|
// Known but getBooleanValue return false for expressions with side-effects
|
|
assertPureBooleanUnknown("{a:foo()}");
|
|
assertPureBooleanUnknown("[foo()]");
|
|
}
|
|
|
|
private void assertPureBooleanTrue(String val) {
|
|
assertEquals(TernaryValue.TRUE, NodeUtil.getPureBooleanValue(getNode(val)));
|
|
}
|
|
|
|
private void assertPureBooleanFalse(String val) {
|
|
assertEquals(
|
|
TernaryValue.FALSE, NodeUtil.getPureBooleanValue(getNode(val)));
|
|
}
|
|
|
|
private void assertPureBooleanUnknown(String val) {
|
|
assertEquals(
|
|
TernaryValue.UNKNOWN, NodeUtil.getPureBooleanValue(getNode(val)));
|
|
}
|
|
|
|
public void testGetExpressionBooleanValue() {
|
|
assertImpureBooleanTrue("a=true");
|
|
assertImpureBooleanFalse("a=false");
|
|
|
|
assertImpureBooleanTrue("a=(false,true)");
|
|
assertImpureBooleanFalse("a=(true,false)");
|
|
|
|
assertImpureBooleanTrue("a=(false || true)");
|
|
assertImpureBooleanFalse("a=(true && false)");
|
|
|
|
assertImpureBooleanTrue("a=!(true && false)");
|
|
|
|
assertImpureBooleanTrue("a,true");
|
|
assertImpureBooleanFalse("a,false");
|
|
|
|
assertImpureBooleanTrue("true||false");
|
|
assertImpureBooleanFalse("false||false");
|
|
|
|
assertImpureBooleanTrue("true&&true");
|
|
assertImpureBooleanFalse("true&&false");
|
|
|
|
assertImpureBooleanFalse("!true");
|
|
assertImpureBooleanTrue("!false");
|
|
assertImpureBooleanTrue("!''");
|
|
|
|
// Assignment ops other than ASSIGN are unknown.
|
|
assertImpureBooleanUnknown("a *= 2");
|
|
|
|
// Complex expressions that contain anything other then "=", ",", or "!" are
|
|
// unknown.
|
|
assertImpureBooleanUnknown("2 + 2");
|
|
|
|
assertImpureBooleanTrue("a=1");
|
|
assertImpureBooleanTrue("a=/a/");
|
|
assertImpureBooleanTrue("a={}");
|
|
|
|
assertImpureBooleanTrue("true");
|
|
assertImpureBooleanTrue("10");
|
|
assertImpureBooleanTrue("'0'");
|
|
assertImpureBooleanTrue("/a/");
|
|
assertImpureBooleanTrue("{}");
|
|
assertImpureBooleanTrue("[]");
|
|
assertImpureBooleanFalse("false");
|
|
assertImpureBooleanFalse("null");
|
|
assertImpureBooleanFalse("0");
|
|
assertImpureBooleanFalse("''");
|
|
assertImpureBooleanFalse("undefined");
|
|
assertImpureBooleanFalse("void 0");
|
|
assertImpureBooleanFalse("void foo()");
|
|
|
|
assertImpureBooleanTrue("a?true:true");
|
|
assertImpureBooleanFalse("a?false:false");
|
|
assertImpureBooleanUnknown("a?true:false");
|
|
assertImpureBooleanUnknown("a?true:foo()");
|
|
|
|
assertImpureBooleanUnknown("b");
|
|
assertImpureBooleanUnknown("-'0.0'");
|
|
|
|
assertImpureBooleanTrue("{a:foo()}");
|
|
assertImpureBooleanTrue("[foo()]");
|
|
}
|
|
|
|
private void assertImpureBooleanTrue(String val) {
|
|
assertEquals(TernaryValue.TRUE,
|
|
NodeUtil.getImpureBooleanValue(getNode(val)));
|
|
}
|
|
|
|
private void assertImpureBooleanFalse(String val) {
|
|
assertEquals(TernaryValue.FALSE,
|
|
NodeUtil.getImpureBooleanValue(getNode(val)));
|
|
}
|
|
|
|
private void assertImpureBooleanUnknown(String val) {
|
|
assertEquals(TernaryValue.UNKNOWN,
|
|
NodeUtil.getImpureBooleanValue(getNode(val)));
|
|
}
|
|
|
|
public void testGetStringValue() {
|
|
assertEquals("true", NodeUtil.getStringValue(getNode("true")));
|
|
assertEquals("10", NodeUtil.getStringValue(getNode("10")));
|
|
assertEquals("1", NodeUtil.getStringValue(getNode("1.0")));
|
|
assertEquals("0", NodeUtil.getStringValue(getNode("'0'")));
|
|
assertEquals(null, NodeUtil.getStringValue(getNode("/a/")));
|
|
assertEquals("[object Object]", NodeUtil.getStringValue(getNode("{}")));
|
|
assertEquals("", NodeUtil.getStringValue(getNode("[]")));
|
|
assertEquals("false", NodeUtil.getStringValue(getNode("false")));
|
|
assertEquals("null", NodeUtil.getStringValue(getNode("null")));
|
|
assertEquals("0", NodeUtil.getStringValue(getNode("0")));
|
|
assertEquals("", NodeUtil.getStringValue(getNode("''")));
|
|
assertEquals("undefined", NodeUtil.getStringValue(getNode("undefined")));
|
|
assertEquals("undefined", NodeUtil.getStringValue(getNode("void 0")));
|
|
assertEquals("undefined", NodeUtil.getStringValue(getNode("void foo()")));
|
|
|
|
assertEquals("NaN", NodeUtil.getStringValue(getNode("NaN")));
|
|
assertEquals("Infinity", NodeUtil.getStringValue(getNode("Infinity")));
|
|
assertEquals(null, NodeUtil.getStringValue(getNode("x")));
|
|
}
|
|
|
|
public void testGetArrayStringValue() {
|
|
assertEquals("", NodeUtil.getStringValue(getNode("[]")));
|
|
assertEquals("", NodeUtil.getStringValue(getNode("['']")));
|
|
assertEquals("", NodeUtil.getStringValue(getNode("[null]")));
|
|
assertEquals("", NodeUtil.getStringValue(getNode("[undefined]")));
|
|
assertEquals("", NodeUtil.getStringValue(getNode("[void 0]")));
|
|
assertEquals("NaN", NodeUtil.getStringValue(getNode("[NaN]")));
|
|
assertEquals(",", NodeUtil.getStringValue(getNode("[,'']")));
|
|
assertEquals(",,", NodeUtil.getStringValue(getNode("[[''],[''],['']]")));
|
|
assertEquals("1,2", NodeUtil.getStringValue(getNode("[[1.0],[2.0]]")));
|
|
assertEquals(null, NodeUtil.getStringValue(getNode("[a]")));
|
|
assertEquals(null, NodeUtil.getStringValue(getNode("[1,a]")));
|
|
}
|
|
|
|
public void testIsObjectLiteralKey1() throws Exception {
|
|
testIsObjectLiteralKey(
|
|
parseExpr("({})"), false);
|
|
testIsObjectLiteralKey(
|
|
parseExpr("a"), false);
|
|
testIsObjectLiteralKey(
|
|
parseExpr("'a'"), false);
|
|
testIsObjectLiteralKey(
|
|
parseExpr("1"), false);
|
|
testIsObjectLiteralKey(
|
|
parseExpr("({a: 1})").getFirstChild(), true);
|
|
testIsObjectLiteralKey(
|
|
parseExpr("({1: 1})").getFirstChild(), true);
|
|
testIsObjectLiteralKey(
|
|
parseExpr("({get a(){}})").getFirstChild(), true);
|
|
testIsObjectLiteralKey(
|
|
parseExpr("({set a(b){}})").getFirstChild(), true);
|
|
}
|
|
|
|
private Node parseExpr(String js) {
|
|
Compiler compiler = new Compiler();
|
|
CompilerOptions options = new CompilerOptions();
|
|
options.setLanguageIn(LanguageMode.ECMASCRIPT5);
|
|
compiler.initOptions(options);
|
|
Node root = compiler.parseTestCode(js);
|
|
return root.getFirstChild().getFirstChild();
|
|
}
|
|
|
|
private void testIsObjectLiteralKey(Node node, boolean expected) {
|
|
assertEquals(expected, NodeUtil.isObjectLitKey(node, node.getParent()));
|
|
}
|
|
|
|
public void testGetFunctionName1() throws Exception {
|
|
Compiler compiler = new Compiler();
|
|
Node parent = compiler.parseTestCode("function name(){}");
|
|
|
|
testGetFunctionName(parent.getFirstChild(), "name");
|
|
}
|
|
|
|
public void testGetFunctionName2() throws Exception {
|
|
Compiler compiler = new Compiler();
|
|
Node parent = compiler.parseTestCode("var name = function(){}")
|
|
.getFirstChild().getFirstChild();
|
|
|
|
testGetFunctionName(parent.getFirstChild(), "name");
|
|
}
|
|
|
|
public void testGetFunctionName3() throws Exception {
|
|
Compiler compiler = new Compiler();
|
|
Node parent = compiler.parseTestCode("qualified.name = function(){}")
|
|
.getFirstChild().getFirstChild();
|
|
|
|
testGetFunctionName(parent.getLastChild(), "qualified.name");
|
|
}
|
|
|
|
public void testGetFunctionName4() throws Exception {
|
|
Compiler compiler = new Compiler();
|
|
Node parent = compiler.parseTestCode("var name2 = function name1(){}")
|
|
.getFirstChild().getFirstChild();
|
|
|
|
testGetFunctionName(parent.getFirstChild(), "name2");
|
|
}
|
|
|
|
public void testGetFunctionName5() throws Exception {
|
|
Compiler compiler = new Compiler();
|
|
Node n = compiler.parseTestCode("qualified.name2 = function name1(){}");
|
|
Node parent = n.getFirstChild().getFirstChild();
|
|
|
|
testGetFunctionName(parent.getLastChild(), "qualified.name2");
|
|
}
|
|
|
|
private void testGetFunctionName(Node function, String name) {
|
|
assertEquals(Token.FUNCTION, function.getType());
|
|
assertEquals(name, NodeUtil.getFunctionName(function));
|
|
}
|
|
|
|
public void testContainsFunctionDeclaration() {
|
|
assertTrue(NodeUtil.containsFunction(
|
|
getNode("function foo(){}")));
|
|
assertTrue(NodeUtil.containsFunction(
|
|
getNode("(b?function(){}:null)")));
|
|
|
|
assertFalse(NodeUtil.containsFunction(
|
|
getNode("(b?foo():null)")));
|
|
assertFalse(NodeUtil.containsFunction(
|
|
getNode("foo()")));
|
|
}
|
|
|
|
private void assertSideEffect(boolean se, String js) {
|
|
Node n = parse(js);
|
|
assertEquals(se, NodeUtil.mayHaveSideEffects(n.getFirstChild()));
|
|
}
|
|
|
|
private void assertSideEffect(boolean se, String js, boolean globalRegExp) {
|
|
Node n = parse(js);
|
|
Compiler compiler = new Compiler();
|
|
compiler.setHasRegExpGlobalReferences(globalRegExp);
|
|
assertEquals(se, NodeUtil.mayHaveSideEffects(n.getFirstChild(), compiler));
|
|
}
|
|
|
|
public void testMayHaveSideEffects() {
|
|
assertSideEffect(true, "i++");
|
|
assertSideEffect(true, "[b, [a, i++]]");
|
|
assertSideEffect(true, "i=3");
|
|
assertSideEffect(true, "[0, i=3]");
|
|
assertSideEffect(true, "b()");
|
|
assertSideEffect(true, "[1, b()]");
|
|
assertSideEffect(true, "b.b=4");
|
|
assertSideEffect(true, "b.b--");
|
|
assertSideEffect(true, "i--");
|
|
assertSideEffect(true, "a[0][i=4]");
|
|
assertSideEffect(true, "a += 3");
|
|
assertSideEffect(true, "a, b, z += 4");
|
|
assertSideEffect(true, "a ? c : d++");
|
|
assertSideEffect(true, "a + c++");
|
|
assertSideEffect(true, "a + c - d()");
|
|
assertSideEffect(true, "a + c - d()");
|
|
|
|
assertSideEffect(true, "function foo() {}");
|
|
assertSideEffect(true, "while(true);");
|
|
assertSideEffect(true, "if(true){a()}");
|
|
|
|
assertSideEffect(false, "if(true){a}");
|
|
assertSideEffect(false, "(function() { })");
|
|
assertSideEffect(false, "(function() { i++ })");
|
|
assertSideEffect(false, "[function a(){}]");
|
|
|
|
assertSideEffect(false, "a");
|
|
assertSideEffect(false, "[b, c [d, [e]]]");
|
|
assertSideEffect(false, "({a: x, b: y, c: z})");
|
|
assertSideEffect(false, "/abc/gi");
|
|
assertSideEffect(false, "'a'");
|
|
assertSideEffect(false, "0");
|
|
assertSideEffect(false, "a + c");
|
|
assertSideEffect(false, "'c' + a[0]");
|
|
assertSideEffect(false, "a[0][1]");
|
|
assertSideEffect(false, "'a' + c");
|
|
assertSideEffect(false, "'a' + a.name");
|
|
assertSideEffect(false, "1, 2, 3");
|
|
assertSideEffect(false, "a, b, 3");
|
|
assertSideEffect(false, "(function(a, b) { })");
|
|
assertSideEffect(false, "a ? c : d");
|
|
assertSideEffect(false, "'1' + navigator.userAgent");
|
|
|
|
assertSideEffect(false, "new RegExp('foobar', 'i')");
|
|
assertSideEffect(true, "new RegExp(SomethingWacky(), 'i')");
|
|
assertSideEffect(false, "new Array()");
|
|
assertSideEffect(false, "new Array");
|
|
assertSideEffect(false, "new Array(4)");
|
|
assertSideEffect(false, "new Array('a', 'b', 'c')");
|
|
assertSideEffect(true, "new SomeClassINeverHeardOf()");
|
|
assertSideEffect(true, "new SomeClassINeverHeardOf()");
|
|
|
|
assertSideEffect(false, "({}).foo = 4");
|
|
assertSideEffect(false, "([]).foo = 4");
|
|
assertSideEffect(false, "(function() {}).foo = 4");
|
|
|
|
assertSideEffect(true, "this.foo = 4");
|
|
assertSideEffect(true, "a.foo = 4");
|
|
assertSideEffect(true, "(function() { return n; })().foo = 4");
|
|
assertSideEffect(true, "([]).foo = bar()");
|
|
|
|
assertSideEffect(false, "undefined");
|
|
assertSideEffect(false, "void 0");
|
|
assertSideEffect(true, "void foo()");
|
|
assertSideEffect(false, "-Infinity");
|
|
assertSideEffect(false, "Infinity");
|
|
assertSideEffect(false, "NaN");
|
|
|
|
assertSideEffect(false, "({}||[]).foo = 2;");
|
|
assertSideEffect(false, "(true ? {} : []).foo = 2;");
|
|
assertSideEffect(false, "({},[]).foo = 2;");
|
|
|
|
assertSideEffect(true, "delete a.b");
|
|
}
|
|
|
|
public void testObjectMethodSideEffects() {
|
|
// "toString" and "valueOf" are assumed to be side-effect free
|
|
assertSideEffect(false, "o.toString()");
|
|
assertSideEffect(false, "o.valueOf()");
|
|
|
|
// other methods depend on the extern definitions
|
|
assertSideEffect(true, "o.watch()");
|
|
}
|
|
|
|
public void testRegExpSideEffect() {
|
|
// A RegExp Object by itself doesn't have any side-effects
|
|
assertSideEffect(false, "/abc/gi", true);
|
|
assertSideEffect(false, "/abc/gi", false);
|
|
|
|
// RegExp instance methods have global side-effects, so whether they are
|
|
// considered side-effect free depends on whether the global properties
|
|
// are referenced.
|
|
assertSideEffect(true, "(/abc/gi).test('')", true);
|
|
assertSideEffect(false, "(/abc/gi).test('')", false);
|
|
assertSideEffect(true, "(/abc/gi).test(a)", true);
|
|
assertSideEffect(false, "(/abc/gi).test(b)", false);
|
|
|
|
assertSideEffect(true, "(/abc/gi).exec('')", true);
|
|
assertSideEffect(false, "(/abc/gi).exec('')", false);
|
|
|
|
// Some RegExp object method that may have side-effects.
|
|
assertSideEffect(true, "(/abc/gi).foo('')", true);
|
|
assertSideEffect(true, "(/abc/gi).foo('')", false);
|
|
|
|
// Try the string RegExp ops.
|
|
assertSideEffect(true, "''.match('a')", true);
|
|
assertSideEffect(false, "''.match('a')", false);
|
|
assertSideEffect(true, "''.match(/(a)/)", true);
|
|
assertSideEffect(false, "''.match(/(a)/)", false);
|
|
|
|
assertSideEffect(true, "''.replace('a')", true);
|
|
assertSideEffect(false, "''.replace('a')", false);
|
|
|
|
assertSideEffect(true, "''.search('a')", true);
|
|
assertSideEffect(false, "''.search('a')", false);
|
|
|
|
assertSideEffect(true, "''.split('a')", true);
|
|
assertSideEffect(false, "''.split('a')", false);
|
|
|
|
// Some non-RegExp string op that may have side-effects.
|
|
assertSideEffect(true, "''.foo('a')", true);
|
|
assertSideEffect(true, "''.foo('a')", false);
|
|
|
|
// 'a' might be a RegExp object with the 'g' flag, in which case
|
|
// the state might change by running any of the string ops.
|
|
// Specifically, using these methods resets the "lastIndex" if used
|
|
// in combination with a RegExp instance "exec" method.
|
|
assertSideEffect(true, "''.match(a)", true);
|
|
assertSideEffect(true, "''.match(a)", false);
|
|
}
|
|
|
|
private void assertMutableState(boolean se, String js) {
|
|
Node n = parse(js);
|
|
assertEquals(se, NodeUtil.mayEffectMutableState(n.getFirstChild()));
|
|
}
|
|
|
|
public void testMayEffectMutableState() {
|
|
assertMutableState(true, "i++");
|
|
assertMutableState(true, "[b, [a, i++]]");
|
|
assertMutableState(true, "i=3");
|
|
assertMutableState(true, "[0, i=3]");
|
|
assertMutableState(true, "b()");
|
|
assertMutableState(true, "void b()");
|
|
assertMutableState(true, "[1, b()]");
|
|
assertMutableState(true, "b.b=4");
|
|
assertMutableState(true, "b.b--");
|
|
assertMutableState(true, "i--");
|
|
assertMutableState(true, "a[0][i=4]");
|
|
assertMutableState(true, "a += 3");
|
|
assertMutableState(true, "a, b, z += 4");
|
|
assertMutableState(true, "a ? c : d++");
|
|
assertMutableState(true, "a + c++");
|
|
assertMutableState(true, "a + c - d()");
|
|
assertMutableState(true, "a + c - d()");
|
|
|
|
assertMutableState(true, "function foo() {}");
|
|
assertMutableState(true, "while(true);");
|
|
assertMutableState(true, "if(true){a()}");
|
|
|
|
assertMutableState(false, "if(true){a}");
|
|
assertMutableState(true, "(function() { })");
|
|
assertMutableState(true, "(function() { i++ })");
|
|
assertMutableState(true, "[function a(){}]");
|
|
|
|
assertMutableState(false, "a");
|
|
assertMutableState(true, "[b, c [d, [e]]]");
|
|
assertMutableState(true, "({a: x, b: y, c: z})");
|
|
// Note: RegExp objects are not immutable, for instance, the exec
|
|
// method maintains state for "global" searches.
|
|
assertMutableState(true, "/abc/gi");
|
|
assertMutableState(false, "'a'");
|
|
assertMutableState(false, "0");
|
|
assertMutableState(false, "a + c");
|
|
assertMutableState(false, "'c' + a[0]");
|
|
assertMutableState(false, "a[0][1]");
|
|
assertMutableState(false, "'a' + c");
|
|
assertMutableState(false, "'a' + a.name");
|
|
assertMutableState(false, "1, 2, 3");
|
|
assertMutableState(false, "a, b, 3");
|
|
assertMutableState(true, "(function(a, b) { })");
|
|
assertMutableState(false, "a ? c : d");
|
|
assertMutableState(false, "'1' + navigator.userAgent");
|
|
|
|
assertMutableState(true, "new RegExp('foobar', 'i')");
|
|
assertMutableState(true, "new RegExp(SomethingWacky(), 'i')");
|
|
assertMutableState(true, "new Array()");
|
|
assertMutableState(true, "new Array");
|
|
assertMutableState(true, "new Array(4)");
|
|
assertMutableState(true, "new Array('a', 'b', 'c')");
|
|
assertMutableState(true, "new SomeClassINeverHeardOf()");
|
|
}
|
|
|
|
|
|
public void testIsFunctionExpression() {
|
|
assertContainsAnonFunc(true, "(function(){})");
|
|
assertContainsAnonFunc(true, "[function a(){}]");
|
|
assertContainsAnonFunc(false, "{x: function a(){}}");
|
|
assertContainsAnonFunc(true, "(function a(){})()");
|
|
assertContainsAnonFunc(true, "x = function a(){};");
|
|
assertContainsAnonFunc(true, "var x = function a(){};");
|
|
assertContainsAnonFunc(true, "if (function a(){});");
|
|
assertContainsAnonFunc(true, "while (function a(){});");
|
|
assertContainsAnonFunc(true, "do; while (function a(){});");
|
|
assertContainsAnonFunc(true, "for (function a(){};;);");
|
|
assertContainsAnonFunc(true, "for (;function a(){};);");
|
|
assertContainsAnonFunc(true, "for (;;function a(){});");
|
|
assertContainsAnonFunc(true, "for (p in function a(){});");
|
|
assertContainsAnonFunc(true, "with (function a(){}) {}");
|
|
assertContainsAnonFunc(false, "function a(){}");
|
|
assertContainsAnonFunc(false, "if (x) function a(){};");
|
|
assertContainsAnonFunc(false, "if (x) { function a(){} }");
|
|
assertContainsAnonFunc(false, "if (x); else function a(){};");
|
|
assertContainsAnonFunc(false, "while (x) function a(){};");
|
|
assertContainsAnonFunc(false, "do function a(){} while (0);");
|
|
assertContainsAnonFunc(false, "for (;;) function a(){}");
|
|
assertContainsAnonFunc(false, "for (p in o) function a(){};");
|
|
assertContainsAnonFunc(false, "with (x) function a(){}");
|
|
}
|
|
|
|
private void assertContainsAnonFunc(boolean expected, String js) {
|
|
Node funcParent = findParentOfFuncDescendant(parse(js));
|
|
assertNotNull("Expected function node in parse tree of: " + js, funcParent);
|
|
Node funcNode = getFuncChild(funcParent);
|
|
assertEquals(expected, NodeUtil.isFunctionExpression(funcNode));
|
|
}
|
|
|
|
private Node findParentOfFuncDescendant(Node n) {
|
|
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
|
|
if (c.isFunction()) {
|
|
return n;
|
|
}
|
|
Node result = findParentOfFuncDescendant(c);
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private Node getFuncChild(Node n) {
|
|
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
|
|
if (c.isFunction()) {
|
|
return c;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void testContainsType() {
|
|
assertTrue(NodeUtil.containsType(
|
|
parse("this"), Token.THIS));
|
|
assertTrue(NodeUtil.containsType(
|
|
parse("function foo(){}(this)"), Token.THIS));
|
|
assertTrue(NodeUtil.containsType(
|
|
parse("b?this:null"), Token.THIS));
|
|
|
|
assertFalse(NodeUtil.containsType(
|
|
parse("a"), Token.THIS));
|
|
assertFalse(NodeUtil.containsType(
|
|
parse("function foo(){}"), Token.THIS));
|
|
assertFalse(NodeUtil.containsType(
|
|
parse("(b?foo():null)"), Token.THIS));
|
|
}
|
|
|
|
public void testReferencesThis() {
|
|
assertTrue(NodeUtil.referencesThis(
|
|
parse("this")));
|
|
// Don't descend into functions (starts at the script node)
|
|
assertFalse(NodeUtil.referencesThis(
|
|
parse("function foo(){this}")));
|
|
// But starting with a function properly check for 'this'
|
|
Node n = parse("function foo(){this}").getFirstChild();
|
|
assertEquals(n.getType(), Token.FUNCTION);
|
|
assertTrue(NodeUtil.referencesThis(n));
|
|
assertTrue(NodeUtil.referencesThis(
|
|
parse("b?this:null")));
|
|
|
|
assertFalse(NodeUtil.referencesThis(
|
|
parse("a")));
|
|
n = parse("function foo(){}").getFirstChild();
|
|
assertEquals(n.getType(), Token.FUNCTION);
|
|
assertFalse(NodeUtil.referencesThis(n));
|
|
assertFalse(NodeUtil.referencesThis(
|
|
parse("(b?foo():null)")));
|
|
}
|
|
|
|
public void testGetNodeTypeReferenceCount() {
|
|
assertEquals(0, NodeUtil.getNodeTypeReferenceCount(
|
|
parse("function foo(){}"), Token.THIS,
|
|
Predicates.<Node>alwaysTrue()));
|
|
assertEquals(1, NodeUtil.getNodeTypeReferenceCount(
|
|
parse("this"), Token.THIS,
|
|
Predicates.<Node>alwaysTrue()));
|
|
assertEquals(2, NodeUtil.getNodeTypeReferenceCount(
|
|
parse("this;function foo(){}(this)"), Token.THIS,
|
|
Predicates.<Node>alwaysTrue()));
|
|
}
|
|
|
|
public void testIsNameReferenceCount() {
|
|
assertTrue(NodeUtil.isNameReferenced(
|
|
parse("function foo(){}"), "foo"));
|
|
assertTrue(NodeUtil.isNameReferenced(
|
|
parse("var foo = function(){}"), "foo"));
|
|
assertFalse(NodeUtil.isNameReferenced(
|
|
parse("function foo(){}"), "undefined"));
|
|
assertTrue(NodeUtil.isNameReferenced(
|
|
parse("undefined"), "undefined"));
|
|
assertTrue(NodeUtil.isNameReferenced(
|
|
parse("undefined;function foo(){}(undefined)"), "undefined"));
|
|
|
|
assertTrue(NodeUtil.isNameReferenced(
|
|
parse("goo.foo"), "goo"));
|
|
assertFalse(NodeUtil.isNameReferenced(
|
|
parse("goo.foo"), "foo"));
|
|
}
|
|
|
|
|
|
public void testGetNameReferenceCount() {
|
|
assertEquals(0, NodeUtil.getNameReferenceCount(
|
|
parse("function foo(){}"), "undefined"));
|
|
assertEquals(1, NodeUtil.getNameReferenceCount(
|
|
parse("undefined"), "undefined"));
|
|
assertEquals(2, NodeUtil.getNameReferenceCount(
|
|
parse("undefined;function foo(){}(undefined)"), "undefined"));
|
|
|
|
assertEquals(1, NodeUtil.getNameReferenceCount(
|
|
parse("goo.foo"), "goo"));
|
|
assertEquals(0, NodeUtil.getNameReferenceCount(
|
|
parse("goo.foo"), "foo"));
|
|
assertEquals(1, NodeUtil.getNameReferenceCount(
|
|
parse("function foo(){}"), "foo"));
|
|
assertEquals(1, NodeUtil.getNameReferenceCount(
|
|
parse("var foo = function(){}"), "foo"));
|
|
}
|
|
|
|
public void testGetVarsDeclaredInBranch() {
|
|
Compiler compiler = new Compiler();
|
|
|
|
assertNodeNames(Sets.newHashSet("foo"),
|
|
NodeUtil.getVarsDeclaredInBranch(
|
|
parse("var foo;")));
|
|
assertNodeNames(Sets.newHashSet("foo", "goo"),
|
|
NodeUtil.getVarsDeclaredInBranch(
|
|
parse("var foo,goo;")));
|
|
assertNodeNames(Sets.<String>newHashSet(),
|
|
NodeUtil.getVarsDeclaredInBranch(
|
|
parse("foo();")));
|
|
assertNodeNames(Sets.<String>newHashSet(),
|
|
NodeUtil.getVarsDeclaredInBranch(
|
|
parse("function f(){var foo;}")));
|
|
assertNodeNames(Sets.newHashSet("goo"),
|
|
NodeUtil.getVarsDeclaredInBranch(
|
|
parse("var goo;function f(){var foo;}")));
|
|
}
|
|
|
|
private void assertNodeNames(Set<String> nodeNames, Collection<Node> nodes) {
|
|
Set<String> actualNames = Sets.newHashSet();
|
|
for (Node node : nodes) {
|
|
actualNames.add(node.getString());
|
|
}
|
|
assertEquals(nodeNames, actualNames);
|
|
}
|
|
|
|
public void testIsControlStructureCodeBlock() {
|
|
Node root = parse("if (x) foo(); else boo();");
|
|
Node ifNode = root.getFirstChild();
|
|
|
|
Node ifCondition = ifNode.getFirstChild();
|
|
Node ifCase = ifNode.getFirstChild().getNext();
|
|
Node elseCase = ifNode.getLastChild();
|
|
|
|
assertFalse(NodeUtil.isControlStructureCodeBlock(ifNode, ifCondition));
|
|
assertTrue(NodeUtil.isControlStructureCodeBlock(ifNode, ifCase));
|
|
assertTrue(NodeUtil.isControlStructureCodeBlock(ifNode, elseCase));
|
|
}
|
|
|
|
public void testIsFunctionExpression1() {
|
|
Node root = parse("(function foo() {})");
|
|
Node statementNode = root.getFirstChild();
|
|
assertTrue(statementNode.isExprResult());
|
|
Node functionNode = statementNode.getFirstChild();
|
|
assertTrue(functionNode.isFunction());
|
|
assertTrue(NodeUtil.isFunctionExpression(functionNode));
|
|
}
|
|
|
|
public void testIsFunctionExpression2() {
|
|
Node root = parse("function foo() {}");
|
|
Node functionNode = root.getFirstChild();
|
|
assertTrue(functionNode.isFunction());
|
|
assertFalse(NodeUtil.isFunctionExpression(functionNode));
|
|
}
|
|
|
|
public void testRemoveChildBlock() {
|
|
// Test removing the inner block.
|
|
Node actual = parse("{{x()}}");
|
|
|
|
Node outerBlockNode = actual.getFirstChild();
|
|
Node innerBlockNode = outerBlockNode.getFirstChild();
|
|
innerBlockNode.setIsSyntheticBlock(true);
|
|
|
|
NodeUtil.removeChild(outerBlockNode, innerBlockNode);
|
|
String expected = "{{}}";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveTryChild1() {
|
|
// Test removing the finally clause.
|
|
Node actual = parse("try {foo()} catch(e) {} finally {}");
|
|
|
|
Node tryNode = actual.getFirstChild();
|
|
Node tryBlock = tryNode.getFirstChild();
|
|
Node catchBlocks = tryNode.getFirstChild().getNext();
|
|
Node finallyBlock = tryNode.getLastChild();
|
|
|
|
NodeUtil.removeChild(tryNode, finallyBlock);
|
|
String expected = "try {foo()} catch(e) {}";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveTryChild2() {
|
|
// Test removing the try clause.
|
|
Node actual = parse("try {foo()} catch(e) {} finally {}");
|
|
|
|
Node tryNode = actual.getFirstChild();
|
|
Node tryBlock = tryNode.getFirstChild();
|
|
Node catchBlocks = tryNode.getFirstChild().getNext();
|
|
|
|
NodeUtil.removeChild(tryNode, tryBlock);
|
|
String expected = "try {} catch(e) {} finally {}";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveTryChild3() {
|
|
// Test removing the catch clause.
|
|
Node actual = parse("try {foo()} catch(e) {} finally {}");
|
|
|
|
Node tryNode = actual.getFirstChild();
|
|
Node tryBlock = tryNode.getFirstChild();
|
|
Node catchBlocks = tryNode.getFirstChild().getNext();
|
|
Node catchBlock = catchBlocks.getFirstChild();
|
|
Node finallyBlock = tryNode.getLastChild();
|
|
|
|
NodeUtil.removeChild(catchBlocks, catchBlock);
|
|
String expected = "try {foo()} finally {}";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveTryChild4() {
|
|
// Test removing the catch clause without a finally.
|
|
Node actual = parse("try {foo()} catch(e) {} finally {}");
|
|
|
|
Node tryNode = actual.getFirstChild();
|
|
Node tryBlock = tryNode.getFirstChild();
|
|
Node catchBlocks = tryNode.getFirstChild().getNext();
|
|
Node catchBlock = catchBlocks.getFirstChild();
|
|
Node finallyBlock = tryNode.getLastChild();
|
|
|
|
NodeUtil.removeChild(tryNode, catchBlocks);
|
|
String expected = "try {foo()} finally {}";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveTryChild5() {
|
|
Node actual = parse("try {foo()} catch(e) {} finally {}");
|
|
|
|
Node tryNode = actual.getFirstChild();
|
|
Node tryBlock = tryNode.getFirstChild();
|
|
Node catchBlocks = tryNode.getFirstChild().getNext();
|
|
Node catchBlock = catchBlocks.getFirstChild();
|
|
Node finallyBlock = tryNode.getLastChild();
|
|
|
|
NodeUtil.removeChild(catchBlocks, catchBlock);
|
|
String expected = "try {foo()} finally {}";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveVarChild() {
|
|
Compiler compiler = new Compiler();
|
|
|
|
// Test removing the first child.
|
|
Node actual = parse("var foo, goo, hoo");
|
|
|
|
Node varNode = actual.getFirstChild();
|
|
Node nameNode = varNode.getFirstChild();
|
|
|
|
NodeUtil.removeChild(varNode, nameNode);
|
|
String expected = "var goo, hoo";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
|
|
|
|
// Test removing the second child.
|
|
actual = parse("var foo, goo, hoo");
|
|
|
|
varNode = actual.getFirstChild();
|
|
nameNode = varNode.getFirstChild().getNext();
|
|
|
|
NodeUtil.removeChild(varNode, nameNode);
|
|
expected = "var foo, hoo";
|
|
difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
|
|
// Test removing the last child of several children.
|
|
actual = parse("var foo, hoo");
|
|
|
|
varNode = actual.getFirstChild();
|
|
nameNode = varNode.getFirstChild().getNext();
|
|
|
|
NodeUtil.removeChild(varNode, nameNode);
|
|
expected = "var foo";
|
|
difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
|
|
// Test removing the last.
|
|
actual = parse("var hoo");
|
|
|
|
varNode = actual.getFirstChild();
|
|
nameNode = varNode.getFirstChild();
|
|
|
|
NodeUtil.removeChild(varNode, nameNode);
|
|
expected = "";
|
|
difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveLabelChild1() {
|
|
Compiler compiler = new Compiler();
|
|
|
|
// Test removing the first child.
|
|
Node actual = parse("foo: goo()");
|
|
|
|
Node labelNode = actual.getFirstChild();
|
|
Node callExpressNode = labelNode.getLastChild();
|
|
|
|
NodeUtil.removeChild(labelNode, callExpressNode);
|
|
String expected = "";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveLabelChild2() {
|
|
// Test removing the first child.
|
|
Node actual = parse("achoo: foo: goo()");
|
|
|
|
Node labelNode = actual.getFirstChild();
|
|
Node callExpressNode = labelNode.getLastChild();
|
|
|
|
NodeUtil.removeChild(labelNode, callExpressNode);
|
|
String expected = "";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
if (difference != null) {
|
|
assertTrue("Nodes do not match:\n" + difference, false);
|
|
}
|
|
}
|
|
|
|
public void testRemoveForChild() {
|
|
Compiler compiler = new Compiler();
|
|
|
|
// Test removing the initializer.
|
|
Node actual = parse("for(var a=0;a<0;a++)foo()");
|
|
|
|
Node forNode = actual.getFirstChild();
|
|
Node child = forNode.getFirstChild();
|
|
|
|
NodeUtil.removeChild(forNode, child);
|
|
String expected = "for(;a<0;a++)foo()";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
assertNull("Nodes do not match:\n" + difference, difference);
|
|
|
|
|
|
// Test removing the condition.
|
|
actual = parse("for(var a=0;a<0;a++)foo()");
|
|
|
|
forNode = actual.getFirstChild();
|
|
child = forNode.getFirstChild().getNext();
|
|
|
|
NodeUtil.removeChild(forNode, child);
|
|
expected = "for(var a=0;;a++)foo()";
|
|
difference = parse(expected).checkTreeEquals(actual);
|
|
assertNull("Nodes do not match:\n" + difference, difference);
|
|
|
|
|
|
// Test removing the increment.
|
|
actual = parse("for(var a=0;a<0;a++)foo()");
|
|
|
|
forNode = actual.getFirstChild();
|
|
child = forNode.getFirstChild().getNext().getNext();
|
|
|
|
NodeUtil.removeChild(forNode, child);
|
|
expected = "for(var a=0;a<0;)foo()";
|
|
difference = parse(expected).checkTreeEquals(actual);
|
|
assertNull("Nodes do not match:\n" + difference, difference);
|
|
|
|
|
|
// Test removing the body.
|
|
actual = parse("for(var a=0;a<0;a++)foo()");
|
|
|
|
forNode = actual.getFirstChild();
|
|
child = forNode.getLastChild();
|
|
|
|
NodeUtil.removeChild(forNode, child);
|
|
expected = "for(var a=0;a<0;a++);";
|
|
difference = parse(expected).checkTreeEquals(actual);
|
|
assertNull("Nodes do not match:\n" + difference, difference);
|
|
|
|
|
|
// Test removing the body.
|
|
actual = parse("for(a in ack)foo();");
|
|
|
|
forNode = actual.getFirstChild();
|
|
child = forNode.getLastChild();
|
|
|
|
NodeUtil.removeChild(forNode, child);
|
|
expected = "for(a in ack);";
|
|
difference = parse(expected).checkTreeEquals(actual);
|
|
assertNull("Nodes do not match:\n" + difference, difference);
|
|
}
|
|
|
|
public void testMergeBlock1() {
|
|
Compiler compiler = new Compiler();
|
|
|
|
// Test removing the initializer.
|
|
Node actual = parse("{{a();b();}}");
|
|
|
|
Node parentBlock = actual.getFirstChild();
|
|
Node childBlock = parentBlock.getFirstChild();
|
|
|
|
assertTrue(NodeUtil.tryMergeBlock(childBlock));
|
|
String expected = "{a();b();}";
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
assertNull("Nodes do not match:\n" + difference, difference);
|
|
}
|
|
|
|
public void testMergeBlock2() {
|
|
Compiler compiler = new Compiler();
|
|
|
|
// Test removing the initializer.
|
|
Node actual = parse("foo:{a();}");
|
|
|
|
Node parentLabel = actual.getFirstChild();
|
|
Node childBlock = parentLabel.getLastChild();
|
|
|
|
assertFalse(NodeUtil.tryMergeBlock(childBlock));
|
|
}
|
|
|
|
public void testMergeBlock3() {
|
|
Compiler compiler = new Compiler();
|
|
|
|
// Test removing the initializer.
|
|
String code = "foo:{a();boo()}";
|
|
Node actual = parse("foo:{a();boo()}");
|
|
|
|
Node parentLabel = actual.getFirstChild();
|
|
Node childBlock = parentLabel.getLastChild();
|
|
|
|
assertFalse(NodeUtil.tryMergeBlock(childBlock));
|
|
String expected = code;
|
|
String difference = parse(expected).checkTreeEquals(actual);
|
|
assertNull("Nodes do not match:\n" + difference, difference);
|
|
}
|
|
|
|
public void testGetSourceName() {
|
|
Node n = new Node(Token.BLOCK);
|
|
Node parent = new Node(Token.BLOCK, n);
|
|
parent.setSourceFileForTesting("foo");
|
|
|
|
assertEquals("foo", NodeUtil.getSourceName(n));
|
|
}
|
|
|
|
public void testLocalValue1() throws Exception {
|
|
// Names are not known to be local.
|
|
assertFalse(testLocalValue("x"));
|
|
assertFalse(testLocalValue("x()"));
|
|
assertFalse(testLocalValue("this"));
|
|
assertFalse(testLocalValue("arguments"));
|
|
|
|
// We can't know if new objects are local unless we know
|
|
// that they don't alias themselves.
|
|
assertFalse(testLocalValue("new x()"));
|
|
|
|
// property references are assume to be non-local
|
|
assertFalse(testLocalValue("(new x()).y"));
|
|
assertFalse(testLocalValue("(new x())['y']"));
|
|
|
|
// Primitive values are local
|
|
assertTrue(testLocalValue("null"));
|
|
assertTrue(testLocalValue("undefined"));
|
|
assertTrue(testLocalValue("Infinity"));
|
|
assertTrue(testLocalValue("NaN"));
|
|
assertTrue(testLocalValue("1"));
|
|
assertTrue(testLocalValue("'a'"));
|
|
assertTrue(testLocalValue("true"));
|
|
assertTrue(testLocalValue("false"));
|
|
assertTrue(testLocalValue("[]"));
|
|
assertTrue(testLocalValue("{}"));
|
|
|
|
// The contents of arrays and objects don't matter
|
|
assertTrue(testLocalValue("[x]"));
|
|
assertTrue(testLocalValue("{'a':x}"));
|
|
|
|
// Pre-increment results in primitive number
|
|
assertTrue(testLocalValue("++x"));
|
|
assertTrue(testLocalValue("--x"));
|
|
|
|
// Post-increment, the previous value matters.
|
|
assertFalse(testLocalValue("x++"));
|
|
assertFalse(testLocalValue("x--"));
|
|
|
|
// The left side of an only assign matters if it is an alias or mutable.
|
|
assertTrue(testLocalValue("x=1"));
|
|
assertFalse(testLocalValue("x=[]"));
|
|
assertFalse(testLocalValue("x=y"));
|
|
// The right hand side of assignment opts don't matter, as they force
|
|
// a local result.
|
|
assertTrue(testLocalValue("x+=y"));
|
|
assertTrue(testLocalValue("x*=y"));
|
|
// Comparisons always result in locals, as they force a local boolean
|
|
// result.
|
|
assertTrue(testLocalValue("x==y"));
|
|
assertTrue(testLocalValue("x!=y"));
|
|
assertTrue(testLocalValue("x>y"));
|
|
// Only the right side of a comma matters
|
|
assertTrue(testLocalValue("(1,2)"));
|
|
assertTrue(testLocalValue("(x,1)"));
|
|
assertFalse(testLocalValue("(x,y)"));
|
|
|
|
// Both the operands of OR matter
|
|
assertTrue(testLocalValue("1||2"));
|
|
assertFalse(testLocalValue("x||1"));
|
|
assertFalse(testLocalValue("x||y"));
|
|
assertFalse(testLocalValue("1||y"));
|
|
|
|
// Both the operands of AND matter
|
|
assertTrue(testLocalValue("1&&2"));
|
|
assertFalse(testLocalValue("x&&1"));
|
|
assertFalse(testLocalValue("x&&y"));
|
|
assertFalse(testLocalValue("1&&y"));
|
|
|
|
// Only the results of HOOK matter
|
|
assertTrue(testLocalValue("x?1:2"));
|
|
assertFalse(testLocalValue("x?x:2"));
|
|
assertFalse(testLocalValue("x?1:x"));
|
|
assertFalse(testLocalValue("x?x:y"));
|
|
|
|
// Results of ops are local values
|
|
assertTrue(testLocalValue("!y"));
|
|
assertTrue(testLocalValue("~y"));
|
|
assertTrue(testLocalValue("y + 1"));
|
|
assertTrue(testLocalValue("y + z"));
|
|
assertTrue(testLocalValue("y * z"));
|
|
|
|
assertTrue(testLocalValue("'a' in x"));
|
|
assertTrue(testLocalValue("typeof x"));
|
|
assertTrue(testLocalValue("x instanceof y"));
|
|
|
|
assertTrue(testLocalValue("void x"));
|
|
assertTrue(testLocalValue("void 0"));
|
|
|
|
assertFalse(testLocalValue("{}.x"));
|
|
|
|
assertTrue(testLocalValue("{}.toString()"));
|
|
assertTrue(testLocalValue("o.toString()"));
|
|
|
|
assertFalse(testLocalValue("o.valueOf()"));
|
|
|
|
assertTrue(testLocalValue("delete a.b"));
|
|
}
|
|
|
|
public void testLocalValue2() {
|
|
Node newExpr = getNode("new x()");
|
|
assertFalse(NodeUtil.evaluatesToLocalValue(newExpr));
|
|
|
|
Preconditions.checkState(newExpr.isNew());
|
|
Node.SideEffectFlags flags = new Node.SideEffectFlags();
|
|
|
|
flags.clearAllFlags();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertTrue(NodeUtil.evaluatesToLocalValue(newExpr));
|
|
|
|
flags.clearAllFlags();
|
|
flags.setMutatesThis();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertTrue(NodeUtil.evaluatesToLocalValue(newExpr));
|
|
|
|
flags.clearAllFlags();
|
|
flags.setReturnsTainted();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertTrue(NodeUtil.evaluatesToLocalValue(newExpr));
|
|
|
|
flags.clearAllFlags();
|
|
flags.setThrows();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertFalse(NodeUtil.evaluatesToLocalValue(newExpr));
|
|
|
|
flags.clearAllFlags();
|
|
flags.setMutatesArguments();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertFalse(NodeUtil.evaluatesToLocalValue(newExpr));
|
|
|
|
flags.clearAllFlags();
|
|
flags.setMutatesGlobalState();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertFalse(NodeUtil.evaluatesToLocalValue(newExpr));
|
|
}
|
|
|
|
public void testCallSideEffects() {
|
|
Node callExpr = getNode("new x().method()");
|
|
assertTrue(NodeUtil.functionCallHasSideEffects(callExpr));
|
|
|
|
Node newExpr = callExpr.getFirstChild().getFirstChild();
|
|
Preconditions.checkState(newExpr.isNew());
|
|
Node.SideEffectFlags flags = new Node.SideEffectFlags();
|
|
|
|
// No side effects, local result
|
|
flags.clearAllFlags();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
flags.clearAllFlags();
|
|
callExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertTrue(NodeUtil.evaluatesToLocalValue(callExpr));
|
|
assertFalse(NodeUtil.functionCallHasSideEffects(callExpr));
|
|
assertFalse(NodeUtil.mayHaveSideEffects(callExpr));
|
|
|
|
// Modifies this, local result
|
|
flags.clearAllFlags();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
flags.clearAllFlags();
|
|
flags.setMutatesThis();
|
|
callExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertTrue(NodeUtil.evaluatesToLocalValue(callExpr));
|
|
assertFalse(NodeUtil.functionCallHasSideEffects(callExpr));
|
|
assertFalse(NodeUtil.mayHaveSideEffects(callExpr));
|
|
|
|
// Modifies this, non-local result
|
|
flags.clearAllFlags();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
flags.clearAllFlags();
|
|
flags.setMutatesThis();
|
|
flags.setReturnsTainted();
|
|
callExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertFalse(NodeUtil.evaluatesToLocalValue(callExpr));
|
|
assertFalse(NodeUtil.functionCallHasSideEffects(callExpr));
|
|
assertFalse(NodeUtil.mayHaveSideEffects(callExpr));
|
|
|
|
// No modifications, non-local result
|
|
flags.clearAllFlags();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
flags.clearAllFlags();
|
|
flags.setReturnsTainted();
|
|
callExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertFalse(NodeUtil.evaluatesToLocalValue(callExpr));
|
|
assertFalse(NodeUtil.functionCallHasSideEffects(callExpr));
|
|
assertFalse(NodeUtil.mayHaveSideEffects(callExpr));
|
|
|
|
// The new modifies global state, no side-effect call, non-local result
|
|
// This call could be removed, but not the new.
|
|
flags.clearAllFlags();
|
|
flags.setMutatesGlobalState();
|
|
newExpr.setSideEffectFlags(flags.valueOf());
|
|
flags.clearAllFlags();
|
|
callExpr.setSideEffectFlags(flags.valueOf());
|
|
|
|
assertTrue(NodeUtil.evaluatesToLocalValue(callExpr));
|
|
assertFalse(NodeUtil.functionCallHasSideEffects(callExpr));
|
|
assertTrue(NodeUtil.mayHaveSideEffects(callExpr));
|
|
}
|
|
|
|
private boolean testLocalValue(String js) {
|
|
return NodeUtil.evaluatesToLocalValue(getNode(js));
|
|
}
|
|
|
|
public void testValidDefine() {
|
|
assertTrue(testValidDefineValue("1"));
|
|
assertTrue(testValidDefineValue("-3"));
|
|
assertTrue(testValidDefineValue("true"));
|
|
assertTrue(testValidDefineValue("false"));
|
|
assertTrue(testValidDefineValue("'foo'"));
|
|
|
|
assertFalse(testValidDefineValue("x"));
|
|
assertFalse(testValidDefineValue("null"));
|
|
assertFalse(testValidDefineValue("undefined"));
|
|
assertFalse(testValidDefineValue("NaN"));
|
|
|
|
assertTrue(testValidDefineValue("!true"));
|
|
assertTrue(testValidDefineValue("-true"));
|
|
assertTrue(testValidDefineValue("1 & 8"));
|
|
assertTrue(testValidDefineValue("1 + 8"));
|
|
assertTrue(testValidDefineValue("'a' + 'b'"));
|
|
|
|
assertFalse(testValidDefineValue("1 & foo"));
|
|
}
|
|
|
|
private boolean testValidDefineValue(String js) {
|
|
Node script = parse("var test = " + js + ";");
|
|
Node var = script.getFirstChild();
|
|
Node name = var.getFirstChild();
|
|
Node value = name.getFirstChild();
|
|
|
|
ImmutableSet<String> defines = ImmutableSet.of();
|
|
return NodeUtil.isValidDefineValue(value, defines);
|
|
}
|
|
|
|
public void testGetNumberValue() {
|
|
// Strings
|
|
assertEquals(1.0, NodeUtil.getNumberValue(getNode("'\\uFEFF1'")));
|
|
assertEquals(0.0, NodeUtil.getNumberValue(getNode("''")));
|
|
assertEquals(0.0, NodeUtil.getNumberValue(getNode("' '")));
|
|
assertEquals(0.0, NodeUtil.getNumberValue(getNode("' \\t'")));
|
|
assertEquals(0.0, NodeUtil.getNumberValue(getNode("'+0'")));
|
|
assertEquals(-0.0, NodeUtil.getNumberValue(getNode("'-0'")));
|
|
assertEquals(2.0, NodeUtil.getNumberValue(getNode("'+2'")));
|
|
assertEquals(-1.6, NodeUtil.getNumberValue(getNode("'-1.6'")));
|
|
assertEquals(16.0, NodeUtil.getNumberValue(getNode("'16'")));
|
|
assertEquals(16.0, NodeUtil.getNumberValue(getNode("' 16 '")));
|
|
assertEquals(16.0, NodeUtil.getNumberValue(getNode("' 16 '")));
|
|
assertEquals(12300.0, NodeUtil.getNumberValue(getNode("'123e2'")));
|
|
assertEquals(12300.0, NodeUtil.getNumberValue(getNode("'123E2'")));
|
|
assertEquals(1.23, NodeUtil.getNumberValue(getNode("'123e-2'")));
|
|
assertEquals(1.23, NodeUtil.getNumberValue(getNode("'123E-2'")));
|
|
assertEquals(-1.23, NodeUtil.getNumberValue(getNode("'-123e-2'")));
|
|
assertEquals(-1.23, NodeUtil.getNumberValue(getNode("'-123E-2'")));
|
|
assertEquals(1.23, NodeUtil.getNumberValue(getNode("'+123e-2'")));
|
|
assertEquals(1.23, NodeUtil.getNumberValue(getNode("'+123E-2'")));
|
|
assertEquals(12300.0, NodeUtil.getNumberValue(getNode("'+123e+2'")));
|
|
assertEquals(12300.0, NodeUtil.getNumberValue(getNode("'+123E+2'")));
|
|
|
|
assertEquals(15.0, NodeUtil.getNumberValue(getNode("'0xf'")));
|
|
assertEquals(15.0, NodeUtil.getNumberValue(getNode("'0xF'")));
|
|
|
|
// Chrome and rhino behavior differently from FF and IE. FF and IE
|
|
// consider a negative hex number to be invalid
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("'-0xf'")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("'-0xF'")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("'+0xf'")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("'+0xF'")));
|
|
|
|
assertEquals(16.0, NodeUtil.getNumberValue(getNode("'0X10'")));
|
|
assertEquals(Double.NaN, NodeUtil.getNumberValue(getNode("'0X10.8'")));
|
|
assertEquals(77.0, NodeUtil.getNumberValue(getNode("'077'")));
|
|
assertEquals(-77.0, NodeUtil.getNumberValue(getNode("'-077'")));
|
|
assertEquals(-77.5, NodeUtil.getNumberValue(getNode("'-077.5'")));
|
|
assertEquals(
|
|
Double.NEGATIVE_INFINITY,
|
|
NodeUtil.getNumberValue(getNode("'-Infinity'")));
|
|
assertEquals(
|
|
Double.POSITIVE_INFINITY,
|
|
NodeUtil.getNumberValue(getNode("'Infinity'")));
|
|
assertEquals(
|
|
Double.POSITIVE_INFINITY,
|
|
NodeUtil.getNumberValue(getNode("'+Infinity'")));
|
|
// Firefox treats "infinity" as "Infinity", IE treats it as NaN
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("'-infinity'")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("'infinity'")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("'+infinity'")));
|
|
|
|
assertEquals(Double.NaN, NodeUtil.getNumberValue(getNode("'NaN'")));
|
|
assertEquals(
|
|
Double.NaN, NodeUtil.getNumberValue(getNode("'some unknown string'")));
|
|
assertEquals(Double.NaN, NodeUtil.getNumberValue(getNode("'123 blah'")));
|
|
|
|
// Literals
|
|
assertEquals(1.0, NodeUtil.getNumberValue(getNode("1")));
|
|
// "-1" is parsed as a literal
|
|
assertEquals(-1.0, NodeUtil.getNumberValue(getNode("-1")));
|
|
// "+1" is parse as an op + literal
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("+1")));
|
|
assertEquals(22.0, NodeUtil.getNumberValue(getNode("22")));
|
|
assertEquals(18.0, NodeUtil.getNumberValue(getNode("022")));
|
|
assertEquals(34.0, NodeUtil.getNumberValue(getNode("0x22")));
|
|
|
|
assertEquals(
|
|
1.0, NodeUtil.getNumberValue(getNode("true")));
|
|
assertEquals(
|
|
0.0, NodeUtil.getNumberValue(getNode("false")));
|
|
assertEquals(
|
|
0.0, NodeUtil.getNumberValue(getNode("null")));
|
|
assertEquals(
|
|
Double.NaN, NodeUtil.getNumberValue(getNode("void 0")));
|
|
assertEquals(
|
|
Double.NaN, NodeUtil.getNumberValue(getNode("void f")));
|
|
// values with side-effects are ignored.
|
|
assertEquals(
|
|
null, NodeUtil.getNumberValue(getNode("void f()")));
|
|
assertEquals(
|
|
Double.NaN, NodeUtil.getNumberValue(getNode("NaN")));
|
|
assertEquals(
|
|
Double.POSITIVE_INFINITY,
|
|
NodeUtil.getNumberValue(getNode("Infinity")));
|
|
assertEquals(
|
|
Double.NEGATIVE_INFINITY,
|
|
NodeUtil.getNumberValue(getNode("-Infinity")));
|
|
|
|
// "infinity" is not a known name.
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("infinity")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("-infinity")));
|
|
|
|
// getNumberValue only converts literals
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("x")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("x.y")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("1/2")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("1-2")));
|
|
assertEquals(null, NodeUtil.getNumberValue(getNode("+1")));
|
|
}
|
|
|
|
public void testIsNumbericResult() {
|
|
assertTrue(NodeUtil.isNumericResult(getNode("1")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("true")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("+true")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("+1")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("-1")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("-Infinity")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("Infinity")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("NaN")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("undefined")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("void 0")));
|
|
|
|
assertTrue(NodeUtil.isNumericResult(getNode("a << b")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("a >> b")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("a >>> b")));
|
|
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a == b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a != b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a === b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a !== b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a < b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a > b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a <= b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a >= b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a in b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a instanceof b")));
|
|
|
|
assertFalse(NodeUtil.isNumericResult(getNode("'a'")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("'a'+b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a+'b'")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a+b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a()")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("''.a")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a.b")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a.b()")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("a().b()")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("new a()")));
|
|
|
|
// Definitely not numeric
|
|
assertFalse(NodeUtil.isNumericResult(getNode("([1,2])")));
|
|
assertFalse(NodeUtil.isNumericResult(getNode("({a:1})")));
|
|
|
|
// Recurse into the expression when necessary.
|
|
assertTrue(NodeUtil.isNumericResult(getNode("1 && 2")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("1 || 2")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("a ? 2 : 3")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("a,1")));
|
|
assertTrue(NodeUtil.isNumericResult(getNode("a=1")));
|
|
}
|
|
|
|
public void testIsBooleanResult() {
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("1")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("true")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("+true")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("+1")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("-1")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("-Infinity")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("Infinity")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("NaN")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("undefined")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("void 0")));
|
|
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a << b")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a >> b")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a >>> b")));
|
|
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a == b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a != b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a === b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a !== b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a < b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a > b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a <= b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a >= b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a in b")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a instanceof b")));
|
|
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("'a'")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("'a'+b")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a+'b'")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a+b")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a()")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("''.a")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a.b")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a.b()")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a().b()")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("new a()")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("delete a")));
|
|
|
|
// Definitely not boolean
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("([true,false])")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("({a:true})")));
|
|
|
|
// These are boolean but aren't handled yet, "false" here means "unknown".
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("true && false")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("true || false")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a ? true : false")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a,true")));
|
|
assertTrue(NodeUtil.isBooleanResult(getNode("a=true")));
|
|
assertFalse(NodeUtil.isBooleanResult(getNode("a=1")));
|
|
}
|
|
|
|
public void testMayBeString() {
|
|
assertFalse(NodeUtil.mayBeString(getNode("1")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("true")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("+true")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("+1")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("-1")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("-Infinity")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("Infinity")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("NaN")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("undefined")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("void 0")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("null")));
|
|
|
|
assertFalse(NodeUtil.mayBeString(getNode("a << b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a >> b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a >>> b")));
|
|
|
|
assertFalse(NodeUtil.mayBeString(getNode("a == b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a != b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a === b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a !== b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a < b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a > b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a <= b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a >= b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a in b")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a instanceof b")));
|
|
|
|
assertTrue(NodeUtil.mayBeString(getNode("'a'")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("'a'+b")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("a+'b'")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("a+b")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("a()")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("''.a")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("a.b")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("a.b()")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("a().b()")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("new a()")));
|
|
|
|
// These can't be strings but they aren't handled yet.
|
|
assertFalse(NodeUtil.mayBeString(getNode("1 && 2")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("1 || 2")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("1 ? 2 : 3")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("1,2")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("a=1")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("1+1")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("true+true")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("null+null")));
|
|
assertFalse(NodeUtil.mayBeString(getNode("NaN+NaN")));
|
|
|
|
// These are not strings but they aren't primitives either
|
|
assertTrue(NodeUtil.mayBeString(getNode("([1,2])")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("({a:1})")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("({}+1)")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("(1+{})")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("([]+1)")));
|
|
assertTrue(NodeUtil.mayBeString(getNode("(1+[])")));
|
|
}
|
|
|
|
public void testValidNames() {
|
|
assertTrue(NodeUtil.isValidPropertyName("a"));
|
|
assertTrue(NodeUtil.isValidPropertyName("a3"));
|
|
assertFalse(NodeUtil.isValidPropertyName("3a"));
|
|
assertFalse(NodeUtil.isValidPropertyName("a."));
|
|
assertFalse(NodeUtil.isValidPropertyName(".a"));
|
|
assertFalse(NodeUtil.isValidPropertyName("a.b"));
|
|
assertFalse(NodeUtil.isValidPropertyName("true"));
|
|
assertFalse(NodeUtil.isValidPropertyName("a.true"));
|
|
assertFalse(NodeUtil.isValidPropertyName("a..b"));
|
|
|
|
assertTrue(NodeUtil.isValidSimpleName("a"));
|
|
assertTrue(NodeUtil.isValidSimpleName("a3"));
|
|
assertFalse(NodeUtil.isValidSimpleName("3a"));
|
|
assertFalse(NodeUtil.isValidSimpleName("a."));
|
|
assertFalse(NodeUtil.isValidSimpleName(".a"));
|
|
assertFalse(NodeUtil.isValidSimpleName("a.b"));
|
|
assertFalse(NodeUtil.isValidSimpleName("true"));
|
|
assertFalse(NodeUtil.isValidSimpleName("a.true"));
|
|
assertFalse(NodeUtil.isValidSimpleName("a..b"));
|
|
|
|
assertTrue(NodeUtil.isValidQualifiedName("a"));
|
|
assertTrue(NodeUtil.isValidQualifiedName("a3"));
|
|
assertFalse(NodeUtil.isValidQualifiedName("3a"));
|
|
assertFalse(NodeUtil.isValidQualifiedName("a."));
|
|
assertFalse(NodeUtil.isValidQualifiedName(".a"));
|
|
assertTrue(NodeUtil.isValidQualifiedName("a.b"));
|
|
assertFalse(NodeUtil.isValidQualifiedName("true"));
|
|
assertFalse(NodeUtil.isValidQualifiedName("a.true"));
|
|
assertFalse(NodeUtil.isValidQualifiedName("a..b"));
|
|
}
|
|
|
|
public void testGetNearestFunctionName() {
|
|
testFunctionName("(function() {})()", null);
|
|
testFunctionName("function a() {}", "a");
|
|
testFunctionName("(function a() {})", "a");
|
|
testFunctionName("({a:function () {}})", "a");
|
|
testFunctionName("({get a() {}})", "a");
|
|
testFunctionName("({set a(b) {}})", "a");
|
|
testFunctionName("({set a(b) {}})", "a");
|
|
testFunctionName("({1:function () {}})", "1");
|
|
testFunctionName("var a = function a() {}", "a");
|
|
testFunctionName("var a;a = function a() {}", "a");
|
|
testFunctionName("var o;o.a = function a() {}", "o.a");
|
|
testFunctionName("this.a = function a() {}", "this.a");
|
|
}
|
|
|
|
public void testGetBestLValue() {
|
|
assertEquals("x", getFunctionLValue("var x = function() {};"));
|
|
assertEquals("x", getFunctionLValue("x = function() {};"));
|
|
assertEquals("x", getFunctionLValue("function x() {};"));
|
|
assertEquals("x", getFunctionLValue("var x = y ? z : function() {};"));
|
|
assertEquals("x", getFunctionLValue("var x = y ? function() {} : z;"));
|
|
assertEquals("x", getFunctionLValue("var x = y && function() {};"));
|
|
assertEquals("x", getFunctionLValue("var x = y || function() {};"));
|
|
assertEquals("x", getFunctionLValue("var x = (y, function() {});"));
|
|
}
|
|
|
|
public void testIsNaN() {
|
|
assertEquals(true, NodeUtil.isNaN(getNode("NaN")));
|
|
assertEquals(false, NodeUtil.isNaN(getNode("Infinity")));
|
|
assertEquals(false, NodeUtil.isNaN(getNode("x")));
|
|
assertEquals(true, NodeUtil.isNaN(getNode("0/0")));
|
|
assertEquals(false, NodeUtil.isNaN(getNode("1/0")));
|
|
assertEquals(false, NodeUtil.isNaN(getNode("0/1")));
|
|
assertEquals(false, NodeUtil.isNaN(IR.number(0.0)));
|
|
}
|
|
|
|
public void testIsExecutedExactlyOnce() {
|
|
assertEquals(true, executedOnceTestCase("x;"));
|
|
|
|
assertEquals(true, executedOnceTestCase("x && 1;"));
|
|
assertEquals(false, executedOnceTestCase("1 && x;"));
|
|
|
|
assertEquals(false, executedOnceTestCase("1 && (x && 1);"));
|
|
|
|
assertEquals(true, executedOnceTestCase("x || 1;"));
|
|
assertEquals(false, executedOnceTestCase("1 || x;"));
|
|
|
|
assertEquals(false, executedOnceTestCase("1 && (x || 1);"));
|
|
|
|
assertEquals(true, executedOnceTestCase("x ? 1 : 2;"));
|
|
assertEquals(false, executedOnceTestCase("1 ? 1 : x;"));
|
|
assertEquals(false, executedOnceTestCase("1 ? x : 2;"));
|
|
|
|
assertEquals(false, executedOnceTestCase("1 && (x ? 1 : 2);"));
|
|
|
|
assertEquals(true, executedOnceTestCase("if (x) {}"));
|
|
assertEquals(false, executedOnceTestCase("if (true) {x;}"));
|
|
assertEquals(false, executedOnceTestCase("if (true) {} else {x;}"));
|
|
|
|
assertEquals(false, executedOnceTestCase("if (1) { if (x) {} }"));
|
|
|
|
assertEquals(true, executedOnceTestCase("for(x;;){}"));
|
|
assertEquals(false, executedOnceTestCase("for(;x;){}"));
|
|
assertEquals(false, executedOnceTestCase("for(;;x){}"));
|
|
assertEquals(false, executedOnceTestCase("for(;;){x;}"));
|
|
|
|
assertEquals(false, executedOnceTestCase("if (1) { for(x;;){} }"));
|
|
|
|
assertEquals(false, executedOnceTestCase("for(x in {}){}"));
|
|
assertEquals(true, executedOnceTestCase("for({}.a in x){}"));
|
|
assertEquals(false, executedOnceTestCase("for({}.a in {}){x}"));
|
|
|
|
assertEquals(false, executedOnceTestCase("if (1) { for(x in {}){} }"));
|
|
|
|
assertEquals(true, executedOnceTestCase("switch (x) {}"));
|
|
assertEquals(false, executedOnceTestCase("switch (1) {case x:}"));
|
|
assertEquals(false, executedOnceTestCase("switch (1) {case 1: x}"));
|
|
assertEquals(false, executedOnceTestCase("switch (1) {default: x}"));
|
|
|
|
assertEquals(false, executedOnceTestCase("if (1) { switch (x) {} }"));
|
|
|
|
assertEquals(false, executedOnceTestCase("while (x) {}"));
|
|
assertEquals(false, executedOnceTestCase("while (1) {x}"));
|
|
|
|
assertEquals(false, executedOnceTestCase("do {} while (x)"));
|
|
assertEquals(false, executedOnceTestCase("do {x} while (1)"));
|
|
|
|
assertEquals(false, executedOnceTestCase("try {x} catch (e) {}"));
|
|
assertEquals(false, executedOnceTestCase("try {} catch (e) {x}"));
|
|
assertEquals(true, executedOnceTestCase("try {} finally {x}"));
|
|
|
|
assertEquals(false, executedOnceTestCase("if (1) { try {} finally {x} }"));
|
|
}
|
|
|
|
private boolean executedOnceTestCase(String code) {
|
|
Node ast = parse(code);
|
|
Node nameNode = getNameNode(ast, "x");
|
|
return NodeUtil.isExecutedExactlyOnce(nameNode);
|
|
}
|
|
|
|
private String getFunctionLValue(String js) {
|
|
Node lVal = NodeUtil.getBestLValue(getFunctionNode(js));
|
|
return lVal == null ? null : lVal.getString();
|
|
}
|
|
|
|
static void testFunctionName(String js, String expected) {
|
|
assertEquals(
|
|
expected,
|
|
NodeUtil.getNearestFunctionName(getFunctionNode(js)));
|
|
}
|
|
|
|
static Node getFunctionNode(String js) {
|
|
Node root = parse(js);
|
|
return getFunctionNode(root);
|
|
}
|
|
|
|
static Node getFunctionNode(Node n) {
|
|
if (n.isFunction()) {
|
|
return n;
|
|
}
|
|
for (Node c : n.children()) {
|
|
Node result = getFunctionNode(c);
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Node getNameNode(Node n, String name) {
|
|
if (n.isName() && n.getString().equals(name)) {
|
|
return n;
|
|
}
|
|
for (Node c : n.children()) {
|
|
Node result = getNameNode(c, name);
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|