Skip to content
Merged
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
4 changes: 4 additions & 0 deletions crates/oxc_parser/src/js/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ impl<'a> ParserImpl<'a> {
if !kind.is_identifier_reference(false, false) {
return self.unexpected();
}
// Track await identifier for potential reparsing in unambiguous mode
if kind == Kind::Await && !self.ctx.has_await() {
self.state.encountered_await_identifier = true;
}
self.check_identifier(kind, self.ctx);
let (span, name) = self.parse_identifier_kind(Kind::Ident);
self.ast.identifier_reference(span, name)
Expand Down
38 changes: 17 additions & 21 deletions crates/oxc_parser/src/js/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl<'a> ParserImpl<'a> {

// Check if we need to track potential await reparsing.
// This is only needed in unambiguous mode at top level when not in await context.
let track_await_reparse =
let mut track_await_reparse =
is_top_level && self.source_type.is_unambiguous() && !self.ctx.has_await();

let mut expecting_directives = true;
Expand All @@ -53,19 +53,29 @@ impl<'a> ParserImpl<'a> {
break;
}

// In unambiguous mode, check if this statement might need reparsing.
// We need to track statements where `await` is followed by `/` on the same line,
// because if ESM is detected later, we need to reparse them.
let checkpoint = if track_await_reparse && self.needs_await_reparse_tracking() {
// Once ESM syntax is detected, enable await context for remaining statements
// and stop tracking (we'll reparse earlier statements at the end)
if track_await_reparse && self.module_record_builder.has_module_syntax() {
track_await_reparse = false;
self.ctx = self.ctx.and_await(true);
}

// Take checkpoint for every statement when tracking await reparse.
// We reset the flag and only store the checkpoint if an await identifier
// was actually encountered during parsing.
let checkpoint = if track_await_reparse {
self.state.encountered_await_identifier = false;
Some((statements.len(), self.checkpoint()))
} else {
None
};

let stmt = self.parse_statement_list_item(stmt_ctx);

// If we took a checkpoint and this might need reparsing, store it.
if let Some((stmt_index, checkpoint)) = checkpoint {
// Store checkpoint only if await identifier was encountered
if let Some((stmt_index, checkpoint)) = checkpoint
&& self.state.encountered_await_identifier
{
self.state.potential_await_reparse.push((stmt_index, checkpoint));
}

Expand Down Expand Up @@ -94,20 +104,6 @@ impl<'a> ParserImpl<'a> {
(directives, statements)
}

/// Check if the current position might need await reparse tracking.
/// Returns true if we're at `await` followed by `/` on the same line.
fn needs_await_reparse_tracking(&mut self) -> bool {
if !self.at(Kind::Await) {
return false;
}
// Check if the next token is `/` on the same line
self.lookahead(|p| {
p.bump_any(); // bump `await`
let token = p.cur_token();
token.kind() == Kind::Slash && !token.is_on_new_line()
})
}

/// `StatementListItem`[Yield, Await, Return] :
/// Statement[?Yield, ?Await, ?Return]
/// Declaration[?Yield, ?Await]
Expand Down
13 changes: 9 additions & 4 deletions crates/oxc_parser/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ pub struct ParserState<'a> {

/// Statements that may need reparsing when `sourceType` is `unambiguous`.
///
/// In unambiguous mode, we initially parse top-level `await / ...` as
/// `await / 0 / u` (identifier divided by something). But if ESM syntax
/// is detected later, we need to reparse these as `await /0/u` (await
/// expression with regex).
/// In unambiguous mode, we initially parse top-level `await ...` as
/// `await(...)` (identifier/function call). But if ESM syntax is detected
/// later, we need to reparse these as await expressions.
///
/// Each entry contains: (statement_index, checkpoint_before_statement)
pub potential_await_reparse: Vec<(usize, ParserCheckpoint<'a>)>,

/// Flag to track if an `await` identifier was encountered during statement parsing.
/// Used to determine if a statement needs to be stored for potential reparsing
/// in unambiguous mode.
pub encountered_await_identifier: bool,
}

impl ParserState<'_> {
Expand All @@ -36,6 +40,7 @@ impl ParserState<'_> {
cover_initialized_name: FxHashMap::default(),
trailing_commas: FxHashMap::default(),
potential_await_reparse: Vec::new(),
encountered_await_identifier: false,
}
}
}
2 changes: 2 additions & 0 deletions tasks/coverage/misc/pass/unambiguous-await-call.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// In unambiguous mode with ESM syntax, `await (...)` should parse as await expression
await (async function () { })(); export {}
2 changes: 2 additions & 0 deletions tasks/coverage/misc/pass/unambiguous-await-in-declaration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// In unambiguous mode with ESM syntax, await in declarations should parse as await expression
const x = await (async function () { })(); export {}
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/codegen_misc.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 59/59 (100.00%)
Positive Passed: 59/59 (100.00%)
AST Parsed : 61/61 (100.00%)
Positive Passed: 61/61 (100.00%)
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/formatter_misc.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
formatter_misc Summary:
AST Parsed : 59/59 (100.00%)
Positive Passed: 59/59 (100.00%)
AST Parsed : 61/61 (100.00%)
Positive Passed: 61/61 (100.00%)
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/parser_misc.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
parser_misc Summary:
AST Parsed : 59/59 (100.00%)
Positive Passed: 59/59 (100.00%)
AST Parsed : 61/61 (100.00%)
Positive Passed: 61/61 (100.00%)
Negative Passed: 130/130 (100.00%)

× Cannot assign to 'arguments' in strict mode
Expand Down
139 changes: 40 additions & 99 deletions tasks/coverage/snapshots/parser_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -19688,109 +19688,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va
· ╰── `as` expected
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:4:1]
× Parenthesized expressions may not have a trailing comma.
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:4:9]
3 │ // reparse call as invalid await should error
4 │ await (1,);
· ─────
5 │ await <number, string>(1);
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:5:1]
4 │ await (1,);
· ─
5 │ await <number, string>(1);
· ─────
6 │
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:8:1]
7 │ // reparse tagged template as invalid await should error
8 │ await <number, string> ``;
· ─────
9 │
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:11:17]
10 │ // reparse class extends clause should fail
11 │ class C extends await<string> {
· ─────
12 │ }
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:15:3]
14 │ // await in class decorators should fail
15 │ @(await)
· ─────
16 │ class C1 {}
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:18:2]
17 │
18 │ @await(x)
· ─────
19 │ class C2 {}
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:21:2]
20 │
21 │ @await
· ─────
22 │ class C3 {}
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:26:6]
25 │ class C4 {
26 │ @await
· ─────
27 │ ["foo"]() {}
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:30:6]
29 │ class C5 {
30 │ @await(1)
· ─────
31 │ ["foo"]() {}
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:34:7]
33 │ class C6 {
34 │ @(await)
· ─────
35 │ ["foo"]() {}
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:40:14]
39 │ class C7 {
40 │ method1(@await [x]) {}
· ─────
41 │ method2(@await(1) [x]) {}
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:41:14]
40 │ method1(@await [x]) {}
41 │ method2(@await(1) [x]) {}
· ─────
42 │ method3(@(await) [x]) {}
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.1.ts:42:15]
41 │ method2(@await(1) [x]) {}
42 │ method3(@(await) [x]) {}
· ─────
43 │ }
╰────
help: Remove the trailing comma here

× Identifier expected. 'await' is a reserved word that cannot be used here.
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.10.ts:2:19]
Expand Down Expand Up @@ -19820,27 +19725,55 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va
· ─────
╰────

× Cannot use `await` as an identifier in an async context
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.12.ts:5:8]
4 │ // await disallowed in import=namespace when in a module
5 │ import await = foo.await;
· ─────
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.12.ts:5:8]
4 │ // await disallowed in import=namespace when in a module
5 │ import await = foo.await;
· ─────
╰────

× Cannot use `await` as an identifier in an async context
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.2.ts:4:5]
3 │ // reparse variable name as await should fail
4 │ var await = 1;
· ─────
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.2.ts:4:5]
3 │ // reparse variable name as await should fail
4 │ var await = 1;
· ─────
╰────

× Cannot use `await` as an identifier in an async context
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.3.ts:4:6]
3 │ // reparse binding pattern as await should fail
4 │ var {await} = {await:1};
· ─────
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.3.ts:4:6]
3 │ // reparse binding pattern as await should fail
4 │ var {await} = {await:1};
· ─────
╰────

× Cannot use `await` as an identifier in an async context
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.4.ts:4:6]
3 │ // reparse binding pattern as await should fail
4 │ var [await] = [1];
· ─────
╰────

× The keyword 'await' is reserved
╭─[typescript/tests/cases/conformance/externalModules/topLevelAwaitErrors.4.ts:4:6]
3 │ // reparse binding pattern as await should fail
Expand Down Expand Up @@ -24107,6 +24040,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va
3 │ }
╰────

× Cannot use `await` as an identifier in an async context
╭─[typescript/tests/cases/conformance/salsa/plainJSBinderErrors.ts:3:7]
2 │ export default 13
3 │ const await = 1
· ─────
4 │ const yield = 2
╰────

× Cannot use `await` as an identifier in an async context
╭─[typescript/tests/cases/conformance/salsa/plainJSBinderErrors.ts:6:11]
5 │ async function f() {
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/semantic_misc.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
semantic_misc Summary:
AST Parsed : 59/59 (100.00%)
Positive Passed: 40/59 (67.80%)
AST Parsed : 61/61 (100.00%)
Positive Passed: 42/61 (68.85%)
semantic Error: tasks/coverage/misc/pass/declare-let-private.ts
Bindings mismatch:
after transform: ScopeId(0): ["private"]
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/transformer_misc.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
transformer_misc Summary:
AST Parsed : 59/59 (100.00%)
Positive Passed: 58/59 (98.31%)
AST Parsed : 61/61 (100.00%)
Positive Passed: 60/61 (98.36%)
Mismatch: tasks/coverage/misc/pass/babel-16776-s.js

Loading