Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,26 +99,19 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function

× `await` is only allowed within async functions and at the top levels of modules
⚠ eslint-plugin-unicorn(prefer-logical-operator-over-ternary): Prefer using a logical operator over a ternary.
╭─[prefer_logical_operator_over_ternary.tsx:1:1]
1 │ await a ? await a : foo
· ─────
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function

× `await` is only allowed within async functions and at the top levels of modules
╭─[prefer_logical_operator_over_ternary.tsx:1:11]
1 │ await a ? await a : foo
· ─────
· ───────────────────────
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Switch to "||" or "??" operator

× `await` is only allowed within async functions and at the top levels of modules
⚠ eslint-plugin-unicorn(prefer-logical-operator-over-ternary): Prefer using a logical operator over a ternary.
╭─[prefer_logical_operator_over_ternary.tsx:1:1]
1 │ await a ? (await (a)) : (foo)
· ─────
· ─────────────────────────────
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Switch to "||" or "??" operator

× `await` is only allowed within async functions and at the top levels of modules
╭─[prefer_logical_operator_over_ternary.tsx:1:2]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ source: crates/oxc_linter/src/tester.rs
· ──
╰────

× `await` is only allowed within async functions and at the top levels of modules
╭─[prefer_node_protocol.tsx:1:1]
⚠ eslint-plugin-unicorn(prefer-node-protocol): Prefer using the `node:` protocol when importing Node.js builtin modules.
╭─[prefer_node_protocol.tsx:1:14]
1 │ await import('assert/strict')
· ─────
· ───────────────
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Prefer `node:assert/strict` over `assert/strict`.
7 changes: 6 additions & 1 deletion crates/oxc_parser/src/js/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1476,7 +1476,7 @@ impl<'a> ParserImpl<'a> {

/// ``AwaitExpression`[Yield]` :
/// await `UnaryExpression`[?Yield, +Await]
fn parse_await_expression(&mut self, lhs_span: u32) -> Expression<'a> {
pub(crate) fn parse_await_expression(&mut self, lhs_span: u32) -> Expression<'a> {
let span = self.start_span();
if !self.ctx.has_await() {
// For `ModuleKind::Unambiguous`, defer the error until we know whether
Expand Down Expand Up @@ -1542,6 +1542,11 @@ impl<'a> ParserImpl<'a> {
if self.ctx.has_await() {
return true;
}
// In Script mode (not module, not unambiguous), `await` at top level is just an identifier.
// Only check lookahead for Module (top-level await) or Unambiguous mode (detection).
if !self.source_type.is_module() && !self.source_type.is_unambiguous() {
return false;
}
return self.lookahead(|p| {
Self::next_token_is_identifier_or_keyword_or_literal_on_same_line(p, true)
});
Expand Down
74 changes: 74 additions & 0 deletions crates/oxc_parser/src/js/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ impl<'a> ParserImpl<'a> {
Kind::Const => self.parse_const_statement(stmt_ctx),
Kind::Using if self.is_using_declaration() => self.parse_using_statement(stmt_ctx),
Kind::Await if self.is_using_statement() => self.parse_using_statement(stmt_ctx),
Kind::Await
if stmt_ctx.is_top_level()
&& self.source_type.is_unambiguous()
&& self.is_unambiguous_await_statement() =>
{
self.parse_await_statement()
}
Kind::Interface
| Kind::Type
| Kind::Module
Expand Down Expand Up @@ -234,6 +241,73 @@ impl<'a> ParserImpl<'a> {
self.parse_expression_statement(span, expr)
}

/// Check if `await` starts an unambiguous await statement.
///
/// Returns true if:
/// - We're at `await`
/// - Not in await context (not already in async/module)
/// - Next token is NOT ambiguous (not +, -, (, [, /, template, of, using, newline)
///
/// This is used in unambiguous mode to detect that `await expr` at top level
/// definitively makes this file an ES module.
pub(crate) fn is_unambiguous_await_statement(&mut self) -> bool {
debug_assert!(self.at(Kind::Await));
if self.ctx.has_await() {
return false;
}
self.lookahead(Self::peek_is_unambiguous_await_argument)
}

/// Check if the token following `await` is unambiguous (not ambiguous).
///
/// Ambiguous tokens are those that could produce different ASTs depending on whether
/// the file is parsed as a script or module:
/// - Line break: `await\n0` could be two expression statements
/// - `+` / `-`: `await + 0` could be binary expression or await of unary
/// - `(`: `await(0)` could be call expression or await of parenthesized
/// - `[`: `await[0]` could be member expression or await of array
/// - `/`: `await /x/` could be division or await of regex
/// - Template: `await\`\`` could be tagged template or await of template
/// - `of`: `for (await of [])` ambiguity
/// - `using`: `await using` has special declaration semantics
/// - `%`: `await %x(0)` is always modulo in script mode (`await % x(0)`)
/// - `;`: `await;` is identifier statement, not await expression (await needs argument)
fn peek_is_unambiguous_await_argument(&mut self) -> bool {
self.bump_any(); // skip `await`
let token = self.cur_token();
if token.is_on_new_line() {
return false;
}
!matches!(
token.kind(),
Kind::Plus
| Kind::Minus
| Kind::LParen
| Kind::LBrack
| Kind::Slash
| Kind::Percent
| Kind::Semicolon
| Kind::TemplateHead
| Kind::NoSubstitutionTemplate
| Kind::Of
| Kind::Using
)
}

/// Parse a top-level await statement in unambiguous mode.
///
/// This marks the file as ESM (since unambiguous await proves this is a module)
/// and parses the full expression (which starts with await) as an expression statement.
pub(crate) fn parse_await_statement(&mut self) -> Statement<'a> {
let span = self.start_span();
// Mark as ESM - unambiguous await proves this is a module
self.module_record_builder.found_unambiguous_await();
// Enable await context and parse the full expression (not just the await part)
// This handles cases like `await a ? b : c` where the await is part of a larger expression
let expr = self.context_add(Context::Await, Self::parse_expr);
self.parse_expression_statement(span, expr)
}

/// Section 14.2 Block Statement
pub(crate) fn parse_block(&mut self) -> Box<'a, BlockStatement<'a>> {
let span = self.start_span();
Expand Down
8 changes: 8 additions & 0 deletions crates/oxc_parser/src/module_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ impl<'a> ModuleRecordBuilder<'a> {
self.module_record.import_metas.push(span);
}

/// Mark as ESM when an unambiguous top-level await is encountered.
///
/// In unambiguous mode, `await expr` where `expr` is clearly an expression (not ambiguous
/// like `+`, `-`, `(`, `[`, etc.) indicates this is definitely an ES module.
pub fn found_unambiguous_await(&mut self) {
self.module_record.has_module_syntax = true;
}

pub fn visit_import_declaration(&mut self, decl: &ImportDeclaration<'a>) {
let module_request = NameSpan::new(decl.source.value, decl.source.span);

Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/codegen_babel.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
commit: 6ef16ca4

codegen_babel Summary:
AST Parsed : 2221/2221 (100.00%)
Positive Passed: 2221/2221 (100.00%)
AST Parsed : 2225/2225 (100.00%)
Positive Passed: 2225/2225 (100.00%)
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/formatter_babel.snap
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
commit: 6ef16ca4

formatter_babel Summary:
AST Parsed : 2221/2221 (100.00%)
Positive Passed: 2215/2221 (99.73%)
AST Parsed : 2225/2225 (100.00%)
Positive Passed: 2219/2225 (99.73%)
Mismatch: tasks/coverage/babel/packages/babel-parser/test/fixtures/comments/basic/async-arrow-function/input.js

Mismatch: tasks/coverage/babel/packages/babel-parser/test/fixtures/comments/basic/class-method/input.js
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/minifier_babel.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
commit: 6ef16ca4

minifier_babel Summary:
AST Parsed : 1783/1783 (100.00%)
Positive Passed: 1783/1783 (100.00%)
AST Parsed : 1787/1787 (100.00%)
Positive Passed: 1787/1787 (100.00%)
62 changes: 27 additions & 35 deletions tasks/coverage/snapshots/parser_babel.snap
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
commit: 6ef16ca4

parser_babel Summary:
AST Parsed : 2217/2221 (99.82%)
Positive Passed: 2204/2221 (99.23%)
AST Parsed : 2221/2225 (99.82%)
Positive Passed: 2208/2225 (99.24%)
Negative Passed: 1654/1689 (97.93%)
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/invalid-for-using-of-no-initializer/input.js

Expand Down Expand Up @@ -1186,12 +1186,12 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
╰────
help: In strict mode code, functions can only be declared at top level or inside a block

× `await` is only allowed within async functions and at the top levels of modules
╭─[babel/packages/babel-parser/test/fixtures/core/sourcetype-commonjs/invalid-top-level-await/input.js:1:1]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[babel/packages/babel-parser/test/fixtures/core/sourcetype-commonjs/invalid-top-level-await/input.js:1:6]
1 │ await Promise.resolve();
· ─────
·
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Try inserting a semicolon here

× A 'yield' expression is only allowed in a generator body.
╭─[babel/packages/babel-parser/test/fixtures/core/sourcetype-commonjs/invalid-top-level-yield/input.js:1:1]
Expand Down Expand Up @@ -5128,21 +5128,21 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
╰────
help: Try inserting a semicolon here

× `await` is only allowed within async functions and at the top levels of modules
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/9/input.js:1:25]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/9/input.js:1:30]
1 │ function foo(promise) { await promise; }
· ─────
·
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Try inserting a semicolon here

× `await` is only allowed within async functions and at the top levels of modules
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/allow-await-outside-function-throw/input.js:2:10]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/allow-await-outside-function-throw/input.js:2:15]
1 │ function a() {
2 │ return await 1
· ─────
·
3 │ }
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Try inserting a semicolon here

× Cannot use `await` as an identifier in an async context
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/async-await-as-arrow-binding-identifier/input.js:1:7]
Expand Down Expand Up @@ -5186,12 +5186,12 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
3 │ }
╰────

× `await` is only allowed within async functions and at the top levels of modules
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-arrow-expression-disallowed/input.js:1:9]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-arrow-expression-disallowed/input.js:1:14]
1 │ () => { await x }
· ─────
·
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Try inserting a semicolon here

× await expression not allowed in formal parameter
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-parameters/input.js:1:23]
Expand Down Expand Up @@ -5225,21 +5225,13 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
3 │ }
╰────

× `await` is only allowed within async functions and at the top levels of modules
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-parameters-of-nested-function/input.js:2:20]
× Expected `,` or `)` but found `decimal`
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-parameters-of-nested-function/input.js:2:26]
1 │ async function foo() {
2 │ function bar(x = await 2) {}
· ─────
3 │ }
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function

× await expression not allowed in formal parameter
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-parameters-of-nested-function/input.js:2:20]
1 │ async function foo() {
2 │ function bar(x = await 2) {}
· ───┬───
· ╰── await expression not allowed in formal parameter
· ┬ ┬
· │ ╰── `,` or `)` expected
· ╰── Opened here
3 │ }
╰────

Expand Down Expand Up @@ -8422,12 +8414,12 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function

× `await` is only allowed within async functions and at the top levels of modules
╭─[babel/packages/babel-parser/test/fixtures/es2022/top-level-await-script/top-level/input.js:1:1]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[babel/packages/babel-parser/test/fixtures/es2022/top-level-await-script/top-level/input.js:1:6]
1 │ await 0;
· ─────
·
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Try inserting a semicolon here

× Invalid regular expression: Invalid unicode flags combination `u` and `v`
╭─[babel/packages/babel-parser/test/fixtures/es2024/regexp-unicode-sets/uv-error/input.js:1:6]
Expand Down
8 changes: 4 additions & 4 deletions tasks/coverage/snapshots/parser_misc.snap
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ Negative Passed: 131/131 (100.00%)
· ──────
╰────

× `await` is only allowed within async functions and at the top levels of modules
╭─[misc/fail/commonjs-top-level-await.cjs:2:1]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/commonjs-top-level-await.cjs:2:6]
1 │ // CommonJS does NOT allow top-level await (only ES modules do)
2 │ await Promise.resolve();
· ─────
·
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function
help: Try inserting a semicolon here

× Encountered diff marker
╭─[misc/fail/diff-markers.js:10:1]
Expand Down
21 changes: 3 additions & 18 deletions tasks/coverage/snapshots/parser_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ commit: 7f6a8467
parser_typescript Summary:
AST Parsed : 9842/9843 (99.99%)
Positive Passed: 9837/9843 (99.94%)
Negative Passed: 1500/2555 (58.71%)
Negative Passed: 1499/2555 (58.67%)
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration3.ts

Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration4.ts
Expand Down Expand Up @@ -1484,6 +1484,8 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/externalM

Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/externalModules/rewriteRelativeImportExtensions/emit.ts

Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/externalModules/topLevelAwaitNonModule.ts

Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/externalModules/topLevelModuleDeclarationAndFile.ts

Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/externalModules/typeOnly/chained2.ts
Expand Down Expand Up @@ -19784,23 +19786,6 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va
· ─────
╰────

× `await` is only allowed within async functions and at the top levels of modules
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitNonModule.ts:1:1]
1 │ await x;
· ─────
2 │
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function

× `await` is only allowed within async functions and at the top levels of modules
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitNonModule.ts:5:5]
4 │
5 │ for await (const item of arr) {
· ─────
6 │ item;
╰────
help: Either remove this `await` or add the `async` keyword to the enclosing function

× Expected `=` but found `;`
╭─[typescript/tests/cases/conformance/externalModules/typeOnly/exportDeclaration_missingBraces.ts:11:16]
10 │ namespace ns {
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/semantic_babel.snap
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
commit: 6ef16ca4

semantic_babel Summary:
AST Parsed : 2221/2221 (100.00%)
Positive Passed: 2022/2221 (91.04%)
AST Parsed : 2225/2225 (100.00%)
Positive Passed: 2026/2225 (91.06%)
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/comments/decorators/decorators-after-export/input.js
Symbol span mismatch for "C":
after transform: SymbolId(0): Span { start: 65, end: 66 }
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/transformer_babel.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
commit: 6ef16ca4

transformer_babel Summary:
AST Parsed : 2221/2221 (100.00%)
Positive Passed: 2220/2221 (99.95%)
AST Parsed : 2225/2225 (100.00%)
Positive Passed: 2224/2225 (99.96%)
Mismatch: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/opts/allowYieldOutsideFunction-true-2/input.js

Loading
Loading