/*
* 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 static com.google.common.truth.Truth.assertThat;
import static com.google.javascript.jscomp.testing.NodeSubject.assertNode;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import junit.framework.TestCase;
/**
* Tests for NodeUtil
*/
public final class NodeUtilTest extends TestCase {
private static Node parse(String js) {
Compiler compiler = new Compiler();
compiler.initCompilerOptionsIfTesting();
compiler.getOptions().setLanguageIn(LanguageMode.ECMASCRIPT_2015);
compiler.getOptions().setStrictModeInput(false);
Node n = compiler.parseTestCode(js);
assertThat(compiler.getErrors()).isEmpty();
return n;
}
private static Node getNode(String js) {
Node root = parse("var a=(" + js + ");");
Node expr = root.getFirstChild();
Node var = expr.getFirstChild();
return var.getFirstChild();
}
public void testGetNodeByLineCol_1() {
Node root = parse("var x = 1;");
assertNull(NodeUtil.getNodeByLineCol(root, 1, 0));
assertNode(NodeUtil.getNodeByLineCol(root, 1, 1)).hasType(Token.VAR);
assertNode(NodeUtil.getNodeByLineCol(root, 1, 2)).hasType(Token.VAR);
assertNode(NodeUtil.getNodeByLineCol(root, 1, 5)).hasType(Token.NAME);
assertNode(NodeUtil.getNodeByLineCol(root, 1, 9)).hasType(Token.NUMBER);
assertNode(NodeUtil.getNodeByLineCol(root, 1, 11)).hasType(Token.VAR);
}
public void testGetNodeByLineCol_2() {
Node root = parse(Joiner.on("\n").join(
"var x = {};",
"x.prop = 123;"));
assertNode(NodeUtil.getNodeByLineCol(root, 2, 1)).hasType(Token.NAME);
assertNode(NodeUtil.getNodeByLineCol(root, 2, 2)).hasType(Token.NAME);
assertNode(NodeUtil.getNodeByLineCol(root, 2, 3)).hasType(Token.STRING);
assertNode(NodeUtil.getNodeByLineCol(root, 2, 8)).hasType(Token.ASSIGN);
assertNode(NodeUtil.getNodeByLineCol(root, 2, 11)).hasType(Token.NUMBER);
assertNode(NodeUtil.getNodeByLineCol(root, 2, 13)).hasType(Token.NUMBER);
assertNode(NodeUtil.getNodeByLineCol(root, 2, 14)).hasType(Token.EXPR_RESULT);
}
public void testGetNodeByLineCol_preferLiterals() {
Node root;
root = parse("x-5;");
assertNode(NodeUtil.getNodeByLineCol(root, 1, 2)).hasType(Token.NAME);
assertNode(NodeUtil.getNodeByLineCol(root, 1, 3)).hasType(Token.NUMBER);
root = parse(Joiner.on("\n").join(
"function f(x) {",
" return x||null;",
"}"));
assertNode(NodeUtil.getNodeByLineCol(root, 2, 11)).hasType(Token.NAME);
assertNode(NodeUtil.getNodeByLineCol(root, 2, 12)).hasType(Token.OR);
assertNode(NodeUtil.getNodeByLineCol(root, 2, 13)).hasType(Token.NULL);
}
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()"));
}
private void assertLiteralAndImmutable(Node n) {
assertTrue(NodeUtil.isLiteralValue(n, true));
assertTrue(NodeUtil.isLiteralValue(n, false));
assertTrue(NodeUtil.isImmutableValue(n));
}
private void assertLiteralButNotImmutable(Node n) {
assertTrue(NodeUtil.isLiteralValue(n, true));
assertTrue(NodeUtil.isLiteralValue(n, false));
assertFalse(NodeUtil.isImmutableValue(n));
}
private 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()]");
assertPureBooleanTrue("`definiteLength`");
assertPureBooleanFalse("``");
assertPureBooleanUnknown("`${indefinite}Length`");
assertPureBooleanTrue("class Klass{}");
assertPureBooleanTrue("new Date()");
}
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()]");
assertImpureBooleanTrue("new Date()");
}
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")));
/* See https://github.com/google/closure-compiler/issues/1262 */
assertEquals(
"1.2323919403474454e+21", NodeUtil.getStringValue(getNode("1.2323919403474454e+21")));
assertEquals("0", NodeUtil.getStringValue(getNode("'0'")));
assertEquals(null, NodeUtil.getStringValue(getNode("/a/")));
assertEquals("[object Object]", NodeUtil.getStringValue(getNode("{}")));
assertThat(NodeUtil.getStringValue(getNode("[]"))).isEmpty();
assertEquals("false", NodeUtil.getStringValue(getNode("false")));
assertEquals("null", NodeUtil.getStringValue(getNode("null")));
assertEquals("0", NodeUtil.getStringValue(getNode("0")));
assertThat(NodeUtil.getStringValue(getNode("''"))).isEmpty();
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() {
assertThat(NodeUtil.getStringValue(getNode("[]"))).isEmpty();
assertThat(NodeUtil.getStringValue(getNode("['']"))).isEmpty();
assertThat(NodeUtil.getStringValue(getNode("[null]"))).isEmpty();
assertThat(NodeUtil.getStringValue(getNode("[undefined]"))).isEmpty();
assertThat(NodeUtil.getStringValue(getNode("[void 0]"))).isEmpty();
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 {
assertIsObjectLiteralKey(
parseExpr("({})"), false);
assertIsObjectLiteralKey(
parseExpr("a"), false);
assertIsObjectLiteralKey(
parseExpr("'a'"), false);
assertIsObjectLiteralKey(
parseExpr("1"), false);
assertIsObjectLiteralKey(
parseExpr("({a: 1})").getFirstChild(), true);
assertIsObjectLiteralKey(
parseExpr("({1: 1})").getFirstChild(), true);
assertIsObjectLiteralKey(
parseExpr("({get a(){}})").getFirstChild(), true);
assertIsObjectLiteralKey(
parseExpr("({set a(b){}})").getFirstChild(), true);
}
private Node parseExpr(String js) {
Node root = parse(js);
return root.getFirstFirstChild();
}
private void assertIsObjectLiteralKey(Node node, boolean expected) {
assertEquals(expected, NodeUtil.isObjectLitKey(node));
}
public void testGetFunctionName1() throws Exception {
Node parent = parse("function name(){}");
assertGetNameResult(parent.getFirstChild(), "name");
}
public void testGetFunctionName2() throws Exception {
Node parent = parse("var name = function(){}")
.getFirstFirstChild();
assertGetNameResult(parent.getFirstChild(), "name");
}
public void testGetFunctionName3() throws Exception {
Node parent = parse("qualified.name = function(){}")
.getFirstFirstChild();
assertGetNameResult(parent.getLastChild(), "qualified.name");
}
public void testGetFunctionName4() throws Exception {
Node parent = parse("var name2 = function name1(){}")
.getFirstFirstChild();
assertGetNameResult(parent.getFirstChild(), "name2");
}
public void testGetFunctionName5() throws Exception {
Node n = parse("qualified.name2 = function name1(){}");
Node parent = n.getFirstFirstChild();
assertGetNameResult(parent.getLastChild(), "qualified.name2");
}
public void testGetBestFunctionName1() throws Exception {
Node parent = parse("function func(){}");
assertEquals("func",
NodeUtil.getNearestFunctionName(parent.getFirstChild()));
}
public void testGetBestFunctionName2() throws Exception {
Node parent = parse("var obj = {memFunc(){}}")
.getFirstFirstChild().getFirstFirstChild();
assertEquals("memFunc",
NodeUtil.getNearestFunctionName(parent.getLastChild()));
}
private void assertGetNameResult(Node function, String name) {
assertEquals(Token.FUNCTION, function.getToken());
assertEquals(name, NodeUtil.getName(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.initCompilerOptionsIfTesting();
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, "class 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, "(class { })");
assertSideEffect(false, "(class { method() { i++ } })");
assertSideEffect(true, "(class { [computedName()]() {} })");
assertSideEffect(false, "(class { [computedName]() {} })");
assertSideEffect(false, "(class Foo extends Bar { })");
assertSideEffect(true, "(class extends foo() { })");
assertSideEffect(false, "a");
assertSideEffect(false, "[b, c [d, [e]]]");
assertSideEffect(false, "({a: x, b: y, c: z})");
assertSideEffect(false, "({a, b, c})");
assertSideEffect(false, "({[a]: x})");
assertSideEffect(true, "({[a()]: x})");
assertSideEffect(true, "({[a]: x()})");
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, "`template`");
assertSideEffect(false, "`template${name}`");
assertSideEffect(false, "`${name}template`");
assertSideEffect(true, "`${naming()}template`");
assertSideEffect(true, "templateFunction`template`");
assertSideEffect(true, "st = `${name}template`");
assertSideEffect(true, "tempFunc = templateFunction`template`");
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");
assertSideEffect(false, "Math.random();");
assertSideEffect(true, "Math.random(seed);");
}
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);
}
public void testRegExpSideEffect2() {
assertSideEffect(true, "'a'.replace(/a/, function (s) {alert(s)})", false);
assertSideEffect(false, "'a'.replace(/a/, 'x')", 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.getToken(), 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.getToken(), Token.FUNCTION);
assertFalse(NodeUtil.referencesThis(n));
assertFalse(NodeUtil.referencesThis(
parse("(b?foo():null)")));
assertTrue(NodeUtil.referencesThis(parse("()=>this")));
assertTrue(NodeUtil.referencesThis(parse("() => { () => alert(this); }")));
}
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() {
assertNodeNames(ImmutableSet.of("foo"),
NodeUtil.getVarsDeclaredInBranch(
parse("var foo;")));
assertNodeNames(ImmutableSet.of("foo", "goo"),
NodeUtil.getVarsDeclaredInBranch(
parse("var foo,goo;")));
assertNodeNames(ImmutableSet.<String>of(),
NodeUtil.getVarsDeclaredInBranch(
parse("foo();")));
assertNodeNames(ImmutableSet.<String>of(),
NodeUtil.getVarsDeclaredInBranch(
parse("function f(){var foo;}")));
assertNodeNames(ImmutableSet.of("goo"),
NodeUtil.getVarsDeclaredInBranch(
parse("var goo;function f(){var foo;}")));
}
private void assertNodeNames(Set<String> nodeNames, Collection<Node> nodes) {
Set<String> actualNames = new HashSet<>();
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.getSecondChild();
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();
NodeUtil.removeChild(outerBlockNode, innerBlockNode);
String expected = "{{}}";
String difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
}
public void testRemoveTryChild1() {
// Test removing the finally clause.
Node actual = parse("try {foo()} catch(e) {} finally {}");
Node tryNode = actual.getFirstChild();
Node finallyBlock = tryNode.getLastChild();
NodeUtil.removeChild(tryNode, finallyBlock);
String expected = "try {foo()} catch(e) {}";
String difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
}
public void testRemoveTryChild2() {
// Test removing the try clause.
Node actual = parse("try {foo()} catch(e) {} finally {}");
Node tryNode = actual.getFirstChild();
Node tryBlock = tryNode.getFirstChild();
NodeUtil.removeChild(tryNode, tryBlock);
String expected = "try {} catch(e) {} finally {}";
String difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
}
public void testRemoveTryChild3() {
// Test removing the catch clause.
Node actual = parse("try {foo()} catch(e) {} finally {}");
Node tryNode = actual.getFirstChild();
Node catchBlocks = tryNode.getSecondChild();
Node catchBlock = catchBlocks.getFirstChild();
NodeUtil.removeChild(catchBlocks, catchBlock);
String expected = "try {foo()} finally {}";
String difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
}
public void testRemoveTryChild4() {
// Test removing the block that contains the catch clause.
Node actual = parse("try {foo()} catch(e) {} finally {}");
Node tryNode = actual.getFirstChild();
Node catchBlocks = tryNode.getSecondChild();
NodeUtil.removeChild(tryNode, catchBlocks);
String expected = "try {foo()} finally {}";
String difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
}
public void testRemoveVarChild() {
// 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) {
fail("Nodes do not match:\n" + difference);
}
// Test removing the second child.
actual = parse("var foo, goo, hoo");
varNode = actual.getFirstChild();
nameNode = varNode.getSecondChild();
NodeUtil.removeChild(varNode, nameNode);
expected = "var foo, hoo";
difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
// Test removing the last child of several children.
actual = parse("var foo, hoo");
varNode = actual.getFirstChild();
nameNode = varNode.getSecondChild();
NodeUtil.removeChild(varNode, nameNode);
expected = "var foo";
difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
// 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) {
fail("Nodes do not match:\n" + difference);
}
}
public void testRemoveLetChild() {
// Test removing the first child.
Node actual = parse("let foo, goo, hoo");
Node letNode = actual.getFirstChild();
Node nameNode = letNode.getFirstChild();
NodeUtil.removeChild(letNode, nameNode);
String expected = "let goo, hoo";
String difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
// Test removing the second child.
actual = parse("let foo, goo, hoo");
letNode = actual.getFirstChild();
nameNode = letNode.getSecondChild();
NodeUtil.removeChild(letNode, nameNode);
expected = "let foo, hoo";
difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
// Test removing the last child of several children.
actual = parse("let foo, hoo");
letNode = actual.getFirstChild();
nameNode = letNode.getSecondChild();
NodeUtil.removeChild(letNode, nameNode);
expected = "let foo";
difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
// Test removing the last.
actual = parse("let hoo");
letNode = actual.getFirstChild();
nameNode = letNode.getFirstChild();
NodeUtil.removeChild(letNode, nameNode);
expected = "";
difference = parse(expected).checkTreeEquals(actual);
if (difference != null) {
fail("Nodes do not match:\n" + difference);
}
}
public void testRemoveLabelChild1() {
// 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) {
fail("Nodes do not match:\n" + difference);
}
}
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) {
fail("Nodes do not match:\n" + difference);
}
}
public void testRemoveForChild() {
// 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.getSecondChild();
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.getSecondChild().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);
}
private static void replaceDeclChild(String js, int declarationChild, String expected) {
Node actual = parse(js);
Node declarationNode = actual.getFirstChild();
Node nameNode = declarationNode.getChildAtIndex(declarationChild);
NodeUtil.replaceDeclarationChild(nameNode, IR.block());
String difference = parse(expected).checkTreeEquals(actual);
assertNull("Nodes do not match:\n" + difference, difference);
}
public void testReplaceDeclarationName() {
replaceDeclChild("var x;", 0, "{}");
replaceDeclChild("var x, y;", 0, "{} var y;");
replaceDeclChild("var x, y;", 1, "var x; {}");
replaceDeclChild("let x, y, z;", 0, "{} let y, z;");
replaceDeclChild("let x, y, z;", 1, "let x; {} let z;");
replaceDeclChild("let x, y, z;", 2, "let x, y; {}");
replaceDeclChild("const x = 1, y = 2, z = 3;", 1, "const x = 1; {} const z = 3;");
replaceDeclChild("const x =1, y = 2, z = 3, w = 4;", 1, "const x = 1; {} const z = 3, w = 4;");
}
public void testMergeBlock1() {
// 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() {
// Test removing the initializer.
Node actual = parse("foo:{a();}");
Node parentLabel = actual.getFirstChild();
Node childBlock = parentLabel.getLastChild();
assertFalse(NodeUtil.tryMergeBlock(childBlock));
}
public void testMergeBlock3() {
// 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(NodeUtil.evaluatesToLocalValue(getNode("x")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x()")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("this")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("arguments")));
// We can't know if new objects are local unless we know
// that they don't alias themselves.
// TODO(tdeegan): Revisit this.
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("new x()")));
// property references are assumed to be non-local
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("(new x()).y")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("(new x())['y']")));
// Primitive values are local
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("null")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("undefined")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("Infinity")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("NaN")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("1")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("'a'")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("true")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("false")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("[]")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{}")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("[x]")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("{'a':x}")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{'a': {'b': 2}}")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("{'a': {'b': global}}")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{get someGetter() { return 1; }}")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{get someGetter() { return global; }}")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{set someSetter(value) {}}")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{[someComputedProperty]: {}}")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("{[someComputedProperty]: global}")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("{[someComputedProperty]: {'a':x}}")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{[someComputedProperty]: {'a':1}}")));
// increment/decrement results in primitive number, the previous value is
// always coersed to a number (even in the post.
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("++x")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("--x")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x++")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x--")));
// The left side of an only assign matters if it is an alias or mutable.
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x=1")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x=[]")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x=y")));
// The right hand side of assignment opts don't matter, as they force
// a local result.
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x+=y")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x*=y")));
// Comparisons always result in locals, as they force a local boolean
// result.
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x==y")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x!=y")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x>y")));
// Only the right side of a comma matters
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("(1,2)")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("(x,1)")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("(x,y)")));
// Both the operands of OR matter
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("1||2")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x||1")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x||y")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("1||y")));
// Both the operands of AND matter
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("1&&2")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x&&1")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x&&y")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("1&&y")));
// Only the results of HOOK matter
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x?1:2")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x?x:2")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x?1:x")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("x?x:y")));
// Results of ops are local values
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("!y")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("~y")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("y + 1")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("y + z")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("y * z")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("'a' in x")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("typeof x")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("x instanceof y")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("void x")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("void 0")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("{}.x")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{}.toString()")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("o.toString()")));
assertFalse(NodeUtil.evaluatesToLocalValue(getNode("o.valueOf()")));
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("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.getFirstFirstChild();
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));
}
public void testValidDefine() {
assertTrue(getIsValidDefineValueResultFor("1"));
assertTrue(getIsValidDefineValueResultFor("-3"));
assertTrue(getIsValidDefineValueResultFor("true"));
assertTrue(getIsValidDefineValueResultFor("false"));
assertTrue(getIsValidDefineValueResultFor("'foo'"));
assertFalse(getIsValidDefineValueResultFor("x"));
assertFalse(getIsValidDefineValueResultFor("null"));
assertFalse(getIsValidDefineValueResultFor("undefined"));
assertFalse(getIsValidDefineValueResultFor("NaN"));
assertTrue(getIsValidDefineValueResultFor("!true"));
assertTrue(getIsValidDefineValueResultFor("-true"));
assertTrue(getIsValidDefineValueResultFor("1 & 8"));
assertTrue(getIsValidDefineValueResultFor("1 + 8"));
assertTrue(getIsValidDefineValueResultFor("'a' + 'b'"));
assertTrue(getIsValidDefineValueResultFor("true ? 'a' : 'b'"));
assertFalse(getIsValidDefineValueResultFor("1 & foo"));
assertFalse(getIsValidDefineValueResultFor("foo ? 'a' : 'b'"));
}
private boolean getIsValidDefineValueResultFor(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);
}
@SuppressWarnings("JUnit3FloatingPointComparisonWithoutDelta")
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
assertNull(NodeUtil.getNumberValue(getNode("'-0xf'")));
assertNull(NodeUtil.getNumberValue(getNode("'-0xF'")));
assertNull(NodeUtil.getNumberValue(getNode("'+0xf'")));
assertNull(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
assertNull(NodeUtil.getNumberValue(getNode("'-infinity'")));
assertNull(NodeUtil.getNumberValue(getNode("'infinity'")));
assertNull(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
assertNull(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.
assertNull(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.
assertNull(NodeUtil.getNumberValue(getNode("infinity")));
assertNull(NodeUtil.getNumberValue(getNode("-infinity")));
// getNumberValue only converts literals
assertNull(NodeUtil.getNumberValue(getNode("x")));
assertNull(NodeUtil.getNumberValue(getNode("x.y")));
assertNull(NodeUtil.getNumberValue(getNode("1/2")));
assertNull(NodeUtil.getNumberValue(getNode("1-2")));
assertNull(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")));
assertFalse(NodeUtil.isNumericResult(getNode("a += 1")));
assertTrue(NodeUtil.isNumericResult(getNode("a -= 1")));
assertTrue(NodeUtil.isNumericResult(getNode("a *= 1")));
assertTrue(NodeUtil.isNumericResult(getNode("--a")));
assertTrue(NodeUtil.isNumericResult(getNode("++a")));
assertTrue(NodeUtil.isNumericResult(getNode("a++")));
assertTrue(NodeUtil.isNumericResult(getNode("a--")));
}
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
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+[])")));
assertTrue(NodeUtil.mayBeString(getNode("a += 'x'")));
assertTrue(NodeUtil.mayBeString(getNode("a += 1")));
}
public void testIsStringResult() {
assertFalse(NodeUtil.isStringResult(getNode("1")));
assertFalse(NodeUtil.isStringResult(getNode("true")));
assertFalse(NodeUtil.isStringResult(getNode("+true")));
assertFalse(NodeUtil.isStringResult(getNode("+1")));
assertFalse(NodeUtil.isStringResult(getNode("-1")));
assertFalse(NodeUtil.isStringResult(getNode("-Infinity")));
assertFalse(NodeUtil.isStringResult(getNode("Infinity")));
assertFalse(NodeUtil.isStringResult(getNode("NaN")));
assertFalse(NodeUtil.isStringResult(getNode("undefined")));
assertFalse(NodeUtil.isStringResult(getNode("void 0")));
assertFalse(NodeUtil.isStringResult(getNode("null")));
assertFalse(NodeUtil.isStringResult(getNode("a << b")));
assertFalse(NodeUtil.isStringResult(getNode("a >> b")));
assertFalse(NodeUtil.isStringResult(getNode("a >>> b")));
assertFalse(NodeUtil.isStringResult(getNode("a == b")));
assertFalse(NodeUtil.isStringResult(getNode("a != b")));
assertFalse(NodeUtil.isStringResult(getNode("a === b")));
assertFalse(NodeUtil.isStringResult(getNode("a !== b")));
assertFalse(NodeUtil.isStringResult(getNode("a < b")));
assertFalse(NodeUtil.isStringResult(getNode("a > b")));
assertFalse(NodeUtil.isStringResult(getNode("a <= b")));
assertFalse(NodeUtil.isStringResult(getNode("a >= b")));
assertFalse(NodeUtil.isStringResult(getNode("a in b")));
assertFalse(NodeUtil.isStringResult(getNode("a instanceof b")));
assertTrue(NodeUtil.isStringResult(getNode("'a'")));
assertTrue(NodeUtil.isStringResult(getNode("'a'+b")));
assertTrue(NodeUtil.isStringResult(getNode("a+'b'")));
assertFalse(NodeUtil.isStringResult(getNode("a+b")));
assertFalse(NodeUtil.isStringResult(getNode("a()")));
assertFalse(NodeUtil.isStringResult(getNode("''.a")));
assertFalse(NodeUtil.isStringResult(getNode("a.b")));
assertFalse(NodeUtil.isStringResult(getNode("a.b()")));
assertFalse(NodeUtil.isStringResult(getNode("a().b()")));
assertFalse(NodeUtil.isStringResult(getNode("new a()")));
// These can't be strings but they aren't handled yet.
assertFalse(NodeUtil.isStringResult(getNode("1 && 2")));
assertFalse(NodeUtil.isStringResult(getNode("1 || 2")));
assertFalse(NodeUtil.isStringResult(getNode("1 ? 2 : 3")));
assertFalse(NodeUtil.isStringResult(getNode("1,2")));
assertFalse(NodeUtil.isStringResult(getNode("a=1")));
assertFalse(NodeUtil.isStringResult(getNode("1+1")));
assertFalse(NodeUtil.isStringResult(getNode("true+true")));
assertFalse(NodeUtil.isStringResult(getNode("null+null")));
assertFalse(NodeUtil.isStringResult(getNode("NaN+NaN")));
// These are not strings but they aren't primitives either
assertFalse(NodeUtil.isStringResult(getNode("([1,2])")));
assertFalse(NodeUtil.isStringResult(getNode("({a:1})")));
assertFalse(NodeUtil.isStringResult(getNode("({}+1)")));
assertFalse(NodeUtil.isStringResult(getNode("(1+{})")));
assertFalse(NodeUtil.isStringResult(getNode("([]+1)")));
assertFalse(NodeUtil.isStringResult(getNode("(1+[])")));
assertTrue(NodeUtil.isStringResult(getNode("a += 'x'")));
}
public void testIsObjectResult() {
assertFalse(NodeUtil.isObjectResult(getNode("1")));
assertFalse(NodeUtil.isObjectResult(getNode("true")));
assertFalse(NodeUtil.isObjectResult(getNode("+true")));
assertFalse(NodeUtil.isObjectResult(getNode("+1")));
assertFalse(NodeUtil.isObjectResult(getNode("-1")));
assertFalse(NodeUtil.isObjectResult(getNode("-Infinity")));
assertFalse(NodeUtil.isObjectResult(getNode("Infinity")));
assertFalse(NodeUtil.isObjectResult(getNode("NaN")));
assertFalse(NodeUtil.isObjectResult(getNode("undefined")));
assertFalse(NodeUtil.isObjectResult(getNode("void 0")));
assertFalse(NodeUtil.isObjectResult(getNode("a << b")));
assertFalse(NodeUtil.isObjectResult(getNode("a >> b")));
assertFalse(NodeUtil.isObjectResult(getNode("a >>> b")));
assertFalse(NodeUtil.isObjectResult(getNode("a == b")));
assertFalse(NodeUtil.isObjectResult(getNode("a != b")));
assertFalse(NodeUtil.isObjectResult(getNode("a === b")));
assertFalse(NodeUtil.isObjectResult(getNode("a !== b")));
assertFalse(NodeUtil.isObjectResult(getNode("a < b")));
assertFalse(NodeUtil.isObjectResult(getNode("a > b")));
assertFalse(NodeUtil.isObjectResult(getNode("a <= b")));
assertFalse(NodeUtil.isObjectResult(getNode("a >= b")));
assertFalse(NodeUtil.isObjectResult(getNode("a in b")));
assertFalse(NodeUtil.isObjectResult(getNode("a instanceof b")));
assertFalse(NodeUtil.isObjectResult(getNode("delete a")));
assertFalse(NodeUtil.isObjectResult(getNode("'a'")));
assertFalse(NodeUtil.isObjectResult(getNode("'a'+b")));
assertFalse(NodeUtil.isObjectResult(getNode("a+'b'")));
assertFalse(NodeUtil.isObjectResult(getNode("a+b")));
assertFalse(NodeUtil.isObjectResult(getNode("{},true")));
// "false" here means "unknown"
assertFalse(NodeUtil.isObjectResult(getNode("a()")));
assertFalse(NodeUtil.isObjectResult(getNode("''.a")));
assertFalse(NodeUtil.isObjectResult(getNode("a.b")));
assertFalse(NodeUtil.isObjectResult(getNode("a.b()")));
assertFalse(NodeUtil.isObjectResult(getNode("a().b()")));
assertFalse(NodeUtil.isObjectResult(getNode("a ? true : {}")));
// These are objects but aren't handled yet.
assertFalse(NodeUtil.isObjectResult(getNode("true && {}")));
assertFalse(NodeUtil.isObjectResult(getNode("true || {}")));
// Definitely objects
assertTrue(NodeUtil.isObjectResult(getNode("new a.b()")));
assertTrue(NodeUtil.isObjectResult(getNode("([true,false])")));
assertTrue(NodeUtil.isObjectResult(getNode("({a:true})")));
assertTrue(NodeUtil.isObjectResult(getNode("a={}")));
assertTrue(NodeUtil.isObjectResult(getNode("[] && {}")));
assertTrue(NodeUtil.isObjectResult(getNode("[] || {}")));
assertTrue(NodeUtil.isObjectResult(getNode("a ? [] : {}")));
assertTrue(NodeUtil.isObjectResult(getNode("{},[]")));
assertTrue(NodeUtil.isObjectResult(getNode("/a/g")));
}
public void testValidNames() {
assertTrue(isValidPropertyName("a"));
assertTrue(isValidPropertyName("a3"));
assertFalse(isValidPropertyName("3a"));
assertFalse(isValidPropertyName("a."));
assertFalse(isValidPropertyName(".a"));
assertFalse(isValidPropertyName("a.b"));
assertFalse(isValidPropertyName("true"));
assertFalse(isValidPropertyName("a.true"));
assertFalse(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(isValidQualifiedName("a"));
assertTrue(isValidQualifiedName("a3"));
assertFalse(isValidQualifiedName("3a"));
assertFalse(isValidQualifiedName("a."));
assertFalse(isValidQualifiedName(".a"));
assertTrue(isValidQualifiedName("a.b"));
assertFalse(isValidQualifiedName("true"));
assertFalse(isValidQualifiedName("a.true"));
assertFalse(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 testGetRValueOfLValue() {
assertTrue(functionIsRValueOfAssign("x = function() {};"));
assertTrue(functionIsRValueOfAssign("x += function() {};"));
assertTrue(functionIsRValueOfAssign("x -= function() {};"));
assertTrue(functionIsRValueOfAssign("x *= function() {};"));
assertTrue(functionIsRValueOfAssign("x /= function() {};"));
assertTrue(functionIsRValueOfAssign("x <<= function() {};"));
assertTrue(functionIsRValueOfAssign("x >>= function() {};"));
assertTrue(functionIsRValueOfAssign("x >>= function() {};"));
assertTrue(functionIsRValueOfAssign("x >>>= function() {};"));
assertFalse(functionIsRValueOfAssign("x = y ? x : function() {};"));
}
public void testIsNaN() {
assertTrue(NodeUtil.isNaN(getNode("NaN")));
assertFalse(NodeUtil.isNaN(getNode("Infinity")));
assertFalse(NodeUtil.isNaN(getNode("x")));
assertTrue(NodeUtil.isNaN(getNode("0/0")));
assertFalse(NodeUtil.isNaN(getNode("1/0")));
assertFalse(NodeUtil.isNaN(getNode("0/1")));
assertFalse(NodeUtil.isNaN(IR.number(0.0)));
}
public void testIsExecutedExactlyOnce() {
assertTrue(executedOnceTestCase("x;"));
assertTrue(executedOnceTestCase("x && 1;"));
assertFalse(executedOnceTestCase("1 && x;"));
assertFalse(executedOnceTestCase("1 && (x && 1);"));
assertTrue(executedOnceTestCase("x || 1;"));
assertFalse(executedOnceTestCase("1 || x;"));
assertFalse(executedOnceTestCase("1 && (x || 1);"));
assertTrue(executedOnceTestCase("x ? 1 : 2;"));
assertFalse(executedOnceTestCase("1 ? 1 : x;"));
assertFalse(executedOnceTestCase("1 ? x : 2;"));
assertFalse(executedOnceTestCase("1 && (x ? 1 : 2);"));
assertTrue(executedOnceTestCase("if (x) {}"));
assertFalse(executedOnceTestCase("if (true) {x;}"));
assertFalse(executedOnceTestCase("if (true) {} else {x;}"));
assertFalse(executedOnceTestCase("if (1) { if (x) {} }"));
assertTrue(executedOnceTestCase("for(x;;){}"));
assertFalse(executedOnceTestCase("for(;x;){}"));
assertFalse(executedOnceTestCase("for(;;x){}"));
assertFalse(executedOnceTestCase("for(;;){x;}"));
assertFalse(executedOnceTestCase("if (1) { for(x;;){} }"));
assertFalse(executedOnceTestCase("for(x in {}){}"));
assertTrue(executedOnceTestCase("for({}.a in x){}"));
assertFalse(executedOnceTestCase("for({}.a in {}){x}"));
assertFalse(executedOnceTestCase("if (1) { for(x in {}){} }"));
assertTrue(executedOnceTestCase("switch (x) {}"));
assertFalse(executedOnceTestCase("switch (1) {case x:}"));
assertFalse(executedOnceTestCase("switch (1) {case 1: x}"));
assertFalse(executedOnceTestCase("switch (1) {default: x}"));
assertFalse(executedOnceTestCase("if (1) { switch (x) {} }"));
assertFalse(executedOnceTestCase("while (x) {}"));
assertFalse(executedOnceTestCase("while (1) {x}"));
assertFalse(executedOnceTestCase("do {} while (x)"));
assertFalse(executedOnceTestCase("do {x} while (1)"));
assertFalse(executedOnceTestCase("try {x} catch (e) {}"));
assertFalse(executedOnceTestCase("try {} catch (e) {x}"));
assertTrue(executedOnceTestCase("try {} finally {x}"));
assertFalse(executedOnceTestCase("if (1) { try {} finally {x} }"));
}
private void assertLValueNamedX(Node n) {
assertThat(n.getString()).isEqualTo("x");
assertThat(NodeUtil.isLValue(n)).isTrue();
}
public void testIsLValue() {
assertLValueNamedX(parse("var x;").getFirstFirstChild());
assertLValueNamedX(parse("var w, x;").getFirstChild().getLastChild());
assertLValueNamedX(
parse("var [...x] = y;").getFirstFirstChild().getFirstFirstChild().getFirstChild());
assertLValueNamedX(parse("var x = y;").getFirstFirstChild());
assertLValueNamedX(parse("x++;").getFirstFirstChild().getFirstChild());
assertLValueNamedX(
NodeUtil.getFunctionParameters(parse("function f(x) {}").getFirstChild()).getFirstChild());
Node x = NodeUtil.getFunctionParameters(parse("function f(x = 3) {}").getFirstChild())
.getFirstChild() // x = 3
.getFirstChild(); // x
assertLValueNamedX(x);
assertLValueNamedX(
parse("({x} = obj)").getFirstFirstChild().getFirstFirstChild());
assertLValueNamedX(
parse("([x] = obj)").getFirstFirstChild().getFirstFirstChild());
}
private void assertNotLValueNamedX(Node n) {
assertThat(n.getString()).isEqualTo("x");
assertThat(NodeUtil.isLValue(n)).isFalse();
}
public void testIsNotLValue() {
assertNotLValueNamedX(parse("var a = x;").getFirstFirstChild().getFirstChild());
Node x = parse("f(...x);") // script
.getFirstChild() // expr result
.getFirstChild() // call
.getLastChild() // spread
.getFirstChild(); // x
assertNotLValueNamedX(x);
x = parse("var a = [...x];") // script
.getFirstChild() // var
.getFirstChild() // a
.getFirstChild() // array
.getFirstChild() // spread
.getFirstChild(); // x
assertNotLValueNamedX(x);
}
public void testLhsByDestructuring1() {
Node root = parse("var [a, b] = obj;");
Node destructLhs = root.getFirstFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Node nameNodeB = nameNodeA.getNext();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
Preconditions.checkState(nameNodeB.getString().equals("b"), nameNodeB);
Node nameNodeObj = destructPat.getNext();
Preconditions.checkState(nameNodeObj.getString().equals("obj"), nameNodeObj);
assertLhsByDestructuring(nameNodeA);
assertLhsByDestructuring(nameNodeB);
assertNotLhsByDestructuring(nameNodeObj);
}
public void testLhsByDestructuring1b() {
Node root = parse("var {a: c, b: d} = obj;");
Node destructLhs = root.getFirstFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isObjectPattern());
Node strKeyNodeA = destructPat.getFirstChild();
Node strKeyNodeB = strKeyNodeA.getNext();
Node nameNodeC = strKeyNodeA.getFirstChild();
Node nameNodeD = strKeyNodeB.getFirstChild();
Preconditions.checkState(strKeyNodeA.getString().equals("a"), strKeyNodeA);
Preconditions.checkState(strKeyNodeB.getString().equals("b"), strKeyNodeB);
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
Preconditions.checkState(nameNodeD.getString().equals("d"), nameNodeD);
Node nameNodeObj = destructPat.getNext();
Preconditions.checkState(nameNodeObj.getString().equals("obj"), nameNodeObj);
assertNotLhsByDestructuring(strKeyNodeA);
assertNotLhsByDestructuring(strKeyNodeB);
assertLhsByDestructuring(nameNodeC);
assertLhsByDestructuring(nameNodeD);
assertNotLhsByDestructuring(nameNodeObj);
}
public void testLhsByDestructuring1c() {
Node root = parse("var {a, b} = obj;");
Node destructLhs = root.getFirstFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isObjectPattern());
Node strKeyNodeA = destructPat.getFirstChild();
Node strKeyNodeB = strKeyNodeA.getNext();
Preconditions.checkState(strKeyNodeA.getString().equals("a"), strKeyNodeA);
Preconditions.checkState(strKeyNodeB.getString().equals("b"), strKeyNodeB);
Node nameNodeObj = destructPat.getNext();
Preconditions.checkState(nameNodeObj.getString().equals("obj"), nameNodeObj);
assertLhsByDestructuring(strKeyNodeA);
assertLhsByDestructuring(strKeyNodeB);
assertNotLhsByDestructuring(nameNodeObj);
}
public void testLhsByDestructuring2() {
Node root = parse("var [a, [b, c]] = obj;");
Node destructLhs = root.getFirstFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Node nameNodeB = nameNodeA.getNext().getFirstChild();
Node nameNodeC = nameNodeB.getNext();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
Preconditions.checkState(nameNodeB.getString().equals("b"), nameNodeB);
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
Node nameNodeObj = destructPat.getNext();
Preconditions.checkState(nameNodeObj.getString().equals("obj"), nameNodeObj);
assertLhsByDestructuring(nameNodeA);
assertLhsByDestructuring(nameNodeB);
assertLhsByDestructuring(nameNodeC);
assertNotLhsByDestructuring(nameNodeObj);
}
public void testLhsByDestructuring2b() {
Node root = parse("var {a: e, b: {c: f, d: g}} = obj;");
Node destructLhs = root.getFirstFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isObjectPattern());
Node strKeyNodeA = destructPat.getFirstChild();
Node strKeyNodeB = strKeyNodeA.getNext();
Node strKeyNodeC = strKeyNodeB.getFirstFirstChild();
Node strKeyNodeD = strKeyNodeC.getNext();
Node nameNodeE = strKeyNodeA.getFirstChild();
Node nameNodeF = strKeyNodeC.getFirstChild();
Node nameNodeG = strKeyNodeD.getFirstChild();
Preconditions.checkState(strKeyNodeA.getString().equals("a"), strKeyNodeA);
Preconditions.checkState(strKeyNodeB.getString().equals("b"), strKeyNodeB);
Preconditions.checkState(strKeyNodeC.getString().equals("c"), strKeyNodeC);
Preconditions.checkState(strKeyNodeD.getString().equals("d"), strKeyNodeD);
Preconditions.checkState(nameNodeE.getString().equals("e"), nameNodeE);
Preconditions.checkState(nameNodeF.getString().equals("f"), nameNodeF);
Preconditions.checkState(nameNodeG.getString().equals("g"), nameNodeG);
Node nameNodeObj = destructPat.getNext();
Preconditions.checkState(nameNodeObj.getString().equals("obj"), nameNodeObj);
assertNotLhsByDestructuring(strKeyNodeA);
assertNotLhsByDestructuring(strKeyNodeB);
assertNotLhsByDestructuring(strKeyNodeC);
assertNotLhsByDestructuring(strKeyNodeD);
assertLhsByDestructuring(nameNodeE);
assertLhsByDestructuring(nameNodeF);
assertLhsByDestructuring(nameNodeG);
assertNotLhsByDestructuring(nameNodeObj);
}
public void testLhsByDestructuring3() {
Node root = parse("var [a, b] = [c, d];");
Node destructLhs = root.getFirstFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Node nameNodeB = nameNodeA.getNext();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
Preconditions.checkState(nameNodeB.getString().equals("b"), nameNodeB);
Node nameNodeC = destructLhs.getLastChild().getFirstChild();
Node nameNodeD = nameNodeC.getNext();
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
Preconditions.checkState(nameNodeD.getString().equals("d"), nameNodeD);
assertLhsByDestructuring(nameNodeA);
assertLhsByDestructuring(nameNodeB);
assertNotLhsByDestructuring(nameNodeC);
assertNotLhsByDestructuring(nameNodeD);
}
public void testLhsByDestructuring3b() {
Node root = parse("var {a: c, b: d} = {a: 1, b: 2};");
Node destructLhs = root.getFirstFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isObjectPattern());
Node strKeyNodeA = destructPat.getFirstChild();
Node strKeyNodeB = strKeyNodeA.getNext();
Node nameNodeC = strKeyNodeA.getFirstChild();
Node nameNodeD = strKeyNodeB.getFirstChild();
Preconditions.checkState(strKeyNodeA.getString().equals("a"), strKeyNodeA);
Preconditions.checkState(strKeyNodeB.getString().equals("b"), strKeyNodeB);
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
Preconditions.checkState(nameNodeD.getString().equals("d"), nameNodeD);
assertNotLhsByDestructuring(strKeyNodeA);
assertNotLhsByDestructuring(strKeyNodeB);
assertLhsByDestructuring(nameNodeC);
assertLhsByDestructuring(nameNodeD);
}
public void testLhsByDestructuring4() {
Node root = parse("for ([a, b] of X){}");
Node destructPat = root.getFirstFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Node nameNodeB = destructPat.getLastChild();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
Preconditions.checkState(nameNodeB.getString().equals("b"), nameNodeB);
assertLhsByDestructuring(nameNodeA);
assertLhsByDestructuring(nameNodeB);
}
public void testLhsByDestructuring5() {
Node root = parse("function fn([a, b] = [c, d]){}");
Node destructPat = root.getFirstChild().getSecondChild()
.getFirstFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Node nameNodeB = destructPat.getLastChild();
Node nameNodeC = destructPat.getNext().getFirstChild();
Node nameNodeD = destructPat.getNext().getLastChild();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
Preconditions.checkState(nameNodeB.getString().equals("b"), nameNodeB);
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
Preconditions.checkState(nameNodeD.getString().equals("d"), nameNodeD);
assertLhsByDestructuring(nameNodeA);
assertLhsByDestructuring(nameNodeB);
assertNotLhsByDestructuring(nameNodeC);
assertNotLhsByDestructuring(nameNodeD);
}
public void testLhsByDestructuring6() {
Node root = parse("for ([{a: b}] of c) {}");
Node destructPat = root.getFirstFirstChild().getFirstChild();
Preconditions.checkArgument(destructPat.isObjectPattern()
&& destructPat.getParent().isArrayPattern());
Node strKeyNodeA = destructPat.getFirstChild();
Node nameNodeB = strKeyNodeA.getFirstChild();
Node nameNodeC = destructPat.getParent().getNext();
Preconditions.checkState(strKeyNodeA.getString().equals("a"), strKeyNodeA);
Preconditions.checkState(nameNodeB.getString().equals("b"), nameNodeB);
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
assertNotLhsByDestructuring(strKeyNodeA);
assertLhsByDestructuring(nameNodeB);
assertNotLhsByDestructuring(nameNodeC);
}
public void testLhsByDestructuring6b() {
Node root = parse("for ([{a: b}] in c) {}");
Node destructPat = root.getFirstFirstChild().getFirstChild();
Preconditions.checkArgument(destructPat.isObjectPattern()
&& destructPat.getParent().isArrayPattern());
Node strKeyNodeA = destructPat.getFirstChild();
Node nameNodeB = strKeyNodeA.getFirstChild();
Node nameNodeC = destructPat.getParent().getNext();
Preconditions.checkState(strKeyNodeA.getString().equals("a"), strKeyNodeA);
Preconditions.checkState(nameNodeB.getString().equals("b"), nameNodeB);
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
assertNotLhsByDestructuring(strKeyNodeA);
assertLhsByDestructuring(nameNodeB);
assertNotLhsByDestructuring(nameNodeC);
}
public void testLhsByDestructuring6c() {
Node root = parse("for (var [{a: b}] = [{a: 1}];;) {}");
Node destructArr = root.getFirstFirstChild().getFirstFirstChild();
Preconditions.checkArgument(destructArr.isArrayPattern());
Node destructPat = destructArr.getFirstChild();
Preconditions.checkArgument(destructPat.isObjectPattern()
&& destructPat.getParent().isArrayPattern());
Node strKeyNodeA = destructPat.getFirstChild();
Node nameNodeB = strKeyNodeA.getFirstChild();
Preconditions.checkState(strKeyNodeA.getString().equals("a"), strKeyNodeA);
Preconditions.checkState(nameNodeB.getString().equals("b"), nameNodeB);
assertNotLhsByDestructuring(strKeyNodeA);
assertLhsByDestructuring(nameNodeB);
}
public void testLhsByDestructuring7() {
Node root = parse("for ([a] of c) {}");
Node destructPat = root.getFirstFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Node nameNodeC = destructPat.getNext();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
assertLhsByDestructuring(nameNodeA);
assertNotLhsByDestructuring(nameNodeC);
}
public void testLhsByDestructuring7b() {
Node root = parse("for ([a] in c) {}");
Node destructPat = root.getFirstFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Node nameNodeC = destructPat.getNext();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
Preconditions.checkState(nameNodeC.getString().equals("c"), nameNodeC);
assertLhsByDestructuring(nameNodeA);
assertNotLhsByDestructuring(nameNodeC);
}
public void testLhsByDestructuring7c() {
Node root = parse("for (var [a] = [1];;) {}");
Node destructLhs = root.getFirstFirstChild().getFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
assertLhsByDestructuring(nameNodeA);
}
public void testLhsByDestructuring7d() {
Node root = parse("for (let [a] = [1];;) {}");
Node destructLhs = root.getFirstFirstChild().getFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
assertLhsByDestructuring(nameNodeA);
}
public void testLhsByDestructuring7e() {
Node root = parse("for (const [a] = [1];;) {}");
Node destructLhs = root.getFirstFirstChild().getFirstChild();
Preconditions.checkArgument(destructLhs.isDestructuringLhs());
Node destructPat = destructLhs.getFirstChild();
Preconditions.checkArgument(destructPat.isArrayPattern());
Node nameNodeA = destructPat.getFirstChild();
Preconditions.checkState(nameNodeA.getString().equals("a"), nameNodeA);
assertLhsByDestructuring(nameNodeA);
}
private static void assertLhsByDestructuring(Node n) {
assertTrue(NodeUtil.isLhsByDestructuring(n));
}
private static void assertNotLhsByDestructuring(Node n) {
assertFalse(NodeUtil.isLhsByDestructuring(n));
}
public void testNewQName1() {
Compiler compiler = new Compiler();
CompilerOptions options = new CompilerOptions();
options.setCodingConvention(new GoogleCodingConvention());
compiler.init(ImmutableList.<SourceFile>of(), ImmutableList.<SourceFile>of(), options);
Node actual = NodeUtil.newQName(compiler, "ns.prop");
Node expected = IR.getprop(
IR.name("ns"),
IR.string("prop"));
assertNodeTreesEqual(expected, actual);
}
public void testNewQualifiedNameNode2() {
Compiler compiler = new Compiler();
CompilerOptions options = new CompilerOptions();
options.setCodingConvention(new GoogleCodingConvention());
compiler.init(ImmutableList.<SourceFile>of(), ImmutableList.<SourceFile>of(), options);
Node actual = NodeUtil.newQName(compiler, "this.prop");
Node expected = IR.getprop(
IR.thisNode(),
IR.string("prop"));
assertNodeTreesEqual(expected, actual);
}
public void testGetBestJsDocInfoForClasses() {
Node classNode = getClassNode("/** @export */ class Foo {}");
assertTrue(NodeUtil.getBestJSDocInfo(classNode).isExport());
classNode = getClassNode("/** @export */ var Foo = class {}");
assertTrue(NodeUtil.getBestJSDocInfo(classNode).isExport());
classNode = getClassNode("/** @export */ var Foo = class Bar {}");
assertTrue(NodeUtil.getBestJSDocInfo(classNode).isExport());
}
public void testGetBestJsDocInfoExport() {
Node classNode = getClassNode("/** @constructor */ export class Foo {}");
assertTrue(NodeUtil.getBestJSDocInfo(classNode).isConstructor());
Node function = getFunctionNode("/** @constructor */ export function Foo() {}");
assertTrue(NodeUtil.getBestJSDocInfo(function).isConstructor());
function = getFunctionNode("/** @constructor */ export var Foo = function() {}");
assertTrue(NodeUtil.getBestJSDocInfo(function).isConstructor());
function = getFunctionNode("/** @constructor */ export let Foo = function() {}");
assertTrue(NodeUtil.getBestJSDocInfo(function).isConstructor());
}
public void testGetDeclaredTypeExpression1() {
Node ast = parse("function f(/** string */ x) {}");
Node x = getNameNode(ast, "x");
JSTypeExpression typeExpr = NodeUtil.getDeclaredTypeExpression(x);
assertThat(typeExpr.getRoot().getString()).isEqualTo("string");
}
public void testGetDeclaredTypeExpression2() {
Node ast = parse("/** @param {string} x */ function f(x) {}");
Node x = getNameNode(ast, "x");
JSTypeExpression typeExpr = NodeUtil.getDeclaredTypeExpression(x);
assertThat(typeExpr.getRoot().getString()).isEqualTo("string");
}
public void testGetDeclaredTypeExpression3() {
Node ast = parse("/** @param {...number} x */ function f(...x) {}");
Node x = getNameNode(ast, "x");
JSTypeExpression typeExpr = NodeUtil.getDeclaredTypeExpression(x);
assertNode(typeExpr.getRoot()).hasType(Token.ELLIPSIS);
assertThat(typeExpr.getRoot().getFirstChild().getString()).isEqualTo("number");
}
public void testGetDeclaredTypeExpression4() {
Node ast = parse("/** @param {number=} x */ function f(x = -1) {}");
Node x = getNameNode(ast, "x");
JSTypeExpression typeExpr = NodeUtil.getDeclaredTypeExpression(x);
assertNode(typeExpr.getRoot()).hasType(Token.EQUALS);
assertThat(typeExpr.getRoot().getFirstChild().getString()).isEqualTo("number");
}
public void testGetLhsNodesOfDeclaration() {
assertThat(getLhsNodesOfDeclaration("var x;")).hasSize(1);
assertThat(getLhsNodesOfDeclaration("var x, y;")).hasSize(2);
assertThat(getLhsNodesOfDeclaration("var f = function(x, y, z) {};")).hasSize(1);
assertThat(getLhsNodesOfDeclaration("var [x=a => a, y = b=>b+1] = arr;")).hasSize(2);
assertThat(getLhsNodesOfDeclaration("var [x=a => a, y = b=>b+1, ...z] = arr;")).hasSize(3);
assertThat(getLhsNodesOfDeclaration("var [ , , , y = b=>b+1, ...z] = arr;")).hasSize(2);
assertThat(getLhsNodesOfDeclaration("var {x = a=>a, y = b=>b+1} = obj;")).hasSize(2);
assertThat(getLhsNodesOfDeclaration("var {p1: x = a=>a, p2: y = b=>b+1} = obj;")).hasSize(2);
assertThat(getLhsNodesOfDeclaration("var {[pname]: x = a=>a, [p2name]: y} = obj;")).hasSize(2);
assertThat(getLhsNodesOfDeclaration("var {lhs1 = a, p2: [lhs2, lhs3 = b] = [notlhs]} = obj;"))
.hasSize(3);
}
public void testIsConstructor() {
assertTrue(NodeUtil.isConstructor(getFunctionNode("/** @constructor */ function Foo() {}")));
assertTrue(NodeUtil.isConstructor(getFunctionNode(
"/** @constructor */ var Foo = function() {}")));
assertTrue(NodeUtil.isConstructor(getFunctionNode(
"var x = {}; /** @constructor */ x.Foo = function() {}")));
assertTrue(NodeUtil.isConstructor(getFunctionNode("class Foo { constructor() {} }")));
assertFalse(NodeUtil.isConstructor(getFunctionNode("function Foo() {}")));
assertFalse(NodeUtil.isConstructor(getFunctionNode("var Foo = function() {}")));
assertFalse(NodeUtil.isConstructor(getFunctionNode("var x = {}; x.Foo = function() {};")));
assertFalse(NodeUtil.isConstructor(getFunctionNode("function constructor() {}")));
assertFalse(NodeUtil.isConstructor(getFunctionNode("class Foo { bar() {} }")));
}
public void testIsGetterOrSetter() {
Node fnNode = getFunctionNode("Object.defineProperty(this, 'bar', {get: function() {}});");
assertTrue(NodeUtil.isGetterOrSetter(fnNode.getParent()));
fnNode = getFunctionNode("Object.defineProperty(this, 'bar', {set: function() {}});");
assertTrue(NodeUtil.isGetterOrSetter(fnNode.getParent()));
fnNode = getFunctionNode("Object.defineProperties(this, {bar: {get: function() {}}});");
assertTrue(NodeUtil.isGetterOrSetter(fnNode.getParent()));
fnNode = getFunctionNode("Object.defineProperties(this, {bar: {set: function() {}}});");
assertTrue(NodeUtil.isGetterOrSetter(fnNode.getParent()));
fnNode = getFunctionNode("var x = {get bar() {}};");
assertTrue(NodeUtil.isGetterOrSetter(fnNode.getParent()));
fnNode = getFunctionNode("var x = {set bar(z) {}};");
assertTrue(NodeUtil.isGetterOrSetter(fnNode.getParent()));
}
public void testIsObjectDefinePropertiesDefinition() {
assertTrue(NodeUtil.isObjectDefinePropertiesDefinition(
getCallNode("Object.defineProperties(this, {});")));
assertTrue(NodeUtil.isObjectDefinePropertiesDefinition(
getCallNode("Object.defineProperties(this, foo);")));
assertFalse(NodeUtil.isObjectDefinePropertiesDefinition(
getCallNode("Object.defineProperties(this, {}, foo);")));
assertFalse(NodeUtil.isObjectDefinePropertiesDefinition(
getCallNode("Object.defineProperties(this);")));
assertFalse(NodeUtil.isObjectDefinePropertiesDefinition(
getCallNode("Object.defineProperties();")));
}
public void testIsObjectDefinePropertyDefinition() {
assertTrue(NodeUtil.isObjectDefinePropertyDefinition(
getCallNode("Object.defineProperty(this, 'foo', {});")));
assertTrue(NodeUtil.isObjectDefinePropertyDefinition(
getCallNode("Object.defineProperty(this, 'foo', foo);")));
assertFalse(NodeUtil.isObjectDefinePropertyDefinition(
getCallNode("Object.defineProperty(this, {});")));
assertFalse(NodeUtil.isObjectDefinePropertyDefinition(
getCallNode("Object.defineProperty(this);")));
assertFalse(NodeUtil.isObjectDefinePropertyDefinition(
getCallNode("Object.defineProperty();")));
}
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();
}
private boolean functionIsRValueOfAssign(String js) {
Node ast = parse(js);
Node nameNode = getNameNode(ast, "x");
Node funcNode = getFunctionNode(ast);
assertNotNull("No function node to test", funcNode);
return funcNode == NodeUtil.getRValueOfLValue(nameNode);
}
private void assertNodeTreesEqual(
Node expected, Node actual) {
String error = expected.checkTreeEquals(actual);
assertNull(error, error);
}
private static void testFunctionName(String js, String expected) {
assertEquals(
expected,
NodeUtil.getNearestFunctionName(getFunctionNode(js)));
}
private static Node getClassNode(String js) {
Node root = parse(js);
return getClassNode(root);
}
private static Iterable<Node> getLhsNodesOfDeclaration(String js) {
Node root = parse(js);
return NodeUtil.getLhsNodesOfDeclaration(root.getFirstChild());
}
private static Node getClassNode(Node n) {
if (n.isClass()) {
return n;
}
for (Node c : n.children()) {
Node result = getClassNode(c);
if (result != null) {
return result;
}
}
return null;
}
private static Node getCallNode(String js) {
Node root = parse(js);
return getCallNode(root);
}
private static Node getCallNode(Node n) {
if (n.isCall()) {
return n;
}
for (Node c : n.children()) {
Node result = getCallNode(c);
if (result != null) {
return result;
}
}
return null;
}
private static Node getFunctionNode(String js) {
Node root = parse(js);
return getFunctionNode(root);
}
private 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;
}
private 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;
}
private static boolean isValidPropertyName(String s) {
return NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, s);
}
private static boolean isValidQualifiedName(String s) {
return NodeUtil.isValidQualifiedName(LanguageMode.ECMASCRIPT3, s);
}
}