fix(rslint_parser): Don't parse JSX as arrow functions
Don't parse `<A extends>()` or `<A extends=B>` or `<A extends ok>()` as arrow functions, these must be starts of a JSX element.
Depends on attribute parsing for the tests to pass
MODIFY
crates/rslint_parser/src/syntax/function.rs
crates/rslint_parser/src/syntax/function.rs
@@ -532,7 +532,6 @@ fn parse_possible_parenthesized_arrow_function_expression(p: &mut Parser) -> Par
fn parse_parenthesized_arrow_function_expression(p: &mut Parser) -> ParsedSyntax {
let is_parenthesized = is_parenthesized_arrow_function_expression(p);
-
match is_parenthesized {
IsParenthesizedArrowFunctionExpression::True => {
let (m, flags) = try_parse_parenthesized_arrow_function_head(p, Ambiguity::Allowed).expect("'CompletedMarker' because function should never return 'Err' if called with 'Ambiguity::Allowed'.");
@@ -653,11 +652,47 @@ fn is_parenthesized_arrow_function_expression_impl(
}
// potential start of type parameters
T![<] => {
- // <a...
- if is_nth_at_identifier(p, n + 1) {
+ if !is_nth_at_identifier(p, n + 1) {
+ // <5...
+ IsParenthesizedArrowFunctionExpression::False
+ }
+ // test jsx type_arguments
+ // // These may look like a valid arrows but are JSX
+ // <A extends>() =</A>;
+ // <A extends="B">() =</A>;
+ // <A extends ok>() =</A>;
+
+ // test tsx tsx_type_arguments
+ // // These are valid type arguments
+ // <A extends B>() => {};
+ // <A=string>() => {};
+ // <A, B>() => {};
+ // <A extends B<C>>() => {}
+
+ // <a... JSX override
+ else if JsSyntaxFeature::Jsx.is_supported(p) {
+ match p.nth(n + 2) {
+ T![extends] => {
+ // `<a extends=` OR `<a extends>` is a JSX start element
+ // and a `extends` type refinement: `<A extends string>`
+ if matches!(p.nth(n + 3), T![=] | T![>]) {
+ IsParenthesizedArrowFunctionExpression::False
+ }
+ // `<A extends B>` Could be either
+ else if is_nth_at_identifier(p, n + 3) {
IsParenthesizedArrowFunctionExpression::Unknown
} else {
- IsParenthesizedArrowFunctionExpression::False
+ // <A extends B> must be type arguments
+ IsParenthesizedArrowFunctionExpression::True
+ }
+ }
+ // `<A=` or `<A,` or always type arguments and never JSX tags
+ T![=] | T![,] => IsParenthesizedArrowFunctionExpression::True,
+ _ => IsParenthesizedArrowFunctionExpression::False,
+ }
+ } else {
+ // <a...
+ IsParenthesizedArrowFunctionExpression::Unknown
}
}
_ => IsParenthesizedArrowFunctionExpression::False,

MODIFY
crates/rslint_parser/src/syntax/jsx/mod.rs
crates/rslint_parser/src/syntax/jsx/mod.rs
@@ -359,10 +359,6 @@ impl ParseNodeList for JsxAttributeList {
}
fn parse_jsx_attribute_name(p: &mut Parser) -> ParsedSyntax {
- if !p.at(JsSyntaxKind::IDENT) {
- return ParsedSyntax::Absent;
- }
-
parse_jsx_name(p)
}

MODIFY
crates/rslint_parser/src/tests.rs
crates/rslint_parser/src/tests.rs
@@ -14,7 +14,7 @@ use std::path::{Path, PathBuf};
#[test]
fn parser_smoke_test() {
let src = r#"
-<a-b-c></a-b-c>
+ <A extends B>() => {};
"#;
let module = parse(src, 0, SourceType::jsx());
@@ -139,14 +139,10 @@ fn run_and_expect_errors(path: &str, _: &str, _: &str, _: &str) {
mod parser {
mod ok {
- tests_macros::gen_tests! {"test_data/inline/ok/**/*.js", crate::tests::run_and_expect_no_errors, ""}
- tests_macros::gen_tests! {"test_data/inline/ok/**/*.ts", crate::tests::run_and_expect_no_errors, ""}
- tests_macros::gen_tests! {"test_data/inline/ok/**/*.jsx", crate::tests::run_and_expect_no_errors, ""}
+ tests_macros::gen_tests! {"test_data/inline/ok/**/*.{js,ts,jsx,tsx}", crate::tests::run_and_expect_no_errors, ""}
}
mod err {
- tests_macros::gen_tests! {"test_data/inline/err/**/*.js", crate::tests::run_and_expect_errors, ""}
- tests_macros::gen_tests! {"test_data/inline/err/**/*.ts", crate::tests::run_and_expect_errors, ""}
- tests_macros::gen_tests! {"test_data/inline/err/**/*.jsx", crate::tests::run_and_expect_errors, ""}
+ tests_macros::gen_tests! {"test_data/inline/err/**/*.{js, ts, jsx, tsx}", crate::tests::run_and_expect_errors, ""}
}
}

ADD
crates/rslint_parser/test_data/inline/ok/tsx_type_arguments.rast
+JsModule {
+ interpreter_token: missing (optional),
+ directives: JsDirectiveList [],
+ items: JsModuleItemList [
+ JsExpressionStatement {
+ expression: JsArrowFunctionExpression {
+ async_token: missing (optional),
+ type_parameters: TsTypeParameters {
+ l_angle_token: L_ANGLE@0..35 "<" [Comments("// These are valid ty ..."), Newline("\n")] [],
+ items: TsTypeParameterList [
+ TsTypeParameter {
+ name: TsTypeParameterName {
+ ident_token: IDENT@35..37 "A" [] [Whitespace(" ")],
+ },
+ constraint: TsTypeConstraintClause {
+ extends_token: EXTENDS_KW@37..45 "extends" [] [Whitespace(" ")],
+ ty: TsReferenceType {
+ name: JsReferenceIdentifier {
+ value_token: IDENT@45..46 "B" [] [],
+ },
+ type_arguments: missing (optional),
+ },
+ },
+ default: missing (optional),
+ },
+ ],
+ r_angle_token: R_ANGLE@46..47 ">" [] [],
+ },
+ parameters: JsParameters {
+ l_paren_token: L_PAREN@47..48 "(" [] [],
+ items: JsParameterList [],
+ r_paren_token: R_PAREN@48..50 ")" [] [Whitespace(" ")],
+ },
+ return_type_annotation: missing (optional),
+ fat_arrow_token: FAT_ARROW@50..53 "=>" [] [Whitespace(" ")],
+ body: JsFunctionBody {
+ l_curly_token: L_CURLY@53..54 "{" [] [],
+ directives: JsDirectiveList [],
+ statements: JsStatementList [],
+ r_curly_token: R_CURLY@54..55 "}" [] [],
+ },
+ },
+ semicolon_token: SEMICOLON@55..56 ";" [] [],
+ },
+ JsExpressionStatement {
+ expression: JsArrowFunctionExpression {
+ async_token: missing (optional),
+ type_parameters: TsTypeParameters {
+ l_angle_token: L_ANGLE@56..58 "<" [Newline("\n")] [],
+ items: TsTypeParameterList [
+ TsTypeParameter {
+ name: TsTypeParameterName {
+ ident_token: IDENT@58..59 "A" [] [],
+ },
+ constraint: missing (optional),
+ default: TsDefaultTypeClause {
+ eq_token: EQ@59..60 "=" [] [],
+ ty: TsStringType {
+ string_token: STRING_KW@60..66 "string" [] [],
+ },
+ },
+ },
+ ],
+ r_angle_token: R_ANGLE@66..67 ">" [] [],
+ },
+ parameters: JsParameters {
+ l_paren_token: L_PAREN@67..68 "(" [] [],
+ items: JsParameterList [],
+ r_paren_token: R_PAREN@68..70 ")" [] [Whitespace(" ")],
+ },
+ return_type_annotation: missing (optional),
+ fat_arrow_token: FAT_ARROW@70..73 "=>" [] [Whitespace(" ")],
+ body: JsFunctionBody {
+ l_curly_token: L_CURLY@73..74 "{" [] [],
+ directives: JsDirectiveList [],
+ statements: JsStatementList [],
+ r_curly_token: R_CURLY@74..75 "}" [] [],
+ },
+ },
+ semicolon_token: SEMICOLON@75..76 ";" [] [],
+ },
+ JsExpressionStatement {
+ expression: JsArrowFunctionExpression {
+ async_token: missing (optional),
+ type_parameters: TsTypeParameters {
+ l_angle_token: L_ANGLE@76..78 "<" [Newline("\n")] [],
+ items: TsTypeParameterList [
+ TsTypeParameter {
+ name: TsTypeParameterName {
+ ident_token: IDENT@78..79 "A" [] [],
+ },
+ constraint: missing (optional),
+ default: missing (optional),
+ },
+ COMMA@79..81 "," [] [Whitespace(" ")],
+ TsTypeParameter {
+ name: TsTypeParameterName {
+ ident_token: IDENT@81..82 "B" [] [],
+ },
+ constraint: missing (optional),
+ default: missing (optional),
+ },
+ ],
+ r_angle_token: R_ANGLE@82..83 ">" [] [],
+ },
+ parameters: JsParameters {
+ l_paren_token: L_PAREN@83..84 "(" [] [],
+ items: JsParameterList [],
+ r_paren_token: R_PAREN@84..86 ")" [] [Whitespace(" ")],
+ },
+ return_type_annotation: missing (optional),
+ fat_arrow_token: FAT_ARROW@86..89 "=>" [] [Whitespace(" ")],
+ body: JsFunctionBody {
+ l_curly_token: L_CURLY@89..90 "{" [] [],
+ directives: JsDirectiveList [],
+ statements: JsStatementList [],
+ r_curly_token: R_CURLY@90..91 "}" [] [],
+ },
+ },
+ semicolon_token: SEMICOLON@91..92 ";" [] [],
+ },
+ JsExpressionStatement {
+ expression: JsArrowFunctionExpression {
+ async_token: missing (optional),
+ type_parameters: TsTypeParameters {
+ l_angle_token: L_ANGLE@92..94 "<" [Newline("\n")] [],
+ items: TsTypeParameterList [
+ TsTypeParameter {
+ name: TsTypeParameterName {
+ ident_token: IDENT@94..96 "A" [] [Whitespace(" ")],
+ },
+ constraint: TsTypeConstraintClause {
+ extends_token: EXTENDS_KW@96..104 "extends" [] [Whitespace(" ")],
+ ty: TsReferenceType {
+ name: JsReferenceIdentifier {
+ value_token: IDENT@104..105 "B" [] [],
+ },
+ type_arguments: TsTypeArguments {
+ l_angle_token: L_ANGLE@105..106 "<" [] [],
+ ts_type_argument_list: TsTypeArgumentList [
+ TsReferenceType {
+ name: JsReferenceIdentifier {
+ value_token: IDENT@106..107 "C" [] [],
+ },
+ type_arguments: missing (optional),
+ },
+ ],
+ r_angle_token: R_ANGLE@107..108 ">" [] [],
+ },
+ },
+ },
+ default: missing (optional),
+ },
+ ],
+ r_angle_token: R_ANGLE@108..109 ">" [] [],
+ },
+ parameters: JsParameters {
+ l_paren_token: L_PAREN@109..110 "(" [] [],
+ items: JsParameterList [],
+ r_paren_token: R_PAREN@110..112 ")" [] [Whitespace(" ")],
+ },
+ return_type_annotation: missing (optional),
+ fat_arrow_token: FAT_ARROW@112..115 "=>" [] [Whitespace(" ")],
+ body: JsFunctionBody {
+ l_curly_token: L_CURLY@115..116 "{" [] [],
+ directives: JsDirectiveList [],
+ statements: JsStatementList [],
+ r_curly_token: R_CURLY@116..117 "}" [] [],
+ },
+ },
+ semicolon_token: missing (optional),
+ },
+ ],
+ eof_token: EOF@117..118 "" [Newline("\n")] [],
+}
+
+0: JS_MODULE@0..118
+ 0: (empty)
+ 1: JS_DIRECTIVE_LIST@0..0
+ 2: JS_MODULE_ITEM_LIST@0..117
+ 0: JS_EXPRESSION_STATEMENT@0..56
+ 0: JS_ARROW_FUNCTION_EXPRESSION@0..55
+ 0: (empty)
+ 1: TS_TYPE_PARAMETERS@0..47
+ 0: L_ANGLE@0..35 "<" [Comments("// These are valid ty ..."), Newline("\n")] []
+ 1: TS_TYPE_PARAMETER_LIST@35..46
+ 0: TS_TYPE_PARAMETER@35..46
+ 0: TS_TYPE_PARAMETER_NAME@35..37
+ 0: IDENT@35..37 "A" [] [Whitespace(" ")]
+ 1: TS_TYPE_CONSTRAINT_CLAUSE@37..46
+ 0: EXTENDS_KW@37..45 "extends" [] [Whitespace(" ")]
+ 1: TS_REFERENCE_TYPE@45..46
+ 0: JS_REFERENCE_IDENTIFIER@45..46
+ 0: IDENT@45..46 "B" [] []
+ 1: (empty)
+ 2: (empty)
+ 2: R_ANGLE@46..47 ">" [] []
+ 2: JS_PARAMETERS@47..50
+ 0: L_PAREN@47..48 "(" [] []
+ 1: JS_PARAMETER_LIST@48..48
+ 2: R_PAREN@48..50 ")" [] [Whitespace(" ")]
+ 3: (empty)
+ 4: FAT_ARROW@50..53 "=>" [] [Whitespace(" ")]
+ 5: JS_FUNCTION_BODY@53..55
+ 0: L_CURLY@53..54 "{" [] []
+ 1: JS_DIRECTIVE_LIST@54..54
+ 2: JS_STATEMENT_LIST@54..54
+ 3: R_CURLY@54..55 "}" [] []
+ 1: SEMICOLON@55..56 ";" [] []
+ 1: JS_EXPRESSION_STATEMENT@56..76
+ 0: JS_ARROW_FUNCTION_EXPRESSION@56..75
+ 0: (empty)
+ 1: TS_TYPE_PARAMETERS@56..67
+ 0: L_ANGLE@56..58 "<" [Newline("\n")] []
+ 1: TS_TYPE_PARAMETER_LIST@58..66
+ 0: TS_TYPE_PARAMETER@58..66
+ 0: TS_TYPE_PARAMETER_NAME@58..59
+ 0: IDENT@58..59 "A" [] []
+ 1: (empty)
+ 2: TS_DEFAULT_TYPE_CLAUSE@59..66
+ 0: EQ@59..60 "=" [] []
+ 1: TS_STRING_TYPE@60..66
+ 0: STRING_KW@60..66 "string" [] []
+ 2: R_ANGLE@66..67 ">" [] []
+ 2: JS_PARAMETERS@67..70
+ 0: L_PAREN@67..68 "(" [] []
+ 1: JS_PARAMETER_LIST@68..68
+ 2: R_PAREN@68..70 ")" [] [Whitespace(" ")]
+ 3: (empty)
+ 4: FAT_ARROW@70..73 "=>" [] [Whitespace(" ")]
+ 5: JS_FUNCTION_BODY@73..75
+ 0: L_CURLY@73..74 "{" [] []
+ 1: JS_DIRECTIVE_LIST@74..74
+ 2: JS_STATEMENT_LIST@74..74
+ 3: R_CURLY@74..75 "}" [] []
+ 1: SEMICOLON@75..76 ";" [] []
+ 2: JS_EXPRESSION_STATEMENT@76..92
+ 0: JS_ARROW_FUNCTION_EXPRESSION@76..91
+ 0: (empty)
+ 1: TS_TYPE_PARAMETERS@76..83
+ 0: L_ANGLE@76..78 "<" [Newline("\n")] []
+ 1: TS_TYPE_PARAMETER_LIST@78..82
+ 0: TS_TYPE_PARAMETER@78..79
+ 0: TS_TYPE_PARAMETER_NAME@78..79
+ 0: IDENT@78..79 "A" [] []
+ 1: (empty)
+ 2: (empty)
+ 1: COMMA@79..81 "," [] [Whitespace(" ")]
+ 2: TS_TYPE_PARAMETER@81..82
+ 0: TS_TYPE_PARAMETER_NAME@81..82
+ 0: IDENT@81..82 "B" [] []
+ 1: (empty)
+ 2: (empty)
+ 2: R_ANGLE@82..83 ">" [] []
+ 2: JS_PARAMETERS@83..86
+ 0: L_PAREN@83..84 "(" [] []
+ 1: JS_PARAMETER_LIST@84..84
+ 2: R_PAREN@84..86 ")" [] [Whitespace(" ")]
+ 3: (empty)
+ 4: FAT_ARROW@86..89 "=>" [] [Whitespace(" ")]
+ 5: JS_FUNCTION_BODY@89..91
+ 0: L_CURLY@89..90 "{" [] []
+ 1: JS_DIRECTIVE_LIST@90..90
+ 2: JS_STATEMENT_LIST@90..90
+ 3: R_CURLY@90..91 "}" [] []
+ 1: SEMICOLON@91..92 ";" [] []
+ 3: JS_EXPRESSION_STATEMENT@92..117
+ 0: JS_ARROW_FUNCTION_EXPRESSION@92..117
+ 0: (empty)
+ 1: TS_TYPE_PARAMETERS@92..109
+ 0: L_ANGLE@92..94 "<" [Newline("\n")] []
+ 1: TS_TYPE_PARAMETER_LIST@94..108
+ 0: TS_TYPE_PARAMETER@94..108
+ 0: TS_TYPE_PARAMETER_NAME@94..96
+ 0: IDENT@94..96 "A" [] [Whitespace(" ")]
+ 1: TS_TYPE_CONSTRAINT_CLAUSE@96..108
+ 0: EXTENDS_KW@96..104 "extends" [] [Whitespace(" ")]
+ 1: TS_REFERENCE_TYPE@104..108
+ 0: JS_REFERENCE_IDENTIFIER@104..105
+ 0: IDENT@104..105 "B" [] []
+ 1: TS_TYPE_ARGUMENTS@105..108
+ 0: L_ANGLE@105..106 "<" [] []
+ 1: TS_TYPE_ARGUMENT_LIST@106..107
+ 0: TS_REFERENCE_TYPE@106..107
+ 0: JS_REFERENCE_IDENTIFIER@106..107
+ 0: IDENT@106..107 "C" [] []
+ 1: (empty)
+ 2: R_ANGLE@107..108 ">" [] []
+ 2: (empty)
+ 2: R_ANGLE@108..109 ">" [] []
+ 2: JS_PARAMETERS@109..112
+ 0: L_PAREN@109..110 "(" [] []
+ 1: JS_PARAMETER_LIST@110..110
+ 2: R_PAREN@110..112 ")" [] [Whitespace(" ")]
+ 3: (empty)
+ 4: FAT_ARROW@112..115 "=>" [] [Whitespace(" ")]
+ 5: JS_FUNCTION_BODY@115..117
+ 0: L_CURLY@115..116 "{" [] []
+ 1: JS_DIRECTIVE_LIST@116..116
+ 2: JS_STATEMENT_LIST@116..116
+ 3: R_CURLY@116..117 "}" [] []
+ 1: (empty)
+ 3: EOF@117..118 "" [Newline("\n")] []

ADD
crates/rslint_parser/test_data/inline/ok/tsx_type_arguments.tsx
+// These are valid type arguments
+<A extends B>() => {};
+<A=string>() => {};
+<A, B>() => {};
+<A extends B<C>>() => {}

ADD
crates/rslint_parser/test_data/inline/ok/type_arguments.jsx
+// These may look like a valid arrows but are JSX
+<A extends>() =</A>;
+<A extends="B">() =</A>;
+<A extends ok>() =</A>;

ADD
crates/rslint_parser/test_data/inline/ok/type_arguments.rast
+JsModule {
+ interpreter_token: missing (optional),
+ directives: JsDirectiveList [],
+ items: JsModuleItemList [
+ JsExpressionStatement {
+ expression: JsxElementExpression {
+ element: JsxElement {
+ opening_element: JsxOpeningElement {
+ l_angle_token: L_ANGLE@0..51 "<" [Comments("// These may look lik ..."), Newline("\n")] [],
+ name: JsxReferenceIdentifier {
+ value_token: JSX_IDENT@51..53 "A" [] [Whitespace(" ")],
+ },
+ attributes: JsxAttributeList [
+ JsxAttribute {
+ name: JsxName {
+ value_token: JSX_IDENT@53..60 "extends" [] [],
+ },
+ initializer: missing (optional),
+ },
+ ],
+ r_angle_token: R_ANGLE@60..61 ">" [] [],
+ },
+ children: JsxTextLiteral {
+ value_token: JSX_TEXT@61..65 "() =" [] [],
+ },
+ closing_element: JsxClosingElement {
+ l_angle_token: L_ANGLE@65..66 "<" [] [],
+ slash_token: SLASH@66..67 "/" [] [],
+ name: JsxReferenceIdentifier {
+ value_token: JSX_IDENT@67..68 "A" [] [],
+ },
+ r_angle_token: R_ANGLE@68..69 ">" [] [],
+ },
+ },
+ },
+ semicolon_token: SEMICOLON@69..70 ";" [] [],
+ },
+ JsExpressionStatement {
+ expression: JsxElementExpression {
+ element: JsxElement {
+ opening_element: JsxOpeningElement {
+ l_angle_token: L_ANGLE@70..72 "<" [Newline("\n")] [],
+ name: JsxReferenceIdentifier {
+ value_token: JSX_IDENT@72..74 "A" [] [Whitespace(" ")],
+ },
+ attributes: JsxAttributeList [
+ JsxAttribute {
+ name: JsxName {
+ value_token: JSX_IDENT@74..81 "extends" [] [],
+ },
+ initializer: JsxAttributeInitializerClause {
+ eq_token: EQ@81..82 "=" [] [],
+ value: JsxStringLiteral {
+ value_token: JS_STRING_LITERAL@82..85 "\"B\"" [] [],
+ },
+ },
+ },
+ ],
+ r_angle_token: R_ANGLE@85..86 ">" [] [],
+ },
+ children: JsxTextLiteral {
+ value_token: JSX_TEXT@86..90 "() =" [] [],
+ },
+ closing_element: JsxClosingElement {
+ l_angle_token: L_ANGLE@90..91 "<" [] [],
+ slash_token: SLASH@91..92 "/" [] [],
+ name: JsxReferenceIdentifier {
+ value_token: JSX_IDENT@92..93 "A" [] [],
+ },
+ r_angle_token: R_ANGLE@93..94 ">" [] [],
+ },
+ },
+ },
+ semicolon_token: SEMICOLON@94..95 ";" [] [],
+ },
+ JsExpressionStatement {
+ expression: JsxElementExpression {
+ element: JsxElement {
+ opening_element: JsxOpeningElement {
+ l_angle_token: L_ANGLE@95..97 "<" [Newline("\n")] [],
+ name: JsxReferenceIdentifier {
+ value_token: JSX_IDENT@97..99 "A" [] [Whitespace(" ")],
+ },
+ attributes: JsxAttributeList [
+ JsxAttribute {
+ name: JsxName {
+ value_token: JSX_IDENT@99..107 "extends" [] [Whitespace(" ")],
+ },
+ initializer: missing (optional),
+ },
+ JsxAttribute {
+ name: JsxName {
+ value_token: JSX_IDENT@107..109 "ok" [] [],
+ },
+ initializer: missing (optional),
+ },
+ ],
+ r_angle_token: R_ANGLE@109..110 ">" [] [],
+ },
+ children: JsxTextLiteral {
+ value_token: JSX_TEXT@110..114 "() =" [] [],
+ },
+ closing_element: JsxClosingElement {
+ l_angle_token: L_ANGLE@114..115 "<" [] [],
+ slash_token: SLASH@115..116 "/" [] [],
+ name: JsxReferenceIdentifier {
+ value_token: JSX_IDENT@116..117 "A" [] [],
+ },
+ r_angle_token: R_ANGLE@117..118 ">" [] [],
+ },
+ },
+ },
+ semicolon_token: SEMICOLON@118..119 ";" [] [],
+ },
+ ],
+ eof_token: EOF@119..120 "" [Newline("\n")] [],
+}
+
+0: JS_MODULE@0..120
+ 0: (empty)
+ 1: JS_DIRECTIVE_LIST@0..0
+ 2: JS_MODULE_ITEM_LIST@0..119
+ 0: JS_EXPRESSION_STATEMENT@0..70
+ 0: JSX_ELEMENT_EXPRESSION@0..69
+ 0: JSX_ELEMENT@0..69
+ 0: JSX_OPENING_ELEMENT@0..61
+ 0: L_ANGLE@0..51 "<" [Comments("// These may look lik ..."), Newline("\n")] []
+ 1: JSX_REFERENCE_IDENTIFIER@51..53
+ 0: JSX_IDENT@51..53 "A" [] [Whitespace(" ")]
+ 2: JSX_ATTRIBUTE_LIST@53..60
+ 0: JSX_ATTRIBUTE@53..60
+ 0: JSX_NAME@53..60
+ 0: JSX_IDENT@53..60 "extends" [] []
+ 1: (empty)
+ 3: R_ANGLE@60..61 ">" [] []
+ 1: JSX_TEXT_LITERAL@61..65
+ 0: JSX_TEXT@61..65 "() =" [] []
+ 2: JSX_CLOSING_ELEMENT@65..69
+ 0: L_ANGLE@65..66 "<" [] []
+ 1: SLASH@66..67 "/" [] []
+ 2: JSX_REFERENCE_IDENTIFIER@67..68
+ 0: JSX_IDENT@67..68 "A" [] []
+ 3: R_ANGLE@68..69 ">" [] []
+ 1: SEMICOLON@69..70 ";" [] []
+ 1: JS_EXPRESSION_STATEMENT@70..95
+ 0: JSX_ELEMENT_EXPRESSION@70..94
+ 0: JSX_ELEMENT@70..94
+ 0: JSX_OPENING_ELEMENT@70..86
+ 0: L_ANGLE@70..72 "<" [Newline("\n")] []
+ 1: JSX_REFERENCE_IDENTIFIER@72..74
+ 0: JSX_IDENT@72..74 "A" [] [Whitespace(" ")]
+ 2: JSX_ATTRIBUTE_LIST@74..85
+ 0: JSX_ATTRIBUTE@74..85
+ 0: JSX_NAME@74..81
+ 0: JSX_IDENT@74..81 "extends" [] []
+ 1: JSX_ATTRIBUTE_INITIALIZER_CLAUSE@81..85
+ 0: EQ@81..82 "=" [] []
+ 1: JSX_STRING_LITERAL@82..85
+ 0: JS_STRING_LITERAL@82..85 "\"B\"" [] []
+ 3: R_ANGLE@85..86 ">" [] []
+ 1: JSX_TEXT_LITERAL@86..90
+ 0: JSX_TEXT@86..90 "() =" [] []
+ 2: JSX_CLOSING_ELEMENT@90..94
+ 0: L_ANGLE@90..91 "<" [] []
+ 1: SLASH@91..92 "/" [] []
+ 2: JSX_REFERENCE_IDENTIFIER@92..93
+ 0: JSX_IDENT@92..93 "A" [] []
+ 3: R_ANGLE@93..94 ">" [] []
+ 1: SEMICOLON@94..95 ";" [] []
+ 2: JS_EXPRESSION_STATEMENT@95..119
+ 0: JSX_ELEMENT_EXPRESSION@95..118
+ 0: JSX_ELEMENT@95..118
+ 0: JSX_OPENING_ELEMENT@95..110
+ 0: L_ANGLE@95..97 "<" [Newline("\n")] []
+ 1: JSX_REFERENCE_IDENTIFIER@97..99
+ 0: JSX_IDENT@97..99 "A" [] [Whitespace(" ")]
+ 2: JSX_ATTRIBUTE_LIST@99..109
+ 0: JSX_ATTRIBUTE@99..107
+ 0: JSX_NAME@99..107
+ 0: JSX_IDENT@99..107 "extends" [] [Whitespace(" ")]
+ 1: (empty)
+ 1: JSX_ATTRIBUTE@107..109
+ 0: JSX_NAME@107..109
+ 0: JSX_IDENT@107..109 "ok" [] []
+ 1: (empty)
+ 3: R_ANGLE@109..110 ">" [] []
+ 1: JSX_TEXT_LITERAL@110..114
+ 0: JSX_TEXT@110..114 "() =" [] []
+ 2: JSX_CLOSING_ELEMENT@114..118
+ 0: L_ANGLE@114..115 "<" [] []
+ 1: SLASH@115..116 "/" [] []
+ 2: JSX_REFERENCE_IDENTIFIER@116..117
+ 0: JSX_IDENT@116..117 "A" [] []
+ 3: R_ANGLE@117..118 ">" [] []
+ 1: SEMICOLON@118..119 ";" [] []
+ 3: EOF@119..120 "" [Newline("\n")] []

MODIFY
xtask/codegen/src/parser_tests.rs
xtask/codegen/src/parser_tests.rs
@@ -52,7 +52,7 @@ pub fn generate_parser_tests(mode: Mode) -> Result<()> {
// ok is never actually read, but it needs to be specified to create a Test in existing_tests
let existing = existing_tests(&tests_dir, true)?;
for t in existing.keys().filter(|&t| !tests.contains_key(t)) {
- panic!("Test is deleted: {}", t);
+ panic!("Test is deleted: '{}'", t);
}
let mut some_file_was_updated = false;
@@ -60,15 +60,9 @@ pub fn generate_parser_tests(mode: Mode) -> Result<()> {
for (name, test) in tests {
let path = match existing.get(name) {
Some((path, _test)) => path.clone(),
- None => {
- let ext = match test.language.as_str() {
- "javascript" => "js",
- "typescript" => "ts",
- ext => ext,
- };
- let file_name = format!("{}.{}", name, ext);
- tests_dir.join(file_name)
- }
+ None => tests_dir
+ .join(name)
+ .with_extension(test.language.extension()),
};
if let crate::UpdateResult::Updated = update(&path, &test.text, mode)? {
some_file_was_updated = true;
@@ -102,7 +96,40 @@ struct Test {
pub name: String,
pub text: String,
pub ok: bool,
- pub language: String,
+ pub language: Language,
+}
+
+#[derive(Debug)]
+enum Language {
+ JavaScript,
+ TypeScript,
+ Jsx,
+ Tsx,
+}
+
+impl Language {
+ const fn extension(&self) -> &'static str {
+ match self {
+ Language::JavaScript => "js",
+ Language::TypeScript => "ts",
+ Language::Jsx => "jsx",
+ Language::Tsx => "tsx",
+ }
+ }
+
+ fn from_extension(extension: &str) -> Option<Language> {
+ let language = match extension {
+ "js" => Language::JavaScript,
+ "ts" => Language::TypeScript,
+ "jsx" => Language::Jsx,
+ "tsx" => Language::Tsx,
+ _ => {
+ return None;
+ }
+ };
+
+ Some(language)
+ }
}
#[derive(Default, Debug)]
@@ -115,39 +142,35 @@ fn collect_tests(s: &str) -> Vec<Test> {
let mut res = Vec::new();
for comment_block in extract_comment_blocks(s, false).into_iter().map(|(_, x)| x) {
let first_line = &comment_block[0];
- let (language, name, ok) = if let Some(first_line) = first_line.strip_prefix("test jsx ") {
- let name = first_line.to_string();
- ("jsx", name, true)
- } else if let Some(first_line) = first_line.strip_prefix("test_err jsx ") {
- let name = first_line.to_string();
- ("jsx", name, false)
- } else if let Some(first_line) = first_line.strip_prefix("test ts ") {
- let name = first_line.to_string();
- ("typescript", name, true)
- } else if let Some(first_line) = first_line.strip_prefix("test_err ts ") {
- let name = first_line.to_string();
- ("typescript", name, false)
- } else if let Some(first_line) = first_line.strip_prefix("test ") {
- let name = first_line.to_string();
- ("javascript", name, true)
- } else if let Some(first_line) = first_line.strip_prefix("test_err ") {
- let name = first_line.to_string();
- ("javascript", name, false)
- } else {
- continue;
+
+ let (ok, suffix) = match first_line.split_once(' ') {
+ Some(("test", suffix)) => (true, suffix),
+ Some(("test_err", suffix)) => (false, suffix),
+ _ => continue,
};
+
+ let (language, name) = match suffix.split_once(' ') {
+ Some(("jsx", name)) => (Language::Jsx, name),
+ Some(("js", name)) => (Language::JavaScript, name),
+ Some(("ts", name)) => (Language::TypeScript, name),
+ Some(("tsx", name)) => (Language::Tsx, name),
+ Some((name, _)) => (Language::JavaScript, name),
+ _ => (Language::JavaScript, suffix),
+ };
+
let text: String = comment_block[1..]
.iter()
.cloned()
.chain(iter::once(String::new()))
.collect::<Vec<_>>()
.join("\n");
+
assert!(!text.trim().is_empty() && text.ends_with('\n'));
res.push(Test {
- name,
+ name: name.to_string(),
text,
ok,
- language: language.to_string(),
+ language,
})
}
res
@@ -183,17 +206,15 @@ fn tests_from_dir(dir: &Path) -> Result<Tests> {
}
fn existing_tests(dir: &Path, ok: bool) -> Result<HashMap<String, (PathBuf, Test)>> {
- let exts = ["js", "ts", "jsx"];
let mut res = HashMap::new();
for file in fs::read_dir(dir)? {
let path = file?.path();
- let ext = path
+ let language = path
.extension()
- .map(|x| x.to_string_lossy().to_string())
- .unwrap_or_default();
- if !exts.contains(&ext.as_str()) {
- continue;
- }
+ .and_then(|ext| ext.to_str())
+ .and_then(Language::from_extension);
+
+ if let Some(language) = language {
let name = path
.file_stem()
.map(|x| x.to_string_lossy().to_string())
@@ -203,11 +224,12 @@ fn existing_tests(dir: &Path, ok: bool) -> Result<HashMap<String, (PathBuf, Test
name: name.clone(),
text,
ok,
- language: ext,
+ language,
};
if let Some(old) = res.insert(name, (path, test)) {
println!("Duplicate test: {:?}", old);
}
}
+ }
Ok(res)
}

fix(rslint_parser): Don't parse JSX as arrow functions (#2240)

Don't parse `<A extends>()` or `<A extends=B>` or `<A extends ok>()` as arrow functions, these must be starts of a JSX element.

Depends on attribute parsing for the tests to pass