/* * 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 com.google.javascript.jscomp.ClosureRewriteModule.DUPLICATE_MODULE; import static com.google.javascript.jscomp.ClosureRewriteModule.DUPLICATE_NAMESPACE; import static com.google.javascript.jscomp.ClosureRewriteModule.ILLEGAL_DESTRUCTURING_DEFAULT_EXPORT; import static com.google.javascript.jscomp.ClosureRewriteModule.ILLEGAL_DESTRUCTURING_NOT_EXPORTED; import static com.google.javascript.jscomp.ClosureRewriteModule.IMPORT_INLINING_SHADOWS_VAR; import static com.google.javascript.jscomp.ClosureRewriteModule.INVALID_EXPORT_COMPUTED_PROPERTY; import static com.google.javascript.jscomp.ClosureRewriteModule.INVALID_FORWARD_DECLARE_NAMESPACE; import static com.google.javascript.jscomp.ClosureRewriteModule.INVALID_GET_ALIAS; import static com.google.javascript.jscomp.ClosureRewriteModule.INVALID_GET_CALL_SCOPE; import static com.google.javascript.jscomp.ClosureRewriteModule.INVALID_GET_NAMESPACE; import static com.google.javascript.jscomp.ClosureRewriteModule.INVALID_MODULE_NAMESPACE; import static com.google.javascript.jscomp.ClosureRewriteModule.INVALID_PROVIDE_CALL; import static com.google.javascript.jscomp.ClosureRewriteModule.INVALID_REQUIRE_NAMESPACE; import static com.google.javascript.jscomp.ClosureRewriteModule.LATE_PROVIDE_ERROR; import static com.google.javascript.jscomp.ClosureRewriteModule.QUALIFIED_REFERENCE_TO_GOOG_MODULE; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; /** * Unit tests for ClosureRewriteModule * @author johnlenz@google.com (John Lenz) * @author stalcup@google.com (John Stalcup) */ public final class ClosureRewriteModuleTest extends Es6CompilerTestCase { @Override protected CompilerPass getProcessor(Compiler compiler) { return new ClosureRewriteModule(compiler, null, null); } @Override protected int getNumRepetitions() { return 1; } @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); options.setWarningLevel(DiagnosticGroups.LINT_CHECKS, CheckLevel.WARNING); return options; } public void testBasic0() { testSame(""); testSame("goog.provide('a');"); } public void testBasic1() { test( "goog.module('a');", "/** @const */ var module$exports$a = {};"); } public void testBasic2() { test( new String[] { "goog.module('ns.b');", LINE_JOINER.join( "goog.module('ns.a');", "var b = goog.require('ns.b');")}, new String[] { "/** @const */ var module$exports$ns$b = {};", "/** @const */ var module$exports$ns$a = {};"}); } public void testBasic3() { // Multivar. test( new String[] { "goog.module('ns.b');", "goog.module('ns.c');", LINE_JOINER.join( "goog.module('ns.a');", "var b = goog.require('ns.b'), c = goog.require('ns.c');")}, new String[] { "/** @const */ var module$exports$ns$b = {};", "/** @const */ var module$exports$ns$c = {};", "/** @const */ var module$exports$ns$a = {};"}); } public void testIjsModule() { allowExternsChanges(true); test( // .i.js file "goog.module('external'); /** @constructor */ exports = function() {};", // source file "goog.module('ns.a'); var b = goog.require('external'); /** @type {b} */ new b;", LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "/** @type {module$exports$external} */ new module$exports$external"), null, null); } public void testDestructuringInsideModule() { // Array destrucuturing testEs6( LINE_JOINER.join( "goog.module('a');", "var [x, y, z] = foo();"), LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "var [module$contents$a_x, module$contents$a_y, module$contents$a_z] = foo();")); // Object destructuring with explicit names testEs6( LINE_JOINER.join( "goog.module('a');", "var {p1: x, p2: y} = foo();"), LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "var {p1: module$contents$a_x, p2: module$contents$a_y} = foo();")); // Object destructuring with short names testEs6( LINE_JOINER.join( "goog.module('a');", "var {x, y} = foo();"), LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "var {x: module$contents$a_x, y: module$contents$a_y} = foo();")); } public void testShortObjectLiteralsInsideModule() { testEs6( LINE_JOINER.join( "goog.module('a');", "var x = foo();", "var o = {x};"), LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "var module$contents$a_x = foo();", "var module$contents$a_o = {x: module$contents$a_x};")); testEs6( LINE_JOINER.join( "goog.module('a');", "var x = foo();", "exports = {x};"), LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "module$exports$a.x = foo();")); } public void testDestructuringImports() { testEs6( new String[] { "goog.module('ns.b'); /** @constructor */ exports.Foo = function() {};", LINE_JOINER.join( "goog.module('ns.a');", "", "var {Foo} = goog.require('ns.b');", "", "/** @type {Foo} */", "var f = new Foo;") }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$ns$b = {};", "/** @constructor @const */ module$exports$ns$b.Foo = function() {};"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {}", "/** @type {module$exports$ns$b.Foo} */", "var module$contents$ns$a_f = new module$exports$ns$b.Foo;")}); testEs6( new String[] { "goog.module('ns.b'); /** @typedef {number} */ exports.Foo;", LINE_JOINER.join( "goog.module('ns.a');", "", "var {Foo} = goog.require('ns.b');", "", "/** @type {Foo} */", "var f = 4;") }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$ns$b = {};", "/** @const @typedef {number} */ module$exports$ns$b.Foo;"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {}", "/** @type {module$exports$ns$b.Foo} */", "var module$contents$ns$a_f = 4;") }); testEs6( new String[] { "goog.provide('ns.b'); /** @constructor */ ns.b.Foo = function() {};", LINE_JOINER.join( "goog.module('ns.a');", "", "var {Foo} = goog.require('ns.b');", "", "/** @type {Foo} */", "var f = new Foo;") }, new String[] { "goog.provide('ns.b'); /** @constructor */ ns.b.Foo = function() {};", LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {}", "/** @type {ns.b.Foo} */", "var module$contents$ns$a_f = new ns.b.Foo;")}); testEs6( new String[] { LINE_JOINER.join( "goog.module('ns.b');", "goog.module.declareLegacyNamespace();", "", "/** @constructor */ exports.Foo = function() {};"), LINE_JOINER.join( "goog.module('ns.a');", "", "var {Foo} = goog.require('ns.b');", "", "/** @type {Foo} */", "var f = new Foo;") }, new String[] { LINE_JOINER.join( "goog.provide('ns.b');", "/** @constructor @const */ ns.b.Foo = function() {};"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {}", "/** @type {ns.b.Foo} */", "var module$contents$ns$a_f = new ns.b.Foo;")}); testEs6( new String[] { "goog.module('ns.b'); /** @constructor */ exports.Foo = function() {};", LINE_JOINER.join( "goog.module('ns.a');", "", "var {Foo: Bar} = goog.require('ns.b');", "", "/** @type {Bar} */", "var f = new Bar;") }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$ns$b = {};", "/** @constructor @const */ module$exports$ns$b.Foo = function() {};"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {}", "/** @type {module$exports$ns$b.Foo} */", "var module$contents$ns$a_f = new module$exports$ns$b.Foo;")}); testEs6( new String[] { "goog.module('modA'); class Foo {} exports.Foo = Foo;", LINE_JOINER.join( "goog.module('modB');", "", "var {Foo:importedFoo} = goog.require('modA');", "", "/** @type {importedFoo} */", "var f = new importedFoo;") }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$modA = {};", "module$exports$modA.Foo = class {}"), LINE_JOINER.join( "/** @const */ var module$exports$modB = {}", "/** @type {module$exports$modA.Foo} */", "var module$contents$modB_f = new module$exports$modA.Foo;")}); testEs6( new String[] { "goog.module('modA'); class Foo {} exports.Foo = Foo;", LINE_JOINER.join( "goog.module('modB');", "", "var {Foo} = goog.require('modA');", "", "/** @type {Foo} */", "var f = new Foo;") }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$modA = {};", "module$exports$modA.Foo = class {}"), LINE_JOINER.join( "/** @const */ var module$exports$modB = {}", "/** @type {module$exports$modA.Foo} */", "var module$contents$modB_f = new module$exports$modA.Foo;")}); testEs6( new String[] { "goog.module('modA'); class Foo {} exports = {Foo};", LINE_JOINER.join( "goog.module('modB');", "", "var {Foo} = goog.require('modA');", "", "/** @type {Foo} */", "var f = new Foo;") }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$modA = {};", "module$exports$modA.Foo = class {};"), LINE_JOINER.join( "/** @const */ var module$exports$modB = {}", "/** @type {module$exports$modA.Foo} */", "var module$contents$modB_f = new module$exports$modA.Foo;")}); testEs6( new String[] { "goog.module('modA'); class Bar {} exports = class Foo {}; exports.Bar = Bar;", LINE_JOINER.join( "goog.module('modB');", "", "var Foo = goog.require('modA');", "", "/** @type {Foo} */", "var f = new Foo;") }, new String[] { LINE_JOINER.join( "class module$contents$modA_Bar {}", "/** @const */ var module$exports$modA = class Foo {};", "/** @const */ module$exports$modA.Bar = module$contents$modA_Bar;"), LINE_JOINER.join( "/** @const */ var module$exports$modB = {}", "/** @type {module$exports$modA} */", "var module$contents$modB_f = new module$exports$modA;")}); testEs6( new String[] { LINE_JOINER.join( "goog.module('modA');", "goog.module.declareLegacyNamespace();", "", "class Foo {}", "exports = {Foo};"), LINE_JOINER.join( "goog.module('modB');", "", "var {Foo} = goog.require('modA');", "", "/** @type {Foo} */", "var f = new Foo;") }, new String[] { LINE_JOINER.join( "goog.provide('modA');", "class module$contents$modA_Foo {}", "/** @const */ modA = {", " /** @const */ Foo: module$contents$modA_Foo", "};"), LINE_JOINER.join( "/** @const */ var module$exports$modB = {}", "/** @type {modA.Foo} */", "var module$contents$modB_f = new modA.Foo;")}); } public void testUninlinableExports() { testEs6( new String[] { "goog.module('ns.b'); /** @constructor */ exports.Foo = function() {};", LINE_JOINER.join( "goog.module('ns.a');", "", "var {Foo} = goog.require('ns.b');", "", "/** @type {Foo} */", "var f = new Foo;") }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$ns$b = {};", "/** @const @constructor */ module$exports$ns$b.Foo = function() {};"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {}", "/** @type {module$exports$ns$b.Foo} */", "var module$contents$ns$a_f = new module$exports$ns$b.Foo;")}); } public void testObjectLiteralDefaultExport() { testErrorEs6( new String[] { LINE_JOINER.join( "goog.module('modA');", "", "class Foo {}", "// This is not a named exports object because of the value literal", "exports = {Foo, Bar: [1,2,3]};"), LINE_JOINER.join("goog.module('modB');", "", "var {Foo} = goog.require('modA');") }, ILLEGAL_DESTRUCTURING_DEFAULT_EXPORT); } public void testUninlinableNamedExports() { testEs6( new String[] { "goog.module('modA'); \n exports = class {};", LINE_JOINER.join( "goog.module('modB');", "", "var Foo = goog.require('modA');", "", "exports.Foo = Foo;"), }, new String[] { "/** @const */ var module$exports$modA = class {};", LINE_JOINER.join( "/** @const */ var module$exports$modB = {};", "/** @const */ module$exports$modB.Foo = module$exports$modA;"), }); testEs6( new String[] { "goog.module('modA'); \n exports = class {};", LINE_JOINER.join( "goog.module('modB');", "", "var Foo = goog.require('modA');", "", "exports = {Foo};"), }, new String[] { "/** @const */ var module$exports$modA = class {};", LINE_JOINER.join( "/** @const */ var module$exports$modB = {", " /** @const */ Foo: module$exports$modA,", "};"), }); testEs6( new String[] { "goog.module('modA'); \n exports = class {};", LINE_JOINER.join( "goog.module('modB');", "", "var Foo = goog.require('modA');", "class Bar {}", "", "exports = {Foo, Bar};"), }, new String[] { "/** @const */ var module$exports$modA = class {};", LINE_JOINER.join( "class module$contents$modB_Bar {}", "/** @const */ var module$exports$modB = {", " /** @const */ Foo: module$exports$modA,", " /** @const */ Bar: module$contents$modB_Bar,", "};"), }); } public void testIllegalDestructuringImports() { testErrorEs6( new String[] { LINE_JOINER.join( "goog.module('p.A');", "/** @constructor */ var A = function() {}", "A.method = function() {}", "exports = A"), LINE_JOINER.join( "goog.module('p.C');", "var {method} = goog.require('p.A');", "function main() {", " method();", "}") }, ILLEGAL_DESTRUCTURING_DEFAULT_EXPORT); testErrorEs6( new String[] { LINE_JOINER.join( "goog.module('p.A');", "/** @constructor */ exports = class { static method() {} }"), LINE_JOINER.join( "goog.module('p.C');", "var {method} = goog.require('p.A');", "function main() {", " method();", "}") }, ILLEGAL_DESTRUCTURING_DEFAULT_EXPORT); testErrorEs6( new String[] { LINE_JOINER.join( "goog.module('p.A');", "", "/** @constructor */ exports.Foo = class {};", "/** @constructor */ exports.Bar = class {};"), LINE_JOINER.join( "goog.module('p.C');", "", "var {Baz} = goog.require('p.A');") }, ILLEGAL_DESTRUCTURING_NOT_EXPORTED); // TODO(blickly): We should warn for this as well, but it's harder to detect. testEs6( new String[] { LINE_JOINER.join( "goog.provide('p.A');", "/** @constructor */ p.A = function() {}", "p.A.method = function() {}"), LINE_JOINER.join( "goog.module('p.C');", "var {method} = goog.require('p.A');", "function main() {", " method();", "}") }, null); } public void testDeclareLegacyNamespace() { test("goog.module('ns.a'); goog.module.declareLegacyNamespace();", "goog.provide('ns.a');"); } public void testSideEffectOnlyModuleImport() { test( new String[] { LINE_JOINER.join( "goog.module('ns.b');", "alert('hello world');"), LINE_JOINER.join( "goog.module('ns.a');", "goog.require('ns.b');")}, new String[] { "/** @const */ var module$exports$ns$b = {}; alert('hello world');", "/** @const */ var module$exports$ns$a = {};"}); } public void testTypeOnlyModuleImport() { test( new String[] { LINE_JOINER.join( "goog.module('ns.B');", "/** @constructor */ exports = function() {};"), LINE_JOINER.join( "goog.module('ns.a');", "", "goog.require('ns.B');", "", "/** @type {ns.B} */ var c;")}, new String[] { "/** @constructor @const */ var module$exports$ns$B = function() {};", LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "/** @type {module$exports$ns$B} */ var module$contents$ns$a_c;")}); } public void testSideEffectOnlyImportOfGoogProvide() { test( new String[] { LINE_JOINER.join( "goog.provide('ns.b');", "", "alert('hello world');"), LINE_JOINER.join( "goog.module('ns.a');", "", "goog.require('ns.b');")}, new String[] { "goog.provide('ns.b'); alert('hello world');", "/** @const */ var module$exports$ns$a = {}; goog.require('ns.b');"}); } public void testSideEffectOnlyImportOfLegacyGoogModule() { test( new String[] { LINE_JOINER.join( "goog.module('ns.b');", "goog.module.declareLegacyNamespace();", "", "alert('hello world');"), LINE_JOINER.join("goog.module('ns.a');", "", "goog.require('ns.b');") }, new String[] { "goog.provide('ns.b'); alert('hello world');", "/** @const */ var module$exports$ns$a = {}; goog.require('ns.b');" }); } public void testTypeOnlyModuleImportFromLegacyFile() { test( new String[] { LINE_JOINER.join( "goog.module('ns.B');", "/** @constructor */ exports = function() {};"), LINE_JOINER.join( "goog.provide('ns.a');", "", "goog.require('ns.B');", "", "/** @type {ns.B} */ var c;")}, new String[] { "/** @constructor @const */ var module$exports$ns$B = function() {};", LINE_JOINER.join( "goog.provide('ns.a');", "", "/** @type {module$exports$ns$B} */ var c;")}); } public void testBundle1() { test( new String[] { "goog.module('ns.b');", LINE_JOINER.join( "goog.loadModule(function(exports) {", " goog.module('ns.a');", " var b = goog.require('ns.b');", " exports.b = b;", " return exports;", "});")}, new String[] { "/** @const */ var module$exports$ns$b = {};", LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "/** @const */ module$exports$ns$a.b = module$exports$ns$b;")}); } public void testBundle2() { test( LINE_JOINER.join( "goog.loadModule(function(exports) {", " goog.module('ns.b');", " return exports;", "});", "goog.loadModule(function(exports) {", " goog.module('ns.a');", " var b = goog.require('ns.b');", " exports.b = b;", " return exports;", "});", "goog.loadModule(function(exports) {", " goog.module('ns.c');", " var b = goog.require('ns.b');", " exports.b = b;", " return exports;", "});"), LINE_JOINER.join( "/** @const */ var module$exports$ns$b = {};", "/** @const */ var module$exports$ns$a = {};", "/** @const */ module$exports$ns$a.b = module$exports$ns$b;", "/** @const */ var module$exports$ns$c = {};", "/** @const */ module$exports$ns$c.b = module$exports$ns$b;")); } public void testBundle3() { test( LINE_JOINER.join( "goog.loadModule(function(exports) {", " goog.module('ns.b');", " return exports;", "});", "goog.loadModule(function(exports) {", " 'use strict';", " goog.module('ns.a');", " goog.module.declareLegacyNamespace();", " var b = goog.require('ns.b');", " return exports;", "});"), LINE_JOINER.join("/** @const */ var module$exports$ns$b = {};", "goog.provide('ns.a');")); } public void testBundle4() { test( LINE_JOINER.join( "goog.loadModule(function(exports) {", " goog.module('goog.asserts');", " return exports;", "});", "goog.loadModule(function(exports) {", " 'use strict';", " goog.module('ns.a');", " var b = goog.require('goog.asserts');", " return exports;", "});"), LINE_JOINER.join( "/** @const */ var module$exports$goog$asserts = {};", "/** @const */ var module$exports$ns$a = {};")); } public void testBundle5() { test( LINE_JOINER.join( "goog.loadModule(function(exports) {", " goog.module('goog.asserts');", " return exports;", "});", "goog.loadModule(function(exports) {", " 'use strict';", " goog.module('xid');", " goog.module.declareLegacyNamespace();", " var asserts = goog.require('goog.asserts');", " exports = function(id) {", " return xid.internal_(id);", " };", " var xid = exports;", " return exports;", "});"), LINE_JOINER.join( "/** @const */ var module$exports$goog$asserts = {};", "goog.provide('xid');", "/** @const */ xid = function(id) {", " return module$contents$xid_xid.internal_(id);", "};", "var module$contents$xid_xid = xid")); } public void testBundle6() { test( LINE_JOINER.join( "goog.loadModule(function(exports) {", " goog.module('goog.asserts');", " return exports;", "});", "goog.loadModule(function(exports) {", " 'use strict';", " goog.module('xid');", " goog.module.declareLegacyNamespace();", " var asserts = goog.require('goog.asserts');", " var xid = function(id) {", " return xid.internal_(id);", " };", " xid.internal_ = function(id) {};", " exports = xid;", " return exports;", "});"), LINE_JOINER.join( "/** @const */ var module$exports$goog$asserts = {};", "goog.provide('xid');", "var module$contents$xid_xid = function(id) {", " return module$contents$xid_xid.internal_(id);", "};", "module$contents$xid_xid.internal_ = function(id) {};", "/** @const */ xid = module$contents$xid_xid ")); } public void testBundleWithDestructuringImport() { testEs6( LINE_JOINER.join( "goog.loadModule(function(exports) { 'use strict';", " goog.module('mod_B');", "", " /** @interface */ function B(){}", "", " exports.B = B;", " return exports;", "});", "goog.loadModule(function(exports) { 'use strict';", " goog.module('mod_A');", "", " var {B} = goog.require('mod_B');", "", " /** @constructor @implements {B} */", " function A() {}", " return exports;", "});"), LINE_JOINER.join( "/** @const */ var module$exports$mod_B = {};", "/** @interface */ module$exports$mod_B.B = function(){};", "", "/** @const */ var module$exports$mod_A = {};", "/** @constructor @implements {module$exports$mod_B.B} */", "function module$contents$mod_A_A(){}")); } public void testGoogLoadModuleString() { testSame("goog.loadModule(\"goog.module('a.b.c'); exports = class {};\");"); } public void testGoogScope1() { // Typedef defined inside a goog.scope(). The typedef is seen and is *not* legacy-to-binary // bridge exported. testSame( LINE_JOINER.join( "goog.provide('a.c.B');", "goog.provide('a.u.M');", "goog.scope(function() {", " /** @constructor */", " a.c.B = function() {}", " /** @typedef {function(!Array<a.u.E>)} */", " a.u.M;", "});")); } public void testTopLevelNames1() { // Vars defined inside functions are not module top level. test( LINE_JOINER.join( "goog.module('a');", "var a, b, c;", "function Outer() {", " var a, b, c;", " function Inner() {", " var a, b, c;", " }", "}"), LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "var module$contents$a_a, module$contents$a_b, module$contents$a_c;", "function module$contents$a_Outer() {", " var a, b, c;", " function Inner() {", " var a, b, c", " }", "}")); } public void testTopLevelNames2() { // Vars in blocks are module top level because they are hoisted to the first execution context. test( LINE_JOINER.join( "goog.module('a.c');", "if (true) {", " var a, b, c;", "}"), LINE_JOINER.join( "/** @const */ var module$exports$a$c = {};", "if (true) {", " var module$contents$a$c_a, module$contents$a$c_b, module$contents$a$c_c;", "}")); } public void testTopLevelNames3() { // Functions in blocks are not module top level because they are block scoped. testEs6( LINE_JOINER.join( "goog.module('a.c');", "if (true) {", " function a() {}", " function b() {}", " function c() {}", "}"), LINE_JOINER.join( "/** @const */ var module$exports$a$c = {};", "if (true) {", " function a() {}", " function b() {}", " function c() {}", "}")); } public void testThis() { // global "this" is retained. test( LINE_JOINER.join( "goog.module('a');", "this;"), LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "this;")); } public void testInvalidModule() { testError("goog.module(a);", INVALID_MODULE_NAMESPACE); } public void testInvalidRequire() { testError("goog.module('ns.a');" + "goog.require(a);", INVALID_REQUIRE_NAMESPACE); } public void testInvalidProvide() { // The ES6 path turns on DependencyOptions.needsManagement() which leads to JsFileLineParser // execution that throws a different exception on some invalid goog.provide()s. testError("goog.module('a'); goog.provide('b');", INVALID_PROVIDE_CALL); } public void testGoogModuleGet1() { test( new String[] { "goog.module('a');", LINE_JOINER.join( "function f() {", " var x = goog.module.get('a');", "}")}, new String[] { "/** @const */ var module$exports$a = {};", LINE_JOINER.join( "function f() {", " var x = module$exports$a;", "}")}); } public void testGoogModuleGet2() { test( new String[] { "goog.module('a.b.c');", LINE_JOINER.join( "function f() {", " var x = goog.module.get('a.b.c');", "}")}, new String[] { "/** @const */ var module$exports$a$b$c = {};", LINE_JOINER.join( "function f() {", " var x = module$exports$a$b$c;", "}")}); } public void testGoogModuleGet3() { testEs6( new String[] { LINE_JOINER.join( "goog.module('a.b.c');", "exports = class {};"), LINE_JOINER.join( "goog.module('x.y.z');", "", "function f() {", " return new (goog.module.get('a.b.c'))();", "}")}, new String[] { "/** @const */ var module$exports$a$b$c = class {};", LINE_JOINER.join( "/** @const */ var module$exports$x$y$z = {};", "function module$contents$x$y$z_f() {", " return new module$exports$a$b$c();", "}")}); } public void testAliasedGoogModuleGet1() { test( new String[] { "goog.module('b');", LINE_JOINER.join( "goog.module('a');", "var x = goog.forwardDeclare('b');", "function f() {", " x = goog.module.get('b');", " new x;", "}")}, new String[] { "/** @const */ var module$exports$b = {};", LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "function module$contents$a_f() {", " new module$exports$b;", "}")}); } public void testAliasedGoogModuleGet2() { test( new String[] { "goog.module('x.y.z');", LINE_JOINER.join( "goog.module('a');", "var x = goog.forwardDeclare('x.y.z');", "function f() {", " x = goog.module.get('x.y.z');", " new x;", "}")}, new String[] { "/** @const */ var module$exports$x$y$z = {};", LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "function module$contents$a_f() {", " new module$exports$x$y$z;", "}")}); } public void testAliasedGoogModuleGet3() { test( new String[] { LINE_JOINER.join( "goog.module('a.b.c');", "/** @constructor */ function C() {}", "exports = C"), LINE_JOINER.join( "/** @type {a.b.c} */ var c;", "function f() {", " var C = goog.module.get('a.b.c');", " c = new C;", "}"), }, new String[] { "/** @constructor */ function module$exports$a$b$c() {}", LINE_JOINER.join( "/** @type {module$exports$a$b$c} */ var c;", "function f() {", " var C = module$exports$a$b$c;", " c = new C;", "}")}); } public void testAliasedGoogModuleGet4() { test( new String[] { LINE_JOINER.join( "goog.module('x.y.z');", "/** @constructor */ function Z() {}", "exports = Z"), LINE_JOINER.join( "goog.module('a');", "/** @type {x.y.z} */ var c;", "var x = goog.forwardDeclare('x.y.z');", "function f() {", " x = goog.module.get('x.y.z');", " c = new x;", "}")}, new String[] { "/** @constructor */ function module$exports$x$y$z() {}", LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "/** @type {module$exports$x$y$z} */ var module$contents$a_c;", "function module$contents$a_f() {", " module$contents$a_c = new module$exports$x$y$z;", "}")}); } public void testAliasedGoogModuleGet5() { test( new String[] { "goog.provide('b');", LINE_JOINER.join( "goog.module('a');", "var x = goog.forwardDeclare('b');", "function f() {", " x = goog.module.get('b');", " new x;", "}")}, new String[] { "goog.provide('b');", LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "goog.forwardDeclare('b');", "function module$contents$a_f() {", " new b;", "}")}); } public void testAliasedGoogModuleGet6() { test( new String[] { "goog.provide('x.y.z');", LINE_JOINER.join( "goog.module('a');", "var z = goog.forwardDeclare('x.y.z');", "function f() {", " z = goog.module.get('x.y.z');", " new z;", "}")}, new String[] { "goog.provide('x.y.z');", LINE_JOINER.join( "/** @const */ var module$exports$a = {};", "goog.forwardDeclare('x.y.z');", "function module$contents$a_f() {", " new x.y.z;", "}")}); } public void testAliasedGoogModuleGet7() { test( new String[] { "goog.module('a.b.c.D');", LINE_JOINER.join( "goog.require('a.b.c.D');", "goog.scope(function() {", "var D = goog.module.get('a.b.c.D');", "});")}, new String[] { "/** @const */ var module$exports$a$b$c$D = {};", LINE_JOINER.join( "goog.scope(function() {", "var D = module$exports$a$b$c$D;", "});")}); } public void testAliasedGoogModuleGet8() { test( new String[] { "goog.module('a.b.c.D');", LINE_JOINER.join( "goog.require('a.b.c.D');", "goog.scope(function() {", "var D = goog.module.get('a.b.c.D');", "var d = new D;", "});")}, new String[] { "/** @const */ var module$exports$a$b$c$D = {};", LINE_JOINER.join( "goog.scope(function() {", "var D = module$exports$a$b$c$D;", "var d = new D;", "});")}); } public void testInvalidGoogForwardDeclareParameter() { // Wrong parameter count. testError( LINE_JOINER.join( "goog.module('a');", "var x = goog.forwardDeclare();"), INVALID_FORWARD_DECLARE_NAMESPACE); // Wrong parameter count. testError( LINE_JOINER.join( "goog.module('a');", "var x = goog.forwardDeclare('a', 'b');"), INVALID_FORWARD_DECLARE_NAMESPACE); // Wrong parameter type. testError( LINE_JOINER.join( "goog.module('a');", "var x = goog.forwardDeclare({});"), INVALID_FORWARD_DECLARE_NAMESPACE); } public void testInvalidGoogModuleGetAlias() { testError( new String[] { "goog.provide('g');", LINE_JOINER.join( "goog.module('a');", "x = goog.module.get('g');"), }, INVALID_GET_ALIAS); testError( new String[] { "goog.provide('g');", LINE_JOINER.join( "goog.module('a');", "var x;", "x = goog.module.get('g');"), }, INVALID_GET_ALIAS); testError( new String[] { "goog.provide('g'); goog.provide('z');", LINE_JOINER.join( "goog.module('a');", "var x = goog.forwardDeclare('z');", "x = goog.module.get('g');"), }, INVALID_GET_ALIAS); } public void testInvalidGoogModuleGet1() { testError( LINE_JOINER.join( "function f() {", " goog.module.get(a);", "}"), INVALID_GET_NAMESPACE); } public void testInvalidGoogModuleGet2() { testError("goog.module.get('a');", INVALID_GET_CALL_SCOPE); } public void testExtractableExport1() { test( LINE_JOINER.join( "goog.module('xid');", "var xid = function() {};", "exports = xid;"), "var module$exports$xid = function() {};"); } public void testExtractableExport2() { test( LINE_JOINER.join( "goog.module('xid');", "function xid() {}", "exports = xid;"), "function module$exports$xid() {}"); } public void testExtractableExport3() { testEs6( LINE_JOINER.join( "goog.module('Foo');", "class Foo {}", "exports = Foo;"), "class module$exports$Foo {}"); } public void testExtractableExport4() { testEs6( LINE_JOINER.join( "goog.module('Foo');", "const Foo = class {}", "exports = Foo;"), "const module$exports$Foo = class {};"); } public void testExport0() { test( "goog.module('ns.a');", "/** @const */ var module$exports$ns$a = {};"); } public void testExport1() { test( LINE_JOINER.join( "goog.module('ns.a');", "exports = {};"), "/** @const */ var module$exports$ns$a = {};"); } public void testExport2() { test( LINE_JOINER.join( "goog.module('ns.a');", "exports.x = 1;"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "/** @const */ module$exports$ns$a.x = 1")); } public void testExport4() { test( LINE_JOINER.join( "goog.module('ns.a');", "exports = { something: 1 };"), "/** @const */ var module$exports$ns$a = { /** @const */ something : 1 };"); } public void testExport5() { test( LINE_JOINER.join( "goog.module('ns.a');", "/** @typedef {string} */ var x;", "exports.x = x;"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "/** @typedef {string} */ module$exports$ns$a.x;")); } public void testExport6() { test( LINE_JOINER.join( "goog.module('ns.a');", "/** @typedef {string} */ var x;", "exports = { something: x };"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "/** @typedef {string} */ module$exports$ns$a.something;")); } public void testExport6_1() { test( LINE_JOINER.join( "goog.module('ns.a');", "/** @typedef {string} */ var x;", "exports.something = x;"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "/** @typedef {string} */ module$exports$ns$a.something;")); } public void testExport7() { test( LINE_JOINER.join( "goog.module('ns.a');", "/** @constructor */", "exports = function() {};"), "/** @constructor @const */ var module$exports$ns$a = function() {};"); } public void testExport8() { test( LINE_JOINER.join( "goog.module('ns.a');", "exports = goog.defineClass({});"), "/** @const */ var module$exports$ns$a = goog.defineClass({});"); } public void testExport9() { // Doesn't legacy-to-binary bridge export a typedef. testSame( LINE_JOINER.join( "goog.provide('goog.ui.ControlContent');", "/** @typedef {string} */ goog.ui.ControlContent;")); } public void testExport10() { // Doesn't rewrite exports in legacy scripts. testSame( LINE_JOINER.join( "(function() {", " /** @constructor */ function S(string) {}", " exports.S = S;", "})();")); } public void testExport11() { // Does rewrite export typedefs and defensively creates the exports root object first. test( LINE_JOINER.join( "goog.module('a.B');", "/** @typedef {string} */ exports.C;"), LINE_JOINER.join( "/** @const */ var module$exports$a$B = {};", "/** @const @typedef {string} */ module$exports$a$B.C;")); } public void testExport12() { test( LINE_JOINER.join( "goog.module('ns.a');", "exports.foo = goog.defineClass({});"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "/** @const */ module$exports$ns$a.foo = goog.defineClass({});")); } public void testExport13() { // Creates the exports root object before export object reads. test( LINE_JOINER.join( "goog.module('a.B');", "var field = exports;"), LINE_JOINER.join( "/** @const */ var module$exports$a$B = {};", "var module$contents$a$B_field = module$exports$a$B;")); } public void testExportEnhancedObjectLiteral() { testEs6( LINE_JOINER.join( "goog.module('ns.a');", "class Something {}", "exports = { Something };"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "module$exports$ns$a.Something = class {};")); testErrorEs6( LINE_JOINER.join( "goog.module('ns.a');", "exports = { [something]: 3 };"), INVALID_EXPORT_COMPUTED_PROPERTY); } public void testImport() { // A goog.module() that imports, jsdocs, and uses both another goog.module() and a legacy // script. test( new String[] { LINE_JOINER.join( "goog.module('p.A');", "/** @constructor */ function A() {}", "exports = A;"), LINE_JOINER.join( "goog.provide('p.B');", "/** @constructor */ p.B = function() {}"), LINE_JOINER.join( "goog.module('p.C');", "var A = goog.require('p.A');", "var B = goog.require('p.B');", "function main() {", " /** @type {A} */ var a = new A;", " /** @type {B} */ var b = new B;", "}")}, new String[] { "/** @constructor */ function module$exports$p$A() {}", LINE_JOINER.join( "goog.provide('p.B');", "/** @constructor */ p.B = function() {}"), LINE_JOINER.join( "/** @const */ var module$exports$p$C = {};", "goog.require('p.B');", "function module$contents$p$C_main() {", " /** @type {module$exports$p$A} */ var a = new module$exports$p$A;", " /** @type {p.B} */ var b = new p.B;", "}")}); } public void testSetTestOnly() { test( LINE_JOINER.join( "goog.module('ns.a');", "goog.setTestOnly();"), LINE_JOINER.join( "/** @const */ var module$exports$ns$a = {};", "goog.setTestOnly();")); } public void testRewriteJsDoc1() { // Inlines JsDoc references to aliases of imported types. test( new String[] { LINE_JOINER.join( "goog.module('p.A');", "/** @constructor */", "function A() {}", "exports = A;"), LINE_JOINER.join( "goog.module('p.B');", "var A = goog.require('p.A');", "function main() {", " /** @type {A} */", " var a = new A;", "}")}, new String[] { "/** @constructor */ function module$exports$p$A() {}", LINE_JOINER.join( "/** @const */ var module$exports$p$B = {};", "function module$contents$p$B_main() {", " /** @type {module$exports$p$A} */", " var a = new module$exports$p$A;", "}")}); } public void testRewriteJsDoc2() { // Inlines JsDoc references to own declared types. test( new String[] { LINE_JOINER.join( "goog.module('p.b');", "/** @constructor */", "function B() {}", "function main() {", " /** @type {B} */", " var b = new B;", "}")}, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$p$b = {};", "/** @constructor */", "function module$contents$p$b_B() {}", "function module$contents$p$b_main() {", " /** @type {module$contents$p$b_B} */", " var b = new module$contents$p$b_B;", "}")}); } public void testRewriteJsDoc3() { // Rewrites fully qualified JsDoc references to types. test( new String[] { LINE_JOINER.join( "goog.module('p.A');", "/** @constructor */", "function A() {}", "exports = A;"), LINE_JOINER.join( "goog.module('p.B');", "var A = goog.require('p.A');", "function main() {", " /** @type {p.A} */", " var a = new A;", "}")}, new String[] { LINE_JOINER.join( "/** @constructor */", "function module$exports$p$A() {}"), LINE_JOINER.join( "/** @const */ var module$exports$p$B = {};", "function module$contents$p$B_main() {", " /** @type {module$exports$p$A} */", " var a = new module$exports$p$A;", "}")}); } public void testRewriteJsDoc4() { // Rewrites fully qualified JsDoc references to types in goog.module() files even if they come // after the reference. test( new String[] { LINE_JOINER.join( "goog.module('p.A');", "/** @constructor */", "function A() {}", "A.prototype.setB = function(/** p.B */ x) {}", "exports = A;"), LINE_JOINER.join( "goog.module('p.B');", "var A = goog.require('p.A');", "/** @constructor @extends {A} */", "function B() {}", "B.prototype = new A;", "exports = B;")}, new String[] { LINE_JOINER.join( "/** @constructor */", "function module$exports$p$A() {}", "module$exports$p$A.prototype.setB = function(/** module$exports$p$B */ x) {}"), LINE_JOINER.join( "/** @constructor @extends {module$exports$p$A} */", "function module$exports$p$B() {}", "module$exports$p$B.prototype = new module$exports$p$A;")}); } public void testRewriteJsDoc5() { test( LINE_JOINER.join( "goog.module('p.A');", "", "/** @constructor */", "function A() {}", "", "/** @type {!A} */", "var x = new A;", "", "exports = A;"), LINE_JOINER.join( "/** @constructor */", "function module$exports$p$A() {}", "/** @type {!module$exports$p$A} */", "var module$contents$p$A_x = new module$exports$p$A;")); } public void testDuplicateModule() { testError( new String[] { "goog.module('ns.a');", "goog.module('ns.a');"}, DUPLICATE_MODULE); } public void testDuplicateNamespace() { testError( new String[] { "goog.module('ns.a');", "goog.provide('ns.a');"}, DUPLICATE_NAMESPACE); } public void testImportInliningShadowsVar() { testError( new String[] { LINE_JOINER.join( "goog.provide('a.b.c');", "a.b.c = 5;"), LINE_JOINER.join( "goog.module('a.b.d');", "var c = goog.require('a.b.c');", "function foo() {", " var a = 10;", " var b = c;", "}")}, IMPORT_INLINING_SHADOWS_VAR); } public void testExportRewritingShadows() { test( LINE_JOINER.join( "goog.module('a.b.c');", "function test() {}", "function f(test) { return test; }", "exports = test;"), LINE_JOINER.join( "function module$exports$a$b$c() {}", "function module$contents$a$b$c_f(test) { return test; }")); test( LINE_JOINER.join( "goog.module('a.b.c');", "function test() {}", "function f(test) { return test; }", "exports.test = test;"), LINE_JOINER.join( "/** @const */ var module$exports$a$b$c = {};", "module$exports$a$b$c.test = function() {};", "function module$contents$a$b$c_f(test) { return test; }")); } public void testRequireTooEarly1() { // Module to Module require. testError( new String[] { LINE_JOINER.join( "goog.module('ns.a');", "goog.require('ns.b')"), "goog.module('ns.b');"}, LATE_PROVIDE_ERROR); } public void testValidEarlyGoogModuleGet() { // Legacy Script to Module goog.module.get. test( new String[] { LINE_JOINER.join( "goog.provide('ns.a');", "function foo() {", " var b = goog.module.get('ns.b');", "}"), "goog.module('ns.b');" }, new String[] { "goog.provide('ns.a'); function foo() { var b = module$exports$ns$b; }", "/** @const */ var module$exports$ns$b = {};" }); } public void testRequireTooEarly3() { // Module to Legacy Script require. testError( new String[] { LINE_JOINER.join( "goog.module('ns.a');", "goog.require('ns.b')"), "goog.provide('ns.b');"}, LATE_PROVIDE_ERROR); } public void testInnerScriptOuterModule() { // Rewrites fully qualified JsDoc references to types but without writing a prefix as // module$exports when there's a longer prefix that references a script. test( new String[] { LINE_JOINER.join( "goog.module('A');", "/** @constructor */", "function A() {}", "exports = A;"), LINE_JOINER.join( "goog.provide('A.b.c.D');", "/** @constructor */", "A.b.c.L = function () {}", "function main() {", " /** @type {A.b.c.L} */", " var l = new A.b.c.L();", "}")}, new String[] { "/** @constructor */ function module$exports$A() {}", LINE_JOINER.join( "goog.provide('A.b.c.D');", "/** @constructor */", "A.b.c.L = function() {};", // Note L not D "function main() {", // Note A.b.c.L was NOT written to module$exports$A.b.c.L. " /** @type {A.b.c.L} */", " var l = new A.b.c.L();", "}")}); } public void testModuleLevelVars() { test( LINE_JOINER.join( "goog.module('b.c.c');", "/** @const */", "var F = 0;"), LINE_JOINER.join( "/** @const */ var module$exports$b$c$c = {};", "/** @const */ var module$contents$b$c$c_F = 0;")); } public void testPublicExport() { test( LINE_JOINER.join( "goog.module('a.b.c');", "goog.module.declareLegacyNamespace();", "/** @public */ exports = 5;"), LINE_JOINER.join( "goog.provide('a.b.c');", "/** @const @public */ a.b.c = 5;")); } public void testGoogModuleReferencedWithGlobalName() { testError( new String[] {"goog.module('a.b.c');", "goog.require('a.b.c'); use(a.b.c);"}, QUALIFIED_REFERENCE_TO_GOOG_MODULE); testError( new String[] {"goog.module('a.b.c');", "goog.require('a.b.c'); use(a.b.c.d);"}, QUALIFIED_REFERENCE_TO_GOOG_MODULE); testError( new String[] { "goog.module('a.b.c');", "goog.module('x.y.z'); var c = goog.require('a.b.c'); use(a.b.c);" }, QUALIFIED_REFERENCE_TO_GOOG_MODULE); testError( new String[] {"goog.module('a.b.c');", "use(a.b.c);"}, QUALIFIED_REFERENCE_TO_GOOG_MODULE); } public void testGoogModuleValidReferences() { test( new String[] { "goog.module('a.b.c');", "goog.module('x.y.z'); var c = goog.require('a.b.c'); use(c);" }, new String[] { "/** @const */ var module$exports$a$b$c={};", "/** @const */ var module$exports$x$y$z={}; use(module$exports$a$b$c);" }); test( new String[] { "goog.module('a.b.c');", LINE_JOINER.join( "goog.require('a.b.c');", "goog.scope(function() {", " var c = goog.module.get('a.b.c');", " use(c);", "});") }, new String[] { "/** @const */ var module$exports$a$b$c={};", "goog.scope(function() { var c = module$exports$a$b$c; use(c); });" }); } public void testLegacyGoogModuleValidReferences() { test( new String[] { "goog.module('a.b.c'); goog.module.declareLegacyNamespace();", "goog.require('a.b.c'); use(a.b.c);" }, new String[] { "goog.provide('a.b.c');", "goog.require('a.b.c'); use(a.b.c);" }); test( new String[] { "goog.module('a.b.c'); goog.module.declareLegacyNamespace();", "goog.module('x.y.z'); var c = goog.require('a.b.c'); use(c);" }, new String[] { "goog.provide('a.b.c');", "/** @const */ var module$exports$x$y$z={}; goog.require('a.b.c'); use(a.b.c);" }); test( new String[] { LINE_JOINER.join( "goog.module('a.b.Foo');", "goog.module.declareLegacyNamespace();", "", "/** @constructor */ exports = function() {};"), "/** @param {a.b.Foo} x */ function f(x) {}" }, new String[] { "goog.provide('a.b.Foo'); /** @constructor @const */ a.b.Foo = function() {};", "/** @param {a.b.Foo} x */ function f(x) {}" }); test( new String[] { LINE_JOINER.join( "goog.module('a.b.c');", "goog.module.declareLegacyNamespace();", "", "exports = function() {};"), "function f() { return goog.module.get('a.b.c'); }" }, new String[] { "goog.provide('a.b.c'); /** @const */ a.b.c = function() {};", "function f() { return a.b.c; }" }); test( new String[] { LINE_JOINER.join( "goog.module('a.b.Foo');", "goog.module.declareLegacyNamespace();", "", "/** @constructor */ function Foo() {}", "", "exports = Foo;"), "/** @param {a.b.Foo} x */ function f(x) {}" }, new String[] { LINE_JOINER.join( "goog.provide('a.b.Foo');", "/** @constructor */ function module$contents$a$b$Foo_Foo() {}", "/** @const */ a.b.Foo = module$contents$a$b$Foo_Foo;"), "/** @param {a.b.Foo} x */ function f(x) {}" }); test( new String[] { LINE_JOINER.join( "goog.module('a.b');", "goog.module.declareLegacyNamespace();", "", "/** @constructor */ function Foo() {};", "", "exports.Foo = Foo;"), "/** @param {a.b.Foo} x */ function f(x) {}" }, new String[] { LINE_JOINER.join( "goog.provide('a.b');", "/** @constructor */ function module$contents$a$b_Foo() {};", "/** @const */ a.b.Foo = module$contents$a$b_Foo;"), "/** @param {a.b.Foo} x */ function f(x) {}" }); } public void testUselessUseStrict() { testWarning( "'use strict'; goog.module('b.c.c');", ClosureRewriteModule.USELESS_USE_STRICT_DIRECTIVE); } public void testRewriteGoogModuleAliases1() { test( new String[] { LINE_JOINER.join( "goog.module('base');", "", "/** @constructor */ var Base = function() {}", "exports = Base;"), LINE_JOINER.join( "goog.module('leaf');", "", "var Base = goog.require('base');", "exports = /** @constructor @extends {Base} */ function Foo() {}") }, new String[] { "/** @constructor */ var module$exports$base = function() {};", LINE_JOINER.join( "/** @const */ var module$exports$leaf = ", "/** @constructor @extends {module$exports$base} */ function Foo() {}") }); } public void testRewriteGoogModuleAliases2() { test( new String[] { LINE_JOINER.join( "goog.module('ns.base');", "", "/** @constructor */ var Base = function() {}", "exports = Base;"), LINE_JOINER.join( "goog.module('leaf');", "", "var Base = goog.require('ns.base');", "exports = /** @constructor @extends {Base} */ function Foo() {}") }, new String[] { "/** @constructor */ var module$exports$ns$base = function() {};", LINE_JOINER.join( "/** @const */ var module$exports$leaf = ", "/** @constructor @extends {module$exports$ns$base} */ function Foo() {}") }); } public void testRewriteGoogModuleAliases3() { test( new String[] { LINE_JOINER.join( "goog.module('ns.base');", "", "/** @constructor */ var Base = function() {};", "/** @constructor */ Base.Foo = function() {};", "exports = Base;"), LINE_JOINER.join( "goog.module('leaf');", "", "var Base = goog.require('ns.base');", "exports = /** @constructor @extends {Base.Foo} */ function Foo() {}") }, new String[] { LINE_JOINER.join( "/** @constructor */ var module$exports$ns$base = function() {};", "/** @constructor */ module$exports$ns$base.Foo = function() {};"), LINE_JOINER.join( "/** @const */ var module$exports$leaf = ", "/** @constructor @extends {module$exports$ns$base.Foo} */ function Foo() {}") }); } public void testRewriteGoogModuleAliases4() { test( new String[] { LINE_JOINER.join( "goog.module('ns.base');", "", "/** @constructor */ var Base = function() {}", "exports = Base;"), LINE_JOINER.join( "goog.module('leaf');", "", "var Base = goog.require('ns.base');", "exports = new Base;") }, new String[] { "/** @constructor */ var module$exports$ns$base = function() {};", "/** @const */ var module$exports$leaf = new module$exports$ns$base;" }); } public void testRewriteGoogModuleAliases5() { test( new String[] { LINE_JOINER.join( "goog.module('ns.base');", "", "/** @constructor */ var Base = function() {}", "exports = Base;"), LINE_JOINER.join( "goog.module('mid');", "", "var Base = goog.require('ns.base');", "exports = Base;"), LINE_JOINER.join( "goog.module('leaf')", "var Base = goog.require('mid');", "new Base;") }, new String[] { "/** @constructor */ var module$exports$ns$base = function() {};", "/** @const */ var module$exports$mid = module$exports$ns$base;", "/** @const */ var module$exports$leaf = {}; new module$exports$mid;", }); } public void testRewriteGoogModuleAliases6() { testEs6( new String[] { LINE_JOINER.join( "goog.module('base');", "", "/** @constructor */ exports.Foo = function() {};"), LINE_JOINER.join( "goog.module('FooWrapper');", "", "const {Foo} = goog.require('base');", "exports = Foo;"), }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$base = {};", "/** @constructor @const */ module$exports$base.Foo = function() {};"), "/** @const */ var module$exports$FooWrapper = module$exports$base.Foo;", }); } public void testRewriteGoogModuleAliases7() { testEs6( new String[] { LINE_JOINER.join( "goog.module('base');", "", "/** @constructor */ exports.Foo = function() {};"), LINE_JOINER.join( "goog.module('FooWrapper');", "", "const {Foo: FooFromBaseModule} = goog.require('base');", "exports = FooFromBaseModule;"), }, new String[] { LINE_JOINER.join( "/** @const */ var module$exports$base = {};", "/** @constructor @const */ module$exports$base.Foo = function() {};"), "/** @const */ var module$exports$FooWrapper = module$exports$base.Foo;", }); } public void testGoogModuleExportsProvidedName() { testEs6( new String[] { LINE_JOINER.join( "goog.provide('Foo');", "", "/** @constructor */ var Foo = function() {};"), LINE_JOINER.join( "goog.module('FooWrapper');", "", "goog.require('Foo');", "", "exports = Foo;"), }, new String[] { LINE_JOINER.join( "goog.provide('Foo');", "", "/** @constructor */ var Foo = function() {};"), "goog.require('Foo'); /** @const */ var module$exports$FooWrapper = Foo;", }); } public void testRewriteGoogModuleAliasesWithPrototypeGets1() { test( new String[] { LINE_JOINER.join( "goog.module('mod_B');", "", "/** @interface */ function B(){}", "B.prototype.f = function(){};", "", "exports = B;"), LINE_JOINER.join( "goog.module('mod_A');", "", "var B = goog.require('mod_B');", "", "/** @type {B} */", "var b;") }, new String[] { LINE_JOINER.join( "/**@interface */ function module$exports$mod_B() {}", "module$exports$mod_B.prototype.f = function() {};"), LINE_JOINER.join( "/** @const */ var module$exports$mod_A = {};", "/**@type {module$exports$mod_B} */ var module$contents$mod_A_b;") }); } public void testRewriteGoogModuleAliasesWithPrototypeGets2() { test( new String[] { LINE_JOINER.join( "goog.module('mod_B');", "", "/** @interface */ function B(){}", "", "exports = B;"), LINE_JOINER.join( "goog.module('mod_A');", "", "var B = goog.require('mod_B');", "B.prototype;", "", "/** @type {B} */", "var b;") }, new String[] { "/**@interface */ function module$exports$mod_B() {}", LINE_JOINER.join( "/** @const */ var module$exports$mod_A = {}", "module$exports$mod_B.prototype;", "/**@type {module$exports$mod_B} */ var module$contents$mod_A_b;") }); } public void testMultiplyExportedSymbolDoesntCrash() { testEs6( LINE_JOINER.join( "goog.module('mod');", "", "class Foo {}", "", "exports.name1 = Foo;", "exports.name2 = Foo;"), LINE_JOINER.join( "/** @const */ var module$exports$mod = {};", "module$exports$mod.name1 = class {};", "/** @const */ module$exports$mod.name2 = module$exports$mod.name1;")); } public void testIjsFileInExterns() { allowExternsChanges(true); test( LINE_JOINER.join( "/** @externs */", "goog.module('mod_B');", "", "/** @interface */ function B(){}", "", "exports = B;"), LINE_JOINER.join( "goog.module('mod_A');", "", "var B = goog.require('mod_B');", "", "/** @constructor @implements {B} */", "function A() {}"), (String) null, null, null); test( LINE_JOINER.join( "/** @externs */", "goog.loadModule(function(exports) { 'use strict';", " goog.module('mod_B');", "", " /** @interface */ function B(){}", "", " exports = B;", " return exports;", "});"), LINE_JOINER.join( "goog.module('mod_A');", "", "var B = goog.require('mod_B');", "", "/** @constructor @implements {B} */", "function A() {}"), (String) null, null, null); setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); test( LINE_JOINER.join( "/** @externs */", "goog.loadModule(function(exports) { 'use strict';", " goog.module('mod_B');", "", " /** @interface */ function B(){}", "", " exports.B = B;", " return exports;", "});"), LINE_JOINER.join( "goog.module('mod_A');", "", "var {B} = goog.require('mod_B');", "", "/** @constructor @implements {B} */", "function A() {}"), (String) null, null, null); } }