Skip to content

Commit

Permalink
Update the ASTValidator to expect MODULE_BODY statements anywhere in …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
rishipal authored and copybara-github committed Aug 10, 2023
1 parent 4201382 commit 7ee0b5e
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/com/google/javascript/jscomp/AstValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(){};";
Expand Down

0 comments on commit 7ee0b5e

Please sign in to comment.