馃捇
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:
- Do not use any form of top-level await to determine the resolved source type; or
- 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.
馃捇
How are you using Babel?
Programmatic API (
babel.transform,babel.parse)Input code
Configuration file name
babel.config.json
Configuration
{ "sourceType": "unambiguous", "plugins": ["explicitResourceManagement"] }Current and expected behavior
The current results are:
program.sourceTypeawait value;moduleawait + 0;scriptfor await (const value of values) {}scriptawait using resource = acquire();scriptThere 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:
await value, produces a module.await + 0, produces a script and a different AST shape.for await (...)andawait 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:This means that the result of parsing with
sourceType: "unambiguous"is not always reproducible by parsing the same source with the resolvedprogram.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 (...), orawait 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 (...)andawait using ...should mark the program as a module just likeawait valuedoes.The Script-valid interpretation of expressions such as
await + 0is 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 forfor awaitandawait 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@babel/parser.parsePossible solution
Choose and document one of the two policies above:
for awaitandawait usingas 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:
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.sourceTyperesult inunambiguousmode.