/* * Copyright 2015 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.ClosureCheckModule.DUPLICATE_NAME_SHORT_REQUIRE; import static com.google.javascript.jscomp.ClosureCheckModule.EXPORT_NOT_A_MODULE_LEVEL_STATEMENT; import static com.google.javascript.jscomp.ClosureCheckModule.EXPORT_REPEATED_ERROR; import static com.google.javascript.jscomp.ClosureCheckModule.GOOG_MODULE_REFERENCES_THIS; import static com.google.javascript.jscomp.ClosureCheckModule.GOOG_MODULE_USES_GOOG_MODULE_GET; import static com.google.javascript.jscomp.ClosureCheckModule.GOOG_MODULE_USES_THROW; import static com.google.javascript.jscomp.ClosureCheckModule.INCORRECT_SHORTNAME_CAPITALIZATION; import static com.google.javascript.jscomp.ClosureCheckModule.INVALID_DESTRUCTURING_REQUIRE; import static com.google.javascript.jscomp.ClosureCheckModule.JSDOC_REFERENCE_TO_SHORT_IMPORT_BY_LONG_NAME_INCLUDING_SHORT_NAME; import static com.google.javascript.jscomp.ClosureCheckModule.LET_GOOG_REQUIRE; import static com.google.javascript.jscomp.ClosureCheckModule.MODULE_AND_PROVIDES; import static com.google.javascript.jscomp.ClosureCheckModule.MULTIPLE_MODULES_IN_FILE; import static com.google.javascript.jscomp.ClosureCheckModule.ONE_REQUIRE_PER_DECLARATION; import static com.google.javascript.jscomp.ClosureCheckModule.REFERENCE_TO_FULLY_QUALIFIED_IMPORT_NAME; import static com.google.javascript.jscomp.ClosureCheckModule.REFERENCE_TO_MODULE_GLOBAL_NAME; import static com.google.javascript.jscomp.ClosureCheckModule.REFERENCE_TO_SHORT_IMPORT_BY_LONG_NAME_INCLUDING_SHORT_NAME; import static com.google.javascript.jscomp.ClosureCheckModule.REQUIRE_NOT_AT_TOP_LEVEL; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; public final class ClosureCheckModuleTest extends CompilerTestCase { @Override protected CompilerPass getProcessor(Compiler compiler) { return new ClosureCheckModule(compiler); } @Override public void setUp() { setLanguage(LanguageMode.ECMASCRIPT_NEXT, LanguageMode.ECMASCRIPT_NEXT); } @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); options.setWarningLevel(DiagnosticGroups.LINT_CHECKS, CheckLevel.ERROR); return options; } public void testGoogModuleReferencesThis() { testError("goog.module('xyz');\nfoo.call(this, 1, 2, 3);", GOOG_MODULE_REFERENCES_THIS); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var x = goog.require('other.x');", "", "if (x) {", " alert(this);", "}"), GOOG_MODULE_REFERENCES_THIS); testSame( LINE_JOINER.join( "goog.module('xyz');", "", "class Foo {", " constructor() {", " this.x = 5;", " }", "}", "", "exports = Foo;")); } public void testGoogModuleUsesThrow() { testError("goog.module('xyz');\nthrow 4;", GOOG_MODULE_USES_THROW); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var x = goog.require('other.x');", "", "if (x) {", " throw 5;", "}"), GOOG_MODULE_USES_THROW); } public void testGoogModuleGetAtTopLevel() { testError("goog.module('xyz');\ngoog.module.get('abc');", GOOG_MODULE_USES_GOOG_MODULE_GET); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var x = goog.require('other.x');", "", "if (x) {", " var y = goog.module.get('abc');", "}"), GOOG_MODULE_USES_GOOG_MODULE_GET); testSame( LINE_JOINER.join( "goog.module('xyz');", "", "var x = goog.require('other.x');", "", "function f() {", " var y = goog.module.get('abc');", "}")); } public void testGoogModuleAndProvide() { testError("goog.module('xyz');\ngoog.provide('abc');", MODULE_AND_PROVIDES); } public void testMultipleGoogModules() { testError( LINE_JOINER.join( "goog.module('xyz');", "goog.module('abc');", "", "var x = goog.require('other.x');"), MULTIPLE_MODULES_IN_FILE); } public void testBundledGoogModules() { testError( LINE_JOINER.join( "goog.loadModule(function(exports){", " 'use strict';", " goog.module('xyz');", " foo.call(this, 1, 2, 3);", " return exports;", "});"), GOOG_MODULE_REFERENCES_THIS); testError( LINE_JOINER.join( "goog.loadModule(function(exports){", " 'use strict';", " goog.module('foo.example.ClassName');", " /** @constructor @export */ function ClassName() {}", " exports = ClassName;", " return exports;", "});"), ClosureCheckModule.AT_EXPORT_IN_GOOG_MODULE); testSame( LINE_JOINER.join( "goog.loadModule(function(exports){", " 'use strict';", " goog.module('Xyz');", " exports = class {}", " return exports;", "});", "goog.loadModule(function(exports){", " goog.module('abc');", " var Foo = goog.require('Xyz');", " var x = new Foo;", " return exports;", "});")); testError( LINE_JOINER.join( "goog.loadModule(function(exports){", " 'use strict';", " goog.module('xyz');", " goog.module('abc');", " var x = goog.require('other.x');", " return exports;", "});"), MULTIPLE_MODULES_IN_FILE); } public void testGoogModuleReferencesGlobalName() { testError("goog.module('x.y.z');\nx.y.z = function() {};", REFERENCE_TO_MODULE_GLOBAL_NAME); } public void testIllegalAtExport() { testError( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "", "/** @constructor @export */ function ClassName() {}", "", "exports = ClassName;"), ClosureCheckModule.AT_EXPORT_IN_GOOG_MODULE); testError( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "", "/** @export */ class ClassName {}", "", "exports = ClassName;"), ClosureCheckModule.AT_EXPORT_IN_GOOG_MODULE); testError( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "", "/** @constructor */ function ClassName() {}", "", "/** @export */", "exports = ClassName;"), ClosureCheckModule.AT_EXPORT_IN_NON_LEGACY_GOOG_MODULE); testError( LINE_JOINER.join( "goog.module('foo.example.ns');", "", "/** @constructor */ function ClassName() {}", "", "/** @export */", "exports.ClassName = ClassName;"), ClosureCheckModule.AT_EXPORT_IN_NON_LEGACY_GOOG_MODULE); testError( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "goog.module.declareLegacyNamespace();", "", "/** @constructor @export */ function ClassName() {}", "", "exports = ClassName;"), ClosureCheckModule.AT_EXPORT_IN_GOOG_MODULE); } public void testLegalAtExport() { testSame( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "", "class ClassName {", " constructor() {", " /** @export */", " this.prop;", " /** @export */", " this.anotherProp = false;", " }", "}", "", "exports = ClassName;")); testSame( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "", "var ClassName = class {", " constructor() {", " /** @export */", " this.prop;", " /** @export */", " this.anotherProp = false;", " }", "}", "", "exports = ClassName;")); testSame( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "", "/** @constructor */", "function ClassName() {", " /** @export */", " this.prop;", " /** @export */", " this.anotherProp = false;", "}", "", "exports = ClassName;")); testSame( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "", "/** @constructor */", "var ClassName = function() {", " /** @export */", " this.prop;", " /** @export */", " this.anotherProp = false;", "};", "", "exports = ClassName;")); testSame( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "goog.module.declareLegacyNamespace();", "", "/** @constructor */ function ClassName() {}", "", "/** @export */", "exports = ClassName;")); testSame( LINE_JOINER.join( "goog.module('foo.example.ns');", "goog.module.declareLegacyNamespace();", "", "/** @constructor */ function ClassName() {}", "", "/** @export */", "exports.ClassName = ClassName;")); testSame( LINE_JOINER.join( "goog.module('foo.example.ClassName');", "", "/** @constructor */ var exports = function() {}", "", "/** @export */", "exports.prototype.fly = function() {};")); } public void testIllegalGoogRequires() { testError( LINE_JOINER.join( "goog.module('xyz');", "", "var foo = goog.require('other.x').foo;"), REQUIRE_NOT_AT_TOP_LEVEL); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var x = goog.require('other.x').foo.toString();"), REQUIRE_NOT_AT_TOP_LEVEL); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var moduleNames = [goog.require('other.x').name];"), REQUIRE_NOT_AT_TOP_LEVEL); testError( LINE_JOINER.join( "goog.module('xyz');", "", "exports = [goog.require('other.x').name];"), REQUIRE_NOT_AT_TOP_LEVEL); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var a = goog.require('foo.a'), b = goog.require('foo.b');"), ONE_REQUIRE_PER_DECLARATION); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var [foo, bar] = goog.require('other.x');"), INVALID_DESTRUCTURING_REQUIRE); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var {foo, bar = 'str'} = goog.require('other.x');"), INVALID_DESTRUCTURING_REQUIRE); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var {foo, bar: {name}} = goog.require('other.x');"), INVALID_DESTRUCTURING_REQUIRE); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var foo = goog.require('abc.foo');", "var foo = goog.require('def.foo');"), DUPLICATE_NAME_SHORT_REQUIRE); testError( LINE_JOINER.join( "goog.module('xyz');", "", "var {foo, bar} = goog.require('abc');", "var foo = goog.require('def.foo');"), DUPLICATE_NAME_SHORT_REQUIRE); } public void testIllegalShortImportReferencedByLongName() { testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "var A = goog.require('foo.A');", "", "exports = function() { return new foo.A; };"), REFERENCE_TO_SHORT_IMPORT_BY_LONG_NAME_INCLUDING_SHORT_NAME); } public void testIllegalShortImportReferencedByLongName_extends() { testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "var A = goog.require('foo.A');", "", "/** @constructor @implements {foo.A} */ function B() {}"), JSDOC_REFERENCE_TO_SHORT_IMPORT_BY_LONG_NAME_INCLUDING_SHORT_NAME); testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "var A = goog.require('foo.A');", "", "/** @type {foo.A} */ var a;"), JSDOC_REFERENCE_TO_SHORT_IMPORT_BY_LONG_NAME_INCLUDING_SHORT_NAME); testSame( LINE_JOINER.join( "goog.module('x.y.z');", "", "var A = goog.require('foo.A');", "", "/** @type {A} */ var a;")); testSame( LINE_JOINER.join( "goog.module('x.y.z');", "", "var Foo = goog.require('Foo');", "", "/** @type {Foo} */ var a;")); testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "var ns = goog.require('some.namespace');", "", "/** @type {some.namespace.Foo} */ var foo;"), JSDOC_REFERENCE_TO_SHORT_IMPORT_BY_LONG_NAME_INCLUDING_SHORT_NAME); testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "var ns = goog.require('some.namespace');", "", "/** @type {Array<some.namespace.Foo>} */ var foos;"), JSDOC_REFERENCE_TO_SHORT_IMPORT_BY_LONG_NAME_INCLUDING_SHORT_NAME); } public void testIllegalShortImportDestructuring() { testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "var {doThing} = goog.require('foo.utils');", "", "exports = function() { return foo.utils.doThing(''); };"), REFERENCE_TO_FULLY_QUALIFIED_IMPORT_NAME); } public void testIllegalImportNoAlias() { testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "goog.require('foo.utils');", "", "exports = function() { return foo.utils.doThing(''); };"), REFERENCE_TO_FULLY_QUALIFIED_IMPORT_NAME); } // TODO(johnlenz): Re-enable these tests (they are a bit tricky). public void disable_testSingleNameImportNoAlias1() { testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "goog.require('foo');", "", "exports = function() { return foo.doThing(''); };"), REFERENCE_TO_FULLY_QUALIFIED_IMPORT_NAME); } public void disable_testSingleNameImportWithAlias() { testError( LINE_JOINER.join( "goog.module('x.y.z');", "", "var bar = goog.require('foo');", "", "exports = function() { return foo.doThing(''); };"), REFERENCE_TO_FULLY_QUALIFIED_IMPORT_NAME); } public void testSingleNameImportCrossAlias() { testSame( LINE_JOINER.join( "goog.module('x.y.z');", "", "var bar = goog.require('foo');", "var foo = goog.require('bar');", "", "exports = function() { return foo.doThing(''); };")); } public void testLegalSingleNameImport() { testSame( LINE_JOINER.join( "goog.module('x.y.z');", "", "var foo = goog.require('foo');", "", "exports = function() { return foo.doThing(''); };")); } public void testIllegalLetShortRequire() { testSame( LINE_JOINER.join( "goog.module('xyz');", "", "let a = goog.forwardDeclare('foo.a');")); testError( LINE_JOINER.join( "goog.module('xyz');", "", "let a = goog.require('foo.a');"), LET_GOOG_REQUIRE); } public void testLegalGoogRequires() { testSame( LINE_JOINER.join( "goog.module('xyz');", "", "var {assert} = goog.require('goog.asserts');")); testSame( LINE_JOINER.join( "goog.module('xyz');", "", "const {assert} = goog.require('goog.asserts');")); testSame( LINE_JOINER.join( "goog.module('xyz');", "", "const {assert, fail} = goog.require('goog.asserts');")); } public void testShorthandNameConvention() { testSame( LINE_JOINER.join( "goog.module('xyz');", "", "const googAsserts = goog.require('goog.asserts');")); testError( LINE_JOINER.join( "goog.module('xyz');", "", "const GoogAsserts = goog.require('goog.asserts');"), INCORRECT_SHORTNAME_CAPITALIZATION, "The capitalization of short name GoogAsserts is incorrect; it should be googAsserts."); testSame( LINE_JOINER.join( "goog.module('xyz');", "", "const Event = goog.require('goog.events.Event');")); testError( LINE_JOINER.join( "goog.module('xyz');", "", "const event = goog.require('goog.events.Event');"), INCORRECT_SHORTNAME_CAPITALIZATION, "The capitalization of short name event is incorrect; it should be Event."); } public void testIllegalExports() { testError( LINE_JOINER.join( "goog.module('xyz');", "", "if (window.exportMe) { exports = 5; }"), EXPORT_NOT_A_MODULE_LEVEL_STATEMENT); testError( LINE_JOINER.join( "goog.module('xyz');", "", "window.exportMe && (exports = 5);"), EXPORT_NOT_A_MODULE_LEVEL_STATEMENT); testError( LINE_JOINER.join( "goog.module('xyz');", "", "if (window.exportMe) { exports.me = 5; }"), EXPORT_NOT_A_MODULE_LEVEL_STATEMENT); testSame( LINE_JOINER.join( "goog.module('xyz');", "", "exports = {};", "if (window.exportMe) { exports.me = 5; }")); testError( LINE_JOINER.join( "goog.module('xyz');", "", "exports = 5;", "exports = 'str';"), EXPORT_REPEATED_ERROR); } public void testDontCrashOnTrailingDot() { testSame( LINE_JOINER.join( "goog.module('foo');", "", "var a = goog.require('abc.');")); } }