fix(rslint_parser): TS type annotation in `catch` declaration
TS supports annotating the type in a catch declaration with either `any` or `unknown`.
This PR adds the optional type annotation to the grammar and
fixes the implementation to call `parse_ts_type_annotation` instead of `parse_ts_type_parameters`
MODIFY
crates/rome_formatter/src/js/declarations/catch_declaration.rs
crates/rome_formatter/src/js/declarations/catch_declaration.rs
-use crate::formatter_traits::FormatTokenAndNode;
+use crate::formatter_traits::{FormatOptionalTokenAndNode, FormatTokenAndNode};
-use crate::{FormatElement, FormatResult, Formatter, ToFormatElement};
+use crate::{format_elements, FormatElement, FormatResult, Formatter, ToFormatElement};
use rslint_parser::ast::JsCatchDeclaration;
use rslint_parser::ast::JsCatchDeclarationFields;
@@ -11,11 +11,15 @@ impl ToFormatElement for JsCatchDeclaration {
l_paren_token,
binding,
r_paren_token,
+ type_annotation,
} = self.as_fields();
formatter.format_delimited_soft_block_indent(
&l_paren_token?,
+ format_elements![
binding.format(formatter)?,
+ type_annotation.format_or_empty(formatter)?
+ ],
&r_paren_token?,
)
}

MODIFY
crates/rslint_parser/src/ast/generated/nodes.rs
crates/rslint_parser/src/ast/generated/nodes.rs
@@ -690,6 +690,7 @@ impl JsCatchDeclaration {
JsCatchDeclarationFields {
l_paren_token: self.l_paren_token(),
binding: self.binding(),
+ type_annotation: self.type_annotation(),
r_paren_token: self.r_paren_token(),
}
}
@@ -699,13 +700,17 @@ impl JsCatchDeclaration {
pub fn binding(&self) -> SyntaxResult<JsAnyBindingPattern> {
support::required_node(&self.syntax, 1usize)
}
+ pub fn type_annotation(&self) -> Option<TsTypeAnnotation> {
+ support::node(&self.syntax, 2usize)
+ }
pub fn r_paren_token(&self) -> SyntaxResult<SyntaxToken> {
- support::required_token(&self.syntax, 2usize)
+ support::required_token(&self.syntax, 3usize)
}
}
pub struct JsCatchDeclarationFields {
pub l_paren_token: SyntaxResult<SyntaxToken>,
pub binding: SyntaxResult<JsAnyBindingPattern>,
+ pub type_annotation: Option<TsTypeAnnotation>,
pub r_paren_token: SyntaxResult<SyntaxToken>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
@@ -9198,6 +9203,10 @@ impl std::fmt::Debug for JsCatchDeclaration {
&support::DebugSyntaxResult(self.l_paren_token()),
)
.field("binding", &support::DebugSyntaxResult(self.binding()))
+ .field(
+ "type_annotation",
+ &support::DebugOptionalElement(self.type_annotation()),
+ )
.field(
"r_paren_token",
&support::DebugSyntaxResult(self.r_paren_token()),

MODIFY
crates/rslint_parser/src/ast/generated/syntax_factory.rs
crates/rslint_parser/src/ast/generated/syntax_factory.rs
@@ -721,7 +721,7 @@ impl SyntaxFactory for JsSyntaxFactory {
}
JS_CATCH_DECLARATION => {
let mut elements = (&children).into_iter();
- let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default();
+ let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default();
let mut current_element = elements.next();
if let Some(element) = &current_element {
if element.kind() == T!['('] {
@@ -737,6 +737,13 @@ impl SyntaxFactory for JsSyntaxFactory {
}
}
slots.next_slot();
+ if let Some(element) = &current_element {
+ if TsTypeAnnotation::can_cast(element.kind()) {
+ slots.mark_present();
+ current_element = elements.next();
+ }
+ }
+ slots.next_slot();
if let Some(element) = &current_element {
if element.kind() == T![')'] {
slots.mark_present();

MODIFY
crates/rslint_parser/src/syntax/stmt.rs
crates/rslint_parser/src/syntax/stmt.rs
@@ -1781,12 +1781,33 @@ fn parse_catch_declaration(p: &mut Parser) -> ParsedSyntax {
p.bump_any(); // bump (
parse_binding_pattern(p, ExpressionContext::default()).or_add_diagnostic(p, expected_binding);
- let type_annotation = parse_ts_type_parameters(p);
+ // test ts ts_catch_declaration
+ // try {} catch (error: any) {}
+ // try {} catch (error: unknown) {}
+ if p.at(T![:]) && is_nth_at_identifier(p, 1) {
+ let type_name_token = p.nth_tok(1);
+
+ // test_err ts ts_catch_declaration_non_any_unknown_type_annotation
+ // try {} catch (error: Error) {}
+ if TypeScript.is_supported(p)
+ && !matches!(
+ p.source(type_name_token.range().as_text_range()),
+ "any" | "unknown"
+ )
+ {
+ p.error(
+ p
+ .err_builder("Catch clause variable type annotation must be 'any' or 'unknown' if specified.")
+ .primary(type_name_token.range(), "")
+ );
+ }
+
JsSyntaxFeature::TypeScript
- .exclusive_syntax(p, type_annotation, |p, annotation| {
+ .parse_exclusive_syntax(p, parse_ts_type_annotation, |p, annotation| {
ts_only_syntax_error(p, "type annotation", annotation.range(p).as_range())
})
.ok();
+ }
p.expect(T![')']);

ADD
crates/rslint_parser/test_data/inline/err/ts_catch_declaration_non_any_unknown_type_annotation.ts
+try {} catch (error: Error) {}

MODIFY
crates/rslint_parser/test_data/inline/ok/try_stmt.rast
crates/rslint_parser/test_data/inline/ok/try_stmt.rast
@@ -33,6 +33,7 @@ JsModule {
binding: JsIdentifierBinding {
name_token: IDENT@30..31 "e" [] [],
},
+ type_annotation: missing (optional),
r_paren_token: R_PAREN@31..33 ")" [] [Whitespace(" ")],
},
body: JsBlockStatement {
@@ -81,6 +82,7 @@ JsModule {
binding: JsIdentifierBinding {
name_token: IDENT@77..78 "e" [] [],
},
+ type_annotation: missing (optional),
r_paren_token: R_PAREN@78..80 ")" [] [Whitespace(" ")],
},
body: JsBlockStatement {
@@ -148,7 +150,8 @@ JsModule {
0: L_PAREN@29..30 "(" [] []
1: JS_IDENTIFIER_BINDING@30..31
0: IDENT@30..31 "e" [] []
- 2: R_PAREN@31..33 ")" [] [Whitespace(" ")]
+ 2: (empty)
+ 3: R_PAREN@31..33 ")" [] [Whitespace(" ")]
2: JS_BLOCK_STATEMENT@33..35
0: L_CURLY@33..34 "{" [] []
1: JS_STATEMENT_LIST@34..34
@@ -184,7 +187,8 @@ JsModule {
0: L_PAREN@76..77 "(" [] []
1: JS_IDENTIFIER_BINDING@77..78
0: IDENT@77..78 "e" [] []
- 2: R_PAREN@78..80 ")" [] [Whitespace(" ")]
+ 2: (empty)
+ 3: R_PAREN@78..80 ")" [] [Whitespace(" ")]
2: JS_BLOCK_STATEMENT@80..83
0: L_CURLY@80..81 "{" [] []
1: JS_STATEMENT_LIST@81..81

ADD
crates/rslint_parser/test_data/inline/ok/ts_catch_declaration.rast
+JsModule {
+ interpreter_token: missing (optional),
+ directives: JsDirectiveList [],
+ items: JsModuleItemList [
+ JsTryStatement {
+ try_token: TRY_KW@0..4 "try" [] [Whitespace(" ")],
+ body: JsBlockStatement {
+ l_curly_token: L_CURLY@4..5 "{" [] [],
+ statements: JsStatementList [],
+ r_curly_token: R_CURLY@5..7 "}" [] [Whitespace(" ")],
+ },
+ catch_clause: JsCatchClause {
+ catch_token: CATCH_KW@7..13 "catch" [] [Whitespace(" ")],
+ declaration: JsCatchDeclaration {
+ l_paren_token: L_PAREN@13..14 "(" [] [],
+ binding: JsIdentifierBinding {
+ name_token: IDENT@14..19 "error" [] [],
+ },
+ type_annotation: TsTypeAnnotation {
+ colon_token: COLON@19..21 ":" [] [Whitespace(" ")],
+ ty: TsAnyType {
+ any_token: ANY_KW@21..24 "any" [] [],
+ },
+ },
+ r_paren_token: R_PAREN@24..26 ")" [] [Whitespace(" ")],
+ },
+ body: JsBlockStatement {
+ l_curly_token: L_CURLY@26..27 "{" [] [],
+ statements: JsStatementList [],
+ r_curly_token: R_CURLY@27..28 "}" [] [],
+ },
+ },
+ },
+ JsTryStatement {
+ try_token: TRY_KW@28..33 "try" [Newline("\n")] [Whitespace(" ")],
+ body: JsBlockStatement {
+ l_curly_token: L_CURLY@33..34 "{" [] [],
+ statements: JsStatementList [],
+ r_curly_token: R_CURLY@34..36 "}" [] [Whitespace(" ")],
+ },
+ catch_clause: JsCatchClause {
+ catch_token: CATCH_KW@36..42 "catch" [] [Whitespace(" ")],
+ declaration: JsCatchDeclaration {
+ l_paren_token: L_PAREN@42..43 "(" [] [],
+ binding: JsIdentifierBinding {
+ name_token: IDENT@43..48 "error" [] [],
+ },
+ type_annotation: TsTypeAnnotation {
+ colon_token: COLON@48..50 ":" [] [Whitespace(" ")],
+ ty: TsUnknownType {
+ unknown_token: UNKNOWN_KW@50..57 "unknown" [] [],
+ },
+ },
+ r_paren_token: R_PAREN@57..59 ")" [] [Whitespace(" ")],
+ },
+ body: JsBlockStatement {
+ l_curly_token: L_CURLY@59..60 "{" [] [],
+ statements: JsStatementList [],
+ r_curly_token: R_CURLY@60..61 "}" [] [],
+ },
+ },
+ },
+ ],
+ eof_token: EOF@61..62 "" [Newline("\n")] [],
+}
+
+0: JS_MODULE@0..62
+ 0: (empty)
+ 1: JS_DIRECTIVE_LIST@0..0
+ 2: JS_MODULE_ITEM_LIST@0..61
+ 0: JS_TRY_STATEMENT@0..28
+ 0: TRY_KW@0..4 "try" [] [Whitespace(" ")]
+ 1: JS_BLOCK_STATEMENT@4..7
+ 0: L_CURLY@4..5 "{" [] []
+ 1: JS_STATEMENT_LIST@5..5
+ 2: R_CURLY@5..7 "}" [] [Whitespace(" ")]
+ 2: JS_CATCH_CLAUSE@7..28
+ 0: CATCH_KW@7..13 "catch" [] [Whitespace(" ")]
+ 1: JS_CATCH_DECLARATION@13..26
+ 0: L_PAREN@13..14 "(" [] []
+ 1: JS_IDENTIFIER_BINDING@14..19
+ 0: IDENT@14..19 "error" [] []
+ 2: TS_TYPE_ANNOTATION@19..24
+ 0: COLON@19..21 ":" [] [Whitespace(" ")]
+ 1: TS_ANY_TYPE@21..24
+ 0: ANY_KW@21..24 "any" [] []
+ 3: R_PAREN@24..26 ")" [] [Whitespace(" ")]
+ 2: JS_BLOCK_STATEMENT@26..28
+ 0: L_CURLY@26..27 "{" [] []
+ 1: JS_STATEMENT_LIST@27..27
+ 2: R_CURLY@27..28 "}" [] []
+ 1: JS_TRY_STATEMENT@28..61
+ 0: TRY_KW@28..33 "try" [Newline("\n")] [Whitespace(" ")]
+ 1: JS_BLOCK_STATEMENT@33..36
+ 0: L_CURLY@33..34 "{" [] []
+ 1: JS_STATEMENT_LIST@34..34
+ 2: R_CURLY@34..36 "}" [] [Whitespace(" ")]
+ 2: JS_CATCH_CLAUSE@36..61
+ 0: CATCH_KW@36..42 "catch" [] [Whitespace(" ")]
+ 1: JS_CATCH_DECLARATION@42..59
+ 0: L_PAREN@42..43 "(" [] []
+ 1: JS_IDENTIFIER_BINDING@43..48
+ 0: IDENT@43..48 "error" [] []
+ 2: TS_TYPE_ANNOTATION@48..57
+ 0: COLON@48..50 ":" [] [Whitespace(" ")]
+ 1: TS_UNKNOWN_TYPE@50..57
+ 0: UNKNOWN_KW@50..57 "unknown" [] []
+ 3: R_PAREN@57..59 ")" [] [Whitespace(" ")]
+ 2: JS_BLOCK_STATEMENT@59..61
+ 0: L_CURLY@59..60 "{" [] []
+ 1: JS_STATEMENT_LIST@60..60
+ 2: R_CURLY@60..61 "}" [] []
+ 3: EOF@61..62 "" [Newline("\n")] []

ADD
crates/rslint_parser/test_data/inline/ok/ts_catch_declaration.ts
+try {} catch (error: any) {}
+try {} catch (error: unknown) {}

MODIFY
xtask/codegen/js.ungram
xtask/codegen/js.ungram
@@ -255,7 +255,10 @@ JsCatchClause =
body: JsBlockStatement
JsCatchDeclaration =
- '(' binding: JsAnyBindingPattern ')'
+ '('
+ binding: JsAnyBindingPattern
+ type_annotation: TsTypeAnnotation?
+ ')'
JsFinallyClause =
'finally'

fix(rslint_parser): TS type annotation in `catch` declaration (#2161)

TS supports annotating the type in a catch declaration with either `any` or `unknown`.

This PR adds the optional type annotation to the grammar and
fixes the implementation to call `parse_ts_type_annotation` instead of `parse_ts_type_parameters`