Skip to content

sourceType: "unambiguous" handles top-level await forms inconsistently#18198

Description

@magic-akari

馃捇

  • Would you like to work on a fix?

How are you using Babel?

Programmatic API (babel.transform, babel.parse)

Input code

import { parse } from "@babel/parser";

const cases = {
  "await value": "await value;",
  "await + 0": "await + 0;",
  "for await": "for await (const value of values) {}",
  "await using": "await using resource = acquire();",
};

for (const [name, code] of Object.entries(cases)) {
  const ast = parse(code, {
    sourceType: "unambiguous",
    plugins: ["explicitResourceManagement"],
  });

  console.log(`${name}: ${ast.program.sourceType}`);
}

Configuration file name

babel.config.json

Configuration

{
  "sourceType": "unambiguous",
  "plugins": ["explicitResourceManagement"]
}

Current and expected behavior

The current results are:

Input program.sourceType
await value; module
await + 0; script
for await (const value of values) {} script
await using resource = acquire(); script

There are two different kinds of inconsistency here.

First, different forms of top-level await are treated differently as evidence that a file is a module:

  • An unambiguous top-level await expression, such as await value, produces a module.
  • An expression that also has a valid Script interpretation, such as await + 0, produces a script and a different AST shape.
  • for await (...) and await using ... produce a script.

Second, the AST can be labeled as a script even though it contains syntax that Babel rejects when sourceType: "script" is specified explicitly:

parse("for await (const value of values) {}", {
  sourceType: "script",
}); // throws

parse("await using resource = acquire();", {
  sourceType: "script",
  plugins: ["explicitResourceManagement"],
}); // throws

This means that the result of parsing with sourceType: "unambiguous" is not always reproducible by parsing the same source with the resolved program.sourceType. Downstream tools that preserve and later reuse the resolved source type can therefore fail on source that Babel initially accepted.

I would expect sourceType: "unambiguous" to follow one consistent policy. Either of the following would be understandable:

Option 1: Do not use top-level await for source type detection

Only explicit import/export-family syntax should cause an unambiguous source to resolve to a module. Top-level await would not be treated as module evidence, regardless of whether it appears as await value, await + 0, for await (...), or await using ....

This is the conservative policy: source type detection remains based on explicit module syntax, while the acceptance and AST representation of TLA-only input can be specified separately.

Option 2: Treat every top-level await form as module evidence

If top-level await participates in source type detection, all of its syntactic forms should participate. In particular, top-level for await (...) and await using ... should mark the program as a module just like await value does.

The Script-valid interpretation of expressions such as await + 0 is a real grammar ambiguity. Babel can continue preferring the Script interpretation, or it can prefer the module parse, but that precedence should be explicitly documented and kept separate from the currently missing handling for for await and await using.

The important invariant is that the policy should not classify one TLA form as module evidence while silently returning other module-only TLA forms in an AST labeled as script.

Environment

  • @babel/parser: 8.0.4
  • Node.js: 24.16.0
  • OS: macOS 26.5.1
  • API: @babel/parser.parse

Possible solution

Choose and document one of the two policies above:

  1. Do not use any form of top-level await to determine the resolved source type; or
  2. Treat top-level for await and await using as module evidence, consistently with an unambiguous top-level await expression.

Whichever policy is selected, it would be useful to add a single source-type test matrix covering:

await value;
await + 0;
for await (const value of values) {}
await using resource = acquire();

Additional context

This report is about source type classification rather than whether these constructs are valid top-level-await syntax. The surprising behavior is the inconsistent public program.sourceType result in unambiguous mode.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions