Skip to content

Commit 3db2553

Browse files
committed
refactor(parser): improve parsing of TypeScript type arguments (#3923)
1 parent 4cf3c76 commit 3db2553

File tree

10 files changed

+135
-71
lines changed

10 files changed

+135
-71
lines changed

crates/oxc_parser/src/cursor.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -254,17 +254,23 @@ impl<'a> ParserImpl<'a> {
254254
}
255255
}
256256

257-
pub(crate) fn re_lex_ts_l_angle(&mut self) {
257+
pub(crate) fn re_lex_l_angle(&mut self) -> Kind {
258258
let kind = self.cur_kind();
259259
if matches!(kind, Kind::ShiftLeft | Kind::ShiftLeftEq | Kind::LtEq) {
260260
self.token = self.lexer.re_lex_as_typescript_l_angle(kind);
261+
self.token.kind
262+
} else {
263+
kind
261264
}
262265
}
263266

264-
pub(crate) fn re_lex_ts_r_angle(&mut self) {
267+
pub(crate) fn re_lex_ts_r_angle(&mut self) -> Kind {
265268
let kind = self.cur_kind();
266269
if matches!(kind, Kind::ShiftRight | Kind::ShiftRight3) {
267270
self.token = self.lexer.re_lex_as_typescript_r_angle(kind);
271+
self.token.kind
272+
} else {
273+
kind
268274
}
269275
}
270276

crates/oxc_parser/src/js/class.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ impl<'a> ParserImpl<'a> {
145145
first_extends = expr.expression;
146146
first_type_argument = Some(expr.type_parameters);
147147
} else {
148-
first_type_argument = self.parse_ts_type_arguments()?;
148+
first_type_argument = self.try_parse_type_arguments()?;
149149
}
150150
extends.push((first_extends, first_type_argument, self.end_span(span)));
151151

@@ -158,7 +158,7 @@ impl<'a> ParserImpl<'a> {
158158
extend = expr.expression;
159159
type_argument = Some(expr.type_parameters);
160160
} else {
161-
type_argument = self.parse_ts_type_arguments()?;
161+
type_argument = self.try_parse_type_arguments()?;
162162
}
163163

164164
extends.push((extend, type_argument, self.end_span(span)));

crates/oxc_parser/src/js/expression.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ impl<'a> ParserImpl<'a> {
602602
}
603603
Kind::LAngle | Kind::ShiftLeft => {
604604
if let Ok(Some(arguments)) =
605-
self.try_parse(Self::parse_ts_type_arguments_in_expression)
605+
self.try_parse(Self::parse_type_arguments_in_expression)
606606
{
607607
lhs = self.ast.ts_instantiation_expression(
608608
self.end_span(lhs_span),
@@ -713,8 +713,7 @@ impl<'a> ParserImpl<'a> {
713713
*in_optional_chain = if optional_call { true } else { *in_optional_chain };
714714

715715
if optional_call {
716-
if let Ok(Some(args)) = self.try_parse(Self::parse_ts_type_arguments_in_expression)
717-
{
716+
if let Ok(Some(args)) = self.try_parse(Self::parse_type_arguments_in_expression) {
718717
type_arguments = Some(args);
719718
}
720719
if self.cur_kind().is_template_start_of_tagged_template() {

crates/oxc_parser/src/jsx/mod.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,8 @@ impl<'a> ParserImpl<'a> {
8888
self.expect(Kind::LAngle)?;
8989
let name = self.parse_jsx_element_name()?;
9090
// <Component<TsType> for tsx
91-
let type_parameters = if self.ts_enabled() {
92-
self.context(Context::default(), self.ctx, Self::parse_ts_type_arguments)?
93-
} else {
94-
None
95-
};
91+
let type_parameters =
92+
if self.ts_enabled() { self.try_parse_type_arguments()? } else { None };
9693
let attributes = self.parse_jsx_attributes()?;
9794
let self_closing = self.eat(Kind::Slash);
9895
if !self_closing || in_jsx_child {

crates/oxc_parser/src/lexer/kind.rs

-9
Original file line numberDiff line numberDiff line change
@@ -643,15 +643,6 @@ impl Kind {
643643
BigInt => "bigint",
644644
}
645645
}
646-
647-
#[rustfmt::skip]
648-
pub fn can_follow_type_arguments_in_expr(self) -> bool {
649-
matches!(self, Self::LParen | Self::NoSubstitutionTemplate | Self::TemplateHead
650-
| Self::Comma | Self::Dot | Self::QuestionDot | Self::RParen | Self::RBrack
651-
| Self::Colon | Self::Semicolon | Self::Question | Self::Eq3 | Self::Eq2 | Self::Eq
652-
| Self::Neq | Self::Neq2 | Self::Amp2 | Self::Pipe2 | Self::Question2
653-
| Self::Caret | Self::Amp | Self::Pipe | Self::RCurly | Self::Eof)
654-
}
655646
}
656647

657648
impl fmt::Display for Kind {

crates/oxc_parser/src/ts/list.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,7 @@ impl<'a> SeparatedList<'a> for TSTypeArgumentList<'a> {
159159

160160
if self.in_expression {
161161
// `a < b> = c`` is valid but `a < b >= c` is BinaryExpression
162-
let kind = p.re_lex_right_angle();
163-
if matches!(kind, Kind::GtEq) {
162+
if matches!(p.re_lex_right_angle(), Kind::GtEq) {
164163
return Err(p.unexpected());
165164
}
166165
p.re_lex_ts_r_angle();

crates/oxc_parser/src/ts/types.rs

+81-31
Original file line numberDiff line numberDiff line change
@@ -649,12 +649,8 @@ impl<'a> ParserImpl<'a> {
649649
self.bump_any(); // `bump `typeof`
650650
let entity_name = self.parse_ts_type_name()?; // TODO: parseEntityName
651651
let entity_name = self.ast.ts_type_query_expr_name_type_name(entity_name);
652-
let type_arguments = if self.cur_token().is_on_new_line {
653-
None
654-
} else {
655-
// TODO: tryParseTypeArguments
656-
self.parse_ts_type_arguments()?
657-
};
652+
let type_arguments =
653+
if self.cur_token().is_on_new_line { None } else { self.try_parse_type_arguments()? };
658654
Ok(self.ast.ts_type_query_type(self.end_span(span), entity_name, type_arguments))
659655
}
660656

@@ -751,18 +747,15 @@ impl<'a> ParserImpl<'a> {
751747
fn parse_type_reference(&mut self) -> Result<TSType<'a>> {
752748
let span = self.start_span();
753749
let type_name = self.parse_ts_type_name()?;
754-
let type_parameters =
755-
if self.cur_token().is_on_new_line { None } else { self.parse_ts_type_arguments()? };
750+
let type_parameters = self.parse_type_arguments_of_type_reference()?;
756751
Ok(self.ast.ts_type_reference(self.end_span(span), type_name, type_parameters))
757752
}
758753

759754
fn parse_ts_implement_name(&mut self) -> Result<TSClassImplements<'a>> {
760755
let span = self.start_span();
761-
let expression = self.parse_ts_type_name()?;
762-
let type_parameters =
763-
if self.cur_token().is_on_new_line { None } else { self.parse_ts_type_arguments()? };
764-
765-
Ok(self.ast.ts_type_implement(self.end_span(span), expression, type_parameters))
756+
let type_name = self.parse_ts_type_name()?;
757+
let type_parameters = self.parse_type_arguments_of_type_reference()?;
758+
Ok(self.ast.ts_type_implement(self.end_span(span), type_name, type_parameters))
766759
}
767760

768761
pub(crate) fn parse_ts_type_name(&mut self) -> Result<TSTypeName<'a>> {
@@ -781,42 +774,58 @@ impl<'a> ParserImpl<'a> {
781774
Ok(left)
782775
}
783776

784-
pub(crate) fn parse_ts_type_arguments(
777+
pub(crate) fn try_parse_type_arguments(
785778
&mut self,
786779
) -> Result<Option<Box<'a, TSTypeParameterInstantiation<'a>>>> {
787-
self.re_lex_ts_l_angle();
788-
if !self.at(Kind::LAngle) {
789-
return Ok(None);
780+
if self.at(Kind::LAngle) {
781+
let span = self.start_span();
782+
let params = TSTypeArgumentList::parse(self, false)?.params;
783+
return Ok(Some(self.ast.ts_type_arguments(self.end_span(span), params)));
790784
}
791-
let span = self.start_span();
792-
let params = TSTypeArgumentList::parse(self, false)?.params;
793-
Ok(Some(self.ast.ts_type_arguments(self.end_span(span), params)))
785+
Ok(None)
786+
}
787+
788+
fn parse_type_arguments_of_type_reference(
789+
&mut self,
790+
) -> Result<Option<Box<'a, TSTypeParameterInstantiation<'a>>>> {
791+
self.re_lex_l_angle();
792+
if !self.cur_token().is_on_new_line && self.re_lex_l_angle() == Kind::LAngle {
793+
let span = self.start_span();
794+
let params = TSTypeArgumentList::parse(self, false)?.params;
795+
return Ok(Some(self.ast.ts_type_arguments(self.end_span(span), params)));
796+
}
797+
Ok(None)
794798
}
795799

796-
pub(crate) fn parse_ts_type_arguments_in_expression(
800+
pub(crate) fn parse_type_arguments_in_expression(
797801
&mut self,
798802
) -> Result<Option<Box<'a, TSTypeParameterInstantiation<'a>>>> {
799803
if !self.ts_enabled() {
800804
return Ok(None);
801805
}
802-
803806
let span = self.start_span();
804-
self.re_lex_ts_l_angle();
805-
if !self.at(Kind::LAngle) {
807+
if self.re_lex_l_angle() != Kind::LAngle {
806808
return Ok(None);
807809
}
808-
809810
let params = TSTypeArgumentList::parse(self, /* in_expression */ true)?.params;
810-
811-
let token = self.cur_token();
812-
813-
if token.is_on_new_line || token.kind.can_follow_type_arguments_in_expr() {
811+
if self.can_follow_type_arguments_in_expr() {
814812
return Ok(Some(self.ast.ts_type_arguments(self.end_span(span), params)));
815813
}
816-
817814
Err(self.unexpected())
818815
}
819816

817+
fn can_follow_type_arguments_in_expr(&mut self) -> bool {
818+
match self.cur_kind() {
819+
Kind::LParen | Kind::NoSubstitutionTemplate | Kind::TemplateHead => true,
820+
Kind::LAngle | Kind::RAngle | Kind::Plus | Kind::Minus => false,
821+
_ => {
822+
self.cur_token().is_on_new_line
823+
|| self.is_binary_operator()
824+
|| !self.is_start_of_expression()
825+
}
826+
}
827+
}
828+
820829
fn parse_tuple_type(&mut self) -> Result<TSType<'a>> {
821830
let span = self.start_span();
822831
let elements = TSTupleElementList::parse(self)?.elements;
@@ -951,7 +960,7 @@ impl<'a> ParserImpl<'a> {
951960
if self.eat(Kind::Comma) { Some(self.parse_ts_import_attributes()?) } else { None };
952961
self.expect(Kind::RParen)?;
953962
let qualifier = if self.eat(Kind::Dot) { Some(self.parse_ts_type_name()?) } else { None };
954-
let type_parameters = self.parse_ts_type_arguments()?;
963+
let type_parameters = self.parse_type_arguments_of_type_reference()?;
955964
Ok(self.ast.ts_import_type(
956965
self.end_span(span),
957966
is_type_of,
@@ -1307,4 +1316,45 @@ impl<'a> ParserImpl<'a> {
13071316
let ty = self.parse_non_array_type()?;
13081317
Ok(self.ast.js_doc_non_nullable_type(self.end_span(span), ty, /* postfix */ false))
13091318
}
1319+
1320+
fn is_binary_operator(&mut self) -> bool {
1321+
if self.ctx.has_in() && self.at(Kind::In) {
1322+
return false;
1323+
}
1324+
self.cur_kind().is_binary_operator()
1325+
}
1326+
1327+
fn is_start_of_expression(&mut self) -> bool {
1328+
if self.is_start_of_left_hand_side_expression() {
1329+
return true;
1330+
}
1331+
match self.cur_kind() {
1332+
kind if kind.is_unary_operator() => true,
1333+
kind if kind.is_update_operator() => true,
1334+
Kind::LAngle | Kind::Await | Kind::Yield | Kind::Private | Kind::At => true,
1335+
kind if kind.is_binary_operator() => true,
1336+
kind => kind.is_identifier(),
1337+
}
1338+
}
1339+
1340+
fn is_start_of_left_hand_side_expression(&mut self) -> bool {
1341+
match self.cur_kind() {
1342+
kind if kind.is_literal() => true,
1343+
kind if kind.is_template_start_of_tagged_template() => true,
1344+
Kind::This
1345+
| Kind::Super
1346+
| Kind::LParen
1347+
| Kind::LBrack
1348+
| Kind::LCurly
1349+
| Kind::Function
1350+
| Kind::Class
1351+
| Kind::New
1352+
| Kind::Slash
1353+
| Kind::SlashEq => true,
1354+
Kind::Import => {
1355+
matches!(self.peek_kind(), Kind::LParen | Kind::LAngle | Kind::Dot)
1356+
}
1357+
kind => kind.is_identifier(),
1358+
}
1359+
}
13101360
}

tasks/coverage/parser_babel.snap

+36-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
commit: 12619ffe
22

33
parser_babel Summary:
4-
AST Parsed : 2095/2101 (99.71%)
5-
Positive Passed: 2087/2101 (99.33%)
4+
AST Parsed : 2091/2101 (99.52%)
5+
Positive Passed: 2083/2101 (99.14%)
66
Negative Passed: 1364/1501 (90.87%)
77
Expect Syntax Error: "annex-b/disabled/1.1-html-comments-close/input.js"
88
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
351351
· ───┬───
352352
· ╰── `,` expected
353353
╰────
354+
Expect to Parse: "typescript/type-arguments-bit-shift-left-like/class-heritage/input.ts"
355+
356+
× Expected `{` but found `<<`
357+
╭─[typescript/type-arguments-bit-shift-left-like/class-heritage/input.ts:1:17]
358+
1 │ (class extends f<<T>(v: T) => void> {});
359+
· ─┬
360+
· ╰── `{` expected
361+
╰────
362+
Expect to Parse: "typescript/type-arguments-bit-shift-left-like/jsx-opening-element/input.tsx"
363+
364+
× Unexpected token
365+
╭─[typescript/type-arguments-bit-shift-left-like/jsx-opening-element/input.tsx:1:11]
366+
1 │ <Component<<T>(v: T) => void> />
367+
· ──
368+
╰────
369+
Expect to Parse: "typescript/type-arguments-bit-shift-left-like-babel-7/class-heritage/input.ts"
370+
371+
× Expected `{` but found `<<`
372+
╭─[typescript/type-arguments-bit-shift-left-like-babel-7/class-heritage/input.ts:1:17]
373+
1 │ (class extends f<<T>(v: T) => void> {});
374+
· ─┬
375+
· ╰── `{` expected
376+
╰────
377+
Expect to Parse: "typescript/type-arguments-bit-shift-left-like-babel-7/jsx-opening-element/input.tsx"
378+
379+
× Unexpected token
380+
╭─[typescript/type-arguments-bit-shift-left-like-babel-7/jsx-opening-element/input.tsx:1:11]
381+
1 │ <Component<<T>(v: T) => void> />
382+
· ──
383+
╰────
354384
Expect to Parse: "typescript/types/const-type-parameters/input.ts"
355385

356386
× Unexpected token
@@ -10563,11 +10593,12 @@ Expect to Parse: "typescript/types/const-type-parameters-babel-7/input.ts"
1056310593
╰────
1056410594
help: Try insert a semicolon here
1056510595

10566-
× Unexpected token
10567-
╭─[typescript/type-arguments/new-without-arguments-missing-semicolon/input.ts:1:10]
10596+
× Expected a semicolon or an implicit semicolon after a statement, but found none
10597+
╭─[typescript/type-arguments/new-without-arguments-missing-semicolon/input.ts:1:9]
1056810598
1 │ new A<T> if (0);
10569-
·
10599+
· ─
1057010600
╰────
10601+
help: Try insert a semicolon here
1057110602

1057210603
× Unexpected token
1057310604
╭─[typescript/type-only-import-export-specifiers/export-invalid-type-only-keyword/input.ts:1:7]

tasks/coverage/parser_typescript.snap

+2-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ commit: d8086f14
33
parser_typescript Summary:
44
AST Parsed : 5280/5283 (99.94%)
55
Positive Passed: 5273/5283 (99.81%)
6-
Negative Passed: 1069/4875 (21.93%)
6+
Negative Passed: 1068/4875 (21.91%)
77
Expect Syntax Error: "compiler/ClassDeclaration10.ts"
88
Expect Syntax Error: "compiler/ClassDeclaration11.ts"
99
Expect Syntax Error: "compiler/ClassDeclaration13.ts"
@@ -967,6 +967,7 @@ Expect Syntax Error: "compiler/inlineSourceMap2.ts"
967967
Expect Syntax Error: "compiler/innerAliases.ts"
968968
Expect Syntax Error: "compiler/innerTypeCheckOfLambdaArgument.ts"
969969
Expect Syntax Error: "compiler/instanceSubtypeCheck2.ts"
970+
Expect Syntax Error: "compiler/instanceofOnInstantiationExpression.ts"
970971
Expect Syntax Error: "compiler/instanceofOperator.ts"
971972
Expect Syntax Error: "compiler/instanceofWithPrimitiveUnion.ts"
972973
Expect Syntax Error: "compiler/instanceofWithStructurallyIdenticalTypes.ts"
@@ -6955,14 +6956,6 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts"
69556956
╰────
69566957
help: Try insert a semicolon here
69576958

6958-
× Unexpected token
6959-
╭─[compiler/instanceofOnInstantiationExpression.ts:14:13]
6960-
13 │
6961-
14 │ Box<number> instanceof Object; // OK
6962-
· ──────────
6963-
15 │ (Box<number>) instanceof Object; // OK
6964-
╰────
6965-
69666959
× Unexpected token
69676960
╭─[compiler/intTypeCheck.ts:37:17]
69686961
36 │ //Index Signatures

tasks/coverage/prettier_babel.snap

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ commit: 12619ffe
22

33
prettier_babel Summary:
44
AST Parsed : 2101/2101 (100.00%)
5-
Positive Passed: 1893/2101 (90.10%)
5+
Positive Passed: 1895/2101 (90.20%)
66
Expect to Parse: "comments/attachComment-false/array-expression-trailing-comma/input.js"
77
Expect to Parse: "comments/basic/array-expression-trailing-comma/input.js"
88
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
149149
Expect to Parse: "typescript/type-arguments/tsx/input.ts"
150150
Expect to Parse: "typescript/type-arguments/whitespace/input.ts"
151151
Expect to Parse: "typescript/type-arguments/whitespace-babel-7/input.ts"
152-
Expect to Parse: "typescript/type-arguments-bit-shift-left-like/jsx-opening-element/input.tsx"
153152
Expect to Parse: "typescript/type-arguments-bit-shift-left-like/type-arguments-like/input.ts"
154-
Expect to Parse: "typescript/type-arguments-bit-shift-left-like-babel-7/jsx-opening-element/input.tsx"
155153
Expect to Parse: "typescript/type-arguments-bit-shift-left-like-babel-7/type-arguments-like/input.ts"
156154
Expect to Parse: "typescript/types/conditional/input.ts"
157155
Expect to Parse: "typescript/types/conditional-infer/input.ts"

0 commit comments

Comments
 (0)