/* * Copyright 2006 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; public final class CheckSideEffectsTest extends Es6CompilerTestCase { public CheckSideEffectsTest() { this.parseTypeInfo = true; allowExternsChanges(true); } @Override protected int getNumRepetitions() { return 1; } @Override protected CompilerPass getProcessor(Compiler compiler) { return new CheckSideEffects(compiler, true, true); } private final DiagnosticType e = CheckSideEffects.USELESS_CODE_ERROR; public void testUselessCode() { testSame("function f(x) { if(x) return; }"); testWarning("function f(x) { if(x); }", e); testSameEs6("var f = x=>x"); testWarningEs6("var f = (x)=>{ if(x); }", e); testSame("if(x) x = y;"); testWarning("if(x) x == bar();", "if(x) JSCOMPILER_PRESERVE(x == bar());", e); testSame("x = 3;"); testWarning("x == 3;", "JSCOMPILER_PRESERVE(x == 3);", e); testSame("var x = 'test'"); testWarning("var x = 'test'\n'Breakstr'", "var x = 'test'\nJSCOMPILER_PRESERVE('Breakstr')", e); testSame(""); testSame("foo();;;;bar();;;;"); testSame("var a, b; a = 5, b = 6"); testWarning("var a, b; a = 5, b == 6", "var a, b; a = 5, JSCOMPILER_PRESERVE(b == 6)", e); testWarning("var a, b; a = (5, 6)", "var a, b; a = (JSCOMPILER_PRESERVE(5), 6)", e); testWarning("var a, b; a = (bar(), 6, 7)", "var a, b; a = (bar(), JSCOMPILER_PRESERVE(6), 7)", e); testWarning("var a, b; a = (bar(), bar(), 7, 8)", "var a, b; a = (bar(), bar(), JSCOMPILER_PRESERVE(7), 8)", e); testSame("var a, b; a = (b = 7, 6)"); testSame( LINE_JOINER.join( "function x(){}", "function f(a, b){}", "f(1,(x(), 2));")); testWarning( LINE_JOINER.join( "function x(){}", "function f(a, b){}", "f(1,(2, 3));"), LINE_JOINER.join( "function x(){}", "function f(a, b){}", "f(1,(JSCOMPILER_PRESERVE(2), 3));"), e); testWarningEs6("var x = `TemplateA`\n'TestB'", "var x = `TemplateA`\nJSCOMPILER_PRESERVE('TestB')", e); testWarningEs6("`LoneTemplate`", "JSCOMPILER_PRESERVE(`LoneTemplate`)", e); testWarningEs6( LINE_JOINER.join( "var name = 'Bad';", "`${name}Template`;"), LINE_JOINER.join( "var name = 'Bad';", "JSCOMPILER_PRESERVE(`${name}Template`)"), e); testWarningEs6( LINE_JOINER.join( "var name = 'BadTail';", "`Template${name}`;"), LINE_JOINER.join( "var name = 'BadTail';", "JSCOMPILER_PRESERVE(`Template${name}`)"), e); testSameEs6( LINE_JOINER.join( "var name = 'Good';", "var templateString = `${name}Template`;")); testSameEs6("var templateString = `Template`;"); testSameEs6("tagged`Template`;"); testSameEs6("tagged`${name}Template`;"); testSameEs6(LINE_JOINER.join( "var obj = {", " itm1: 1,", " itm2: 2", "}", "var { itm1: de_item1, itm2: de_item2 } = obj;")); testSameEs6(LINE_JOINER.join( "var obj = {", " itm1: 1,", " itm2: 2", "}", "var { itm1, itm2 } = obj;")); testSameEs6(LINE_JOINER.join( "var arr = ['item1', 'item2', 'item3'];", "var [ itm1 = 1, itm2 = 2 ] = arr;")); testSameEs6(LINE_JOINER.join( "var arr = ['item1', 'item2', 'item3'];", "var [ itm1 = 1, itm2 = 2 ] = badArr;")); testSameEs6(LINE_JOINER.join( "var arr = ['item1', 'item2', 'item3'];", "function f(){}", "var [ itm1 = f(), itm2 = 2 ] = badArr;")); testSameEs6("function c(a, b = 1) {}; c(1);"); testSameEs6("function c(a, b = f()) {}; c(1);"); testSameEs6("function c(a, {b, c}) {}; c(1);"); testSameEs6("function c(a, {b, c}) {}; c(1, {b: 2, c: 3});"); testWarningEs6("var f = s => {key:s}", e); testWarningEs6("var f = s => {key:s + 1}", e); testWarningEs6("var f = s => {s}", e); } public void testUselessCodeInFor() { testSame("for(var x = 0; x < 100; x++) { foo(x) }"); testSame("for(; true; ) { bar() }"); testSame("for(foo(); true; foo()) { bar() }"); testWarning("for(void 0; true; foo()) { bar() }", "for(JSCOMPILER_PRESERVE(void 0); true; foo()) { bar() }", e); testWarning("for(foo(); true; void 0) { bar() }", "for(foo(); true; JSCOMPILER_PRESERVE(void 0)) { bar() }", e); testWarning("for(foo(); true; (1, bar())) { bar() }", "for(foo(); true; (JSCOMPILER_PRESERVE(1), bar())) { bar() }", e); testSame("for(foo in bar) { foo() }"); testSame("for (i = 0; el = el.previousSibling; i++) {}"); testSame("for (i = 0; el = el.previousSibling; i++);"); } public void testTypeAnnotations() { testWarning("x;", "JSCOMPILER_PRESERVE(x);", e); testWarning("a.b.c.d;", "JSCOMPILER_PRESERVE(a.b.c.d);", e); testSame("/** @type {Number} */ a.b.c.d;"); testSame("if (true) { /** @type {Number} */ a.b.c.d; }"); testWarning("function A() { this.foo; }", "function A() { JSCOMPILER_PRESERVE(this.foo); }", e); testSame("function A() { /** @type {Number} */ this.foo; }"); } public void testJSDocComments() { testSame("function A() { /** This is a JsDoc comment */ this.foo; }"); testWarning("function A() { /* This is a normal comment */ this.foo; }", "function A() { " + " /* This is a normal comment */ JSCOMPILER_PRESERVE(this.foo); }", e); } public void testIssue80() { testSame("(0, eval)('alert');"); testWarning( "(0, foo)('alert');", "(JSCOMPILER_PRESERVE(0), foo)('alert');", e); } public void testIsue504() { test("void f();", "JSCOMPILER_PRESERVE(void f());", null, e, "Suspicious code. The result of the 'void' operator is not being used."); } public void testExternFunctions() { String externs = LINE_JOINER.join( "/** @return {boolean}", " * @nosideeffects */", "function noSideEffectsExtern(){}", "/** @return {boolean}", " * @nosideeffects */", "var noSideEffectsExtern2 = function(){};", "/** @return {boolean} */ function hasSideEffectsExtern(){}", "/** @return {boolean} */ var hasSideEffectsExtern2 = function(){}"); testSame(externs, "alert(noSideEffectsExtern());", null); test(externs, "noSideEffectsExtern();", "JSCOMPILER_PRESERVE(noSideEffectsExtern());", null, e, "Suspicious code. The result of the extern function call " + "'noSideEffectsExtern' is not being used."); test(externs, "noSideEffectsExtern2();", "JSCOMPILER_PRESERVE(noSideEffectsExtern2());", null, e, "Suspicious code. The result of the extern function call " + "'noSideEffectsExtern2' is not being used."); testSame(externs, "hasSideEffectsExtern()", null); testSame(externs, "hasSideEffectsExtern2()", null); // Methods redefined in inner scopes should not trigger a warning testSame(externs, "(function() { function noSideEffectsExtern() {}; " + "noSideEffectsExtern(); })()", null); } public void testExternPropertyFunctions() { String externs = LINE_JOINER.join( "/** @const */ var foo = {};", "/** @return {boolean}", " * @nosideeffects */", "foo.noSideEffectsExtern = function(){}"); testSame(externs, "alert(foo.noSideEffectsExtern());", null); test(externs, "foo.noSideEffectsExtern();", "JSCOMPILER_PRESERVE(foo.noSideEffectsExtern());", null, e, "Suspicious code. The result of the extern function call " + "'foo.noSideEffectsExtern' is not being used."); // Methods redefined in inner scopes should not trigger a warning testSame(externs, "(function() { var foo = {}; " + "foo.noSideEffectsExtern = function() {}; " + "noSideEffectsExtern(); })()", null); } }