diff --git a/src/com/google/javascript/jscomp/AstValidator.java b/src/com/google/javascript/jscomp/AstValidator.java index 442af2af3c1..364e5c46fd5 100644 --- a/src/com/google/javascript/jscomp/AstValidator.java +++ b/src/com/google/javascript/jscomp/AstValidator.java @@ -291,6 +291,21 @@ public void validateStatement(Node n, boolean isAmbient) { case NAMESPACE: validateNamespace(n, isAmbient); return; + case MODULE_BODY: + // Uncommon case where a module body is not the first child of a script. This may happen in + // a specific circumstance where the {@code LateEs6ToEs3Rewriter} pass injects code above a + // module body. Valid only when skipNonTranspilationPasses=true and + // setWrapGoogModulesForWhitespaceOnly=false + // TODO: b/294420383 Ideally the LateEs6ToEs3Rewriter pass should not inject code above the + // module body node + if (compiler.getOptions().skipNonTranspilationPasses) { + if (!compiler.getOptions().wrapGoogModulesForWhitespaceOnly) { + validateModuleContents(n); + return; + } + } + violation("Expected statement but was " + n.getToken() + ".", n); + return; default: violation("Expected statement but was " + n.getToken() + ".", n); } diff --git a/test/com/google/javascript/jscomp/integration/TranspileOnlyIntegrationTest.java b/test/com/google/javascript/jscomp/integration/TranspileOnlyIntegrationTest.java index 52fd69f9518..d1a42925d16 100644 --- a/test/com/google/javascript/jscomp/integration/TranspileOnlyIntegrationTest.java +++ b/test/com/google/javascript/jscomp/integration/TranspileOnlyIntegrationTest.java @@ -15,8 +15,13 @@ */ package com.google.javascript.jscomp.integration; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.truth.Truth.assertThat; import static com.google.javascript.jscomp.base.JSCompStrings.lines; +import static org.junit.Assert.assertThrows; +import com.google.javascript.jscomp.AstValidator; +import com.google.javascript.jscomp.Compiler; import com.google.javascript.jscomp.CompilerOptions; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import org.jspecify.nullness.Nullable; @@ -43,6 +48,55 @@ public void init() { options.setEmitUseStrict(false); } + /** + * Tests an uncommon case where a module body is not the first child of a script. This may happen + * in a specific circumstance where the {@code LateEs6ToEs3Rewriter} pass injects code above a + * module body. Valid only when skipNonTranspilationPasses=true and + * setWrapGoogModulesForWhitespaceOnly=false + */ + @Test + public void testASTValidator_transpileOnly_withModuleNotFirstChildOfScript() { + // to ensure tagged template literals transpiled + this.options.setLanguageOut(LanguageMode.ECMASCRIPT3); + // to preserve modules during transpilation + this.options.setWrapGoogModulesForWhitespaceOnly(false); + this.options.setSkipNonTranspilationPasses(true); + + String source = + lines( + "goog.module('x');", // + "function tag(x) {", + " console.log(x);", + "}", + " tag``"); + + String expected = + "var $jscomp$templatelit$98447280$0=$jscomp.createTemplateTagFirstArg([\"\"]);" + + // + "goog.module(\"x\");" + + "function tag(x){" + + "console.log(x)" + + "}" + + "tag($jscomp$templatelit$98447280$0)"; + + Compiler compiler = compile(options, source); + + // Verify that there are no compiler errors + assertThat(compiler.getErrors()).isEmpty(); + assertThat(compiler.getWarnings()).isEmpty(); + assertThat(compiler.toSource(compiler.getRoot().getLastChild())).isEqualTo(expected); + // Create an astValidator and validate the script + AstValidator astValidator = new AstValidator(compiler); + checkState(compiler.getRoot().getLastChild().getOnlyChild().isScript()); + astValidator.validateScript(compiler.getRoot().getLastChild().getOnlyChild()); + + // In regular (non transpile-only) compilation this is reported + compiler.getOptions().setSkipNonTranspilationPasses(false); + assertThrows( + IllegalStateException.class, + () -> astValidator.validateScript(compiler.getRoot().getLastChild().getOnlyChild())); + } + @Test public void esModuleNoTranspilationForSameLanguageLevel() { String js = "export default function fn(){};";