/* * Copyright 2008 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 com.google.javascript.jscomp.CheckGlobalNames.NAME_DEFINED_LATE_WARNING; import static com.google.javascript.jscomp.CheckGlobalNames.UNDEFINED_NAME_WARNING; import static com.google.javascript.jscomp.CheckGlobalNames.STRICT_MODULE_DEP_QNAME; import com.google.javascript.rhino.Node; /** * Tests for {@code CheckGlobalNames.java}. * * @author nicksantos@google.com (Nick Santos) */ public class CheckGlobalNamesTest extends CompilerTestCase { private boolean injectNamespace = false; public CheckGlobalNamesTest() { super("function alert() {}" + "/** @constructor */ function Object(){}" + "Object.prototype.hasOwnProperty = function() {};" + "/** @constructor */ function Function(){}" + "Function.prototype.call = function() {};"); } @Override protected CompilerPass getProcessor(final Compiler compiler) { final CheckGlobalNames checkGlobalNames = new CheckGlobalNames( compiler, CheckLevel.WARNING); if (injectNamespace) { return new CompilerPass() { @Override public void process(Node externs, Node js) { checkGlobalNames.injectNamespace( new GlobalNamespace(compiler, externs, js)) .process(externs, js); } }; } else { return checkGlobalNames; } } @Override public void setUp() { injectNamespace = false; STRICT_MODULE_DEP_QNAME.level = CheckLevel.WARNING; } private static final String GET_NAMES = "var a = {get d() {return 1}}; a.b = 3; a.c = {get e() {return 5}};"; private static final String SET_NAMES = "var a = {set d(x) {}}; a.b = 3; a.c = {set e(y) {}};"; private static final String NAMES = "var a = {d: 1}; a.b = 3; a.c = {e: 5};"; public void testRefToDefinedProperties1() { testSame(NAMES + "alert(a.b); alert(a.c.e);"); testSame(GET_NAMES + "alert(a.b); alert(a.c.e);"); testSame(SET_NAMES + "alert(a.b); alert(a.c.e);"); } public void testRefToDefinedProperties2() { testSame(NAMES + "a.x={}; alert(a.c);"); testSame(GET_NAMES + "a.x={}; alert(a.c);"); testSame(SET_NAMES + "a.x={}; alert(a.c);"); } public void testRefToDefinedProperties3() { testSame(NAMES + "alert(a.d);"); testSame(GET_NAMES + "alert(a.d);"); testSame(SET_NAMES + "alert(a.d);"); } public void testRefToMethod1() { testSame("function foo() {}; foo.call();"); } public void testRefToMethod2() { testSame("function foo() {}; foo.call.call();"); } public void testCallUndefinedFunctionGivesNoWaring() { // We don't bother checking undeclared variables--there's another // pass that does this already. testSame("foo();"); } public void testRefToPropertyOfAliasedName() { // this is OK, because "a" was aliased testSame(NAMES + "alert(a); alert(a.x);"); } public void testRefToUndefinedProperty1() { testSame(NAMES + "alert(a.x);", UNDEFINED_NAME_WARNING); } public void testRefToUndefinedProperty2() { testSame(NAMES + "a.x();", UNDEFINED_NAME_WARNING); } public void testRefToUndefinedProperty3() { testSame(NAMES + "alert(a.c.x);", UNDEFINED_NAME_WARNING); testSame(GET_NAMES + "alert(a.c.x);", UNDEFINED_NAME_WARNING); testSame(SET_NAMES + "alert(a.c.x);", UNDEFINED_NAME_WARNING); } public void testRefToUndefinedProperty4() { testSame(NAMES + "alert(a.d.x);"); testSame(GET_NAMES + "alert(a.d.x);"); testSame(SET_NAMES + "alert(a.d.x);"); } public void testRefToDescendantOfUndefinedProperty1() { testSame(NAMES + "var c = a.x.b;", UNDEFINED_NAME_WARNING); } public void testRefToDescendantOfUndefinedProperty2() { testSame(NAMES + "a.x.b();", UNDEFINED_NAME_WARNING); } public void testRefToDescendantOfUndefinedProperty3() { testSame(NAMES + "a.x.b = 3;", UNDEFINED_NAME_WARNING); } public void testUndefinedPrototypeMethodRefGivesNoWarning() { testSame("function Foo() {} var a = new Foo(); a.bar();"); } public void testComplexPropAssignGivesNoWarning() { testSame("var a = {}; var b = a.b = 3;"); } public void testTypedefGivesNoWarning() { testSame("var a = {}; /** @typedef {number} */ a.b;"); } public void testRefToDescendantOfUndefinedPropertyGivesCorrectWarning() { testSame("", NAMES + "a.x.b = 3;", UNDEFINED_NAME_WARNING, UNDEFINED_NAME_WARNING.format("a.x")); } public void testNamespaceInjection() { injectNamespace = true; testSame(NAMES + "var c = a.x.b;", UNDEFINED_NAME_WARNING); } public void testSuppressionOfUndefinedNamesWarning() { testSame(new String[] { NAMES + "/** @constructor */ function Foo() { };" + "/** @suppress {undefinedNames} */" + "Foo.prototype.bar = function() {" + " alert(a.x);" + " alert(a.x.b());" + " a.x();" + " var c = a.x.b;" + " var c = a.x.b();" + " a.x.b();" + " a.x.b = 3;" + "};", }); } public void testNoWarningForSimpleVarModuleDep1() { testSame(createModuleChain( NAMES, "var c = a;" )); } public void testNoWarningForSimpleVarModuleDep2() { testSame(createModuleChain( "var c = a;", NAMES )); } public void testNoWarningForGoodModuleDep1() { testSame(createModuleChain( NAMES, "var c = a.b;" )); } public void testBadModuleDep1() { testSame(createModuleChain( "var c = a.b;", NAMES ), STRICT_MODULE_DEP_QNAME); } public void testBadModuleDep2() { testSame(createModuleStar( NAMES, "a.xxx = 3;", "var x = a.xxx;" ), STRICT_MODULE_DEP_QNAME); } public void testSelfModuleDep() { testSame(createModuleChain( NAMES + "var c = a.b;" )); } public void testUndefinedModuleDep1() { testSame(createModuleChain( "var c = a.xxx;", NAMES ), UNDEFINED_NAME_WARNING); } public void testLateDefinedName1() { testSame("x.y = {}; var x = {};", NAME_DEFINED_LATE_WARNING); } public void testLateDefinedName2() { testSame("var x = {}; x.y.z = {}; x.y = {};", NAME_DEFINED_LATE_WARNING); } public void testLateDefinedName3() { testSame("var x = {}; x.y.z = {}; x.y = {z: {}};", NAME_DEFINED_LATE_WARNING); } public void testLateDefinedName4() { testSame("var x = {}; x.y.z.bar = {}; x.y = {z: {}};", NAME_DEFINED_LATE_WARNING); } public void testLateDefinedName5() { testSame("var x = {}; /** @typedef {number} */ x.y.z; x.y = {};", NAME_DEFINED_LATE_WARNING); } public void testLateDefinedName6() { testSame( "var x = {}; x.y.prototype.z = 3;" + "/** @constructor */ x.y = function() {};", NAME_DEFINED_LATE_WARNING); } public void testOkLateDefinedName1() { testSame("function f() { x.y = {}; } var x = {};"); } public void testOkLateDefinedName2() { testSame("var x = {}; function f() { x.y.z = {}; } x.y = {};"); } public void testPathologicalCaseThatsOkAnyway() { testSame( "var x = {};" + "switch (x) { " + " default: x.y.z = {}; " + " case (x.y = {}): break;" + "}", NAME_DEFINED_LATE_WARNING); } public void testOkGlobalDeclExpr() { testSame("var x = {}; /** @type {string} */ x.foo;"); } public void testBadInterfacePropRef() { testSame( "/** @interface */ function F() {}" + "F.bar();", UNDEFINED_NAME_WARNING); } public void testInterfaceFunctionPropRef() { testSame( "/** @interface */ function F() {}" + "F.call(); F.hasOwnProperty('z');"); } public void testObjectPrototypeProperties() { testSame("var x = {}; var y = x.hasOwnProperty('z');"); } public void testCustomObjectPrototypeProperties() { testSame("Object.prototype.seal = function() {};" + "var x = {}; x.seal();"); } public void testFunctionPrototypeProperties() { testSame("var x = {}; var y = x.hasOwnProperty('z');"); } public void testIndirectlyDeclaredProperties() { testSame( "Function.prototype.inherits = function(ctor) {" + " this.superClass_ = ctor;" + "};" + "/** @constructor */ function Foo() {}" + "Foo.prototype.bar = function() {};" + "/** @constructor */ function SubFoo() {}" + "SubFoo.inherits(Foo);" + "SubFoo.superClass_.bar();"); } public void testGoogInheritsAlias() { testSame( "Function.prototype.inherits = function(ctor) {" + " this.superClass_ = ctor;" + "};" + "/** @constructor */ function Foo() {}" + "Foo.prototype.bar = function() {};" + "/** @constructor */ function SubFoo() {}" + "SubFoo.inherits(Foo);" + "SubFoo.superClass_.bar();"); } public void testGoogInheritsAlias2() { testSame( CompilerTypeTestCase.CLOSURE_DEFS + "/** @constructor */ function Foo() {}" + "Foo.prototype.bar = function() {};" + "/** @constructor */ function SubFoo() {}" + "goog.inherits(SubFoo, Foo);" + "SubFoo.superClazz();", UNDEFINED_NAME_WARNING); } }