/* * Copyright 2005 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 com.google.javascript.rhino.Node; public class VarCheckTest extends CompilerTestCase { private static final String EXTERNS = "var window; function alert() {}"; private CheckLevel strictModuleDepErrorLevel; private boolean sanityCheck = false; private CheckLevel externValidationErrorLevel; private CompilerPass testSetupPass; public VarCheckTest() { super(EXTERNS); } @Override protected void setUp() throws Exception { super.setUp(); // Setup value set by individual tests to the appropriate defaults. super.allowExternsChanges(true); super.enableAstValidation(true); strictModuleDepErrorLevel = CheckLevel.OFF; externValidationErrorLevel = null; sanityCheck = false; testSetupPass = null; } @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); options.setWarningLevel(DiagnosticGroups.STRICT_MODULE_DEP_CHECK, strictModuleDepErrorLevel); if (externValidationErrorLevel != null) { options.setWarningLevel(DiagnosticGroups.EXTERNS_VALIDATION, externValidationErrorLevel); } return options; } @Override protected CompilerPass getProcessor(final Compiler compiler) { if (!sanityCheck) { return new CompilerPass() { @Override public void process(Node externs, Node root) { if (testSetupPass != null) { testSetupPass.process(externs, root); } new VarCheck(compiler, false).process(externs, root); if (!compiler.hasErrors()) { new VarCheck(compiler, true).process(externs, root); } } }; } return new VarCheck(compiler, sanityCheck); } @Override protected int getNumRepetitions() { // Because we synthesize externs, the second pass won't emit a warning. return 1; } public void testBreak() { testSame("a: while(1) break a;"); } public void testContinue() { testSame("a: while(1) continue a;"); } public void testReferencedVarNotDefined() { test("x = 0;", null, VarCheck.UNDEFINED_VAR_ERROR); } public void testReferencedVarDefined1() { testSame("var x, y; x=1;"); } public void testReferencedVarDefined2() { testSame("var x; function y() {x=1;}"); } public void testReferencedVarsExternallyDefined() { testSame("var x = window; alert(x);"); } public void testMultiplyDeclaredVars1() { test("var x = 1; var x = 2;", null, SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR); } public void testMultiplyDeclaredVars2() { test("var y; try { y=1 } catch (x) {}" + "try { y=1 } catch (x) {}", "var y;try{y=1}catch(x){}try{y=1}catch(x){}"); } public void testMultiplyDeclaredVars3() { test("try { var x = 1; x *=2; } catch (x) {}", null, SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR); } public void testMultiplyDeclaredVars4() { testSame("x;", "var x = 1; var x = 2;", SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR, true); } public void testVarReferenceInExterns() { testSame("asdf;", "var asdf;", VarCheck.NAME_REFERENCE_IN_EXTERNS_ERROR); } public void testCallInExterns() { testSame("yz();", "function yz() {}", VarCheck.NAME_REFERENCE_IN_EXTERNS_ERROR); } public void testPropReferenceInExterns1() { testSame("asdf.foo;", "var asdf;", VarCheck.UNDEFINED_EXTERN_VAR_ERROR); } public void testPropReferenceInExterns2() { testSame("asdf.foo;", "", VarCheck.UNDEFINED_VAR_ERROR, true); } public void testPropReferenceInExterns3() { testSame("asdf.foo;", "var asdf;", VarCheck.UNDEFINED_EXTERN_VAR_ERROR); externValidationErrorLevel = CheckLevel.ERROR; test( "asdf.foo;", "var asdf;", "", VarCheck.UNDEFINED_EXTERN_VAR_ERROR, null); externValidationErrorLevel = CheckLevel.OFF; test("asdf.foo;", "var asdf;", "var asdf;", null, null); } public void testVarInWithBlock() { test("var a = {b:5}; with (a){b;}", null, VarCheck.UNDEFINED_VAR_ERROR); } public void testValidFunctionExpr() { testSame("(function() {});"); } public void testRecursiveFunction() { testSame("(function a() { return a(); })();"); } public void testRecursiveFunction2() { testSame("var a = 3; (function a() { return a(); })();"); } public void testLegalVarReferenceBetweenModules() { testDependentModules("var x = 10;", "var y = x++;", null); } public void testMissingModuleDependencyDefault() { testIndependentModules("var x = 10;", "var y = x++;", null, VarCheck.MISSING_MODULE_DEP_ERROR); } public void testViolatedModuleDependencyDefault() { testDependentModules("var y = x++;", "var x = 10;", VarCheck.VIOLATED_MODULE_DEP_ERROR); } public void testMissingModuleDependencySkipNonStrict() { sanityCheck = true; testIndependentModules("var x = 10;", "var y = x++;", null, null); } public void testViolatedModuleDependencySkipNonStrict() { sanityCheck = true; testDependentModules("var y = x++;", "var x = 10;", null); } public void testMissingModuleDependencySkipNonStrictNotPromoted() { sanityCheck = true; strictModuleDepErrorLevel = CheckLevel.ERROR; testIndependentModules("var x = 10;", "var y = x++;", null, null); } public void testViolatedModuleDependencyNonStrictNotPromoted() { sanityCheck = true; strictModuleDepErrorLevel = CheckLevel.ERROR; testDependentModules("var y = x++;", "var x = 10;", null); } public void testDependentStrictModuleDependencyCheck() { strictModuleDepErrorLevel = CheckLevel.ERROR; testDependentModules("var f = function() {return new B();};", "var B = function() {}", VarCheck.STRICT_MODULE_DEP_ERROR); } public void testIndependentStrictModuleDependencyCheck() { strictModuleDepErrorLevel = CheckLevel.ERROR; testIndependentModules("var f = function() {return new B();};", "var B = function() {}", VarCheck.STRICT_MODULE_DEP_ERROR, null); } public void testStarStrictModuleDependencyCheck() { strictModuleDepErrorLevel = CheckLevel.WARNING; testSame(createModuleStar("function a() {}", "function b() { a(); c(); }", "function c() { a(); }"), VarCheck.STRICT_MODULE_DEP_ERROR); } public void testForwardVarReferenceInLocalScope1() { testDependentModules("var x = 10; function a() {y++;}", "var y = 11; a();", null); } public void testForwardVarReferenceInLocalScope2() { // It would be nice if this pass could use a call graph to flag this case // as an error, but it currently doesn't. testDependentModules("var x = 10; function a() {y++;} a();", "var y = 11;", null); } private void testDependentModules(String code1, String code2, DiagnosticType error) { testDependentModules(code1, code2, error, null); } private void testDependentModules(String code1, String code2, DiagnosticType error, DiagnosticType warning) { testTwoModules(code1, code2, true, error, warning); } private void testIndependentModules(String code1, String code2, DiagnosticType error, DiagnosticType warning) { testTwoModules(code1, code2, false, error, warning); } private void testTwoModules(String code1, String code2, boolean m2DependsOnm1, DiagnosticType error, DiagnosticType warning) { JSModule m1 = new JSModule("m1"); m1.add(SourceFile.fromCode("input1", code1)); JSModule m2 = new JSModule("m2"); m2.add(SourceFile.fromCode("input2", code2)); if (m2DependsOnm1) { m2.addDependency(m1); } test(new JSModule[] { m1, m2 }, new String[] { code1, code2 }, error, warning); } ////////////////////////////////////////////////////////////////////////////// // Test synthesis of externs public void testSimple() { checkSynthesizedExtern("x", "var x;"); checkSynthesizedExtern("var x", ""); } public void testSimpleSanityCheck() { sanityCheck = true; try { checkSynthesizedExtern("x", ""); } catch (RuntimeException e) { assertTrue(e.getMessage().indexOf("Unexpected variable x") != -1); } } public void testParameter() { checkSynthesizedExtern("function f(x){}", ""); } public void testLocalVar() { checkSynthesizedExtern("function f(){x}", "var x"); } public void testTwoLocalVars() { checkSynthesizedExtern("function f(){x}function g() {x}", "var x"); } public void testInnerFunctionLocalVar() { checkSynthesizedExtern("function f(){function g() {x}}", "var x"); } public void testNoCreateVarsForLabels() { checkSynthesizedExtern("x:var y", ""); } public void testVariableInNormalCodeUsedInExterns1() { checkSynthesizedExtern( "x.foo;", "var x;", "var x; x.foo;"); } public void testVariableInNormalCodeUsedInExterns2() { checkSynthesizedExtern( "x;", "var x;", "var x; x;"); } public void testVariableInNormalCodeUsedInExterns3() { checkSynthesizedExtern( "x.foo;", "function x() {}", "var x; x.foo; "); } public void testVariableInNormalCodeUsedInExterns4() { checkSynthesizedExtern( "x;", "function x() {}", "var x; x; "); } private final static class VariableTestCheck implements CompilerPass { final AbstractCompiler compiler; VariableTestCheck(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverseRoots(compiler, Lists.newArrayList(externs, root), new AbstractPostOrderCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName() && !parent.isFunction() && !parent.isLabel()) { assertTrue("Variable " + n.getString() + " should have be declared", t.getScope().isDeclared(n.getString(), true)); } } }); } } public void checkSynthesizedExtern( String input, String expectedExtern) { checkSynthesizedExtern("", input, expectedExtern); } public void checkSynthesizedExtern( String extern, String input, String expectedExtern) { Compiler compiler = new Compiler(); CompilerOptions options = new CompilerOptions(); options.setWarningLevel( DiagnosticGroup.forType(VarCheck.UNDEFINED_VAR_ERROR), CheckLevel.OFF); compiler.init( ImmutableList.of(SourceFile.fromCode("extern", extern)), ImmutableList.of(SourceFile.fromCode("input", input)), options); compiler.parseInputs(); assertFalse(compiler.hasErrors()); Node externsAndJs = compiler.getRoot(); Node root = externsAndJs.getLastChild(); Node rootOriginal = root.cloneTree(); Node externs = externsAndJs.getFirstChild(); Node expected = compiler.parseTestCode(expectedExtern); assertFalse(compiler.hasErrors()); (new VarCheck(compiler, sanityCheck)) .process(externs, root); if (!sanityCheck) { (new VariableTestCheck(compiler)).process(externs, root); } String externsCode = compiler.toSource(externs); String expectedCode = compiler.toSource(expected); assertEquals(expectedCode, externsCode); } }