From bf515e29b810e5794d4be71e5ed65276106fed86 Mon Sep 17 00:00:00 2001 From: Boshen Date: Fri, 23 Jan 2026 22:04:38 +0800 Subject: [PATCH] feat(parser): support top-level await detection in unambiguous mode Add support for detecting ES modules via top-level await in unambiguous parsing mode, conforming with Babel's behavior. In unambiguous mode, when `await ` is encountered at the top level and the expression is unambiguous (not an ambiguous token), the file is detected as an ES module. Ambiguous tokens after `await` that don't definitively indicate ESM: - `+`, `-` (unary vs binary operators) - `(`, `[` (call/member vs grouped expression) - `/` (regex vs division) - `%` (modulo operator) - `;` (semicolon - empty await) - Template literals - `of`, `using` (contextual keywords) - Newlines (ASI boundary) Also fixes Script mode parsing where `await` at top level should always be an identifier (not a keyword), preventing `await / 0 / u` from being incorrectly parsed as `await /0/u` (await + regex). Co-Authored-By: Claude Opus 4.5 --- ..._prefer_logical_operator_over_ternary.snap | 19 ++--- .../unicorn_prefer_node_protocol.snap | 8 +- crates/oxc_parser/src/js/expression.rs | 7 +- crates/oxc_parser/src/js/statement.rs | 74 +++++++++++++++++++ crates/oxc_parser/src/module_record.rs | 8 ++ tasks/coverage/snapshots/codegen_babel.snap | 4 +- tasks/coverage/snapshots/formatter_babel.snap | 4 +- tasks/coverage/snapshots/minifier_babel.snap | 4 +- tasks/coverage/snapshots/parser_babel.snap | 62 +++++++--------- tasks/coverage/snapshots/parser_misc.snap | 8 +- .../coverage/snapshots/parser_typescript.snap | 21 +----- tasks/coverage/snapshots/semantic_babel.snap | 4 +- .../coverage/snapshots/transformer_babel.snap | 4 +- .../coverage/snapshots/transformer_misc.snap | 4 +- .../snapshots/transformer_typescript.snap | 18 ++++- tasks/coverage/src/babel/mod.rs | 2 - tasks/coverage/src/driver.rs | 3 + tasks/coverage/src/tools/minifier.rs | 15 +++- tasks/coverage/src/tools/transformer.rs | 6 +- 19 files changed, 180 insertions(+), 95 deletions(-) diff --git a/crates/oxc_linter/src/snapshots/unicorn_prefer_logical_operator_over_ternary.snap b/crates/oxc_linter/src/snapshots/unicorn_prefer_logical_operator_over_ternary.snap index b13daeaf1659a..f0d9cd1f4cc88 100644 --- a/crates/oxc_linter/src/snapshots/unicorn_prefer_logical_operator_over_ternary.snap +++ b/crates/oxc_linter/src/snapshots/unicorn_prefer_logical_operator_over_ternary.snap @@ -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] diff --git a/crates/oxc_linter/src/snapshots/unicorn_prefer_node_protocol.snap b/crates/oxc_linter/src/snapshots/unicorn_prefer_node_protocol.snap index 3fba1c243fe13..bb946d03f5eac 100644 --- a/crates/oxc_linter/src/snapshots/unicorn_prefer_node_protocol.snap +++ b/crates/oxc_linter/src/snapshots/unicorn_prefer_node_protocol.snap @@ -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`. diff --git a/crates/oxc_parser/src/js/expression.rs b/crates/oxc_parser/src/js/expression.rs index 143e14dd10d48..51d511b8ca120 100644 --- a/crates/oxc_parser/src/js/expression.rs +++ b/crates/oxc_parser/src/js/expression.rs @@ -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 @@ -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) }); diff --git a/crates/oxc_parser/src/js/statement.rs b/crates/oxc_parser/src/js/statement.rs index e84e34d75a252..d214043500885 100644 --- a/crates/oxc_parser/src/js/statement.rs +++ b/crates/oxc_parser/src/js/statement.rs @@ -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 @@ -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(); diff --git a/crates/oxc_parser/src/module_record.rs b/crates/oxc_parser/src/module_record.rs index 190efdb4f217c..4315cf0d8d065 100644 --- a/crates/oxc_parser/src/module_record.rs +++ b/crates/oxc_parser/src/module_record.rs @@ -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); diff --git a/tasks/coverage/snapshots/codegen_babel.snap b/tasks/coverage/snapshots/codegen_babel.snap index 12a521286905c..bf07f99da066d 100644 --- a/tasks/coverage/snapshots/codegen_babel.snap +++ b/tasks/coverage/snapshots/codegen_babel.snap @@ -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%) diff --git a/tasks/coverage/snapshots/formatter_babel.snap b/tasks/coverage/snapshots/formatter_babel.snap index dea32d16444af..2a0462c1f049b 100644 --- a/tasks/coverage/snapshots/formatter_babel.snap +++ b/tasks/coverage/snapshots/formatter_babel.snap @@ -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 diff --git a/tasks/coverage/snapshots/minifier_babel.snap b/tasks/coverage/snapshots/minifier_babel.snap index 2b4b0820d8cf5..40a84c7186418 100644 --- a/tasks/coverage/snapshots/minifier_babel.snap +++ b/tasks/coverage/snapshots/minifier_babel.snap @@ -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%) diff --git a/tasks/coverage/snapshots/parser_babel.snap b/tasks/coverage/snapshots/parser_babel.snap index 84301aaa98337..c643a1ab30766 100644 --- a/tasks/coverage/snapshots/parser_babel.snap +++ b/tasks/coverage/snapshots/parser_babel.snap @@ -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 @@ -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] @@ -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] @@ -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] @@ -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 │ } ╰──── @@ -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] diff --git a/tasks/coverage/snapshots/parser_misc.snap b/tasks/coverage/snapshots/parser_misc.snap index ebce7d0fa0695..87344ce7a63a5 100644 --- a/tasks/coverage/snapshots/parser_misc.snap +++ b/tasks/coverage/snapshots/parser_misc.snap @@ -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] diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index 2c4bd01801704..62b864544b07e 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -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 @@ -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 @@ -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 { diff --git a/tasks/coverage/snapshots/semantic_babel.snap b/tasks/coverage/snapshots/semantic_babel.snap index 6bace7e0ffe46..c48e7ff589d96 100644 --- a/tasks/coverage/snapshots/semantic_babel.snap +++ b/tasks/coverage/snapshots/semantic_babel.snap @@ -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 } diff --git a/tasks/coverage/snapshots/transformer_babel.snap b/tasks/coverage/snapshots/transformer_babel.snap index 123a10a0138f7..c8e8ce815d446 100644 --- a/tasks/coverage/snapshots/transformer_babel.snap +++ b/tasks/coverage/snapshots/transformer_babel.snap @@ -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 diff --git a/tasks/coverage/snapshots/transformer_misc.snap b/tasks/coverage/snapshots/transformer_misc.snap index 56ef1c69aa893..7ccc8759d7902 100644 --- a/tasks/coverage/snapshots/transformer_misc.snap +++ b/tasks/coverage/snapshots/transformer_misc.snap @@ -1,5 +1,3 @@ transformer_misc Summary: AST Parsed : 64/64 (100.00%) -Positive Passed: 63/64 (98.44%) -Mismatch: tasks/coverage/misc/pass/babel-16776-s.js - +Positive Passed: 64/64 (100.00%) diff --git a/tasks/coverage/snapshots/transformer_typescript.snap b/tasks/coverage/snapshots/transformer_typescript.snap index 0b389a4f88d3a..2a2d19ec2d02a 100644 --- a/tasks/coverage/snapshots/transformer_typescript.snap +++ b/tasks/coverage/snapshots/transformer_typescript.snap @@ -2,8 +2,24 @@ commit: 7f6a8467 transformer_typescript Summary: AST Parsed : 9843/9843 (100.00%) -Positive Passed: 9841/9843 (99.98%) +Positive Passed: 9833/9843 (99.90%) +Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxEmptyExpressionNotCountedAsChild.tsx + +Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxPartialSpread.tsx + +Mismatch: tasks/coverage/typescript/tests/cases/compiler/reactImportUnusedInNewJSXEmit.tsx + +Mismatch: tasks/coverage/typescript/tests/cases/compiler/tsxNoTypeAnnotatedSFC.tsx + Mismatch: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/autoAccessor2.ts +Mismatch: tasks/coverage/typescript/tests/cases/conformance/declarationEmit/leaveOptionalParameterAsWritten.ts + Mismatch: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classDeclaration/fields/esDecorators-classDeclaration-fields-staticPrivateAccessor.ts +Mismatch: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classExpression/namedEvaluation/esDecorators-classExpression-namedEvaluation.8.ts + +Mismatch: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classExpression/namedEvaluation/esDecorators-classExpression-namedEvaluation.9.ts + +Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/checkJsxGenericTagHasCorrectInferences.tsx + diff --git a/tasks/coverage/src/babel/mod.rs b/tasks/coverage/src/babel/mod.rs index 4ce71643167ed..e9e0ba656f819 100644 --- a/tasks/coverage/src/babel/mod.rs +++ b/tasks/coverage/src/babel/mod.rs @@ -51,8 +51,6 @@ impl Suite for BabelSuite { "typescript/arrow-function/arrow-like-in-conditional-2", // TypeScript allows `satisfies const`, Babel doesn't "typescript/cast/satisfies-const-error", - // Babel's heuristic for detecting module via unambiguous `await` - not in ES spec, we follow TypeScript - "es2022/top-level-await-unambiguous", // Escaped `of` binding in using declarations - not worth supporting "explicit-resource-management/valid-for-await-using-binding-escaped-of-of", "explicit-resource-management/valid-for-using-binding-escaped-of-of", diff --git a/tasks/coverage/src/driver.rs b/tasks/coverage/src/driver.rs index d8c8eca184c41..3ea61a4f4967b 100644 --- a/tasks/coverage/src/driver.rs +++ b/tasks/coverage/src/driver.rs @@ -38,6 +38,8 @@ pub struct Driver { pub panicked: bool, pub errors: Vec, pub printed: String, + /// The source type detected after parsing (for unambiguous mode) + pub source_type: Option, } impl CompilerInterface for Driver { @@ -74,6 +76,7 @@ impl CompilerInterface for Driver { fn after_parse(&mut self, parser_return: &mut ParserReturn) -> ControlFlow<()> { let ParserReturn { program, panicked, errors, .. } = parser_return; self.panicked = *panicked; + self.source_type = Some(program.source_type); self.check_ast_nodes(program); if self.check_comments(&program.comments) { return ControlFlow::Break(()); diff --git a/tasks/coverage/src/tools/minifier.rs b/tasks/coverage/src/tools/minifier.rs index c9b00b8bad882..4c4ef1fafae34 100644 --- a/tasks/coverage/src/tools/minifier.rs +++ b/tasks/coverage/src/tools/minifier.rs @@ -14,8 +14,19 @@ use crate::{ /// Idempotency test fn get_result(source_text: &str, source_type: SourceType) -> TestResult { - Driver { compress: Some(CompressOptions::smallest()), codegen: true, ..Driver::default() } - .idempotency("Compress", source_text, source_type) + let mut driver = + Driver { compress: Some(CompressOptions::smallest()), codegen: true, ..Driver::default() }; + driver.run(source_text, source_type); + let printed1 = driver.printed.clone(); + // Use detected source type from first pass (preserves module/script detection for unambiguous mode) + let detected_source_type = driver.source_type.unwrap_or(source_type); + driver.run(&printed1, detected_source_type); + let printed2 = driver.printed.clone(); + if printed1 == printed2 { + TestResult::Passed + } else { + TestResult::Mismatch("Compress", printed1, printed2) + } } pub struct MinifierTest262Case { diff --git a/tasks/coverage/src/tools/transformer.rs b/tasks/coverage/src/tools/transformer.rs index 90090b228c11c..d419a436cfa8d 100644 --- a/tasks/coverage/src/tools/transformer.rs +++ b/tasks/coverage/src/tools/transformer.rs @@ -31,11 +31,13 @@ fn get_result( driver.run(source_text, source_type); driver.printed.clone() }; - // Second pass with only JavaScript syntax + // Second pass with detected source type from first pass (preserves module/script detection) + let detected_source_type = driver.source_type.unwrap_or(source_type); let transformed2 = { - driver.run(&transformed1, SourceType::default().with_module(source_type.is_module())); + driver.run(&transformed1, detected_source_type); driver.printed.clone() }; + if transformed1 == transformed2 { TestResult::Passed } else {