From 3db2553dc24f0faaf923c08ef9ac6d9d431d74e7 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Wed, 26 Jun 2024 07:16:18 +0000 Subject: [PATCH] refactor(parser): improve parsing of TypeScript type arguments (#3923) --- crates/oxc_parser/src/cursor.rs | 10 ++- crates/oxc_parser/src/js/class.rs | 4 +- crates/oxc_parser/src/js/expression.rs | 5 +- crates/oxc_parser/src/jsx/mod.rs | 7 +- crates/oxc_parser/src/lexer/kind.rs | 9 -- crates/oxc_parser/src/ts/list.rs | 3 +- crates/oxc_parser/src/ts/types.rs | 112 ++++++++++++++++++------- tasks/coverage/parser_babel.snap | 41 +++++++-- tasks/coverage/parser_typescript.snap | 11 +-- tasks/coverage/prettier_babel.snap | 4 +- 10 files changed, 135 insertions(+), 71 deletions(-) diff --git a/crates/oxc_parser/src/cursor.rs b/crates/oxc_parser/src/cursor.rs index 03a163294..6152a33cc 100644 --- a/crates/oxc_parser/src/cursor.rs +++ b/crates/oxc_parser/src/cursor.rs @@ -254,17 +254,23 @@ impl<'a> ParserImpl<'a> { } } - pub(crate) fn re_lex_ts_l_angle(&mut self) { + pub(crate) fn re_lex_l_angle(&mut self) -> Kind { let kind = self.cur_kind(); if matches!(kind, Kind::ShiftLeft | Kind::ShiftLeftEq | Kind::LtEq) { self.token = self.lexer.re_lex_as_typescript_l_angle(kind); + self.token.kind + } else { + kind } } - pub(crate) fn re_lex_ts_r_angle(&mut self) { + pub(crate) fn re_lex_ts_r_angle(&mut self) -> Kind { let kind = self.cur_kind(); if matches!(kind, Kind::ShiftRight | Kind::ShiftRight3) { self.token = self.lexer.re_lex_as_typescript_r_angle(kind); + self.token.kind + } else { + kind } } diff --git a/crates/oxc_parser/src/js/class.rs b/crates/oxc_parser/src/js/class.rs index 76d6e3cf4..a4b5da73d 100644 --- a/crates/oxc_parser/src/js/class.rs +++ b/crates/oxc_parser/src/js/class.rs @@ -145,7 +145,7 @@ impl<'a> ParserImpl<'a> { first_extends = expr.expression; first_type_argument = Some(expr.type_parameters); } else { - first_type_argument = self.parse_ts_type_arguments()?; + first_type_argument = self.try_parse_type_arguments()?; } extends.push((first_extends, first_type_argument, self.end_span(span))); @@ -158,7 +158,7 @@ impl<'a> ParserImpl<'a> { extend = expr.expression; type_argument = Some(expr.type_parameters); } else { - type_argument = self.parse_ts_type_arguments()?; + type_argument = self.try_parse_type_arguments()?; } extends.push((extend, type_argument, self.end_span(span))); diff --git a/crates/oxc_parser/src/js/expression.rs b/crates/oxc_parser/src/js/expression.rs index ec3ccb72e..08d74d4c7 100644 --- a/crates/oxc_parser/src/js/expression.rs +++ b/crates/oxc_parser/src/js/expression.rs @@ -602,7 +602,7 @@ impl<'a> ParserImpl<'a> { } Kind::LAngle | Kind::ShiftLeft => { if let Ok(Some(arguments)) = - self.try_parse(Self::parse_ts_type_arguments_in_expression) + self.try_parse(Self::parse_type_arguments_in_expression) { lhs = self.ast.ts_instantiation_expression( self.end_span(lhs_span), @@ -713,8 +713,7 @@ impl<'a> ParserImpl<'a> { *in_optional_chain = if optional_call { true } else { *in_optional_chain }; if optional_call { - if let Ok(Some(args)) = self.try_parse(Self::parse_ts_type_arguments_in_expression) - { + if let Ok(Some(args)) = self.try_parse(Self::parse_type_arguments_in_expression) { type_arguments = Some(args); } if self.cur_kind().is_template_start_of_tagged_template() { diff --git a/crates/oxc_parser/src/jsx/mod.rs b/crates/oxc_parser/src/jsx/mod.rs index e4a495984..40cd2332c 100644 --- a/crates/oxc_parser/src/jsx/mod.rs +++ b/crates/oxc_parser/src/jsx/mod.rs @@ -88,11 +88,8 @@ impl<'a> ParserImpl<'a> { self.expect(Kind::LAngle)?; let name = self.parse_jsx_element_name()?; // for tsx - let type_parameters = if self.ts_enabled() { - self.context(Context::default(), self.ctx, Self::parse_ts_type_arguments)? - } else { - None - }; + let type_parameters = + if self.ts_enabled() { self.try_parse_type_arguments()? } else { None }; let attributes = self.parse_jsx_attributes()?; let self_closing = self.eat(Kind::Slash); if !self_closing || in_jsx_child { diff --git a/crates/oxc_parser/src/lexer/kind.rs b/crates/oxc_parser/src/lexer/kind.rs index 3b2c1eeb5..a9ac38009 100644 --- a/crates/oxc_parser/src/lexer/kind.rs +++ b/crates/oxc_parser/src/lexer/kind.rs @@ -643,15 +643,6 @@ impl Kind { BigInt => "bigint", } } - - #[rustfmt::skip] - pub fn can_follow_type_arguments_in_expr(self) -> bool { - matches!(self, Self::LParen | Self::NoSubstitutionTemplate | Self::TemplateHead - | Self::Comma | Self::Dot | Self::QuestionDot | Self::RParen | Self::RBrack - | Self::Colon | Self::Semicolon | Self::Question | Self::Eq3 | Self::Eq2 | Self::Eq - | Self::Neq | Self::Neq2 | Self::Amp2 | Self::Pipe2 | Self::Question2 - | Self::Caret | Self::Amp | Self::Pipe | Self::RCurly | Self::Eof) - } } impl fmt::Display for Kind { diff --git a/crates/oxc_parser/src/ts/list.rs b/crates/oxc_parser/src/ts/list.rs index fae36a904..73f041797 100644 --- a/crates/oxc_parser/src/ts/list.rs +++ b/crates/oxc_parser/src/ts/list.rs @@ -159,8 +159,7 @@ impl<'a> SeparatedList<'a> for TSTypeArgumentList<'a> { if self.in_expression { // `a < b> = c`` is valid but `a < b >= c` is BinaryExpression - let kind = p.re_lex_right_angle(); - if matches!(kind, Kind::GtEq) { + if matches!(p.re_lex_right_angle(), Kind::GtEq) { return Err(p.unexpected()); } p.re_lex_ts_r_angle(); diff --git a/crates/oxc_parser/src/ts/types.rs b/crates/oxc_parser/src/ts/types.rs index 9b740c35c..a8bf6b7ba 100644 --- a/crates/oxc_parser/src/ts/types.rs +++ b/crates/oxc_parser/src/ts/types.rs @@ -649,12 +649,8 @@ impl<'a> ParserImpl<'a> { self.bump_any(); // `bump `typeof` let entity_name = self.parse_ts_type_name()?; // TODO: parseEntityName let entity_name = self.ast.ts_type_query_expr_name_type_name(entity_name); - let type_arguments = if self.cur_token().is_on_new_line { - None - } else { - // TODO: tryParseTypeArguments - self.parse_ts_type_arguments()? - }; + let type_arguments = + if self.cur_token().is_on_new_line { None } else { self.try_parse_type_arguments()? }; Ok(self.ast.ts_type_query_type(self.end_span(span), entity_name, type_arguments)) } @@ -751,18 +747,15 @@ impl<'a> ParserImpl<'a> { fn parse_type_reference(&mut self) -> Result> { let span = self.start_span(); let type_name = self.parse_ts_type_name()?; - let type_parameters = - if self.cur_token().is_on_new_line { None } else { self.parse_ts_type_arguments()? }; + let type_parameters = self.parse_type_arguments_of_type_reference()?; Ok(self.ast.ts_type_reference(self.end_span(span), type_name, type_parameters)) } fn parse_ts_implement_name(&mut self) -> Result> { let span = self.start_span(); - let expression = self.parse_ts_type_name()?; - let type_parameters = - if self.cur_token().is_on_new_line { None } else { self.parse_ts_type_arguments()? }; - - Ok(self.ast.ts_type_implement(self.end_span(span), expression, type_parameters)) + let type_name = self.parse_ts_type_name()?; + let type_parameters = self.parse_type_arguments_of_type_reference()?; + Ok(self.ast.ts_type_implement(self.end_span(span), type_name, type_parameters)) } pub(crate) fn parse_ts_type_name(&mut self) -> Result> { @@ -781,42 +774,58 @@ impl<'a> ParserImpl<'a> { Ok(left) } - pub(crate) fn parse_ts_type_arguments( + pub(crate) fn try_parse_type_arguments( &mut self, ) -> Result>>> { - self.re_lex_ts_l_angle(); - if !self.at(Kind::LAngle) { - return Ok(None); + if self.at(Kind::LAngle) { + let span = self.start_span(); + let params = TSTypeArgumentList::parse(self, false)?.params; + return Ok(Some(self.ast.ts_type_arguments(self.end_span(span), params))); } - let span = self.start_span(); - let params = TSTypeArgumentList::parse(self, false)?.params; - Ok(Some(self.ast.ts_type_arguments(self.end_span(span), params))) + Ok(None) } - pub(crate) fn parse_ts_type_arguments_in_expression( + fn parse_type_arguments_of_type_reference( + &mut self, + ) -> Result>>> { + self.re_lex_l_angle(); + if !self.cur_token().is_on_new_line && self.re_lex_l_angle() == Kind::LAngle { + let span = self.start_span(); + let params = TSTypeArgumentList::parse(self, false)?.params; + return Ok(Some(self.ast.ts_type_arguments(self.end_span(span), params))); + } + Ok(None) + } + + pub(crate) fn parse_type_arguments_in_expression( &mut self, ) -> Result>>> { if !self.ts_enabled() { return Ok(None); } - let span = self.start_span(); - self.re_lex_ts_l_angle(); - if !self.at(Kind::LAngle) { + if self.re_lex_l_angle() != Kind::LAngle { return Ok(None); } - let params = TSTypeArgumentList::parse(self, /* in_expression */ true)?.params; - - let token = self.cur_token(); - - if token.is_on_new_line || token.kind.can_follow_type_arguments_in_expr() { + if self.can_follow_type_arguments_in_expr() { return Ok(Some(self.ast.ts_type_arguments(self.end_span(span), params))); } - Err(self.unexpected()) } + fn can_follow_type_arguments_in_expr(&mut self) -> bool { + match self.cur_kind() { + Kind::LParen | Kind::NoSubstitutionTemplate | Kind::TemplateHead => true, + Kind::LAngle | Kind::RAngle | Kind::Plus | Kind::Minus => false, + _ => { + self.cur_token().is_on_new_line + || self.is_binary_operator() + || !self.is_start_of_expression() + } + } + } + fn parse_tuple_type(&mut self) -> Result> { let span = self.start_span(); let elements = TSTupleElementList::parse(self)?.elements; @@ -951,7 +960,7 @@ impl<'a> ParserImpl<'a> { if self.eat(Kind::Comma) { Some(self.parse_ts_import_attributes()?) } else { None }; self.expect(Kind::RParen)?; let qualifier = if self.eat(Kind::Dot) { Some(self.parse_ts_type_name()?) } else { None }; - let type_parameters = self.parse_ts_type_arguments()?; + let type_parameters = self.parse_type_arguments_of_type_reference()?; Ok(self.ast.ts_import_type( self.end_span(span), is_type_of, @@ -1307,4 +1316,45 @@ impl<'a> ParserImpl<'a> { let ty = self.parse_non_array_type()?; Ok(self.ast.js_doc_non_nullable_type(self.end_span(span), ty, /* postfix */ false)) } + + fn is_binary_operator(&mut self) -> bool { + if self.ctx.has_in() && self.at(Kind::In) { + return false; + } + self.cur_kind().is_binary_operator() + } + + fn is_start_of_expression(&mut self) -> bool { + if self.is_start_of_left_hand_side_expression() { + return true; + } + match self.cur_kind() { + 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, + kind => kind.is_identifier(), + } + } + + fn is_start_of_left_hand_side_expression(&mut self) -> bool { + match self.cur_kind() { + kind if kind.is_literal() => true, + kind if kind.is_template_start_of_tagged_template() => true, + Kind::This + | Kind::Super + | Kind::LParen + | Kind::LBrack + | Kind::LCurly + | Kind::Function + | Kind::Class + | Kind::New + | Kind::Slash + | Kind::SlashEq => true, + Kind::Import => { + matches!(self.peek_kind(), Kind::LParen | Kind::LAngle | Kind::Dot) + } + kind => kind.is_identifier(), + } + } } diff --git a/tasks/coverage/parser_babel.snap b/tasks/coverage/parser_babel.snap index f154f8049..a5643ebee 100644 --- a/tasks/coverage/parser_babel.snap +++ b/tasks/coverage/parser_babel.snap @@ -1,8 +1,8 @@ commit: 12619ffe parser_babel Summary: -AST Parsed : 2095/2101 (99.71%) -Positive Passed: 2087/2101 (99.33%) +AST Parsed : 2091/2101 (99.52%) +Positive Passed: 2083/2101 (99.14%) Negative Passed: 1364/1501 (90.87%) Expect Syntax Error: "annex-b/disabled/1.1-html-comments-close/input.js" Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions/input.js" @@ -351,6 +351,36 @@ Expect to Parse: "typescript/regression/nested-extends-in-arrow-type-param-babel · ───┬─── · ╰── `,` expected ╰──── +Expect to Parse: "typescript/type-arguments-bit-shift-left-like/class-heritage/input.ts" + + × Expected `{` but found `<<` + ╭─[typescript/type-arguments-bit-shift-left-like/class-heritage/input.ts:1:17] + 1 │ (class extends f<(v: T) => void> {}); + · ─┬ + · ╰── `{` expected + ╰──── +Expect to Parse: "typescript/type-arguments-bit-shift-left-like/jsx-opening-element/input.tsx" + + × Unexpected token + ╭─[typescript/type-arguments-bit-shift-left-like/jsx-opening-element/input.tsx:1:11] + 1 │ (v: T) => void> /> + · ── + ╰──── +Expect to Parse: "typescript/type-arguments-bit-shift-left-like-babel-7/class-heritage/input.ts" + + × Expected `{` but found `<<` + ╭─[typescript/type-arguments-bit-shift-left-like-babel-7/class-heritage/input.ts:1:17] + 1 │ (class extends f<(v: T) => void> {}); + · ─┬ + · ╰── `{` expected + ╰──── +Expect to Parse: "typescript/type-arguments-bit-shift-left-like-babel-7/jsx-opening-element/input.tsx" + + × Unexpected token + ╭─[typescript/type-arguments-bit-shift-left-like-babel-7/jsx-opening-element/input.tsx:1:11] + 1 │ (v: T) => void> /> + · ── + ╰──── Expect to Parse: "typescript/types/const-type-parameters/input.ts" × Unexpected token @@ -10563,11 +10593,12 @@ Expect to Parse: "typescript/types/const-type-parameters-babel-7/input.ts" ╰──── help: Try insert a semicolon here - × Unexpected token - ╭─[typescript/type-arguments/new-without-arguments-missing-semicolon/input.ts:1:10] + × Expected a semicolon or an implicit semicolon after a statement, but found none + ╭─[typescript/type-arguments/new-without-arguments-missing-semicolon/input.ts:1:9] 1 │ new A if (0); - · ── + · ─ ╰──── + help: Try insert a semicolon here × Unexpected token ╭─[typescript/type-only-import-export-specifiers/export-invalid-type-only-keyword/input.ts:1:7] diff --git a/tasks/coverage/parser_typescript.snap b/tasks/coverage/parser_typescript.snap index f790605b2..d613b7363 100644 --- a/tasks/coverage/parser_typescript.snap +++ b/tasks/coverage/parser_typescript.snap @@ -3,7 +3,7 @@ commit: d8086f14 parser_typescript Summary: AST Parsed : 5280/5283 (99.94%) Positive Passed: 5273/5283 (99.81%) -Negative Passed: 1069/4875 (21.93%) +Negative Passed: 1068/4875 (21.91%) Expect Syntax Error: "compiler/ClassDeclaration10.ts" Expect Syntax Error: "compiler/ClassDeclaration11.ts" Expect Syntax Error: "compiler/ClassDeclaration13.ts" @@ -967,6 +967,7 @@ Expect Syntax Error: "compiler/inlineSourceMap2.ts" Expect Syntax Error: "compiler/innerAliases.ts" Expect Syntax Error: "compiler/innerTypeCheckOfLambdaArgument.ts" Expect Syntax Error: "compiler/instanceSubtypeCheck2.ts" +Expect Syntax Error: "compiler/instanceofOnInstantiationExpression.ts" Expect Syntax Error: "compiler/instanceofOperator.ts" Expect Syntax Error: "compiler/instanceofWithPrimitiveUnion.ts" Expect Syntax Error: "compiler/instanceofWithStructurallyIdenticalTypes.ts" @@ -6955,14 +6956,6 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" ╰──── help: Try insert a semicolon here - × Unexpected token - ╭─[compiler/instanceofOnInstantiationExpression.ts:14:13] - 13 │ - 14 │ Box instanceof Object; // OK - · ────────── - 15 │ (Box) instanceof Object; // OK - ╰──── - × Unexpected token ╭─[compiler/intTypeCheck.ts:37:17] 36 │ //Index Signatures diff --git a/tasks/coverage/prettier_babel.snap b/tasks/coverage/prettier_babel.snap index eb12149b1..463f69f47 100644 --- a/tasks/coverage/prettier_babel.snap +++ b/tasks/coverage/prettier_babel.snap @@ -2,7 +2,7 @@ commit: 12619ffe prettier_babel Summary: AST Parsed : 2101/2101 (100.00%) -Positive Passed: 1893/2101 (90.10%) +Positive Passed: 1895/2101 (90.20%) Expect to Parse: "comments/attachComment-false/array-expression-trailing-comma/input.js" Expect to Parse: "comments/basic/array-expression-trailing-comma/input.js" Expect to Parse: "comments/basic/object-expression-trailing-comma/input.js" @@ -149,9 +149,7 @@ Expect to Parse: "typescript/type-alias/generic-complex-tokens-true-babel-7/inpu Expect to Parse: "typescript/type-arguments/tsx/input.ts" Expect to Parse: "typescript/type-arguments/whitespace/input.ts" Expect to Parse: "typescript/type-arguments/whitespace-babel-7/input.ts" -Expect to Parse: "typescript/type-arguments-bit-shift-left-like/jsx-opening-element/input.tsx" Expect to Parse: "typescript/type-arguments-bit-shift-left-like/type-arguments-like/input.ts" -Expect to Parse: "typescript/type-arguments-bit-shift-left-like-babel-7/jsx-opening-element/input.tsx" Expect to Parse: "typescript/type-arguments-bit-shift-left-like-babel-7/type-arguments-like/input.ts" Expect to Parse: "typescript/types/conditional/input.ts" Expect to Parse: "typescript/types/conditional-infer/input.ts"