Skip to content

Commit

Permalink
refactor(parser): improve parsing of TypeScript type arguments (#3923)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Jun 26, 2024
1 parent 4cf3c76 commit 3db2553
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 71 deletions.
10 changes: 8 additions & 2 deletions crates/oxc_parser/src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_parser/src/js/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));

Expand All @@ -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)));
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_parser/src/js/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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() {
Expand Down
7 changes: 2 additions & 5 deletions crates/oxc_parser/src/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,8 @@ impl<'a> ParserImpl<'a> {
self.expect(Kind::LAngle)?;
let name = self.parse_jsx_element_name()?;
// <Component<TsType> 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 {
Expand Down
9 changes: 0 additions & 9 deletions crates/oxc_parser/src/lexer/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 1 addition & 2 deletions crates/oxc_parser/src/ts/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
112 changes: 81 additions & 31 deletions crates/oxc_parser/src/ts/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down Expand Up @@ -751,18 +747,15 @@ impl<'a> ParserImpl<'a> {
fn parse_type_reference(&mut self) -> Result<TSType<'a>> {
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<TSClassImplements<'a>> {
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<TSTypeName<'a>> {
Expand All @@ -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<Option<Box<'a, TSTypeParameterInstantiation<'a>>>> {
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)
}

fn parse_type_arguments_of_type_reference(
&mut self,
) -> Result<Option<Box<'a, TSTypeParameterInstantiation<'a>>>> {
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_ts_type_arguments_in_expression(
pub(crate) fn parse_type_arguments_in_expression(
&mut self,
) -> Result<Option<Box<'a, TSTypeParameterInstantiation<'a>>>> {
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<TSType<'a>> {
let span = self.start_span();
let elements = TSTupleElementList::parse(self)?.elements;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
}
}
}
41 changes: 36 additions & 5 deletions tasks/coverage/parser_babel.snap
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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<<T>(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 │ <Component<<T>(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<<T>(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 │ <Component<<T>(v: T) => void> />
· ──
╰────
Expect to Parse: "typescript/types/const-type-parameters/input.ts"

× Unexpected token
Expand Down Expand Up @@ -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<T> 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]
Expand Down
11 changes: 2 additions & 9 deletions tasks/coverage/parser_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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<number> instanceof Object; // OK
· ──────────
15 │ (Box<number>) instanceof Object; // OK
╰────

× Unexpected token
╭─[compiler/intTypeCheck.ts:37:17]
36 │ //Index Signatures
Expand Down
4 changes: 1 addition & 3 deletions tasks/coverage/prettier_babel.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 3db2553

Please sign in to comment.