From 3b36543095a746ae42dd938463a221d447ac1025 Mon Sep 17 00:00:00 2001 From: Boshen Date: Thu, 29 Oct 2026 16:30:19 +0800 Subject: [PATCH] fix(parser): preserve in-context after TypeScript type arguments (#28300) Type arguments followed by `in` must preserve the surrounding grammar context. Accept the binary operator in expressions while leaving it available as the separator in `for-in` heads. AI-assisted implementation. --- crates/oxc_parser/src/ts/types.rs | 4 ++-- .../coverage/misc/pass/instantiation-before-for-in.cts | 5 +++++ tasks/coverage/misc/pass/instantiation-in-operator.ts | 10 ++++++++++ tasks/coverage/snapshots/codegen_misc.snap | 4 ++-- tasks/coverage/snapshots/formatter_misc.snap | 4 ++-- tasks/coverage/snapshots/lexer_misc.snap | 4 ++-- tasks/coverage/snapshots/parser_misc.snap | 4 ++-- tasks/coverage/snapshots/semantic_misc.snap | 4 ++-- tasks/coverage/snapshots/transformer_misc.snap | 4 ++-- 9 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 tasks/coverage/misc/pass/instantiation-before-for-in.cts create mode 100644 tasks/coverage/misc/pass/instantiation-in-operator.ts diff --git a/crates/oxc_parser/src/ts/types.rs b/crates/oxc_parser/src/ts/types.rs index c1f3a5b17e..abb681e2ee 100644 --- a/crates/oxc_parser/src/ts/types.rs +++ b/crates/oxc_parser/src/ts/types.rs @@ -1610,7 +1610,7 @@ impl<'a, C: Config> ParserImpl<'a, C> { } fn is_binary_operator(&self) -> bool { - if self.ctx.has_in() && self.at(Kind::In) { + if !self.ctx.has_in() && self.at(Kind::In) { return false; } self.cur_kind().is_binary_operator() @@ -1624,7 +1624,7 @@ impl<'a, C: Config> ParserImpl<'a, C> { kind if kind.is_unary_operator() => true, kind if kind.is_update_operator() => true, Kind::LAngle | Kind::Await | Kind::Yield | Kind::Private | Kind::At => true, - kind if kind.is_binary_operator() => true, + _ if self.is_binary_operator() => true, kind => kind.is_ts_identifier(self.ctx.has_yield(), self.ctx.has_await()), } } diff --git a/tasks/coverage/misc/pass/instantiation-before-for-in.cts b/tasks/coverage/misc/pass/instantiation-before-for-in.cts new file mode 100644 index 0000000000..a69cf38d30 --- /dev/null +++ b/tasks/coverage/misc/pass/instantiation-before-for-in.cts @@ -0,0 +1,5 @@ +const factory = null as unknown as string & { (): T }; +const object = {}; + +// Keep the instantiation before the for-in delimiter in this non-strict CommonJS file. +for (var key = factory in object) {} diff --git a/tasks/coverage/misc/pass/instantiation-in-operator.ts b/tasks/coverage/misc/pass/instantiation-in-operator.ts new file mode 100644 index 0000000000..aacb5ec75c --- /dev/null +++ b/tasks/coverage/misc/pass/instantiation-in-operator.ts @@ -0,0 +1,10 @@ +const factory = null as unknown as string & { (): T }; +const object = {}; +type Box = { value: T }; + +const direct = factory in object; +const nested = factory> in object; + +if (factory in object) {} +for (; factory in object; ) {} +for (let present = (factory in object); present; ) {} diff --git a/tasks/coverage/snapshots/codegen_misc.snap b/tasks/coverage/snapshots/codegen_misc.snap index 07a1794e08..4a12f1eca1 100644 --- a/tasks/coverage/snapshots/codegen_misc.snap +++ b/tasks/coverage/snapshots/codegen_misc.snap @@ -1,3 +1,3 @@ codegen_misc Summary: -AST Parsed : 125/125 (100.00%) -Positive Passed: 125/125 (100.00%) +AST Parsed : 127/127 (100.00%) +Positive Passed: 127/127 (100.00%) diff --git a/tasks/coverage/snapshots/formatter_misc.snap b/tasks/coverage/snapshots/formatter_misc.snap index 4b5067b2a2..5388c229e6 100644 --- a/tasks/coverage/snapshots/formatter_misc.snap +++ b/tasks/coverage/snapshots/formatter_misc.snap @@ -1,3 +1,3 @@ formatter_misc Summary: -AST Parsed : 125/125 (100.00%) -Positive Passed: 125/125 (100.00%) +AST Parsed : 127/127 (100.00%) +Positive Passed: 127/127 (100.00%) diff --git a/tasks/coverage/snapshots/lexer_misc.snap b/tasks/coverage/snapshots/lexer_misc.snap index 5e9f34f5b3..14f55c3caf 100644 --- a/tasks/coverage/snapshots/lexer_misc.snap +++ b/tasks/coverage/snapshots/lexer_misc.snap @@ -1,3 +1,3 @@ lexer_misc Summary: -AST Parsed : 388/388 (100.00%) -Positive Passed: 388/388 (100.00%) +AST Parsed : 390/390 (100.00%) +Positive Passed: 390/390 (100.00%) diff --git a/tasks/coverage/snapshots/parser_misc.snap b/tasks/coverage/snapshots/parser_misc.snap index dd2143fa7d..6c0cd00a7c 100644 --- a/tasks/coverage/snapshots/parser_misc.snap +++ b/tasks/coverage/snapshots/parser_misc.snap @@ -1,6 +1,6 @@ parser_misc Summary: -AST Parsed : 125/125 (100.00%) -Positive Passed: 125/125 (100.00%) +AST Parsed : 127/127 (100.00%) +Positive Passed: 127/127 (100.00%) Negative Passed: 265/265 (100.00%) × TS(1275): 'accessor' modifier can only appear on a property declaration. diff --git a/tasks/coverage/snapshots/semantic_misc.snap b/tasks/coverage/snapshots/semantic_misc.snap index d4fc6e1e1b..7265e7438e 100644 --- a/tasks/coverage/snapshots/semantic_misc.snap +++ b/tasks/coverage/snapshots/semantic_misc.snap @@ -1,6 +1,6 @@ semantic_misc Summary: -AST Parsed : 125/125 (100.00%) -Positive Passed: 119/125 (95.20%) +AST Parsed : 127/127 (100.00%) +Positive Passed: 121/127 (95.28%) semantic Error: tasks/coverage/misc/pass/declare-let-private.ts Bindings mismatch: after transform: ScopeId(0): ["private"] diff --git a/tasks/coverage/snapshots/transformer_misc.snap b/tasks/coverage/snapshots/transformer_misc.snap index ad3db84808..bf3cd541a5 100644 --- a/tasks/coverage/snapshots/transformer_misc.snap +++ b/tasks/coverage/snapshots/transformer_misc.snap @@ -1,3 +1,3 @@ transformer_misc Summary: -AST Parsed : 125/125 (100.00%) -Positive Passed: 125/125 (100.00%) +AST Parsed : 127/127 (100.00%) +Positive Passed: 127/127 (100.00%)