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