diff --git a/src/com/google/javascript/jscomp/Compiler.java b/src/com/google/javascript/jscomp/Compiler.java index 52fbf7398cc..0594142fd2d 100644 --- a/src/com/google/javascript/jscomp/Compiler.java +++ b/src/com/google/javascript/jscomp/Compiler.java @@ -1823,10 +1823,6 @@ Node parseInputs() { if (!inputsToRewrite.isEmpty()) { forceToEs6Modules(inputsToRewrite.values()); } - - if (options.needsTranspilationFrom(FeatureSet.ES6_MODULES)) { - processEs6Modules(); - } } else { // Use an empty module loader if we're not actually dealing with modules. this.moduleLoader = ModuleLoader.EMPTY; @@ -2060,10 +2056,6 @@ Map processJsonInputs(List inputsToProcess) { return rewriteJson.getPackageJsonMainEntries(); } - void processEs6Modules() { - processEs6Modules(parsePotentialModules(inputs)); - } - void forceToEs6Modules(Collection inputsToProcess) { for (CompilerInput input : inputsToProcess) { input.setCompiler(this); @@ -2098,20 +2090,6 @@ private List parsePotentialModules(List inputsToPr return filteredInputs; } - void processEs6Modules(List inputsToProcess) { - for (CompilerInput input : inputsToProcess) { - input.setCompiler(this); - Node root = input.getAstRoot(this); - if (root == null) { - continue; - } - if (Es6RewriteModules.isEs6ModuleRoot(root)) { - new Es6RewriteModules(this).processFile(root); - } - } - setFeatureSet(featureSet.without(Feature.MODULES)); - } - /** * Transforms AMD and CJS modules to something closure compiler can * process and creates JSModules and the corresponding dependency tree diff --git a/src/com/google/javascript/jscomp/CompilerOptions.java b/src/com/google/javascript/jscomp/CompilerOptions.java index d604ae0cdde..ef5ac42f343 100644 --- a/src/com/google/javascript/jscomp/CompilerOptions.java +++ b/src/com/google/javascript/jscomp/CompilerOptions.java @@ -1915,6 +1915,11 @@ public boolean needsTranspilationFrom(FeatureSet languageLevel) { && !getLanguageOut().toFeatureSet().contains(languageLevel); } + public boolean needsTranspilationOf(FeatureSet.Feature feature) { + return getLanguageIn().toFeatureSet().has(feature) + && !getLanguageOut().toFeatureSet().has(feature); + } + /** * Set which set of builtin externs to use. */ diff --git a/src/com/google/javascript/jscomp/DefaultPassConfig.java b/src/com/google/javascript/jscomp/DefaultPassConfig.java index 2d7f63ba4ac..07dac402881 100644 --- a/src/com/google/javascript/jscomp/DefaultPassConfig.java +++ b/src/com/google/javascript/jscomp/DefaultPassConfig.java @@ -191,6 +191,10 @@ protected List getTranspileOnlyPasses() { passes.add(convertEs6TypedToEs6); } + if (options.needsTranspilationOf(FeatureSet.Feature.MODULES)) { + TranspilationPasses.addEs6ModulePass(passes); + } + passes.add(checkMissingSuper); passes.add(checkVariableReferencesForTranspileOnly); @@ -261,6 +265,7 @@ protected List getChecks() { List checks = new ArrayList<>(); if (options.shouldGenerateTypedExterns()) { + TranspilationPasses.addEs6ModulePass(checks); checks.add(closureGoogScopeAliases); checks.add(closureRewriteClass); checks.add(generateIjs); @@ -273,6 +278,14 @@ protected List getChecks() { // Verify JsDoc annotations checks.add(checkJsDoc); + if (options.needsTranspilationFrom(TYPESCRIPT)) { + checks.add(convertEs6TypedToEs6); + } + + if (options.needsTranspilationOf(FeatureSet.Feature.MODULES)) { + TranspilationPasses.addEs6ModulePass(checks); + } + if (options.enables(DiagnosticGroups.LINT_CHECKS)) { checks.add(lintChecks); } @@ -294,10 +307,6 @@ protected List getChecks() { checks.add(closureRewriteModule); } - if (options.needsTranspilationFrom(TYPESCRIPT)) { - checks.add(convertEs6TypedToEs6); - } - if (options.declaredGlobalExternsOnWindow) { checks.add(declaredGlobalExternsOnWindow); } diff --git a/src/com/google/javascript/jscomp/Es6RewriteModules.java b/src/com/google/javascript/jscomp/Es6RewriteModules.java index b3abde46214..611deb1ee91 100644 --- a/src/com/google/javascript/jscomp/Es6RewriteModules.java +++ b/src/com/google/javascript/jscomp/Es6RewriteModules.java @@ -15,6 +15,8 @@ */ package com.google.javascript.jscomp; +import static com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature.MODULES; + import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; @@ -34,13 +36,13 @@ import java.util.Set; /** - * Rewrites a ES6 module into a form that can be safely concatenated. - * Note that we treat a file as an ES6 module if it has at least one import or - * export statement. + * Rewrites a ES6 module into a form that can be safely concatenated. Note that we treat a file as + * an ES6 module if it has at least one import or export statement. * * @author moz@google.com (Michael Zhou) */ -public final class Es6RewriteModules extends AbstractPostOrderCallback { +public final class Es6RewriteModules extends AbstractPostOrderCallback + implements HotSwapCompilerPass { private static final String DEFAULT_EXPORT_NAME = "$jscompDefaultExport"; static final DiagnosticType LHS_OF_GOOG_REQUIRE_MUST_BE_CONST = @@ -54,13 +56,13 @@ public final class Es6RewriteModules extends AbstractPostOrderCallback { "Namespace imports ('goog:some.Namespace') cannot use import * as. " + "Did you mean to import {0} from ''{1}'';?"); - private final Compiler compiler; - private int scriptNodeCount = 0; + private final AbstractCompiler compiler; + private int scriptNodeCount; /** * Maps exported names to their names in current module. */ - private Map exportMap = new LinkedHashMap<>(); + private Map exportMap; /** * Maps symbol names to a pair of (moduleName, originalName). The original @@ -69,12 +71,12 @@ public final class Es6RewriteModules extends AbstractPostOrderCallback { * object. Eg: "import {foo as f} from 'm'" maps 'f' to the pair ('m', 'foo'). * In the entry for "import * as ns", the originalName will be the empty string. */ - private Map importMap = new HashMap<>(); + private Map importMap; - private Set classes = new HashSet<>(); - private Set typedefs = new HashSet<>(); + private Set classes; + private Set typedefs; - private Set alreadyRequired = new HashSet<>(); + private Set alreadyRequired; private boolean forceRewrite; @@ -84,7 +86,7 @@ public final class Es6RewriteModules extends AbstractPostOrderCallback { * Creates a new Es6RewriteModules instance which can be used to rewrite * ES6 modules to a concatenable form. */ - public Es6RewriteModules(Compiler compiler) { + public Es6RewriteModules(AbstractCompiler compiler) { this.compiler = compiler; } @@ -104,6 +106,8 @@ public static boolean isEs6ModuleRoot(Node scriptNode) { * "import" or "export" statements. Fails if the file contains a goog.provide or goog.module. * * @return True, if the file is now an ES6 module. False, if the file must remain a script. + * TODO(blickly): Move this logic out of this pass, since it is independent of whether or + * not we are actually transpiling modules */ public boolean forceToEs6Module(Node root) { if (isEs6ModuleRoot(root)) { @@ -120,15 +124,42 @@ public boolean forceToEs6Module(Node root) { return true; } + @Override + public void process(Node externs, Node root) { + Preconditions.checkState(compiler.getOptions().getLanguageIn().toFeatureSet().has(MODULES)); + for (Node file = root.getFirstChild(); file != null; file = file.getNext()) { + hotSwapScript(file, null); + } + compiler.setFeatureSet(compiler.getFeatureSet().without(MODULES)); + } + + @Override + public void hotSwapScript(Node scriptNode, Node originalRoot) { + if (isEs6ModuleRoot(scriptNode)) { + processFile(scriptNode); + } + } + /** * Rewrite a single ES6 module file to a global script version. */ - public void processFile(Node root) { + private void processFile(Node root) { Preconditions.checkArgument(isEs6ModuleRoot(root), root); - this.forceRewrite = true; + clearState(); NodeTraversal.traverseEs6(compiler, root, this); } + public void clearState() { + this.scriptNodeCount = 0; + this.exportMap = new LinkedHashMap<>(); + this.importMap = new HashMap<>(); + this.classes = new HashSet<>(); + this.typedefs = new HashSet<>(); + this.alreadyRequired = new HashSet<>(); + this.forceRewrite = true; + this.googRequireInsertSpot = null; + } + /** * Avoid processing if we find the appearance of goog.provide or goog.module. * @@ -284,8 +315,7 @@ private void visitExport(NodeTraversal t, Node export, Node parent) { } if (name != null) { - Node decl = child.cloneTree(); - decl.setJSDocInfo(child.getJSDocInfo()); + Node decl = child.detach(); parent.replaceChild(export, decl); exportMap.put("default", new NameNodePair(name, child)); } else { @@ -556,6 +586,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { n.setString(newName); n.setOriginalName(name); } + t.reportCodeChange(n); } else if (var == null && importMap.containsKey(name)) { // Change to property access on the imported module object. if (parent.isCall() && parent.getFirstChild() == n) { @@ -571,6 +602,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { IR.getprop(moduleAccess, IR.string(pair.originalName)) .useSourceInfoIfMissingFromForTree(n)); } + t.reportCodeChange(moduleAccess); } } } diff --git a/src/com/google/javascript/jscomp/TranspilationPasses.java b/src/com/google/javascript/jscomp/TranspilationPasses.java index 50afa09f672..7a5805686c4 100644 --- a/src/com/google/javascript/jscomp/TranspilationPasses.java +++ b/src/com/google/javascript/jscomp/TranspilationPasses.java @@ -30,6 +30,10 @@ public class TranspilationPasses { private TranspilationPasses() {} + public static void addEs6ModulePass(List passes) { + passes.add(es6RewriteModule); + } + public static void addEs2017Passes(List passes) { passes.add(rewriteAsyncFunctions); } @@ -73,6 +77,20 @@ public static void addRewritePolyfillPass(List passes) { passes.add(rewritePolyfills); } + /** Rewrites ES6 modules */ + private static final HotSwapPassFactory es6RewriteModule = + new HotSwapPassFactory("es6RewriteModule", true) { + @Override + protected HotSwapCompilerPass create(AbstractCompiler compiler) { + return new Es6RewriteModules(compiler); + } + + @Override + protected FeatureSet featureSet() { + return FeatureSet.ES8_MODULES; + } + }; + private static final PassFactory rewriteAsyncFunctions = new PassFactory("rewriteAsyncFunctions", true) { @Override diff --git a/test/com/google/javascript/jscomp/CompilerTestCase.java b/test/com/google/javascript/jscomp/CompilerTestCase.java index 469c1e62f27..4f8d2fa961b 100644 --- a/test/com/google/javascript/jscomp/CompilerTestCase.java +++ b/test/com/google/javascript/jscomp/CompilerTestCase.java @@ -1712,6 +1712,7 @@ private Compiler multistageSerializeAndDeserialize( private static void transpileToEs5(AbstractCompiler compiler, Node externsRoot, Node codeRoot) { List factories = new ArrayList<>(); + TranspilationPasses.addEs6ModulePass(factories); TranspilationPasses.addEs2017Passes(factories); TranspilationPasses.addEs6EarlyPasses(factories); TranspilationPasses.addEs6LatePasses(factories); diff --git a/test/com/google/javascript/jscomp/Es6RewriteClassTest.java b/test/com/google/javascript/jscomp/Es6RewriteClassTest.java index e1f2aacc613..8379a67707e 100644 --- a/test/com/google/javascript/jscomp/Es6RewriteClassTest.java +++ b/test/com/google/javascript/jscomp/Es6RewriteClassTest.java @@ -22,7 +22,7 @@ import static com.google.javascript.jscomp.Es6RewriteClass.DYNAMIC_EXTENDS_TYPE; import static com.google.javascript.jscomp.Es6ToEs3Converter.CANNOT_CONVERT; import static com.google.javascript.jscomp.Es6ToEs3Converter.CANNOT_CONVERT_YET; -import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES6; +import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES6_MODULES; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.jscomp.parsing.parser.FeatureSet; @@ -84,7 +84,7 @@ protected CompilerPass create(AbstractCompiler compiler) { @Override protected FeatureSet featureSet() { - return ES6; + return ES6_MODULES; } }; } diff --git a/test/com/google/javascript/jscomp/Es6RewriteModulesTest.java b/test/com/google/javascript/jscomp/Es6RewriteModulesTest.java index 4cbf1343b35..af8b4ff870f 100644 --- a/test/com/google/javascript/jscomp/Es6RewriteModulesTest.java +++ b/test/com/google/javascript/jscomp/Es6RewriteModulesTest.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableList; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.jscomp.deps.ModuleLoader; -import com.google.javascript.rhino.Node; /** * Unit tests for {@link Es6RewriteModules} @@ -43,7 +42,7 @@ 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); + options.setWarningLevel(DiagnosticGroups.LINT_CHECKS, CheckLevel.ERROR); if (moduleRoots != null) { options.setModuleRoots(moduleRoots); @@ -54,12 +53,7 @@ protected CompilerOptions getOptions() { @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. - } - }; + return new Es6RewriteModules(compiler); } @Override @@ -72,10 +66,6 @@ void testModules(String input, String expected) { "/** @fileoverview\n * @suppress {missingProvide|missingRequire}\n */" + expected); } - private void testModules(String input, DiagnosticType error) { - ModulesTestUtils.testModules(this, input, error); - } - public void testImport() { testModules( "import name from './other.js'; use(name);", @@ -97,10 +87,8 @@ public void testImport() { } 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);"); + ModulesTestUtils.testModulesWarning(this, "import name from './does_not_exist'; use(name);", + ModuleLoader.LOAD_WARNING); } public void testImportStar() { @@ -523,19 +511,17 @@ public void testGoogRequires_rewrite() { } public void testGoogRequires_nonConst() { - testModules( - "var bar = goog.require('foo.bar'); export var x;", + ModulesTestUtils.testModulesError(this, "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');", + ModulesTestUtils.testModulesError(this, "export var x; var bar = goog.require('foo.bar');", LHS_OF_GOOG_REQUIRE_MUST_BE_CONST); - testModules( + ModulesTestUtils.testModulesError(this, "import * as s from './other.js'; var bar = goog.require('foo.bar');", LHS_OF_GOOG_REQUIRE_MUST_BE_CONST); - testModules( + ModulesTestUtils.testModulesError(this, "var bar = goog.require('foo.bar'); import * as s from './other.js';", LHS_OF_GOOG_REQUIRE_MUST_BE_CONST); } @@ -555,19 +541,15 @@ public void testGoogRequiresDestructuring_rewrite() { "} = some.name.space;", "use(foo$$module$testcode, bar$$module$testcode);")); - testModules( - LINE_JOINER.join( + ModulesTestUtils.testModulesError(this, LINE_JOINER.join( "import * as s from './other.js';", "var {foo, bar} = goog.require('some.name.space');", - "use(foo, bar);"), - LHS_OF_GOOG_REQUIRE_MUST_BE_CONST); + "use(foo, bar);"), LHS_OF_GOOG_REQUIRE_MUST_BE_CONST); - testModules( - LINE_JOINER.join( + ModulesTestUtils.testModulesError(this, LINE_JOINER.join( "import * as s from './other.js';", "let {foo, bar} = goog.require('some.name.space');", - "use(foo, bar);"), - LHS_OF_GOOG_REQUIRE_MUST_BE_CONST); + "use(foo, bar);"), LHS_OF_GOOG_REQUIRE_MUST_BE_CONST); } public void testNamespaceImports() { @@ -595,7 +577,7 @@ public void testNamespaceImports() { "/** @type {other.Foo} */", "var foo$$module$testcode = new other.Foo();")); - testModules("import * as Foo from 'goog:other.Foo';", + ModulesTestUtils.testModulesError(this, "import * as Foo from 'goog:other.Foo';", Es6RewriteModules.NAMESPACE_IMPORT_CANNOT_USE_STAR); } @@ -626,13 +608,8 @@ public void testImportWithoutReferences() { } public void testUselessUseStrict() { - setExpectParseWarningsThisTest(); - testModules(LINE_JOINER.join( - "'use strict';", - "export default undefined;"), - LINE_JOINER.join( - "'use strict';", - "export default undefined;")); + ModulesTestUtils.testModulesError(this, "'use strict'; \n export default undefined;", + ClosureRewriteModule.USELESS_USE_STRICT_DIRECTIVE); } public void testUseStrict_noWarning() { diff --git a/test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java b/test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java index 405a646084b..9064ac3d2e2 100644 --- a/test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java +++ b/test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java @@ -23,8 +23,7 @@ import static com.google.javascript.jscomp.Es6ToEs3Converter.CANNOT_CONVERT; import static com.google.javascript.jscomp.Es6ToEs3Converter.CANNOT_CONVERT_YET; import static com.google.javascript.jscomp.TypeCheck.INSTANTIATE_ABSTRACT_CLASS; - -import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES7; +import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES7_MODULES; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.jscomp.parsing.parser.FeatureSet; @@ -137,7 +136,7 @@ protected CompilerPass create(AbstractCompiler compiler) { @Override protected FeatureSet featureSet() { - return ES7; + return ES7_MODULES; } }; } diff --git a/test/com/google/javascript/jscomp/LazyParsedDependencyInfoTest.java b/test/com/google/javascript/jscomp/LazyParsedDependencyInfoTest.java index b4826ed2f60..1387a40846c 100644 --- a/test/com/google/javascript/jscomp/LazyParsedDependencyInfoTest.java +++ b/test/com/google/javascript/jscomp/LazyParsedDependencyInfoTest.java @@ -122,7 +122,9 @@ public void testParseIsLazy() { public void testModuleConflict() { Compiler compiler = new Compiler(); - compiler.initOptions(new CompilerOptions()); + CompilerOptions options = new CompilerOptions(); + options.setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT_2015); + compiler.initOptions(options); JsAst ast = new JsAst(SourceFile.fromCode("file.js", "export let foo = 42;")); SimpleDependencyInfo delegate = new SimpleDependencyInfo("", "my/js.js", EMPTY, EMPTY, ImmutableMap.of("module", "goog")); diff --git a/test/com/google/javascript/jscomp/ModulesTestUtils.java b/test/com/google/javascript/jscomp/ModulesTestUtils.java index 01058e35dd0..b70e191e8d8 100644 --- a/test/com/google/javascript/jscomp/ModulesTestUtils.java +++ b/test/com/google/javascript/jscomp/ModulesTestUtils.java @@ -49,10 +49,17 @@ static void testModules( test.test(inputs, expecteds); } - static void testModules(CompilerTestCase test, String input, DiagnosticType error) { + static void testModulesError(CompilerTestCase test, String input, DiagnosticType error) { String fileName = test.getFilename() + ".js"; ImmutableList inputs = ImmutableList.of(SourceFile.fromCode("other.js", ""), SourceFile.fromCode(fileName, input)); test.testError(inputs, error); } + + static void testModulesWarning(CompilerTestCase test, String input, DiagnosticType warning) { + String fileName = test.getFilename() + ".js"; + ImmutableList inputs = + ImmutableList.of(SourceFile.fromCode("other.js", ""), SourceFile.fromCode(fileName, input)); + test.testWarning(inputs, warning); + } } diff --git a/test/com/google/javascript/jscomp/ProcessCommonJSModulesTest.java b/test/com/google/javascript/jscomp/ProcessCommonJSModulesTest.java index 5d77663f1a1..b63e9784a2d 100644 --- a/test/com/google/javascript/jscomp/ProcessCommonJSModulesTest.java +++ b/test/com/google/javascript/jscomp/ProcessCommonJSModulesTest.java @@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableList; import com.google.javascript.jscomp.deps.ModuleLoader; -import com.google.javascript.rhino.Node; /** * Unit tests for {@link ProcessCommonJSModules} @@ -44,12 +43,9 @@ protected CompilerOptions getOptions() { @Override protected CompilerPass getProcessor(Compiler compiler) { - return new CompilerPass() { - @Override - public void process(Node externs, Node root) { - // No-op, CommonJS module handling is done directly after parsing. - } - }; + // CommonJS module handling is done directly after parsing, so not included here. + // It also depends on es6 module rewriting, however, so that must be explicitly included. + return new Es6RewriteModules(compiler); } @Override diff --git a/test/com/google/javascript/jscomp/TypeCheckTest.java b/test/com/google/javascript/jscomp/TypeCheckTest.java index 6508f190a26..9036799e876 100644 --- a/test/com/google/javascript/jscomp/TypeCheckTest.java +++ b/test/com/google/javascript/jscomp/TypeCheckTest.java @@ -17908,8 +17908,8 @@ private TypeCheckResult parseAndTypeCheckWithScope(String externs, String js) { 0, compiler.getErrorCount()); if (compiler.getOptions().needsTranspilationFrom(FeatureSet.ES6)) { - compiler.processEs6Modules(); List passes = new ArrayList<>(); + TranspilationPasses.addEs6ModulePass(passes); TranspilationPasses.addEs2017Passes(passes); TranspilationPasses.addEs6EarlyPasses(passes); TranspilationPasses.addEs6LatePasses(passes);