/*
* 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.ProcessEs6Modules.LHS_OF_GOOG_REQUIRE_MUST_BE_CONST;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.rhino.Node;
/**
* Unit tests for {@link ProcessEs6Modules}
*/
public final class ProcessEs6ModulesTest extends CompilerTestCase {
private ImmutableList<String> moduleRoots = null;
@Override
public void setUp() {
// ECMASCRIPT5 to trigger module processing after parsing.
setLanguage(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
runTypeCheckAfterProcessing = true;
}
@Override
protected CompilerOptions getOptions() {
CompilerOptions options = super.getOptions();
// ECMASCRIPT5 to Trigger module processing after parsing.
options.setLanguageOut(LanguageMode.ECMASCRIPT5);
options.setWarningLevel(DiagnosticGroups.LINT_CHECKS, CheckLevel.WARNING);
if (moduleRoots != null) {
options.setModuleRoots(moduleRoots);
}
return options;
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
// No-op, ES6 module handling is done directly after parsing.
}
};
}
@Override
protected int getNumRepetitions() {
return 1;
}
static void testModules(CompilerTestCase test, String input, String expected) {
// Shared with ProcessCommonJSModulesTest.
String fileName = test.getFilename() + ".js";
ImmutableList<SourceFile> inputs =
ImmutableList.of(
SourceFile.fromCode("other.js", "goog.provide('module$other');"),
SourceFile.fromCode("yet_another.js", "goog.provide('module$yet_another');"),
SourceFile.fromCode(fileName, input));
ImmutableList<SourceFile> expecteds =
ImmutableList.of(
SourceFile.fromCode("other.js", "goog.provide('module$other');"),
SourceFile.fromCode("yet_another.js", "goog.provide('module$yet_another');"),
SourceFile.fromCode(fileName, expected));
test.test(inputs, expecteds);
}
static void testModules(
CompilerTestCase test, ImmutableList<SourceFile> inputs, String expected) {
ImmutableList<SourceFile> expecteds =
ImmutableList.of(
SourceFile.fromCode("other.js", "goog.provide('module$other');"),
SourceFile.fromCode(test.getFilename() + ".js", expected));
test.test(inputs, expecteds);
}
void testModules(String input, String expected) {
testModules(this, input,
"/** @fileoverview\n * @suppress {missingProvide|missingRequire}\n */" + expected);
}
private static void testModules(CompilerTestCase test, String input, DiagnosticType error) {
String fileName = test.getFilename() + ".js";
ImmutableList<SourceFile> inputs =
ImmutableList.of(SourceFile.fromCode("other.js", ""), SourceFile.fromCode(fileName, input));
test.test(inputs, null, error);
}
private void testModules(String input, DiagnosticType error) {
testModules(this, input, error);
}
public void testImport() {
testModules(
"import name from './other'; use(name);",
"goog.require('module$other'); use(module$other.default);");
testModules("import {n as name} from './other';", "goog.require('module$other');");
testModules(
"import x, {f as foo, b as bar} from './other'; use(x);",
"goog.require('module$other'); use(module$other.default);");
testModules(
"import {default as name} from './other'; use(name);",
"goog.require('module$other'); use(module$other.default);");
testModules(
"import {class as name} from './other'; use(name);",
"goog.require('module$other'); use(module$other.class);");
}
public void testImport_missing() {
setExpectParseWarningsThisTest(); // JSC_ES6_MODULE_LOAD_WARNING
testModules(
"import name from './does_not_exist'; use(name);",
"goog.require('module$does_not_exist'); use(module$does_not_exist.default);");
}
public void testImportStar() {
testModules(
"import * as name from './other'; use(name.foo);",
"goog.require('module$other'); use(module$other.foo)");
}
public void testTypeNodeRewriting() {
testModules(
"import * as name from './other'; /** @type {name.foo} */ var x;",
"goog.require('module$other');"
+ "/** @type {module$other.foo} */ var x$$module$testcode;");
}
public void testExport() {
testModules(
"export var a = 1, b = 2;",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var a$$module$testcode = 1, b$$module$testcode = 2;",
"module$testcode.a = a$$module$testcode;",
"module$testcode.b = b$$module$testcode;"));
testModules(
"export var a; export var b;",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var a$$module$testcode; var b$$module$testcode;",
"module$testcode.a = a$$module$testcode;",
"module$testcode.b = b$$module$testcode;"));
testModules(
"export function f() {};",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"function f$$module$testcode() {}",
"module$testcode.f = f$$module$testcode;"));
testModules(
"export function f() {}; function g() { f(); }",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"function f$$module$testcode() {}",
"function g$$module$testcode() { f$$module$testcode(); }",
"module$testcode.f = f$$module$testcode;"));
testModules(
LINE_JOINER.join("export function MyClass() {};", "MyClass.prototype.foo = function() {};"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"function MyClass$$module$testcode() {}",
"MyClass$$module$testcode.prototype.foo = function() {};",
"module$testcode.MyClass = MyClass$$module$testcode;"));
testModules(
"var f = 1; var b = 2; export {f as foo, b as bar};",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var f$$module$testcode = 1;",
"var b$$module$testcode = 2;",
"module$testcode.foo = f$$module$testcode;",
"module$testcode.bar = b$$module$testcode;"));
testModules(
"var f = 1; export {f as default};",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var f$$module$testcode = 1;",
"module$testcode.default = f$$module$testcode;"));
testModules(
"var f = 1; export {f as class};",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var f$$module$testcode = 1;",
"module$testcode.class = f$$module$testcode;"));
}
public void testExportWithJsDoc() {
testModules(
"/** @constructor */ export function F() { return '';}",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"/** @constructor */",
"function F$$module$testcode() { return ''; }",
"module$testcode.F = F$$module$testcode"));
testModules(
"/** @return {string} */ export function f() { return '';}",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"/** @return {string} */",
"function f$$module$testcode() { return ''; }",
"module$testcode.f = f$$module$testcode"));
testModules(
"/** @return {string} */ export var f = function() { return '';}",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"/** @return {string} */",
"var f$$module$testcode = function() { return ''; }",
"module$testcode.f = f$$module$testcode"));
testModules(
"/** @type {number} */ export var x = 3",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"/** @type {number} */",
"var x$$module$testcode = 3;",
"module$testcode.x = x$$module$testcode"));
}
public void testImportAndExport() {
testModules(
LINE_JOINER.join("import {name as n} from './other';", "use(n);", "export {n as name};"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('module$other');",
"use(module$other.name);",
"module$testcode.name = module$other.name;"));
}
public void testExportFrom() {
testModules(
LINE_JOINER.join(
"export {name} from './other';",
"export {default} from './other';",
"export {class} from './other';"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('module$other');",
"module$testcode.name = module$other.name;",
"module$testcode.default = module$other.default;",
"module$testcode.class = module$other.class;"));
testModules(
"export {a, b as c, d} from './other';",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('module$other');",
"module$testcode.a = module$other.a;",
"module$testcode.c = module$other.b;",
"module$testcode.d = module$other.d;"));
testModules(
"export {a as b, b as a} from './other';",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('module$other');",
"module$testcode.b = module$other.a;",
"module$testcode.a = module$other.b;"));
testModules(
LINE_JOINER.join(
"export {default as a} from './other';",
"export {a as a2, default as b} from './other';",
"export {class as switch} from './other';"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('module$other');",
"module$testcode.a = module$other.default;",
"module$testcode.a2 = module$other.a;",
"module$testcode.b = module$other.default;",
"module$testcode.switch = module$other.class;"));
}
public void testExportDefault() {
testModules(
"export default 'someString';",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var $jscompDefaultExport$$module$testcode = 'someString';",
"module$testcode.default = $jscompDefaultExport$$module$testcode;"));
testModules(
"var x = 5; export default x;",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var x$$module$testcode = 5;",
"var $jscompDefaultExport$$module$testcode = x$$module$testcode;",
"module$testcode.default = $jscompDefaultExport$$module$testcode;"));
testModules(
"export default function f(){}; var x = f();",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"function f$$module$testcode() {}",
"var x$$module$testcode = f$$module$testcode();",
"module$testcode.default = f$$module$testcode;"));
testModules(
"export default class Foo {}; var x = new Foo;",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"class Foo$$module$testcode {}",
"var x$$module$testcode = new Foo$$module$testcode;",
"module$testcode.default = Foo$$module$testcode;"));
}
public void testExportDefault_anonymous() {
testModules(
"export default class {};",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var $jscompDefaultExport$$module$testcode = class {};",
"module$testcode.default = $jscompDefaultExport$$module$testcode;"));
testModules(
"export default function() {}",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var $jscompDefaultExport$$module$testcode = function() {}",
"module$testcode.default = $jscompDefaultExport$$module$testcode;"));
}
public void testExtendImportedClass() {
testModules(
LINE_JOINER.join(
"import {Parent} from './other';",
"class Child extends Parent {",
" /** @param {Parent} parent */",
" useParent(parent) {}",
"}"),
LINE_JOINER.join(
"goog.require('module$other');",
"class Child$$module$testcode extends module$other.Parent {",
" /** @param {Parent$$module$other} parent */",
" useParent(parent) {}",
"}"));
testModules(
LINE_JOINER.join(
"import {Parent} from './other';",
"class Child extends Parent {",
" /** @param {./other.Parent} parent */",
" useParent(parent) {}",
"}"),
LINE_JOINER.join(
"goog.require('module$other');",
"class Child$$module$testcode extends module$other.Parent {",
" /** @param {module$other.Parent} parent */",
" useParent(parent) {}",
"}"));
testModules(
LINE_JOINER.join(
"import {Parent} from './other';",
"export class Child extends Parent {",
" /** @param {Parent} parent */",
" useParent(parent) {}",
"}"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('module$other');",
"class Child$$module$testcode extends module$other.Parent {",
" /** @param {Parent$$module$other} parent */",
" useParent(parent) {}",
"}",
"/** @const */ module$testcode.Child = Child$$module$testcode;"));
}
public void testFixTypeNode() {
testModules(
LINE_JOINER.join(
"export class Child {", " /** @param {Child} child */", " useChild(child) {}", "}"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"class Child$$module$testcode {",
" /** @param {Child$$module$testcode} child */",
" useChild(child) {}",
"}",
"/** @const */ module$testcode.Child = Child$$module$testcode;"));
testModules(
LINE_JOINER.join(
"export class Child {",
" /** @param {Child.Foo.Bar.Baz} baz */",
" useBaz(baz) {}",
"}"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"class Child$$module$testcode {",
" /** @param {Child$$module$testcode.Foo.Bar.Baz} baz */",
" useBaz(baz) {}",
"}",
"/** @const */ module$testcode.Child = Child$$module$testcode;"));
}
public void testReferenceToTypeFromOtherModule() {
testModules(
LINE_JOINER.join(
"export class Foo {", " /** @param {./other.Baz} baz */", " useBaz(baz) {}", "}"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"class Foo$$module$testcode {",
" /** @param {module$other.Baz} baz */",
" useBaz(baz) {}",
"}",
"/** @const */ module$testcode.Foo = Foo$$module$testcode;"));
testModules(
LINE_JOINER.join(
"export class Foo {",
" /** @param {/other.Baz} baz */",
" useBaz(baz) {}",
"}"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"class Foo$$module$testcode {",
" /** @param {module$other.Baz} baz */",
" useBaz(baz) {}",
"}",
"/** @const */ module$testcode.Foo = Foo$$module$testcode;"));
}
public void testRenameTypedef() {
testModules(
LINE_JOINER.join(
"import './other';", "/** @typedef {string|!Object} */", "export var UnionType;"),
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('module$other');",
"/** @typedef {string|!Object} */",
"var UnionType$$module$testcode;",
"/** @typedef {UnionType$$module$testcode} */",
"module$testcode.UnionType;"));
}
public void testRenameImportedReference() {
testModules(
LINE_JOINER.join(
"import {f} from './other';",
"import {b as bar} from './other';",
"f();",
"function g() {",
" f();",
" bar++;",
" function h() {",
" var f = 3;",
" { let f = 4; }",
" }",
"}"),
LINE_JOINER.join(
"goog.require('module$other');",
"module$other.f();",
"function g$$module$testcode() {",
" module$other.f();",
" module$other.b++;",
" function h() {",
" var f = 3;",
" { let f = 4; }",
" }",
"}"));
}
public void testGoogRequires_noChange() {
testSame("goog.require('foo.bar');");
testSame("var bar = goog.require('foo.bar');");
testModules(
"goog.require('foo.bar'); export var x;",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('foo.bar');",
"var x$$module$testcode;",
"module$testcode.x = x$$module$testcode"));
testModules(
"export var x; goog.require('foo.bar');",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var x$$module$testcode;",
"goog.require('foo.bar');",
"module$testcode.x = x$$module$testcode"));
testModules(
"import * as s from './other'; goog.require('foo.bar');",
"goog.require('module$other'); goog.require('foo.bar');");
testModules(
"goog.require('foo.bar'); import * as s from './other';",
"goog.require('module$other'); goog.require('foo.bar'); ");
}
public void testGoogRequires_rewrite() {
testModules(
"const bar = goog.require('foo.bar'); export var x;",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"goog.require('foo.bar');",
"const bar$$module$testcode = foo.bar;",
"var x$$module$testcode;",
"module$testcode.x = x$$module$testcode"));
testModules(
"export var x; const bar = goog.require('foo.bar');",
LINE_JOINER.join(
"goog.provide('module$testcode');",
"var x$$module$testcode;",
"goog.require('foo.bar');",
"const bar$$module$testcode = foo.bar;",
"module$testcode.x = x$$module$testcode"));
testModules(
"import * as s from './other'; const bar = goog.require('foo.bar');",
LINE_JOINER.join(
"goog.require('module$other');",
"goog.require('foo.bar');",
"const bar$$module$testcode = foo.bar;"));
testModules(
"const bar = goog.require('foo.bar'); import * as s from './other';",
LINE_JOINER.join(
"goog.require('module$other');",
"goog.require('foo.bar');",
"const bar$$module$testcode = foo.bar;"));
}
public void testGoogRequires_nonConst() {
testModules(
"var bar = goog.require('foo.bar'); export var x;",
LHS_OF_GOOG_REQUIRE_MUST_BE_CONST);
testModules(
"export var x; var bar = goog.require('foo.bar');",
LHS_OF_GOOG_REQUIRE_MUST_BE_CONST);
testModules(
"import * as s from './other'; var bar = goog.require('foo.bar');",
LHS_OF_GOOG_REQUIRE_MUST_BE_CONST);
testModules(
"var bar = goog.require('foo.bar'); import * as s from './other';",
LHS_OF_GOOG_REQUIRE_MUST_BE_CONST);
}
public void testGoogRequiresDestructuring_rewrite() {
testModules(
LINE_JOINER.join(
"import * as s from './other';",
"const {foo, bar} = goog.require('some.name.space');",
"use(foo, bar);"),
LINE_JOINER.join(
"goog.require('module$other');",
"goog.require('some.name.space');",
"const {",
" foo: foo$$module$testcode,",
" bar: bar$$module$testcode,",
"} = some.name.space;",
"use(foo$$module$testcode, bar$$module$testcode);"));
testModules(
LINE_JOINER.join(
"import * as s from './other';",
"var {foo, bar} = goog.require('some.name.space');",
"use(foo, bar);"),
LHS_OF_GOOG_REQUIRE_MUST_BE_CONST);
testModules(
LINE_JOINER.join(
"import * as s from './other';",
"let {foo, bar} = goog.require('some.name.space');",
"use(foo, bar);"),
LHS_OF_GOOG_REQUIRE_MUST_BE_CONST);
}
public void testNamespaceImports() {
testModules(
LINE_JOINER.join(
"import Foo from 'goog:other.Foo';",
"use(Foo);"),
LINE_JOINER.join(
"goog.require('other.Foo');",
"use(other.Foo)"));
testModules(
LINE_JOINER.join(
"import {x, y} from 'goog:other.Foo';",
"use(x);",
"use(y);"),
LINE_JOINER.join(
"goog.require('other.Foo');",
"use(other.Foo.x); use(other.Foo.y);"));
testModules(
LINE_JOINER.join(
"import Foo from 'goog:other.Foo';",
"/** @type {Foo} */ var foo = new Foo();"),
LINE_JOINER.join(
"goog.require('other.Foo');",
"/** @type {other.Foo} */",
"var foo$$module$testcode = new other.Foo();"));
testModules("import * as Foo from 'goog:other.Foo';",
ProcessEs6Modules.NAMESPACE_IMPORT_CANNOT_USE_STAR);
}
public void testObjectDestructuringAndObjLitShorthand() {
testModules(
LINE_JOINER.join(
"import {f} from './other';",
"const foo = 1;",
"const {a, b} = f({foo});",
"use(a, b);"),
LINE_JOINER.join(
"goog.require('module$other');",
"const foo$$module$testcode = 1;",
"const {",
" a: a$$module$testcode,",
" b: b$$module$testcode,",
"} = module$other.f({foo: foo$$module$testcode});",
"use(a$$module$testcode, b$$module$testcode);"));
}
public void testImportWithoutReferences() {
testModules("import './other';", "goog.require('module$other');");
// GitHub issue #1819: https://github.com/google/closure-compiler/issues/1819
// Need to make sure the order of the goog.requires matches the order of the imports.
testModules(
"import './other'; import './yet_another';",
"goog.require('module$other'); goog.require('module$yet_another');");
}
public void testUselessUseStrict() {
setExpectParseWarningsThisTest();
testModules(LINE_JOINER.join(
"'use strict';",
"export default undefined;"),
LINE_JOINER.join(
"'use strict';",
"export default undefined;"));
}
public void testUseStrict_noWarning() {
testSame(LINE_JOINER.join(
"'use strict';",
"var x;"));
}
public void testAbsoluteImportsWithModuleRoots() {
moduleRoots = ImmutableList.of("/base");
compareJsDoc = false;
test(
ImmutableList.of(
SourceFile.fromCode(Compiler.joinPathParts("base", "mod", "name.js"), ""),
SourceFile.fromCode(
Compiler.joinPathParts("base", "test", "sub.js"),
"import * as foo from '/mod/name';")),
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("base", "mod", "name.js"),
LINE_JOINER.join(
"/** @fileoverview",
" * @suppress {missingProvide|missingRequire}",
" */",
"goog.provide('module$mod$name');")),
SourceFile.fromCode(
Compiler.joinPathParts("base", "test", "sub.js"),
"goog.require('module$mod$name');")));
}
}