/* * 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.common.truth.Truth.assertThat; import static com.google.javascript.jscomp.CheckRequiresForConstructors.MISSING_REQUIRE_FOR_GOOG_SCOPE; import static com.google.javascript.jscomp.CheckRequiresForConstructors.MISSING_REQUIRE_STRICT_WARNING; import static com.google.javascript.jscomp.CheckRequiresForConstructors.MISSING_REQUIRE_WARNING; import com.google.common.collect.ImmutableList; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import java.util.List; /** * Tests for the "missing requires" check in {@link CheckRequiresForConstructors}. * */ public final class MissingRequireTest extends Es6CompilerTestCase { private CheckRequiresForConstructors.Mode mode; @Override public void setUp() { mode = CheckRequiresForConstructors.Mode.FULL_COMPILE; } @Override protected CompilerOptions getOptions(CompilerOptions options) { options.setWarningLevel(DiagnosticGroups.STRICT_MISSING_REQUIRE, CheckLevel.WARNING); return super.getOptions(options); } @Override protected CompilerPass getProcessor(Compiler compiler) { return new CheckRequiresForConstructors(compiler, mode); } private void testMissingRequireStrict(String js, String warningText) { test(js, js, null, MISSING_REQUIRE_STRICT_WARNING, warningText); } private void testMissingRequire(String js, String warningText) { test(js, js, null, MISSING_REQUIRE_WARNING, warningText); } private void testMissingRequire(String[] js, String warningText) { test(js, js, null, MISSING_REQUIRE_WARNING, warningText); } private void testMissingRequireForScope(String[] js, String warningText) { test(js, js, null, MISSING_REQUIRE_FOR_GOOG_SCOPE, warningText); } public void testPassWithNoNewNodes() { String js = "var str = 'g4'; /* does not use new */"; testSame(js); } public void testPassWithOneNew() { String js = LINE_JOINER.join( "goog.require('foo.bar.goo');", "var bar = new foo.bar.goo();"); testSame(js); } public void testPassWithNewDeclaredClass() { testSameEs6("class C {}; var c = new C();"); } public void testClassRecognizedAsConstructor() { testSameEs6("/** @constructor */ module$test.A = function() {};" + "class C extends module$test.A {}"); testSameEs6("module$test.A = class {}; class C extends module$test.A {}"); } public void testPassWithOneNewOuterClass() { String js = LINE_JOINER.join( "goog.require('goog.foo.Bar');", "var bar = new goog.foo.Bar.Baz();"); testSame(js); } public void testPassWithOneNewOuterClassWithUpperPrefix() { String js = LINE_JOINER.join( "goog.require('goog.foo.IDBar');", "var bar = new goog.foo.IDBar.Baz();"); testSame(js); } public void testSuppression() { testSame("/** @suppress {missingRequire} */ var x = new foo.Bar();"); testSame("/** @suppress {missingRequire} */ function f() { var x = new foo.Bar(); }"); } public void testFailWithOneNew() { String js = "goog.provide('foo'); var bar = new foo.abc.bar();"; String warning = "missing require: 'foo.abc.bar'"; testMissingRequire(js, warning); } public void testPassWithTwoNewNodes() { String js = LINE_JOINER.join( "goog.require('goog.foo.Bar');", "goog.require('goog.foo.Baz');", "var str = new goog.foo.Bar('g4'),", " num = new goog.foo.Baz(5);"); testSame(js); } public void testPassWithNestedNewNodes() { String js = LINE_JOINER.join( "goog.require('goog.foo.Bar');", "var str = new goog.foo.Bar(new goog.foo.Bar('5'));"); testSame(js); } public void testPassWithInnerClassInExtends() { String js = LINE_JOINER.join( "goog.require('goog.foo.Bar');", "", "/** @constructor @extends {goog.foo.Bar.Inner} */", "function SubClass() {}"); testSame(js); } public void testPassEs6ClassExtends() { testSameEs6( LINE_JOINER.join( "goog.require('goog.foo.Bar');", "", "class SubClass extends goog.foo.Bar.Inner {}")); testSameEs6( LINE_JOINER.join( "goog.require('goog.foo.Bar');", "", "class SubClass extends goog.foo.Bar {}")); } public void testPassPolymer() { testSame( LINE_JOINER.join( "var Example = Polymer({});", "new Example();")); testSame( LINE_JOINER.join( "foo.bar.Example = Polymer({});", "new foo.bar.Example();")); } public void testPassGoogDefineClass() { testSameEs6( LINE_JOINER.join( "var Example = goog.defineClass(null, {constructor() {}});", "new Example();")); testSameEs6( LINE_JOINER.join( "foo.bar.Example = goog.defineClass(null, {constructor() {}});", "new foo.bar.Example();")); } public void testPassGoogDefineClass_noRewriting() { testSameEs6( LINE_JOINER.join( "var Example = goog.defineClass(null, {constructor() {}});", "new Example();")); testSameEs6( LINE_JOINER.join( "foo.bar.Example = goog.defineClass(null, {constructor() {}});", "new foo.bar.Example();")); } public void testWarnGoogModule_noRewriting() { testMissingRequireStrict( LINE_JOINER.join( "goog.module('example');", "", "/**", " * @param {Array<string>} ids", " * @return {Array<HTMLElement>}", " */", "function getElems(ids) {", " return ids.map(function(id) { return goog.dom.getElement(id); });", "}", "", "exports = getElems;"), "missing require: 'goog.dom'"); } public void testPassForwardDeclare() { testSameEs6( LINE_JOINER.join( "goog.module('example');", "", "var Event = goog.forwardDeclare('goog.events.Event');", "", "/**", " * @param {!Event} event", " */", "function listener(event) {", " alert(event);", "}", "", "exports = listener;")); } public void testFailForwardDeclare() { // TODO(tbreisacher): This should be a missing-require error. testSame( LINE_JOINER.join( "goog.module('example');", "", "var Event = goog.forwardDeclare('goog.events.Event');", "", "var e = new Event();", "", "exports = listener;")); } public void testPassGoogModule_noRewriting() { testSameEs6( LINE_JOINER.join( "goog.module('example');", "", "var dom = goog.require('goog.dom');", "", "/**", " * @param {Array<string>} ids", " * @return {Array<HTMLElement>}", " */", "function getElems(ids) {", " return ids.map(id => dom.getElement(id));", "}", "", "exports = getElems;")); testSameEs6( LINE_JOINER.join( "goog.module('example');", "goog.module.declareLegacyNamespace();", "", "var dom = goog.require('goog.dom');", "", "/**", " * @param {Array<string>} ids", " * @return {Array<HTMLElement>}", " */", "function getElems(ids) {", " return ids.map(id => dom.getElement(id));", "}", "", "exports = getElems;")); testSameEs6( LINE_JOINER.join( "goog.module('example');", "", "var {getElement} = goog.require('goog.dom');", "", "/**", " * @param {Array<string>} ids", " * @return {Array<HTMLElement>}", " */", "function getElems(ids) {", " return ids.map(id => getElement(id));", "}", "", "exports = getElems;")); testSameEs6( LINE_JOINER.join( "goog.module('example');", "", "var {getElement: getEl} = goog.require('goog.dom');", "", "/**", " * @param {Array<string>} ids", " * @return {Array<HTMLElement>}", " */", "function getElems(ids) {", " return ids.map(id => getEl(id));", "}", "", "exports = getElems;")); testSameEs6( LINE_JOINER.join( "goog.module('example');", "", "goog.require('goog.dom');", "", "/**", " * @param {Array<string>} ids", " * @return {Array<HTMLElement>}", " */", "function getElems(ids) {", " return ids.map(id => goog.dom.getElement(id));", "}", "", "exports = getElems;")); } public void testGoogModuleGet() { testSame( LINE_JOINER.join( "goog.provide('x.y');", "", "x.y = function() { var bar = goog.module.get('foo.bar'); }")); } public void testDirectCall() { String js = "foo.bar.baz();"; testMissingRequireStrict(js, "missing require: 'foo.bar'"); List<SourceFile> externs = ImmutableList.of(SourceFile.fromCode("externs", "var foo;")); test(externs, js, js, null, null, null); testSame("goog.require('foo.bar.baz'); " + js); testSame("goog.require('foo.bar'); " + js); } public void testDotCall() { String js = "foo.bar.baz.call();"; testMissingRequireStrict(js, "missing require: 'foo.bar.baz'"); List<SourceFile> externs = ImmutableList.of(SourceFile.fromCode("externs", "var foo;")); test(externs, js, js, null, null, null); testSame("goog.require('foo.bar.baz.call'); " + js); testSame("goog.require('foo.bar.baz'); " + js); testSame("goog.require('foo.bar'); " + js); } public void testDotApply() { String js = "foo.bar.baz.apply();"; testMissingRequireStrict(js, "missing require: 'foo.bar.baz'"); List<SourceFile> externs = ImmutableList.of(SourceFile.fromCode("externs", "var foo;")); test(externs, js, js, null, null, null); testSame("goog.require('foo.bar.baz.apply'); " + js); testSame("goog.require('foo.bar.baz'); " + js); testSame("goog.require('foo.bar'); " + js); } public void testCallLocal() { testSame("function f(foo) { foo.bar.baz(); }"); } public void testCallWithParentNamespaceProvided() { testSame("goog.require('foo.bar'); foo.bar.baz();"); } public void testCallOnInnerClass_namespaceRequire() { testSame("goog.require('foo.bar'); foo.bar.Outer.Inner.hello();"); } public void testCallOnInnerClass_outerRequired() { testSame("goog.require('foo.bar.Outer'); foo.bar.Outer.Inner.hello();"); } public void testCallOnInnerClass_innerRequired() { testSame("goog.require('foo.bar.Outer.Inner'); foo.bar.Outer.Inner.hello();"); } /** * When the inner class is a substring of the outer class. */ public void testCallOnInnerClass_substring() { testSame("goog.require('foo.bar'); foo.bar.JavaScript.Java.hello();"); testSame("goog.require('foo.bar.JavaScript'); foo.bar.JavaScript.Java.hello();"); testSame("goog.require('foo.bar.JavaScript.Java'); foo.bar.JavaScript.Java.hello();"); } public void testGoogLocale() { testSame("var locale = goog.LOCALE.replace('_', '-');"); } public void testGoogArray() { testMissingRequireStrict( "goog.array.forEach(arr, fn);", "missing require: 'goog.array'"); } public void testGoogDom() { testMissingRequireStrict( "goog.dom.getElement('x');", "missing require: 'goog.dom'"); } public void testLongNameNoClasses() { testMissingRequireStrict( "example.of.a.long.qualified.name(arr, fn);", "missing require: 'example.of.a.long.qualified'"); } // Occasionally people use namespaces that start with a capital letter, so this // check thinks it's a class name. Predictably, we don't handle this well. public void testClassNameAtStart() { testMissingRequireStrict( "Example.of.a.namespace.that.looks.like.a.class.name(arr, fn);", "missing require: 'Example'"); } public void testGoogTimerCallOnce() { testMissingRequireStrict( "goog.Timer.callOnce(goog.nullFunction, 0);", "missing require: 'goog.Timer'"); } public void testGoogTimer() { testMissingRequire( "var t = new goog.Timer();", "missing require: 'goog.Timer'"); } public void testFailEs6ClassExtends() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); String js = "var goog = {}; class SubClass extends goog.foo.Bar.Inner {}"; String warning = "missing require: 'goog.foo.Bar'"; testMissingRequire(js, warning); } public void testFailEs6ClassExtendsSomethingWithoutNS() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); String js = "var goog = {}; class SubClass extends SomethingWithoutNS {}"; String warning = "missing require: 'SomethingWithoutNS'"; testMissingRequire(js, warning); } public void testEs6ClassExtendsSomethingInExterns() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); String js = "var goog = {}; class SubClass extends SomethingInExterns {}"; List<SourceFile> externs = ImmutableList.of(SourceFile.fromCode("externs", "/** @constructor */ var SomethingInExterns;")); test(externs, js, js, null, null, null); } public void testEs6ClassExtendsSomethingInExternsWithNS() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); String js = "var goog = {}; class SubClass extends MyExterns.SomethingInExterns {}"; List<SourceFile> externs = ImmutableList.of(SourceFile.fromCode("externs", "var MyExterns;\n" + "/** @constructor */ MyExterns.SomethingInExterns;")); test(externs, js, js, null, null, null); } public void testFailConstant() { mode = CheckRequiresForConstructors.Mode.SINGLE_FILE; testMissingRequireStrict( "goog.require('example.Class'); alert(example.Constants.FOO);", "missing require: 'example.Constants'"); testMissingRequireStrict( "goog.require('example.Class'); alert(example.Outer.Inner.FOO);", "missing require: 'example.Outer'"); } public void testFailGoogArray() { mode = CheckRequiresForConstructors.Mode.SINGLE_FILE; testMissingRequireStrict( "console.log(goog.array.contains([1, 2, 3], 4));", "missing require: 'goog.array'"); } public void testPassConstant() { testSame("goog.require('example.Constants'); alert(example.Constants.FOO);"); testSame("goog.require('example.Outer'); alert(example.Outer.Inner.FOO);"); } public void testPassLHSFromProvide() { testSame("goog.provide('example.foo.Outer.Inner'); example.foo.Outer.Inner = {};"); } public void testPassTypedef() { testSame("/** @typedef {string|number} */\nexample.TypeDef;"); } public void testPassConstantFromExterns() { test("var example;", "alert(example.Constants.FOO);", (String) null, null, null); } public void testFailWithNestedNewNodes() { String js = "goog.require('goog.foo.Bar'); var str = new goog.foo.Bar(new goog.foo.Baz('5'));"; String warning = "missing require: 'goog.foo.Baz'"; testMissingRequire(js, warning); } public void testFailWithImplements() { String[] js = new String[] { "var goog = {};" + "goog.provide('example.Foo'); /** @interface */ example.Foo = function() {};", "/** @constructor @implements {example.Foo} */ var Ctor = function() {};" }; String warning = "missing require: 'example.Foo'"; testMissingRequire(js, warning); } public void testFailWithImplements_class() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); String[] js = new String[] { "var goog = {};" + "goog.provide('example.Foo'); /** @interface */ example.Foo = function() {};", "/** @implements {example.Foo} */ var SomeClass = class {};" }; String warning = "missing require: 'example.Foo'"; testMissingRequire(js, warning); } public void testFailWithImplements_class2() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); String[] js = new String[] { "var goog = {};" + "goog.provide('example.Foo'); /** @interface */ example.Foo = function() {};", "goog.provide('example.Bar'); /** @implements {example.Foo} */ example.Bar = class {};" }; String warning = "missing require: 'example.Foo'"; testMissingRequire(js, warning); } public void testFailWithImplements_googModule() { String[] js = new String[] { "goog.provide('example.Interface'); /** @interface */ example.Interface = function() {};", "goog.module('foo.Bar');" + "/** @constructor @implements {example.Interface} */ function Bar() {}; exports = Bar;" }; String warning = "missing require: 'example.Interface'"; testMissingRequire(js, warning); } public void testFailWithImplements_class_googModule() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); String[] js = new String[] { "goog.provide('example.Interface'); /** @interface */ example.Interface = function() {};", "goog.module('foo.Bar');" + "/** @implements {example.Interface} */ class Bar {}; exports = Bar;" }; String warning = "missing require: 'example.Interface'"; testMissingRequire(js, warning); } public void testInterfaceExtends() { String js = LINE_JOINER.join( "/**", " * @interface", " * @extends {some.other.Interface}", " */", "function AnInterface() {}"); String warning = "missing require: 'some.other.Interface'"; testMissingRequire(js, warning); } public void testPassWithImplements() { String js = "goog.require('example.Foo');" + "/** @constructor @implements {example.Foo} */" + "var Ctor = function() {};"; testSame(js); } public void testFailWithExtends1() { String[] js = new String[] { "var goog = {};\n" + "goog.provide('example.Foo');\n" + "/** @constructor */ example.Foo = function() {};", "/** @constructor @extends {example.Foo} */ var Ctor = function() {};" }; String warning = "missing require: 'example.Foo'"; testMissingRequire(js, warning); } public void testFailWithExtends2() { String[] js = new String[] { "var goog = {};\n" + "goog.provide('Foo');\n" + "/** @constructor */ var Foo = function() {};", "/** @constructor @extends {Foo} */ var Ctor = function() {};" }; String warning = "missing require: 'Foo'"; testMissingRequire(js, warning); } public void testPassWithExtends() { String js = "goog.require('example.Foo');" + "/** @constructor @extends {example.Foo} */" + "var Ctor = function() {};"; testSame(js); // When @extends is on a non-function (typically an alias) don't warn. js = "goog.require('some.other.Class');" + "/** @constructor @extends {example.Foo} */" + "var LocalAlias = some.other.Class;"; testSame(js); } public void testPassWithLocalFunctions() { String js = "/** @constructor */ function tempCtor() {}; var foo = new tempCtor();"; testSame(js); } public void testPassWithLocalVariables() { String js = "/** @constructor */ var nodeCreator = function() {};" + "var newNode = new nodeCreator();"; testSame(js); } public void testFailWithLocalVariableInMoreThanOneFile() { // there should be a warning for the 2nd script because it is only declared // in the 1st script String localVar = LINE_JOINER.join( "/** @constructor */ function tempCtor() {}", "function baz() {", " /** @constructor */ function tempCtor() {}", " var foo = new tempCtor();", "}"); String[] js = new String[] {localVar, " var foo = new tempCtor();"}; String warning = "missing require: 'tempCtor'"; testMissingRequire(js, warning); } public void testNewNodesMetaTraditionalFunctionForm() { // the class in this script creates an instance of itself // there should be no warning because the class should not have to // goog.require itself . String js = "/** @constructor */ function Bar(){}; " + "Bar.prototype.bar = function(){ return new Bar();};"; testSame(js); } public void testNewNodesMeta() { String js = LINE_JOINER.join( "/** @constructor */", "goog.ui.Option = function() {};", "goog.ui.Option.optionDecorator = function() {", " return new goog.ui.Option();", "};"); testSame(js); } public void testShouldWarnWhenInstantiatingObjectsDefinedInGlobalScope() { // there should be a warning for the 2nd script because // Bar was declared in the 1st file, not the 2nd String good = "/** @constructor */ function Bar(){}; " + "Bar.prototype.bar = function(){return new Bar();};"; String bad = "/** @constructor */ function Foo(){ var bar = new Bar();}"; String[] js = new String[] {good, bad}; String warning = "missing require: 'Bar'"; testMissingRequire(js, warning); } public void testShouldWarnWhenInstantiatingGlobalClassesFromGlobalScope() { // there should be a warning for the 2nd script because Baz // was declared in the first file, not the 2nd String good = "/** @constructor */ function Baz(){}; " + "Baz.prototype.bar = function(){return new Baz();};"; String bad = "var baz = new Baz()"; String[] js = new String[] {good, bad}; String warning = "missing require: 'Baz'"; testMissingRequire(js, warning); } public void testIgnoresNativeObject() { String externs = "/** @constructor */ function String(val) {}"; String js = "var str = new String('4');"; test(externs, js, js, null, null); } public void testPassExterns() { String externs = "/** @const */ var google = {};"; String js = "var ll = new google.maps.LatLng();"; test(externs, js, js, null, null); } public void testNewNodesWithMoreThanOneFile() { // Bar is created, and goog.require()ed, but in different files. String[] js = new String[] { LINE_JOINER.join( "/** @constructor */", "function Bar() {}", "/** @suppress {extraRequire} */", "goog.require('Bar');"), "var bar = new Bar();" }; String warning = "missing require: 'Bar'"; testMissingRequire(js, warning); } public void testPassWithoutWarningsAndMultipleFiles() { String[] js = new String[] { LINE_JOINER.join( "goog.require('Foo');", "var foo = new Foo();"), "goog.require('Bar'); var bar = new Bar();" }; testSame(js); } public void testFailWithWarningsAndMultipleFiles() { /* goog.require is in the code base, but not in the correct file */ String[] js = new String[] { LINE_JOINER.join( "/** @constructor */", "function Bar() {}", "/** @suppress {extraRequire} */", "goog.require('Bar');"), "var bar = new Bar();" }; String warning = "missing require: 'Bar'"; testMissingRequire(js, warning); } public void testCanStillCallNumberWithoutNewOperator() { String externs = "/** @constructor */ function Number(opt_value) {}"; String js = "var n = Number('42');"; test(externs, js, js, null, null); js = "var n = Number();"; test(externs, js, js, null, null); } public void testRequiresAreCaughtBeforeProcessed() { String js = "goog.provide('foo'); var bar = new foo.bar.goo();"; SourceFile input = SourceFile.fromCode("foo.js", js); Compiler compiler = new Compiler(); CompilerOptions opts = new CompilerOptions(); opts.setWarningLevel(DiagnosticGroups.MISSING_REQUIRE, CheckLevel.WARNING); opts.setClosurePass(true); Result result = compiler.compile(ImmutableList.<SourceFile>of(), ImmutableList.of(input), opts); JSError[] warnings = result.warnings; assertNotNull(warnings); assertThat(warnings).isNotEmpty(); String expectation = "missing require: 'foo.bar.goo'"; for (JSError warning : warnings) { if (expectation.equals(warning.description)) { return; } } fail("Could not find the following warning:" + expectation); } public void testNoWarningsForThisConstructor() { String js = LINE_JOINER.join( "/** @constructor */goog.Foo = function() {};", "goog.Foo.bar = function() {", " return new this.constructor;", "};"); testSame(js); } public void testBug2062487() { testSame( LINE_JOINER.join( "/** @constructor */", "goog.Foo = function() {", " /** @constructor */", " this.x_ = function() {};", " this.y_ = new this.x_();", "};")); } public void testIgnoreDuplicateWarningsForSingleClasses(){ // no use telling them the same thing twice String[] js = new String[] { LINE_JOINER.join( "/** @constructor */", "example.Foo = function() {};", "example.Foo.bar = function(){", " var first = new example.Forgot();", " var second = new example.Forgot();", "};") }; String warning = "missing require: 'example.Forgot'"; testMissingRequire(js, warning); } public void testVarConstructorName() { String js = "/** @type {function(new:Date)} */var bar = Date; new bar();"; testSame(js); } public void testVarConstructorFunction() { String js = "/** @type {function(new:Date)} */var bar = function() {}; new bar();"; testSame(js); } public void testLetConstConstructorName() { testSameEs6("/** @type {function(new:Date)} */let bar = Date; new bar();"); testSameEs6("/** @type {function(new:Date)} */const bar = Date; new bar();"); } public void testLetConstConstructorFunction() { testSameEs6( "/** @type {function(new:Date)} */let bar = function() {}; new bar();"); testSameEs6( "/** @type {function(new:Date)} */const bar = function() {}; new bar();"); } public void testAssignConstructorName() { String js = LINE_JOINER.join( "var foo = {};", "/** @type {function(new:Date)} */", "foo.bar = Date;", "new foo.bar();"); testSame(js); } public void testAssignConstructorFunction() { String js = LINE_JOINER.join( "var foo = {};", "/** @type {function(new:Date)} */", "foo.bar = function() {};", "new foo.bar();"); testSame(js); } public void testConstructorFunctionReference() { String js = "/** @type {function(new:Date)} */function bar() {}; new bar();"; testSame(js); } public void testMissingGoogRequireNoRootScope() { String good = "" + "goog.provide('foo.Bar');\n" + "/** @constructor */\n" + "foo.Bar = function() {};\n"; String bad = "" + "function someFn() {\n" + " var bar = new foo.Bar();\n" + "}\n"; String[] js = new String[] {good, bad}; String warning = "missing require: 'foo.Bar'"; testMissingRequire(js, warning); } public void testMissingGoogRequireFromGoogDefineClass() { String good = "" + "goog.provide('foo.Bar');\n" + "foo.Bar = goog.defineClass(null, {\n" + " constructor: function() {}\n" + "});\n"; String bad = "" + "function someFn() {\n" + " var bar = new foo.Bar();\n" + "}\n"; String[] js = new String[] {good, bad}; String warning = "missing require: 'foo.Bar'"; testMissingRequire(js, warning); } public void testNoMissingGoogRequireFromGoogDefineClass() { String good = "" + "goog.provide('foo.Bar');\n" + "foo.Bar = goog.defineClass(null, {\n" + " constructor: function() {}\n" + "});\n"; String bad = "" + "goog.require('foo.Bar');\n" + "function someFn() {\n" + " var bar = new foo.Bar();\n" + "}\n"; String[] js = new String[] {good, bad}; testSame(js); } public void testNoMissingGoogRequireFromGoogDefineClassSameFile() { String js = "" + "goog.provide('foo.Bar');\n" + "foo.Bar = goog.defineClass(null, {\n" + " constructor: function() {}\n" + "});\n" + "function someFn() {\n" + " var bar = new foo.Bar();\n" + "}\n"; testSame(js); } public void testAliasConstructorToPrivateVariable() { String js = "" + "var foo = {};\n" + "/** @constructor */\n" + "foo.Bar = function() {}\n" + "/** @private */\n" + "foo.Bar.baz_ = Date;\n" + "function someFn() {\n" + " var qux = new foo.Bar.baz_();\n" + "}"; testSame(js); } public void testMissingGoogRequireFromGoogScope1() { String good = "" + "goog.provide('foo.bar.Baz');\n" + "/** @constructor */\n" + "foo.bar.Baz = function() {}\n"; String bad = "" + "goog.scope(function() {\n" + " var Baz = foo.bar.Baz;\n" + " function someFn() {\n" + " var qux = new Baz();\n" + " }\n" + "});\n"; String[] js = new String[] {good, bad}; String warning = "missing require: 'foo.bar.Baz'"; testMissingRequireForScope(js, warning); } public void testMissingGoogRequireFromGoogScope2() { String good = "" + "goog.provide('foo.bar.Baz');\n" + "/** @constructor */\n" + "foo.bar.Baz = function() {}\n"; String bad = "" + "goog.require('foo.bar.Baz');\n" + "goog.scope(function() {\n" + " var bar = foo.bar;\n" + " use(new bar.Baz);\n" + "});"; String[] js = new String[] {good, bad}; String warning = "missing require: 'foo.bar'"; testMissingRequireForScope(js, warning); } public void testNoMissingGoogRequireFromGoogScope() { String good = "" + "goog.provide('foo.bar.Baz');\n" + "/** @constructor */\n" + "foo.bar.Baz = function() {}\n"; String alsoGood = "" + "goog.require('foo.bar.Baz');\n" + "goog.scope(function() {\n" + " var Baz = foo.bar.Baz;\n" + " function someFn() {\n" + " var qux = new Baz();\n" + " }\n" + "});\n"; String[] js = new String[] {good, alsoGood}; testSame(js); } public void testNoMissingGoogRequireFromGoogScopeSameFile() { String js = "" + "goog.provide('foo.bar.Baz');\n" + "/** @constructor */\n" + "foo.bar.Baz = function() {}\n" + "goog.scope(function() {\n" + " var Baz = foo.bar.Baz;\n" + " function someFn() {\n" + " var qux = new Baz();\n" + " }\n" + "});\n"; testSame(js); } public void testNoMissingGoogRequireFromGoogScopeExterns() { String externs = "var location;"; String js = "" + "goog.scope(function() {\n" + " var BASE_URL = location.href;\n" + "});"; testSame(externs, js, null); } public void testTypedefInGoogScope() { String js = LINE_JOINER.join( "goog.scope(function() {", " /** @typedef {string} */", " var Baz_;", "});"); testSame(js); } public void testMissingGoogRequireFromGoogModule() { String good = "" + "goog.module('foo');\n" + "\n" + "var Atom = goog.defineClass(null, {\n" + " constructor: function() {}\n" + "});\n"; String bad = "" + "goog.module('fooTest');" + "function someFn() {\n" + " var bar = new foo.Atom();\n" + "}\n"; String[] js = new String[] {good, bad}; String warning = "missing require: 'foo.Atom'"; testMissingRequire(js, warning); } public void testNoMissingGoogRequireFromGoogModule() { String good = "" + "goog.module('foo.bar');\n" + "\n" + "var Atom = goog.defineClass(null, {\n" + " constructor: function() {}\n" + "});\n"; String alsoGood = "" + "goog.module('foo.barTest');\n" + "var bar = goog.require('foo.bar');" + "function someFn() {\n" + " var baz = new bar.Atom();\n" + "}\n"; String[] js = new String[] {good, alsoGood}; testSame(js); } public void testNoMissingGoogRequireFromGoogModuleSameFile() { String js = "" + "goog.module('foo.bar');\n" + "\n" + "var Atom = goog.defineClass(null, {\n" + " constructor: function() {}\n" + "});\n" + "function someFn() {\n" + " var baz = new Atom();\n" + "}\n"; testSame(js); } public void testNoMissingGoogRequireFromSameFile() { String js = LINE_JOINER.join( "var Atom = constructorFactory();", "function someFn() {", " var baz = new Atom();", "}"); testSame(js); } public void testReferenceToLocalNamespace() { testSame( LINE_JOINER.join( "/** @constructor */ function FooBar() {};", "FooBar.Subclass = constructorFactory();", "new FooBar.Subclass();")); } public void testReferenceInDestructuringParam() { testSameEs6(LINE_JOINER.join( "goog.require('Bar');", "function func( {a} ){}", "func( {a: new Bar()} );")); testWarningEs6(LINE_JOINER.join( "function func( {a} ){}", "func( {a: new Bar()} );"), MISSING_REQUIRE_WARNING); testSameEs6(LINE_JOINER.join( "/** @constructor */ var Bar = function(){};", "function func( {a} ){}", "func( {a: new Bar()} );")); } public void testReferenceInDefaultParam() { testWarningEs6(LINE_JOINER.join( "function func( a = new Bar() ){}", "func();"), MISSING_REQUIRE_WARNING); testSameEs6(LINE_JOINER.join( "/** @constructor */ var Bar = function(){};", "function func( a = new Bar() ){}", "func();")); } public void testPassModule() { testSameEs6( LINE_JOINER.join( "import {Foo} from 'bar';", "new Foo();")); } // Check to make sure that we still get warnings when processing a non-module after processing // a module. public void testFailAfterModule() { setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); String module = "import {Foo} from 'bar';"; String script = "var x = new example.X()"; String[] js = new String[] {module, script}; String warning = "missing require: 'example.X'"; testMissingRequire(js, warning); } }