Skip to content

Commit 7ee0b5e

Browse files
rishipalcopybara-github
authored andcommitted
Update the ASTValidator to expect MODULE_BODY statements anywhere in the SCRIPT (not just at the top) and validate the module contents.
From http://b/294420383, we know that modules can exist during the transpiler passes and the compiler inserts some code above the goog.modules statements. However, ASTValidator crashes if it notices that a MODULE_BODY node is not the first child of a SCRIPT. This CL updates the validator to expect MODULE_BODY statement and validates the module contents. PiperOrigin-RevId: 555635449
1 parent 4201382 commit 7ee0b5e

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

src/com/google/javascript/jscomp/AstValidator.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,21 @@ public void validateStatement(Node n, boolean isAmbient) {
291291
case NAMESPACE:
292292
validateNamespace(n, isAmbient);
293293
return;
294+
case MODULE_BODY:
295+
// Uncommon case where a module body is not the first child of a script. This may happen in
296+
// a specific circumstance where the {@code LateEs6ToEs3Rewriter} pass injects code above a
297+
// module body. Valid only when skipNonTranspilationPasses=true and
298+
// setWrapGoogModulesForWhitespaceOnly=false
299+
// TODO: b/294420383 Ideally the LateEs6ToEs3Rewriter pass should not inject code above the
300+
// module body node
301+
if (compiler.getOptions().skipNonTranspilationPasses) {
302+
if (!compiler.getOptions().wrapGoogModulesForWhitespaceOnly) {
303+
validateModuleContents(n);
304+
return;
305+
}
306+
}
307+
violation("Expected statement but was " + n.getToken() + ".", n);
308+
return;
294309
default:
295310
violation("Expected statement but was " + n.getToken() + ".", n);
296311
}

test/com/google/javascript/jscomp/integration/TranspileOnlyIntegrationTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@
1515
*/
1616
package com.google.javascript.jscomp.integration;
1717

18+
import static com.google.common.base.Preconditions.checkState;
19+
import static com.google.common.truth.Truth.assertThat;
1820
import static com.google.javascript.jscomp.base.JSCompStrings.lines;
21+
import static org.junit.Assert.assertThrows;
1922

23+
import com.google.javascript.jscomp.AstValidator;
24+
import com.google.javascript.jscomp.Compiler;
2025
import com.google.javascript.jscomp.CompilerOptions;
2126
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
2227
import org.jspecify.nullness.Nullable;
@@ -43,6 +48,55 @@ public void init() {
4348
options.setEmitUseStrict(false);
4449
}
4550

51+
/**
52+
* Tests an uncommon case where a module body is not the first child of a script. This may happen
53+
* in a specific circumstance where the {@code LateEs6ToEs3Rewriter} pass injects code above a
54+
* module body. Valid only when skipNonTranspilationPasses=true and
55+
* setWrapGoogModulesForWhitespaceOnly=false
56+
*/
57+
@Test
58+
public void testASTValidator_transpileOnly_withModuleNotFirstChildOfScript() {
59+
// to ensure tagged template literals transpiled
60+
this.options.setLanguageOut(LanguageMode.ECMASCRIPT3);
61+
// to preserve modules during transpilation
62+
this.options.setWrapGoogModulesForWhitespaceOnly(false);
63+
this.options.setSkipNonTranspilationPasses(true);
64+
65+
String source =
66+
lines(
67+
"goog.module('x');", //
68+
"function tag(x) {",
69+
" console.log(x);",
70+
"}",
71+
" tag``");
72+
73+
String expected =
74+
"var $jscomp$templatelit$98447280$0=$jscomp.createTemplateTagFirstArg([\"\"]);"
75+
+ //
76+
"goog.module(\"x\");"
77+
+ "function tag(x){"
78+
+ "console.log(x)"
79+
+ "}"
80+
+ "tag($jscomp$templatelit$98447280$0)";
81+
82+
Compiler compiler = compile(options, source);
83+
84+
// Verify that there are no compiler errors
85+
assertThat(compiler.getErrors()).isEmpty();
86+
assertThat(compiler.getWarnings()).isEmpty();
87+
assertThat(compiler.toSource(compiler.getRoot().getLastChild())).isEqualTo(expected);
88+
// Create an astValidator and validate the script
89+
AstValidator astValidator = new AstValidator(compiler);
90+
checkState(compiler.getRoot().getLastChild().getOnlyChild().isScript());
91+
astValidator.validateScript(compiler.getRoot().getLastChild().getOnlyChild());
92+
93+
// In regular (non transpile-only) compilation this is reported
94+
compiler.getOptions().setSkipNonTranspilationPasses(false);
95+
assertThrows(
96+
IllegalStateException.class,
97+
() -> astValidator.validateScript(compiler.getRoot().getLastChild().getOnlyChild()));
98+
}
99+
46100
@Test
47101
public void esModuleNoTranspilationForSameLanguageLevel() {
48102
String js = "export default function fn(){};";

0 commit comments

Comments
 (0)