/* * Copyright 2009 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.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import static com.google.javascript.jscomp.MarkNoSideEffectCalls.INVALID_NO_SIDE_EFFECT_ANNOTATION; import com.google.javascript.rhino.Node; import java.util.Collections; import java.util.List; /** * Tests for {@link MarkNoSideEffectCalls} * */ public class MarkNoSideEffectCallsTest extends CompilerTestCase { List noSideEffectCalls = Lists.newArrayList(); private static String kExterns = "function externSef1(){}" + "/**@nosideeffects*/function externNsef1(){}" + "var externSef2 = function(){};" + "/**@nosideeffects*/var externNsef2 = function(){};" + "var externNsef3 = /**@nosideeffects*/function(){};" + "var externObj;" + "externObj.sef1 = function(){};" + "/**@nosideeffects*/externObj.nsef1 = function(){};" + "externObj.nsef2 = /**@nosideeffects*/function(){};" + "externObj.sef2;" + "/**@nosideeffects*/externObj.nsef3;"; public MarkNoSideEffectCallsTest() { super(kExterns); } @Override protected int getNumRepetitions() { // run pass once. return 1; } @Override protected void tearDown() throws Exception { super.tearDown(); noSideEffectCalls.clear(); } public void testFunctionAnnotation() throws Exception { testMarkCalls("/**@nosideeffects*/function f(){}", "f()", ImmutableList.of("f")); testMarkCalls("/**@nosideeffects*/var f = function(){};", "f()", ImmutableList.of("f")); testMarkCalls("var f = /**@nosideeffects*/function(){};", "f()", ImmutableList.of("f")); testMarkCalls("var f; f = /**@nosideeffects*/function(){};", "f()", ImmutableList.of("f")); testMarkCalls("var f; /**@nosideeffects*/ f = function(){};", "f()", ImmutableList.of("f")); // no annotation testMarkCalls("function f(){}", Collections.emptyList()); testMarkCalls("function f(){} f()", Collections.emptyList()); // 2 annotations testMarkCalls("/**@nosideeffects*/var f = " + "/**@nosideeffects*/function(){};", "f()", ImmutableList.of("f")); } public void testNamespaceAnnotation() throws Exception { testMarkCalls("var o = {}; o.f = /**@nosideeffects*/function(){};", "o.f()", ImmutableList.of("o.f")); testMarkCalls("var o = {}; o.f = /**@nosideeffects*/function(){};", "o.f()", ImmutableList.of("o.f")); testMarkCalls("var o = {}; o.f = function(){}; o.f()", Collections.emptyList()); } public void testConstructorAnnotation() throws Exception { testMarkCalls("/**@nosideeffects*/function c(){};", "new c", ImmutableList.of("c")); testMarkCalls("var c = /**@nosideeffects*/function(){};", "new c", ImmutableList.of("c")); testMarkCalls("/**@nosideeffects*/var c = function(){};", "new c", ImmutableList.of("c")); testMarkCalls("function c(){}; new c", Collections.emptyList()); } public void testMultipleDefinition() throws Exception { testMarkCalls("/**@nosideeffects*/function f(){}" + "/**@nosideeffects*/f = function(){};", "f()", ImmutableList.of("f")); testMarkCalls("function f(){}" + "/**@nosideeffects*/f = function(){};", "f()", Collections.emptyList()); testMarkCalls("/**@nosideeffects*/function f(){}", "f = function(){};" + "f()", Collections.emptyList()); } public void testAssignNoFunction() throws Exception { testMarkCalls("/**@nosideeffects*/function f(){}", "f = 1; f()", ImmutableList.of("f")); testMarkCalls("/**@nosideeffects*/function f(){}", "f = 1 || 2; f()", Collections.emptyList()); } public void testPrototype() throws Exception { testMarkCalls("function c(){};" + "/**@nosideeffects*/c.prototype.g = function(){};", "var o = new c; o.g()", ImmutableList.of("o.g")); testMarkCalls("function c(){};" + "/**@nosideeffects*/c.prototype.g = function(){};", "function f(){}" + "var o = new c; o.g(); f()", ImmutableList.of("o.g")); // replace o.f with a function that has side effects testMarkCalls("function c(){};" + "/**@nosideeffects*/c.prototype.g = function(){};", "var o = new c;" + "o.g = function(){};" + "o.g()", ImmutableList.of()); // two classes with same property; neither has side effects testMarkCalls("function c1(){};" + "/**@nosideeffects*/c1.prototype.f = function(){};" + "function c2(){};" + "/**@nosideeffects*/c2.prototype.f = function(){};", "var o = new c1;" + "o.f()", ImmutableList.of("o.f")); // two classes with same property; one has side effects testMarkCalls("function c1(){};" + "/**@nosideeffects*/c1.prototype.f = function(){};", "function c2(){};" + "c2.prototype.f = function(){};" + "var o = new c1;" + "o.f()", Collections.emptyList()); } public void testAnnotationInExterns() throws Exception { testMarkCalls("externSef1()", Collections.emptyList()); testMarkCalls("externSef2()", Collections.emptyList()); testMarkCalls("externNsef1()", ImmutableList.of("externNsef1")); testMarkCalls("externNsef2()", ImmutableList.of("externNsef2")); testMarkCalls("externNsef3()", ImmutableList.of("externNsef3")); } public void testNamespaceAnnotationInExterns() throws Exception { testMarkCalls("externObj.sef1()", Collections.emptyList()); testMarkCalls("externObj.sef2()", Collections.emptyList()); testMarkCalls("externObj.nsef1()", ImmutableList.of("externObj.nsef1")); testMarkCalls("externObj.nsef2()", ImmutableList.of("externObj.nsef2")); testMarkCalls("externObj.nsef3()", ImmutableList.of("externObj.nsef3")); } public void testOverrideDefinitionInSource() throws Exception { // both have side effects. testMarkCalls("var obj = {}; obj.sef1 = function(){}; obj.sef1()", Collections.emptyList()); // extern has side effects. testMarkCalls("var obj = {};" + "/**@nosideeffects*/obj.sef1 = function(){};", "obj.sef1()", Collections.emptyList()); // override in source also has side effects. testMarkCalls("var obj = {}; obj.nsef1 = function(){}; obj.nsef1()", Collections.emptyList()); // override in source also has no side effects. testMarkCalls("var obj = {};" + "/**@nosideeffects*/obj.nsef1 = function(){};", "obj.nsef1()", ImmutableList.of("obj.nsef1")); } public void testApply1() throws Exception { testMarkCalls("/**@nosideeffects*/ var f = function() {}", "f.apply()", ImmutableList.of("f.apply")); } public void testApply2() throws Exception { testMarkCalls("var f = function() {}", "f.apply()", ImmutableList.of()); } public void testCall1() throws Exception { testMarkCalls("/**@nosideeffects*/ var f = function() {}", "f.call()", ImmutableList.of("f.call")); } public void testCall2() throws Exception { testMarkCalls("var f = function() {}", "f.call()", ImmutableList.of()); } public void testInvalidAnnotation1() throws Exception { test("/** @nosideeffects */ function foo() {}", null, INVALID_NO_SIDE_EFFECT_ANNOTATION); } public void testInvalidAnnotation2() throws Exception { test("var f = /** @nosideeffects */ function() {}", null, INVALID_NO_SIDE_EFFECT_ANNOTATION); } public void testInvalidAnnotation3() throws Exception { test("/** @nosideeffects */ var f = function() {}", null, INVALID_NO_SIDE_EFFECT_ANNOTATION); } public void testInvalidAnnotation4() throws Exception { test("var f = function() {};" + "/** @nosideeffects */ f.x = function() {}", null, INVALID_NO_SIDE_EFFECT_ANNOTATION); } public void testInvalidAnnotation5() throws Exception { test("var f = function() {};" + "f.x = /** @nosideeffects */ function() {}", null, INVALID_NO_SIDE_EFFECT_ANNOTATION); } void testMarkCalls(String source, List expected) { testMarkCalls("", source, expected); } void testMarkCalls( String extraExterns, String source, List expected) { testSame(kExterns + extraExterns, source, null); assertEquals(expected, noSideEffectCalls); noSideEffectCalls.clear(); } @Override protected CompilerPass getProcessor(Compiler compiler) { return new NoSideEffectCallEnumerator(compiler); } /** * Run MarkNoSideEffectCalls, then gather a list of calls that are * marked as having no side effects. */ private class NoSideEffectCallEnumerator extends AbstractPostOrderCallback implements CompilerPass { private final MarkNoSideEffectCalls passUnderTest; private final Compiler compiler; NoSideEffectCallEnumerator(Compiler compiler) { this.passUnderTest = new MarkNoSideEffectCalls(compiler); this.compiler = compiler; } @Override public void process(Node externs, Node root) { passUnderTest.process(externs, root); NodeTraversal.traverse(compiler, externs, this); NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isNew()) { if (!NodeUtil.constructorCallHasSideEffects(n)) { noSideEffectCalls.add(n.getFirstChild().getQualifiedName()); } } else if (n.isCall()) { if (!NodeUtil.functionCallHasSideEffects(n)) { noSideEffectCalls.add(n.getFirstChild().getQualifiedName()); } } } } }