/* * Copyright 2014 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 junit.framework.TestCase.assertFalse; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.rhino.Node; import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; /** * Tests for {@link InferConsts}. * @author tbreisacher@google.com (Tyler Breisacher) */ public final class InferConstsTest extends TestCase { public void testSimple() { testConsts("var x = 3;", "x"); testConsts("/** @const */ var x;", "x"); testConsts("var x = 3, y = 4;", "x", "y"); testConsts("var x = 3, y;", "x"); testConsts("var x = 3; function f(){x;}", "x"); } public void testSimpleLetConst() { testConsts("let x = 3, y", "x"); testConsts("let x = 3; let y = 4;", "x", "y"); testConsts("let x = 3, y = 4; x++;", "y"); testConsts("let x = 3; function f(){let x = 4;}", "x"); testConsts("/** @const */ let x;", "x"); testConsts("const x = 1;", "x"); } public void testUnfound() { testNotConsts("var x = 2; x++;", "x"); testNotConsts("var x = 2; x = 3;", "x"); testNotConsts("var x = 3; function f(){x++;}", "x"); testNotConsts("let x = 3; x++;", "x"); testNotConsts("let x = 3; x = 2;", "x", "y"); testNotConsts("/** @const */let x; let y;", "y"); testNotConsts("let x = 3; function f() {let x = 4; x++;} x++;", "x"); } public void testForOf() { testNotConsts("var x = 0; for (x of [1, 2, 3]) {}", "x"); testNotConsts("var x = 0; for (x of {a, b, c}) {}", "x"); } public void testForIn() { testNotConsts("var x = 0; for (x in {a, b}) {}", "x"); } public void testForVar() { testNotConsts("for (var x = 0; x < 2; x++) {}", "x"); testNotConsts("for (var x in [1, 2, 3]) {}", "x"); testNotConsts("for (var x of {a, b, c}) {}", "x"); } public void testForLet() { testNotConsts("for (let x = 0; x < 2; x++) {}", "x"); testNotConsts("for (let x in [1, 2, 3]) {}", "x"); testNotConsts("for (let x of {a, b, c}) {}", "x"); } public void testForConst() { // Using 'const' here is not allowed, and ConstCheck should warn for this testConsts("for (const x = 0; x < 2; x++) {}", "x"); testConsts("for (const x in [1, 2, 3]) {}", "x"); testConsts("for (const x of {a, b, c}) {}", "x"); } public void testFunctionParam() { testConsts("var x = function(){};", "x"); testConsts("var x = ()=>{};", "x"); testConsts("function fn(a){var b = a + 1}; ", "a", "b"); testConsts("function fn(a = 1){var b = a + 1}; ", "a", "b"); testConsts("function fn(a, {b, c}){var d = a + 1}; ", "a", "b", "c", "d"); } public void testClass() { testConsts("var Foo = class {}", "Foo"); testConsts("class Foo {}", "Foo"); testConsts("var Foo = function() {};", "Foo"); testConsts("function Foo() {}", "Foo"); } public void testArguments() { testConsts("var arguments = 3;", "arguments"); } public void testDestructuring() { testConsts("var [a, b, c] = [1, 2, 3];", "a", "b", "c"); testNotConsts("var [a, b, c] = obj;", "obj"); testNotConsts("" + "var [a, b, c] = [1, 2, 3];" + "[a, b, c] = [1, 2, 3];", "a", "b", "c"); testConsts("" + "var [a, b, c] = [1, 2, 3];" + "[a, b]= [1, 2];", "c"); testConsts("var {a: b} = {a: 1}", "b"); testNotConsts("var {a: b} = {a: 1}", "a"); // Note that this "a" looks for the destructured "a" testConsts("var obj = {a: 1}; var {a} = obj", "a"); testNotConsts("" + "var [{a: x} = {a: 'y'}] = [{a: 'x'}];" + "[{a: x} = {a: 'x'}] = {};", "x"); testNotConsts("" + "let fg = '', bg = '';" + "({fg, bg} = pal[val - 1]);", "fg", "bg"); } public void testDefaultValue() { testConsts("function fn(a = 1){}", "a"); testNotConsts("function fn(a = 1){a = 2}", "a"); testConsts("function fn({b, c} = {b:1, c:2}){}", "b", "c"); testNotConsts("function fn({b, c} = {b:1, c:2}){c = 1}", "c"); } public void testVarInBlock() { testConsts("function f() { if (true) { var x = function() {}; x(); } }", "x"); } private void testConsts(String js, String... constants) { testInferConstsHelper(true, js, constants); } private void testNotConsts(String js, String... constants) { testInferConstsHelper(false, js, constants); } private void testInferConstsHelper(boolean constExpected, String js, String... constants) { Compiler compiler = new Compiler(); SourceFile input = SourceFile.fromCode("js", js); compiler.init(ImmutableList.<SourceFile>of(), ImmutableList.of(input), new CompilerOptions()); compiler.options.setLanguageIn(LanguageMode.ECMASCRIPT_2015); compiler.setLanguageMode(LanguageMode.ECMASCRIPT_2015); Node root = compiler.parseInputs(); assertNotNull("Unexpected parse error(s): " + Joiner.on('\n').join(compiler.getErrors()), root); CompilerPass inferConsts = new InferConsts(compiler); inferConsts.process( compiler.getExternsRoot(), compiler.getJsRoot()); Node n = compiler.getRoot().getLastChild(); FindConstants constFinder = new FindConstants(constants); NodeTraversal.traverseEs6(compiler, n, constFinder); for (String name : constants) { if (constExpected) { assertTrue("Expect constant: " + name, constFinder.foundNodes.containsKey(name)); } else { assertFalse("Unexpected constant: " + name, constFinder.foundNodes.containsKey(name)); } } } private static class FindConstants extends NodeTraversal.AbstractPostOrderCallback { final String[] names; final Map<String, Node> foundNodes; FindConstants(String[] names) { this.names = names; foundNodes = new HashMap<>(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { for (String name : names) { if ((n.matchesQualifiedName(name) || ((n.isStringKey() || n.isMemberFunctionDef()) && n.getString().equals(name))) && n.getBooleanProp(Node.IS_CONSTANT_VAR)) { foundNodes.put(name, n); } } } } }