From 12c9f6beb9002cdbb985564d2b54a50bad9aa5c2 Mon Sep 17 00:00:00 2001 From: johnplaisted Date: Tue, 27 Nov 2018 16:18:34 -0800 Subject: [PATCH] Use ModuleMetadata in the AbstractModuleCallback. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=223077471 --- .../javascript/jscomp/ClosureCheckModule.java | 85 +++++----- .../javascript/jscomp/CompilerOptions.java | 4 + .../javascript/jscomp/DefaultPassConfig.java | 14 +- .../jscomp/GatherModuleMetadata.java | 28 +++- .../javascript/jscomp/LintPassConfig.java | 20 ++- .../javascript/jscomp/NodeTraversal.java | 103 +++++++++--- .../google/javascript/jscomp/NodeUtil.java | 2 +- .../jscomp/ClosureCheckModuleTest.java | 29 +--- ...GatherModuleMetadataReplaceScriptTest.java | 156 ++++++++++++++++++ 9 files changed, 342 insertions(+), 99 deletions(-) create mode 100644 test/com/google/javascript/jscomp/GatherModuleMetadataReplaceScriptTest.java diff --git a/src/com/google/javascript/jscomp/ClosureCheckModule.java b/src/com/google/javascript/jscomp/ClosureCheckModule.java index 30f7f78c4a5..7bbf50e3e55 100644 --- a/src/com/google/javascript/jscomp/ClosureCheckModule.java +++ b/src/com/google/javascript/jscomp/ClosureCheckModule.java @@ -23,6 +23,8 @@ import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_DESTRUCTURING_FORWARD_DECLARE; import static com.google.javascript.jscomp.ClosurePrimitiveErrors.MODULE_USES_GOOG_MODULE_GET; +import com.google.common.collect.Iterables; +import com.google.javascript.jscomp.ModuleMetadataMap.ModuleMetadata; import com.google.javascript.jscomp.NodeTraversal.AbstractModuleCallback; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; @@ -31,6 +33,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; /** * Checks that goog.module() is used correctly. @@ -79,18 +82,13 @@ public final class ClosureCheckModule extends AbstractModuleCallback static final DiagnosticType LET_GOOG_REQUIRE = DiagnosticType.disabled( "JSC_LET_GOOG_REQUIRE", - "Module imports must be constant. Please use 'const' instead of 'let'."); + "Module imports must be constant. Please use ''const'' instead of ''let''."); static final DiagnosticType MULTIPLE_MODULES_IN_FILE = DiagnosticType.error( "JSC_MULTIPLE_MODULES_IN_FILE", "There should only be a single goog.module() statement per file."); - static final DiagnosticType MODULE_AND_PROVIDES = - DiagnosticType.error( - "JSC_MODULE_AND_PROVIDES", - "A file using goog.module() may not also use goog.provide() statements."); - static final DiagnosticType ONE_REQUIRE_PER_DECLARATION = DiagnosticType.error( "JSC_ONE_REQUIRE_PER_DECLARATION", @@ -149,8 +147,6 @@ public final class ClosureCheckModule extends AbstractModuleCallback DiagnosticType.error( "JSC_REQUIRE_NOT_AT_TOP_LEVEL", "goog.require() must be called at file scope."); - private final AbstractCompiler compiler; - private static class ModuleInfo { // Name of the module in question (i.e. the argument to goog.module) private final String name; @@ -168,10 +164,10 @@ private static class ModuleInfo { } } - private ModuleInfo currentModule = null; + private ModuleInfo currentModuleInfo = null; - public ClosureCheckModule(AbstractCompiler compiler) { - this.compiler = compiler; + public ClosureCheckModule(AbstractCompiler compiler, ModuleMetadataMap moduleMetadataMap) { + super(compiler, moduleMetadataMap); } @Override @@ -185,31 +181,29 @@ public void hotSwapScript(Node scriptRoot, Node originalRoot) { } @Override - public void enterModule(NodeTraversal t, Node scopeRoot) { - Node firstStatement = scopeRoot.getFirstChild(); - if (NodeUtil.isExprCall(firstStatement)) { - Node call = firstStatement.getFirstChild(); - Node callee = call.getFirstChild(); - if (callee.matchesQualifiedName("goog.module")) { - checkState(currentModule == null); - String moduleName = extractFirstArgumentName(call); - if (moduleName == null) { - t.report(scopeRoot, ClosureRewriteModule.INVALID_MODULE_NAMESPACE); - } else { - currentModule = new ModuleInfo(moduleName); - } - } + public void enterModule(ModuleMetadata currentModule, Node moduleScopeRoot) { + if (!currentModule.isGoogModule()) { + return; } + + checkState(currentModuleInfo == null); + checkState(!currentModule.googNamespaces().isEmpty()); + currentModuleInfo = new ModuleInfo(Iterables.getFirst(currentModule.googNamespaces(), "")); } @Override - public void exitModule(NodeTraversal t, Node scopeRoot) { - currentModule = null; + public void exitModule(@Nullable ModuleMetadata currentModule, @Nullable Node moduleScopeRoot) { + currentModuleInfo = null; } @Override - public void visit(NodeTraversal t, Node n, Node parent) { - if (currentModule == null) { + protected void visit( + NodeTraversal t, + Node n, + @Nullable ModuleMetadata currentModule, + @Nullable Node moduleScopeRoot) { + Node parent = n.getParent(); + if (currentModuleInfo == null) { if (NodeUtil.isCallTo(n, "goog.module")) { t.report(n, GOOG_MODULE_IN_NON_MODULE); } else if (NodeUtil.isGoogModuleDeclareLegacyNamespaceCall(n)) { @@ -225,10 +219,8 @@ public void visit(NodeTraversal t, Node n, Node parent) { case CALL: Node callee = n.getFirstChild(); if (callee.matchesQualifiedName("goog.module") - && !currentModule.name.equals(extractFirstArgumentName(n))) { + && !currentModuleInfo.name.equals(extractFirstArgumentName(n))) { t.report(n, MULTIPLE_MODULES_IN_FILE); - } else if (callee.matchesQualifiedName("goog.provide")) { - t.report(n, MODULE_AND_PROVIDES); } else if (callee.matchesQualifiedName("goog.require") || callee.matchesQualifiedName("goog.forwardDeclare")) { checkRequireCall(t, n, parent); @@ -272,10 +264,10 @@ public void visit(NodeTraversal t, Node n, Node parent) { } break; case GETPROP: - if (n.matchesQualifiedName(currentModule.name)) { + if (n.matchesQualifiedName(currentModuleInfo.name)) { t.report(n, REFERENCE_TO_MODULE_GLOBAL_NAME); - } else if (currentModule.importsByLongRequiredName.containsKey(n.getQualifiedName())) { - Node importLhs = currentModule.importsByLongRequiredName.get(n.getQualifiedName()); + } else if (currentModuleInfo.importsByLongRequiredName.containsKey(n.getQualifiedName())) { + Node importLhs = currentModuleInfo.importsByLongRequiredName.get(n.getQualifiedName()); if (importLhs == null) { t.report(n, REFERENCE_TO_FULLY_QUALIFIED_IMPORT_NAME, n.getQualifiedName()); } else if (importLhs.isName()) { @@ -344,8 +336,8 @@ public void visit(Node node) { } String type = node.getString(); while (true) { - if (currentModule.importsByLongRequiredName.containsKey(type)) { - Node importLhs = currentModule.importsByLongRequiredName.get(type); + if (currentModuleInfo.importsByLongRequiredName.containsKey(type)) { + Node importLhs = currentModuleInfo.importsByLongRequiredName.get(type); if (importLhs == null || !importLhs.isName()) { t.report(node, JSDOC_REFERENCE_TO_FULLY_QUALIFIED_IMPORT_NAME, type); } else if (!importLhs.getString().equals(type)) { @@ -401,18 +393,18 @@ private void checkModuleExport(NodeTraversal t, Node n, Node parent) { Node lhs = n.getFirstChild(); checkState(isExportLhs(lhs)); // Check multiple exports of the same name - Node previousDefinition = currentModule.exportNodesByName.get(lhs.getQualifiedName()); + Node previousDefinition = currentModuleInfo.exportNodesByName.get(lhs.getQualifiedName()); if (previousDefinition != null && !isPermittedTypeScriptMultipleExportPattern(previousDefinition, lhs)) { int previousLine = previousDefinition.getLineno(); t.report(n, EXPORT_REPEATED_ERROR, String.valueOf(previousLine)); } // Check exports in invalid program position - Node defaultExportNode = currentModule.exportNodesByName.get("exports"); + Node defaultExportNode = currentModuleInfo.exportNodesByName.get("exports"); // If we have never seen an `exports =` default export assignment, or this is the // default export, then treat this assignment as an export and do the checks it is well formed. if (defaultExportNode == null || lhs.matchesQualifiedName("exports")) { - currentModule.exportNodesByName.put(lhs.getQualifiedName(), lhs); + currentModuleInfo.exportNodesByName.put(lhs.getQualifiedName(), lhs); if (!t.inModuleScope()) { t.report(n, EXPORT_NOT_AT_MODULE_SCOPE); } else if (!parent.isExprResult()) { @@ -439,15 +431,12 @@ private String extractFirstArgumentName(Node callNode) { private void checkRequireCall(NodeTraversal t, Node callNode, Node parent) { checkState(callNode.isCall()); - if (!callNode.getLastChild().isString()) { - t.report(callNode, ProcessClosurePrimitives.INVALID_ARGUMENT_ERROR, "goog.require"); - return; - } + checkState(callNode.getLastChild().isString()); switch (parent.getToken()) { case EXPR_RESULT: String key = extractFirstArgumentName(callNode); - if (!currentModule.importsByLongRequiredName.containsKey(key)) { - currentModule.importsByLongRequiredName.put(key, parent); + if (!currentModuleInfo.importsByLongRequiredName.containsKey(key)) { + currentModuleInfo.importsByLongRequiredName.put(key, parent); } return; case NAME: @@ -481,10 +470,10 @@ private void checkShortGoogRequireCall(NodeTraversal t, Node callNode, Node decl checkState(lhs.isName()); checkShortName(t, lhs, callNode.getLastChild().getString()); } - currentModule.importsByLongRequiredName.put(extractFirstArgumentName(callNode), lhs); + currentModuleInfo.importsByLongRequiredName.put(extractFirstArgumentName(callNode), lhs); for (Node nameNode : NodeUtil.findLhsNodesInNode(declaration)) { String name = nameNode.getString(); - if (!currentModule.shortImportNames.add(name)) { + if (!currentModuleInfo.shortImportNames.add(name)) { t.report(nameNode, DUPLICATE_NAME_SHORT_REQUIRE, name); } } diff --git a/src/com/google/javascript/jscomp/CompilerOptions.java b/src/com/google/javascript/jscomp/CompilerOptions.java index b0dac898422..f0a484f7f67 100644 --- a/src/com/google/javascript/jscomp/CompilerOptions.java +++ b/src/com/google/javascript/jscomp/CompilerOptions.java @@ -2721,6 +2721,10 @@ public void setProcessCommonJSModules(boolean processCommonJSModules) { this.processCommonJSModules = processCommonJSModules; } + public boolean getProcessCommonJSModules() { + return processCommonJSModules; + } + /** * How ES6 modules should be transformed. */ diff --git a/src/com/google/javascript/jscomp/DefaultPassConfig.java b/src/com/google/javascript/jscomp/DefaultPassConfig.java index 9c62ed2a927..1a4417d7955 100644 --- a/src/com/google/javascript/jscomp/DefaultPassConfig.java +++ b/src/com/google/javascript/jscomp/DefaultPassConfig.java @@ -1091,6 +1091,12 @@ private void assertValidOrderForChecks(List checks) { checkVariableReferences, closureGoogScopeAliases, "Variable checking must happen before goog.scope processing."); + + assertPassOrder( + checks, + gatherModuleMetadataPass, + closureCheckModule, + "Need to gather module metadata before checking closure modules."); } /** @@ -1472,7 +1478,7 @@ protected FeatureSet featureSet() { new HotSwapPassFactory("closureCheckModule") { @Override protected HotSwapCompilerPass create(AbstractCompiler compiler) { - return new ClosureCheckModule(compiler); + return new ClosureCheckModule(compiler, compiler.getModuleMetadataMap()); } @Override @@ -3432,10 +3438,10 @@ protected FeatureSet featureSet() { } }; - private final PassFactory gatherModuleMetadataPass = - new PassFactory(PassNames.GATHER_MODULE_METADATA, /* isOneTimePass= */ true) { + private final HotSwapPassFactory gatherModuleMetadataPass = + new HotSwapPassFactory(PassNames.GATHER_MODULE_METADATA) { @Override - protected CompilerPass create(AbstractCompiler compiler) { + protected HotSwapCompilerPass create(AbstractCompiler compiler) { return new GatherModuleMetadata( compiler, options.processCommonJSModules, options.moduleResolutionMode); } diff --git a/src/com/google/javascript/jscomp/GatherModuleMetadata.java b/src/com/google/javascript/jscomp/GatherModuleMetadata.java index b35f46a62a8..df9b7ae35a5 100644 --- a/src/com/google/javascript/jscomp/GatherModuleMetadata.java +++ b/src/com/google/javascript/jscomp/GatherModuleMetadata.java @@ -37,7 +37,7 @@ * Gathers metadata around modules that is useful for checking imports / requires and creates a * {@link ModuleMetadataMap}. */ -public final class GatherModuleMetadata implements CompilerPass { +public final class GatherModuleMetadata implements HotSwapCompilerPass { static final DiagnosticType MIXED_MODULE_TYPE = DiagnosticType.error("JSC_MIXED_MODULE_TYPE", "A file cannot be both {0} and {1}."); @@ -420,4 +420,30 @@ public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, new Finder()); compiler.setModuleMetadataMap(new ModuleMetadataMap(modulesByPath, modulesByGoogNamespace)); } + + @Override + public void hotSwapScript(Node scriptRoot, Node originalRoot) { + // This pass is run as either a hot swap or full pass. So if we're running in hot swap this is + // a different instance from the full pass, and we need to populate these again. + modulesByPath.putAll(compiler.getModuleMetadataMap().getModulesByPath()); + modulesByGoogNamespace.putAll(compiler.getModuleMetadataMap().getModulesByGoogNamespace()); + + ModuleMetadata oldMetadata = + modulesByPath.remove(compiler.getInput(originalRoot.getInputId()).getPath().toString()); + + if (oldMetadata != null) { + for (String namespace : oldMetadata.googNamespaces()) { + modulesByGoogNamespace.remove(namespace); + } + + for (ModuleMetadata nestedMetadata : oldMetadata.nestedModules()) { + for (String namespace : nestedMetadata.googNamespaces()) { + modulesByGoogNamespace.remove(namespace); + } + } + } + + NodeTraversal.traverse(compiler, scriptRoot, new Finder()); + compiler.setModuleMetadataMap(new ModuleMetadataMap(modulesByPath, modulesByGoogNamespace)); + } } \ No newline at end of file diff --git a/src/com/google/javascript/jscomp/LintPassConfig.java b/src/com/google/javascript/jscomp/LintPassConfig.java index 1829a919f66..f56c25372a1 100644 --- a/src/com/google/javascript/jscomp/LintPassConfig.java +++ b/src/com/google/javascript/jscomp/LintPassConfig.java @@ -16,6 +16,7 @@ package com.google.javascript.jscomp; import com.google.common.collect.ImmutableList; +import com.google.javascript.jscomp.PassFactory.HotSwapPassFactory; import com.google.javascript.jscomp.lint.CheckDuplicateCase; import com.google.javascript.jscomp.lint.CheckEmptyStatements; import com.google.javascript.jscomp.lint.CheckEnums; @@ -45,6 +46,7 @@ class LintPassConfig extends PassConfig.PassConfigDelegate { @Override protected List getChecks() { return ImmutableList.of( + gatherModuleMetadataPass, earlyLintChecks, checkRequires, variableReferenceCheck, @@ -57,6 +59,22 @@ protected List getOptimizations() { return ImmutableList.of(); } + private final HotSwapPassFactory gatherModuleMetadataPass = + new HotSwapPassFactory(PassNames.GATHER_MODULE_METADATA) { + @Override + protected HotSwapCompilerPass create(AbstractCompiler compiler) { + return new GatherModuleMetadata( + compiler, + compiler.getOptions().getProcessCommonJSModules(), + compiler.getOptions().getModuleResolutionMode()); + } + + @Override + protected FeatureSet featureSet() { + return FeatureSet.latest().withoutTypes(); + } + }; + private final PassFactory earlyLintChecks = new PassFactory("earlyLintChecks", true) { @Override @@ -72,7 +90,7 @@ protected CompilerPass create(AbstractCompiler compiler) { new CheckMissingSemicolon(compiler), new CheckSuper(compiler), new CheckPrimitiveAsObject(compiler), - new ClosureCheckModule(compiler), + new ClosureCheckModule(compiler, compiler.getModuleMetadataMap()), new CheckNullabilityModifiers(compiler), new CheckRequiresAndProvidesSorted(compiler), new CheckSideEffects( diff --git a/src/com/google/javascript/jscomp/NodeTraversal.java b/src/com/google/javascript/jscomp/NodeTraversal.java index 66226151c2b..c5d543bd20e 100644 --- a/src/com/google/javascript/jscomp/NodeTraversal.java +++ b/src/com/google/javascript/jscomp/NodeTraversal.java @@ -21,6 +21,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.nullToEmpty; +import com.google.javascript.jscomp.ModuleMetadataMap.ModuleMetadata; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; @@ -211,41 +212,97 @@ public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node pa } /** - * Abstract callback that knows when goog.modules (and in the future ES6 modules) are entered - * and exited. This includes both whole file modules and bundled modules. + * Abstract callback that knows when goog.provide, goog.module, and ES modules are entered and + * exited. This includes both whole file modules and bundled modules. */ - public abstract static class AbstractModuleCallback implements ScopedCallback { + public abstract static class AbstractModuleCallback implements Callback { + protected final AbstractCompiler compiler; + private final ModuleMetadataMap moduleMetadataMap; - /** - * Called immediately after entering a module. - */ - public abstract void enterModule(NodeTraversal t, Node scopeRoot); - - /** - * Called immediately before exiting a module. - */ - public abstract void exitModule(NodeTraversal t, Node scopeRoot); + private ModuleMetadata currentModule; + private Node scopeRoot; + private boolean inLoadModule; - @Override - public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { - return true; + AbstractModuleCallback(AbstractCompiler compiler, ModuleMetadataMap moduleMetadataMap) { + this.compiler = compiler; + this.moduleMetadataMap = moduleMetadataMap; } + protected void enterModule(ModuleMetadata currentModule, Node moduleScopRoot) {} + + protected void exitModule( + @Nullable ModuleMetadata currentModule, @Nullable Node moduleScopRoot) {} + @Override - public final void enterScope(NodeTraversal t) { - Node scopeRoot = t.getScopeRoot(); - if (NodeUtil.isModuleScopeRoot(scopeRoot)) { - enterModule(t, scopeRoot); + public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { + switch (n.getToken()) { + case SCRIPT: + currentModule = + moduleMetadataMap.getModulesByPath().get(t.getInput().getPath().toString()); + scopeRoot = n.hasChildren() && n.getFirstChild().isModuleBody() ? n.getFirstChild() : n; + enterModule(currentModule, scopeRoot); + break; + case BLOCK: + if (NodeUtil.isBundledGoogModuleScopeRoot(n)) { + scopeRoot = n; + inLoadModule = true; + } + break; + case CALL: + if (inLoadModule && n.getFirstChild().matchesQualifiedName("goog.module")) { + ModuleMetadata newModule = + moduleMetadataMap.getModulesByGoogNamespace().get(n.getLastChild().getString()); + // In the event of multiple goog.module statements (an error), don't call enterModule + // more than once. + if (newModule != currentModule) { + currentModule = newModule; + enterModule(currentModule, scopeRoot); + } + } + break; + default: + break; } + return shouldTraverse(t, n, currentModule, scopeRoot); + } + + protected boolean shouldTraverse( + NodeTraversal t, + Node n, + @Nullable ModuleMetadata currentModule, + @Nullable Node moduleScopeRoot) { + return true; } @Override - public final void exitScope(NodeTraversal t) { - Node scopeRoot = t.getScopeRoot(); - if (NodeUtil.isModuleScopeRoot(scopeRoot)) { - exitModule(t, scopeRoot); + public final void visit(NodeTraversal t, Node n, Node parent) { + switch (n.getToken()) { + case SCRIPT: + currentModule = null; + scopeRoot = null; + exitModule(currentModule, scopeRoot); + break; + case BLOCK: + if (NodeUtil.isBundledGoogModuleScopeRoot(n)) { + scopeRoot = n.getGrandparent().getGrandparent(); + inLoadModule = false; + currentModule = + moduleMetadataMap.getModulesByPath().get(t.getInput().getPath().toString()); + exitModule(currentModule, scopeRoot); + } + break; + default: + break; } + + visit(t, n, currentModule, scopeRoot); } + + protected void visit( + NodeTraversal t, + Node n, + @Nullable ModuleMetadata currentModule, + @Nullable Node moduleScopeRoot) {} } /** diff --git a/src/com/google/javascript/jscomp/NodeUtil.java b/src/com/google/javascript/jscomp/NodeUtil.java index d8e38db7b4a..5cbe4d8a342 100644 --- a/src/com/google/javascript/jscomp/NodeUtil.java +++ b/src/com/google/javascript/jscomp/NodeUtil.java @@ -5642,7 +5642,7 @@ static boolean isModuleScopeRoot(Node n) { return n.isModuleBody() || isBundledGoogModuleScopeRoot(n); } - private static boolean isBundledGoogModuleScopeRoot(Node n) { + static boolean isBundledGoogModuleScopeRoot(Node n) { if (!n.isBlock() || !n.hasChildren() || !isGoogModuleCall(n.getFirstChild())) { return false; } diff --git a/test/com/google/javascript/jscomp/ClosureCheckModuleTest.java b/test/com/google/javascript/jscomp/ClosureCheckModuleTest.java index 9c5d9cdfa39..a1b4358c315 100644 --- a/test/com/google/javascript/jscomp/ClosureCheckModuleTest.java +++ b/test/com/google/javascript/jscomp/ClosureCheckModuleTest.java @@ -26,7 +26,6 @@ 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; @@ -37,6 +36,7 @@ import static com.google.javascript.jscomp.ClosurePrimitiveErrors.MODULE_USES_GOOG_MODULE_GET; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; +import com.google.javascript.jscomp.deps.ModuleLoader.ResolutionMode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,7 +46,13 @@ public final class ClosureCheckModuleTest extends CompilerTestCase { @Override protected CompilerPass getProcessor(Compiler compiler) { - return new ClosureCheckModule(compiler); + return (externs, root) -> { + GatherModuleMetadata gatherModuleMetadata = + new GatherModuleMetadata( + compiler, /* processCommonJsModules= */ false, ResolutionMode.BROWSER); + gatherModuleMetadata.process(externs, root); + new ClosureCheckModule(compiler, compiler.getModuleMetadataMap()).process(externs, root); + }; } @Override @@ -133,11 +139,6 @@ public void testGoogModuleGetAtTopLevel() { "}")); } - @Test - public void testGoogModuleAndProvide() { - testError("goog.module('xyz');\ngoog.provide('abc');", MODULE_AND_PROVIDES); - } - @Test public void testMultipleGoogModules() { testError( @@ -427,20 +428,6 @@ public void testIllegalGoogRequires() { "var {foo, bar} = goog.require('abc');", "var foo = goog.require('def.foo');"), DUPLICATE_NAME_SHORT_REQUIRE); - - testError( - lines( - "goog.module('xyz');", - "", - "const localName = goog.require(namespace.without.the.quotes);"), - ProcessClosurePrimitives.INVALID_ARGUMENT_ERROR); - - testError( - lines( - "goog.module('xyz');", - "", - "goog.require(namespace.without.the.quotes);"), - ProcessClosurePrimitives.INVALID_ARGUMENT_ERROR); } @Test diff --git a/test/com/google/javascript/jscomp/GatherModuleMetadataReplaceScriptTest.java b/test/com/google/javascript/jscomp/GatherModuleMetadataReplaceScriptTest.java new file mode 100644 index 00000000000..61de46facb9 --- /dev/null +++ b/test/com/google/javascript/jscomp/GatherModuleMetadataReplaceScriptTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2018 The Closure Compiler Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.javascript.jscomp; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.javascript.jscomp.CompilerTestCase.lines; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests the hotswap functionality of {@link GatherModuleMetadata}. */ + +@RunWith(JUnit4.class) +public class GatherModuleMetadataReplaceScriptTest extends BaseReplaceScriptTestCase { + @Test + public void testAddScript() { + CompilerOptions options = getOptions(); + + String src = "goog.provide('Bar');"; + String newSrc = "goog.provide('Baz');"; + + Compiler compiler = + runAddScript( + options, + ImmutableList.of(src), + /* expectedCompileErrors= */ 0, + /* expectedCompileWarnings= */ 0, + newSrc, + /* flushResults= */ false); + + assertThat(compiler.getModuleMetadataMap().getModulesByGoogNamespace().keySet()) + .containsExactly("Bar", "Baz"); + } + + @Test + public void testAddNestedModule() { + CompilerOptions options = getOptions(); + + String src = ""; + String newSrc = + lines( + "goog.loadModule(function(exports) {", // + " goog.module('new.module');", + " return exports;", + "});"); + + Compiler compiler = + runAddScript( + options, + ImmutableList.of(src), + /* expectedCompileErrors= */ 0, + /* expectedCompileWarnings= */ 0, + newSrc, + /* flushResults= */ false); + + assertThat(compiler.getModuleMetadataMap().getModulesByGoogNamespace().keySet()) + .containsExactly("new.module"); + } + + @Test + public void testRemoveNestedModule() { + CompilerOptions options = getOptions(); + + String src = + lines( + "goog.loadModule(function(exports) {", // + " goog.module('a.module');", + " return exports;", + "});", + "goog.loadModule(function(exports) {", // + " goog.module('b.module');", + " return exports;", + "});"); + String newSrc = + lines( + "goog.loadModule(function(exports) {", // + " goog.module('a.module');", + " return exports;", + "});"); + + Compiler compiler = + runReplaceScript( + options, + ImmutableList.of(src), + /* expectedCompileErrors= */ 0, + /* expectedCompileWarnings= */ 0, + newSrc, + /* newSourceInd= */ 0, + /* flushResults= */ false); + + assertThat(compiler.getModuleMetadataMap().getModulesByGoogNamespace().keySet()) + .containsExactly("a.module"); + } + + @Test + public void testChangeNamespace() { + CompilerOptions options = getOptions(); + + String src = "goog.provide('Bar');"; + String newSrc = "goog.provide('Baz');"; + + Compiler compiler = + runReplaceScript( + options, + ImmutableList.of(src), + /* expectedCompileErrors= */ 0, + /* expectedCompileWarnings= */ 0, + newSrc, + /* newSourceInd= */ 0, + /* flushResults= */ false); + + assertThat(compiler.getModuleMetadataMap().getModulesByGoogNamespace().keySet()) + .containsExactly("Baz"); + } + + @Test + public void testChangeModuleType() { + CompilerOptions options = getOptions(); + + String src = "goog.provide('Bar');"; + String newSrc = "goog.module('Bar');"; + + Compiler compiler = + runReplaceScript( + options, + ImmutableList.of(src), + /* expectedCompileErrors= */ 0, + /* expectedCompileWarnings= */ 0, + newSrc, + /* newSourceInd= */ 0, + /* flushResults= */ false); + + assertThat( + compiler + .getModuleMetadataMap() + .getModulesByGoogNamespace() + .get("Bar") + .isNonLegacyGoogModule()) + .isTrue(); + } +}