diff --git a/.changeset/fix-js-parser-9548.md b/.changeset/fix-js-parser-9548.md new file mode 100644 index 000000000000..f4efa03828b5 --- /dev/null +++ b/.changeset/fix-js-parser-9548.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9548](https://github.com/biomejs/biome/issues/9548): Biome now parses conditional expressions whose consequent is an arrow function returning a parenthesized object expression. diff --git a/crates/biome_js_parser/src/syntax/expr.rs b/crates/biome_js_parser/src/syntax/expr.rs index 5b23ac83dc3c..7b157b5a5fda 100644 --- a/crates/biome_js_parser/src/syntax/expr.rs +++ b/crates/biome_js_parser/src/syntax/expr.rs @@ -53,6 +53,7 @@ enum ExpressionContextFlag { AllowObjectExpression = 1 << 1, InDecorator = 1 << 2, AllowTSTypeAssertion = 1 << 3, + InConditionalConsequent = 1 << 4, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -80,6 +81,10 @@ impl ExpressionContextFlags { const ALLOW_TS_TYPE_ASSERTION: Self = Self(make_bitflags!(ExpressionContextFlag::{AllowTSTypeAssertion})); + /// If `true`, the parser is inside the consequent of a conditional expression. + const IN_CONDITIONAL_CONSEQUENT: Self = + Self(make_bitflags!(ExpressionContextFlag::{InConditionalConsequent})); + pub fn contains(&self, other: impl Into) -> bool { self.0.contains(other.into().0) } @@ -124,6 +129,13 @@ impl ExpressionContext { self.and(ExpressionContextFlags::ALLOW_TS_TYPE_ASSERTION, allowed) } + pub(crate) fn and_in_conditional_consequent(self, in_conditional_consequent: bool) -> Self { + self.and( + ExpressionContextFlags::IN_CONDITIONAL_CONSEQUENT, + in_conditional_consequent, + ) + } + /// Returns true if object expressions or object patterns are valid in this context pub(crate) fn is_object_expression_allowed(&self) -> bool { self.0 @@ -140,6 +152,11 @@ impl ExpressionContext { self.0.contains(ExpressionContextFlags::IN_DECORATOR) } + pub(crate) fn is_in_conditional_consequent(&self) -> bool { + self.0 + .contains(ExpressionContextFlags::IN_CONDITIONAL_CONSEQUENT) + } + /// Adds the `flag` if `set` is `true`, otherwise removes the `flag` fn and(self, flag: ExpressionContextFlags, set: bool) -> Self { Self(if set { self.0 | flag } else { self.0 - flag }) @@ -275,7 +292,7 @@ pub(crate) fn parse_assignment_expression_or_higher( p: &mut JsParser, context: ExpressionContext, ) -> ParsedSyntax { - let arrow_expression = parse_arrow_function_expression(p); + let arrow_expression = parse_arrow_function_expression(p, context); if arrow_expression.is_present() { return arrow_expression; @@ -302,6 +319,13 @@ fn parse_assignment_expression_or_higher_base( .and_then(|target| parse_assign_expr_recursive(p, target, checkpoint, context)) } +pub(crate) fn parse_assignment_expression_or_higher_no_arrow( + p: &mut JsParser, + context: ExpressionContext, +) -> ParsedSyntax { + parse_assignment_expression_or_higher_base(p, context) +} + // test js assign_expr // foo += bar = b ??= 3; // foo -= bar; @@ -463,8 +487,11 @@ pub(super) fn parse_conditional_expr(p: &mut JsParser, context: ExpressionContex let m = marker.precede(p); p.bump(T![?]); - parse_conditional_expr_consequent(p, ExpressionContext::default()) - .or_add_diagnostic(p, js_parse_error::expected_expression_assignment); + parse_conditional_expr_consequent( + p, + ExpressionContext::default().and_in_conditional_consequent(true), + ) + .or_add_diagnostic(p, js_parse_error::expected_expression_assignment); p.expect(T![:]); @@ -486,7 +513,7 @@ pub(super) fn parse_conditional_expr(p: &mut JsParser, context: ExpressionContex fn parse_conditional_expr_consequent(p: &mut JsParser, context: ExpressionContext) -> ParsedSyntax { let checkpoint = p.checkpoint(); - let arrow_expression = parse_arrow_function_expression(p); + let arrow_expression = parse_arrow_function_expression(p, context); if arrow_expression.is_present() && p.at(T![:]) { return arrow_expression; } diff --git a/crates/biome_js_parser/src/syntax/function.rs b/crates/biome_js_parser/src/syntax/function.rs index 86e51b06cca2..8969c66bf6a8 100644 --- a/crates/biome_js_parser/src/syntax/function.rs +++ b/crates/biome_js_parser/src/syntax/function.rs @@ -9,6 +9,7 @@ use crate::syntax::class::{ }; use crate::syntax::expr::{ ExpressionContext, is_nth_at_identifier, parse_assignment_expression_or_higher, + parse_assignment_expression_or_higher_no_arrow, }; use crate::syntax::js_parse_error; use crate::syntax::js_parse_error::{ @@ -523,9 +524,12 @@ impl Ambiguity { } } -pub(crate) fn parse_arrow_function_expression(p: &mut JsParser) -> ParsedSyntax { - parse_parenthesized_arrow_function_expression(p) - .or_else(|| parse_arrow_function_with_single_parameter(p)) +pub(crate) fn parse_arrow_function_expression( + p: &mut JsParser, + context: ExpressionContext, +) -> ParsedSyntax { + parse_parenthesized_arrow_function_expression(p, context) + .or_else(|| parse_arrow_function_with_single_parameter(p, context)) } /// Tries to parse the header of a parenthesized arrow function expression. @@ -608,7 +612,10 @@ fn try_parse_parenthesized_arrow_function_head( // test ts ts_arrow_function_type_parameters // let a = (a: A, b: B, c: C) => "hello"; // let b = async (a: A, b: B): Promise => "hello"; -fn parse_possible_parenthesized_arrow_function_expression(p: &mut JsParser) -> ParsedSyntax { +fn parse_possible_parenthesized_arrow_function_expression( + p: &mut JsParser, + context: ExpressionContext, +) -> ParsedSyntax { let start_pos = p.cur_range().start(); // Test if we already tried to parse this position as an arrow function and failed. @@ -621,7 +628,8 @@ fn parse_possible_parenthesized_arrow_function_expression(p: &mut JsParser) -> P try_parse_parenthesized_arrow_function_head(p, Ambiguity::Disallowed) }) { Ok((m, flags)) => { - parse_arrow_body(p, flags).or_add_diagnostic(p, js_parse_error::expected_arrow_body); + parse_arrow_body(p, flags, context) + .or_add_diagnostic(p, js_parse_error::expected_arrow_body); Present(m.complete(p, JS_ARROW_FUNCTION_EXPRESSION)) } @@ -636,16 +644,22 @@ fn parse_possible_parenthesized_arrow_function_expression(p: &mut JsParser) -> P } } -fn parse_parenthesized_arrow_function_expression(p: &mut JsParser) -> ParsedSyntax { +fn parse_parenthesized_arrow_function_expression( + p: &mut JsParser, + context: ExpressionContext, +) -> 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'."); - parse_arrow_body(p, flags).or_add_diagnostic(p, js_parse_error::expected_arrow_body); + 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'."); + parse_arrow_body(p, flags, context) + .or_add_diagnostic(p, js_parse_error::expected_arrow_body); Present(m.complete(p, JS_ARROW_FUNCTION_EXPRESSION)) } IsParenthesizedArrowFunctionExpression::Unknown => { - parse_possible_parenthesized_arrow_function_expression(p) + parse_possible_parenthesized_arrow_function_expression(p, context) } IsParenthesizedArrowFunctionExpression::False => Absent, } @@ -840,7 +854,10 @@ fn arrow_function_parameter_flags(p: &JsParser, mut flags: SignatureFlags) -> Si // await => {} // baz => // {} -fn parse_arrow_function_with_single_parameter(p: &mut JsParser) -> ParsedSyntax { +fn parse_arrow_function_with_single_parameter( + p: &mut JsParser, + context: ExpressionContext, +) -> ParsedSyntax { if !is_arrow_function_with_single_parameter(p) { return Absent; } @@ -863,7 +880,7 @@ fn parse_arrow_function_with_single_parameter(p: &mut JsParser) -> ParsedSyntax .expect("Expected function parameter to be present as guaranteed by is_arrow_function_with_simple_parameter"); p.bump(T![=>]); - parse_arrow_body(p, flags).or_add_diagnostic(p, js_parse_error::expected_arrow_body); + parse_arrow_body(p, flags, context).or_add_diagnostic(p, js_parse_error::expected_arrow_body); Present(m.complete(p, JS_ARROW_FUNCTION_EXPRESSION)) } @@ -885,7 +902,11 @@ fn is_arrow_function_with_single_parameter(p: &mut JsParser) -> bool { } } -fn parse_arrow_body(p: &mut JsParser, mut flags: SignatureFlags) -> ParsedSyntax { +fn parse_arrow_body( + p: &mut JsParser, + mut flags: SignatureFlags, + context: ExpressionContext, +) -> ParsedSyntax { // test js arrow_in_constructor // class A { // constructor() { @@ -901,7 +922,14 @@ fn parse_arrow_body(p: &mut JsParser, mut flags: SignatureFlags) -> ParsedSyntax parse_function_body(p, flags) } else { p.with_state(EnterFunction(flags), |p| { - parse_assignment_expression_or_higher(p, ExpressionContext::default()) + if context.is_in_conditional_consequent() + && matches!(p.cur(), T!['(']) + && matches!(p.nth(1), T!['{'] | T!['[']) + { + parse_assignment_expression_or_higher_no_arrow(p, context) + } else { + parse_assignment_expression_or_higher(p, context) + } }) } } diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.js b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.js new file mode 100644 index 000000000000..487bf64adfe0 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.js @@ -0,0 +1,3 @@ +const slotFn = isFirstMount + ? i => ({ [CONTENT_SLOT]: i }) + : i => wrapSlotExpr(newExprs[i]); diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.js.snap b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.js.snap new file mode 100644 index 000000000000..9b91a9a3fe85 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.js.snap @@ -0,0 +1,211 @@ +--- +source: crates/biome_js_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```js +const slotFn = isFirstMount + ? i => ({ [CONTENT_SLOT]: i }) + : i => wrapSlotExpr(newExprs[i]); + +``` + + +## AST + +``` +JsModule { + bom_token: missing (optional), + interpreter_token: missing (optional), + directives: JsDirectiveList [], + items: JsModuleItemList [ + JsVariableStatement { + declaration: JsVariableDeclaration { + await_token: missing (optional), + kind: CONST_KW@0..6 "const" [] [Whitespace(" ")], + declarators: JsVariableDeclaratorList [ + JsVariableDeclarator { + id: JsIdentifierBinding { + name_token: IDENT@6..13 "slotFn" [] [Whitespace(" ")], + }, + variable_annotation: missing (optional), + initializer: JsInitializerClause { + eq_token: EQ@13..15 "=" [] [Whitespace(" ")], + expression: JsConditionalExpression { + test: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@15..27 "isFirstMount" [] [], + }, + }, + question_mark_token: QUESTION@27..32 "?" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + consequent: JsArrowFunctionExpression { + async_token: missing (optional), + type_parameters: missing (optional), + parameters: JsIdentifierBinding { + name_token: IDENT@32..34 "i" [] [Whitespace(" ")], + }, + return_type_annotation: missing (optional), + fat_arrow_token: FAT_ARROW@34..37 "=>" [] [Whitespace(" ")], + body: JsParenthesizedExpression { + l_paren_token: L_PAREN@37..38 "(" [] [], + expression: JsObjectExpression { + l_curly_token: L_CURLY@38..40 "{" [] [Whitespace(" ")], + members: JsObjectMemberList [ + JsPropertyObjectMember { + name: JsComputedMemberName { + l_brack_token: L_BRACK@40..41 "[" [] [], + expression: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@41..53 "CONTENT_SLOT" [] [], + }, + }, + r_brack_token: R_BRACK@53..54 "]" [] [], + }, + colon_token: COLON@54..56 ":" [] [Whitespace(" ")], + value: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@56..58 "i" [] [Whitespace(" ")], + }, + }, + }, + ], + r_curly_token: R_CURLY@58..59 "}" [] [], + }, + r_paren_token: R_PAREN@59..60 ")" [] [], + }, + }, + colon_token: COLON@60..65 ":" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternate: JsArrowFunctionExpression { + async_token: missing (optional), + type_parameters: missing (optional), + parameters: JsIdentifierBinding { + name_token: IDENT@65..67 "i" [] [Whitespace(" ")], + }, + return_type_annotation: missing (optional), + fat_arrow_token: FAT_ARROW@67..70 "=>" [] [Whitespace(" ")], + body: JsCallExpression { + callee: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@70..82 "wrapSlotExpr" [] [], + }, + }, + optional_chain_token: missing (optional), + type_arguments: missing (optional), + arguments: JsCallArguments { + l_paren_token: L_PAREN@82..83 "(" [] [], + args: JsCallArgumentList [ + JsComputedMemberExpression { + object: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@83..91 "newExprs" [] [], + }, + }, + optional_chain_token: missing (optional), + l_brack_token: L_BRACK@91..92 "[" [] [], + member: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@92..93 "i" [] [], + }, + }, + r_brack_token: R_BRACK@93..94 "]" [] [], + }, + ], + r_paren_token: R_PAREN@94..95 ")" [] [], + }, + }, + }, + }, + }, + }, + ], + }, + semicolon_token: SEMICOLON@95..96 ";" [] [], + }, + ], + eof_token: EOF@96..97 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: JS_MODULE@0..97 + 0: (empty) + 1: (empty) + 2: JS_DIRECTIVE_LIST@0..0 + 3: JS_MODULE_ITEM_LIST@0..96 + 0: JS_VARIABLE_STATEMENT@0..96 + 0: JS_VARIABLE_DECLARATION@0..95 + 0: (empty) + 1: CONST_KW@0..6 "const" [] [Whitespace(" ")] + 2: JS_VARIABLE_DECLARATOR_LIST@6..95 + 0: JS_VARIABLE_DECLARATOR@6..95 + 0: JS_IDENTIFIER_BINDING@6..13 + 0: IDENT@6..13 "slotFn" [] [Whitespace(" ")] + 1: (empty) + 2: JS_INITIALIZER_CLAUSE@13..95 + 0: EQ@13..15 "=" [] [Whitespace(" ")] + 1: JS_CONDITIONAL_EXPRESSION@15..95 + 0: JS_IDENTIFIER_EXPRESSION@15..27 + 0: JS_REFERENCE_IDENTIFIER@15..27 + 0: IDENT@15..27 "isFirstMount" [] [] + 1: QUESTION@27..32 "?" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] + 2: JS_ARROW_FUNCTION_EXPRESSION@32..60 + 0: (empty) + 1: (empty) + 2: JS_IDENTIFIER_BINDING@32..34 + 0: IDENT@32..34 "i" [] [Whitespace(" ")] + 3: (empty) + 4: FAT_ARROW@34..37 "=>" [] [Whitespace(" ")] + 5: JS_PARENTHESIZED_EXPRESSION@37..60 + 0: L_PAREN@37..38 "(" [] [] + 1: JS_OBJECT_EXPRESSION@38..59 + 0: L_CURLY@38..40 "{" [] [Whitespace(" ")] + 1: JS_OBJECT_MEMBER_LIST@40..58 + 0: JS_PROPERTY_OBJECT_MEMBER@40..58 + 0: JS_COMPUTED_MEMBER_NAME@40..54 + 0: L_BRACK@40..41 "[" [] [] + 1: JS_IDENTIFIER_EXPRESSION@41..53 + 0: JS_REFERENCE_IDENTIFIER@41..53 + 0: IDENT@41..53 "CONTENT_SLOT" [] [] + 2: R_BRACK@53..54 "]" [] [] + 1: COLON@54..56 ":" [] [Whitespace(" ")] + 2: JS_IDENTIFIER_EXPRESSION@56..58 + 0: JS_REFERENCE_IDENTIFIER@56..58 + 0: IDENT@56..58 "i" [] [Whitespace(" ")] + 2: R_CURLY@58..59 "}" [] [] + 2: R_PAREN@59..60 ")" [] [] + 3: COLON@60..65 ":" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] + 4: JS_ARROW_FUNCTION_EXPRESSION@65..95 + 0: (empty) + 1: (empty) + 2: JS_IDENTIFIER_BINDING@65..67 + 0: IDENT@65..67 "i" [] [Whitespace(" ")] + 3: (empty) + 4: FAT_ARROW@67..70 "=>" [] [Whitespace(" ")] + 5: JS_CALL_EXPRESSION@70..95 + 0: JS_IDENTIFIER_EXPRESSION@70..82 + 0: JS_REFERENCE_IDENTIFIER@70..82 + 0: IDENT@70..82 "wrapSlotExpr" [] [] + 1: (empty) + 2: (empty) + 3: JS_CALL_ARGUMENTS@82..95 + 0: L_PAREN@82..83 "(" [] [] + 1: JS_CALL_ARGUMENT_LIST@83..94 + 0: JS_COMPUTED_MEMBER_EXPRESSION@83..94 + 0: JS_IDENTIFIER_EXPRESSION@83..91 + 0: JS_REFERENCE_IDENTIFIER@83..91 + 0: IDENT@83..91 "newExprs" [] [] + 1: (empty) + 2: L_BRACK@91..92 "[" [] [] + 3: JS_IDENTIFIER_EXPRESSION@92..93 + 0: JS_REFERENCE_IDENTIFIER@92..93 + 0: IDENT@92..93 "i" [] [] + 4: R_BRACK@93..94 "]" [] [] + 2: R_PAREN@94..95 ")" [] [] + 1: SEMICOLON@95..96 ";" [] [] + 4: EOF@96..97 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.ts b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.ts new file mode 100644 index 000000000000..487bf64adfe0 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.ts @@ -0,0 +1,3 @@ +const slotFn = isFirstMount + ? i => ({ [CONTENT_SLOT]: i }) + : i => wrapSlotExpr(newExprs[i]); diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.ts.snap b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.ts.snap new file mode 100644 index 000000000000..575ee5987758 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_object_body_in_consequent.ts.snap @@ -0,0 +1,211 @@ +--- +source: crates/biome_js_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```ts +const slotFn = isFirstMount + ? i => ({ [CONTENT_SLOT]: i }) + : i => wrapSlotExpr(newExprs[i]); + +``` + + +## AST + +``` +JsModule { + bom_token: missing (optional), + interpreter_token: missing (optional), + directives: JsDirectiveList [], + items: JsModuleItemList [ + JsVariableStatement { + declaration: JsVariableDeclaration { + await_token: missing (optional), + kind: CONST_KW@0..6 "const" [] [Whitespace(" ")], + declarators: JsVariableDeclaratorList [ + JsVariableDeclarator { + id: JsIdentifierBinding { + name_token: IDENT@6..13 "slotFn" [] [Whitespace(" ")], + }, + variable_annotation: missing (optional), + initializer: JsInitializerClause { + eq_token: EQ@13..15 "=" [] [Whitespace(" ")], + expression: JsConditionalExpression { + test: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@15..27 "isFirstMount" [] [], + }, + }, + question_mark_token: QUESTION@27..32 "?" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + consequent: JsArrowFunctionExpression { + async_token: missing (optional), + type_parameters: missing (optional), + parameters: JsIdentifierBinding { + name_token: IDENT@32..34 "i" [] [Whitespace(" ")], + }, + return_type_annotation: missing (optional), + fat_arrow_token: FAT_ARROW@34..37 "=>" [] [Whitespace(" ")], + body: JsParenthesizedExpression { + l_paren_token: L_PAREN@37..38 "(" [] [], + expression: JsObjectExpression { + l_curly_token: L_CURLY@38..40 "{" [] [Whitespace(" ")], + members: JsObjectMemberList [ + JsPropertyObjectMember { + name: JsComputedMemberName { + l_brack_token: L_BRACK@40..41 "[" [] [], + expression: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@41..53 "CONTENT_SLOT" [] [], + }, + }, + r_brack_token: R_BRACK@53..54 "]" [] [], + }, + colon_token: COLON@54..56 ":" [] [Whitespace(" ")], + value: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@56..58 "i" [] [Whitespace(" ")], + }, + }, + }, + ], + r_curly_token: R_CURLY@58..59 "}" [] [], + }, + r_paren_token: R_PAREN@59..60 ")" [] [], + }, + }, + colon_token: COLON@60..65 ":" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternate: JsArrowFunctionExpression { + async_token: missing (optional), + type_parameters: missing (optional), + parameters: JsIdentifierBinding { + name_token: IDENT@65..67 "i" [] [Whitespace(" ")], + }, + return_type_annotation: missing (optional), + fat_arrow_token: FAT_ARROW@67..70 "=>" [] [Whitespace(" ")], + body: JsCallExpression { + callee: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@70..82 "wrapSlotExpr" [] [], + }, + }, + optional_chain_token: missing (optional), + type_arguments: missing (optional), + arguments: JsCallArguments { + l_paren_token: L_PAREN@82..83 "(" [] [], + args: JsCallArgumentList [ + JsComputedMemberExpression { + object: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@83..91 "newExprs" [] [], + }, + }, + optional_chain_token: missing (optional), + l_brack_token: L_BRACK@91..92 "[" [] [], + member: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@92..93 "i" [] [], + }, + }, + r_brack_token: R_BRACK@93..94 "]" [] [], + }, + ], + r_paren_token: R_PAREN@94..95 ")" [] [], + }, + }, + }, + }, + }, + }, + ], + }, + semicolon_token: SEMICOLON@95..96 ";" [] [], + }, + ], + eof_token: EOF@96..97 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: JS_MODULE@0..97 + 0: (empty) + 1: (empty) + 2: JS_DIRECTIVE_LIST@0..0 + 3: JS_MODULE_ITEM_LIST@0..96 + 0: JS_VARIABLE_STATEMENT@0..96 + 0: JS_VARIABLE_DECLARATION@0..95 + 0: (empty) + 1: CONST_KW@0..6 "const" [] [Whitespace(" ")] + 2: JS_VARIABLE_DECLARATOR_LIST@6..95 + 0: JS_VARIABLE_DECLARATOR@6..95 + 0: JS_IDENTIFIER_BINDING@6..13 + 0: IDENT@6..13 "slotFn" [] [Whitespace(" ")] + 1: (empty) + 2: JS_INITIALIZER_CLAUSE@13..95 + 0: EQ@13..15 "=" [] [Whitespace(" ")] + 1: JS_CONDITIONAL_EXPRESSION@15..95 + 0: JS_IDENTIFIER_EXPRESSION@15..27 + 0: JS_REFERENCE_IDENTIFIER@15..27 + 0: IDENT@15..27 "isFirstMount" [] [] + 1: QUESTION@27..32 "?" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] + 2: JS_ARROW_FUNCTION_EXPRESSION@32..60 + 0: (empty) + 1: (empty) + 2: JS_IDENTIFIER_BINDING@32..34 + 0: IDENT@32..34 "i" [] [Whitespace(" ")] + 3: (empty) + 4: FAT_ARROW@34..37 "=>" [] [Whitespace(" ")] + 5: JS_PARENTHESIZED_EXPRESSION@37..60 + 0: L_PAREN@37..38 "(" [] [] + 1: JS_OBJECT_EXPRESSION@38..59 + 0: L_CURLY@38..40 "{" [] [Whitespace(" ")] + 1: JS_OBJECT_MEMBER_LIST@40..58 + 0: JS_PROPERTY_OBJECT_MEMBER@40..58 + 0: JS_COMPUTED_MEMBER_NAME@40..54 + 0: L_BRACK@40..41 "[" [] [] + 1: JS_IDENTIFIER_EXPRESSION@41..53 + 0: JS_REFERENCE_IDENTIFIER@41..53 + 0: IDENT@41..53 "CONTENT_SLOT" [] [] + 2: R_BRACK@53..54 "]" [] [] + 1: COLON@54..56 ":" [] [Whitespace(" ")] + 2: JS_IDENTIFIER_EXPRESSION@56..58 + 0: JS_REFERENCE_IDENTIFIER@56..58 + 0: IDENT@56..58 "i" [] [Whitespace(" ")] + 2: R_CURLY@58..59 "}" [] [] + 2: R_PAREN@59..60 ")" [] [] + 3: COLON@60..65 ":" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] + 4: JS_ARROW_FUNCTION_EXPRESSION@65..95 + 0: (empty) + 1: (empty) + 2: JS_IDENTIFIER_BINDING@65..67 + 0: IDENT@65..67 "i" [] [Whitespace(" ")] + 3: (empty) + 4: FAT_ARROW@67..70 "=>" [] [Whitespace(" ")] + 5: JS_CALL_EXPRESSION@70..95 + 0: JS_IDENTIFIER_EXPRESSION@70..82 + 0: JS_REFERENCE_IDENTIFIER@70..82 + 0: IDENT@70..82 "wrapSlotExpr" [] [] + 1: (empty) + 2: (empty) + 3: JS_CALL_ARGUMENTS@82..95 + 0: L_PAREN@82..83 "(" [] [] + 1: JS_CALL_ARGUMENT_LIST@83..94 + 0: JS_COMPUTED_MEMBER_EXPRESSION@83..94 + 0: JS_IDENTIFIER_EXPRESSION@83..91 + 0: JS_REFERENCE_IDENTIFIER@83..91 + 0: IDENT@83..91 "newExprs" [] [] + 1: (empty) + 2: L_BRACK@91..92 "[" [] [] + 3: JS_IDENTIFIER_EXPRESSION@92..93 + 0: JS_REFERENCE_IDENTIFIER@92..93 + 0: IDENT@92..93 "i" [] [] + 4: R_BRACK@93..94 "]" [] [] + 2: R_PAREN@94..95 ")" [] [] + 1: SEMICOLON@95..96 ";" [] [] + 4: EOF@96..97 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_return_type_in_consequent.ts b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_return_type_in_consequent.ts new file mode 100644 index 000000000000..800af14a9bf5 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_return_type_in_consequent.ts @@ -0,0 +1,3 @@ +const slotFn = isFirstMount + ? ((i): Slot => ({ [CONTENT_SLOT]: i })) + : i => wrapSlotExpr(newExprs[i]); diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_return_type_in_consequent.ts.snap b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_return_type_in_consequent.ts.snap new file mode 100644 index 000000000000..9a6143acd7f5 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_arrow_return_type_in_consequent.ts.snap @@ -0,0 +1,252 @@ +--- +source: crates/biome_js_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```ts +const slotFn = isFirstMount + ? ((i): Slot => ({ [CONTENT_SLOT]: i })) + : i => wrapSlotExpr(newExprs[i]); + +``` + + +## AST + +``` +JsModule { + bom_token: missing (optional), + interpreter_token: missing (optional), + directives: JsDirectiveList [], + items: JsModuleItemList [ + JsVariableStatement { + declaration: JsVariableDeclaration { + await_token: missing (optional), + kind: CONST_KW@0..6 "const" [] [Whitespace(" ")], + declarators: JsVariableDeclaratorList [ + JsVariableDeclarator { + id: JsIdentifierBinding { + name_token: IDENT@6..13 "slotFn" [] [Whitespace(" ")], + }, + variable_annotation: missing (optional), + initializer: JsInitializerClause { + eq_token: EQ@13..15 "=" [] [Whitespace(" ")], + expression: JsConditionalExpression { + test: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@15..27 "isFirstMount" [] [], + }, + }, + question_mark_token: QUESTION@27..32 "?" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + consequent: JsParenthesizedExpression { + l_paren_token: L_PAREN@32..33 "(" [] [], + expression: JsArrowFunctionExpression { + async_token: missing (optional), + type_parameters: missing (optional), + parameters: JsParameters { + l_paren_token: L_PAREN@33..34 "(" [] [], + items: JsParameterList [ + JsFormalParameter { + decorators: JsDecoratorList [], + binding: JsIdentifierBinding { + name_token: IDENT@34..35 "i" [] [], + }, + question_mark_token: missing (optional), + type_annotation: missing (optional), + initializer: missing (optional), + }, + ], + r_paren_token: R_PAREN@35..36 ")" [] [], + }, + return_type_annotation: TsReturnTypeAnnotation { + colon_token: COLON@36..38 ":" [] [Whitespace(" ")], + ty: TsReferenceType { + name: JsReferenceIdentifier { + value_token: IDENT@38..43 "Slot" [] [Whitespace(" ")], + }, + type_arguments: missing (optional), + }, + }, + fat_arrow_token: FAT_ARROW@43..46 "=>" [] [Whitespace(" ")], + body: JsParenthesizedExpression { + l_paren_token: L_PAREN@46..47 "(" [] [], + expression: JsObjectExpression { + l_curly_token: L_CURLY@47..49 "{" [] [Whitespace(" ")], + members: JsObjectMemberList [ + JsPropertyObjectMember { + name: JsComputedMemberName { + l_brack_token: L_BRACK@49..50 "[" [] [], + expression: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@50..62 "CONTENT_SLOT" [] [], + }, + }, + r_brack_token: R_BRACK@62..63 "]" [] [], + }, + colon_token: COLON@63..65 ":" [] [Whitespace(" ")], + value: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@65..67 "i" [] [Whitespace(" ")], + }, + }, + }, + ], + r_curly_token: R_CURLY@67..68 "}" [] [], + }, + r_paren_token: R_PAREN@68..69 ")" [] [], + }, + }, + r_paren_token: R_PAREN@69..70 ")" [] [], + }, + colon_token: COLON@70..75 ":" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternate: JsArrowFunctionExpression { + async_token: missing (optional), + type_parameters: missing (optional), + parameters: JsIdentifierBinding { + name_token: IDENT@75..77 "i" [] [Whitespace(" ")], + }, + return_type_annotation: missing (optional), + fat_arrow_token: FAT_ARROW@77..80 "=>" [] [Whitespace(" ")], + body: JsCallExpression { + callee: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@80..92 "wrapSlotExpr" [] [], + }, + }, + optional_chain_token: missing (optional), + type_arguments: missing (optional), + arguments: JsCallArguments { + l_paren_token: L_PAREN@92..93 "(" [] [], + args: JsCallArgumentList [ + JsComputedMemberExpression { + object: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@93..101 "newExprs" [] [], + }, + }, + optional_chain_token: missing (optional), + l_brack_token: L_BRACK@101..102 "[" [] [], + member: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@102..103 "i" [] [], + }, + }, + r_brack_token: R_BRACK@103..104 "]" [] [], + }, + ], + r_paren_token: R_PAREN@104..105 ")" [] [], + }, + }, + }, + }, + }, + }, + ], + }, + semicolon_token: SEMICOLON@105..106 ";" [] [], + }, + ], + eof_token: EOF@106..107 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: JS_MODULE@0..107 + 0: (empty) + 1: (empty) + 2: JS_DIRECTIVE_LIST@0..0 + 3: JS_MODULE_ITEM_LIST@0..106 + 0: JS_VARIABLE_STATEMENT@0..106 + 0: JS_VARIABLE_DECLARATION@0..105 + 0: (empty) + 1: CONST_KW@0..6 "const" [] [Whitespace(" ")] + 2: JS_VARIABLE_DECLARATOR_LIST@6..105 + 0: JS_VARIABLE_DECLARATOR@6..105 + 0: JS_IDENTIFIER_BINDING@6..13 + 0: IDENT@6..13 "slotFn" [] [Whitespace(" ")] + 1: (empty) + 2: JS_INITIALIZER_CLAUSE@13..105 + 0: EQ@13..15 "=" [] [Whitespace(" ")] + 1: JS_CONDITIONAL_EXPRESSION@15..105 + 0: JS_IDENTIFIER_EXPRESSION@15..27 + 0: JS_REFERENCE_IDENTIFIER@15..27 + 0: IDENT@15..27 "isFirstMount" [] [] + 1: QUESTION@27..32 "?" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] + 2: JS_PARENTHESIZED_EXPRESSION@32..70 + 0: L_PAREN@32..33 "(" [] [] + 1: JS_ARROW_FUNCTION_EXPRESSION@33..69 + 0: (empty) + 1: (empty) + 2: JS_PARAMETERS@33..36 + 0: L_PAREN@33..34 "(" [] [] + 1: JS_PARAMETER_LIST@34..35 + 0: JS_FORMAL_PARAMETER@34..35 + 0: JS_DECORATOR_LIST@34..34 + 1: JS_IDENTIFIER_BINDING@34..35 + 0: IDENT@34..35 "i" [] [] + 2: (empty) + 3: (empty) + 4: (empty) + 2: R_PAREN@35..36 ")" [] [] + 3: TS_RETURN_TYPE_ANNOTATION@36..43 + 0: COLON@36..38 ":" [] [Whitespace(" ")] + 1: TS_REFERENCE_TYPE@38..43 + 0: JS_REFERENCE_IDENTIFIER@38..43 + 0: IDENT@38..43 "Slot" [] [Whitespace(" ")] + 1: (empty) + 4: FAT_ARROW@43..46 "=>" [] [Whitespace(" ")] + 5: JS_PARENTHESIZED_EXPRESSION@46..69 + 0: L_PAREN@46..47 "(" [] [] + 1: JS_OBJECT_EXPRESSION@47..68 + 0: L_CURLY@47..49 "{" [] [Whitespace(" ")] + 1: JS_OBJECT_MEMBER_LIST@49..67 + 0: JS_PROPERTY_OBJECT_MEMBER@49..67 + 0: JS_COMPUTED_MEMBER_NAME@49..63 + 0: L_BRACK@49..50 "[" [] [] + 1: JS_IDENTIFIER_EXPRESSION@50..62 + 0: JS_REFERENCE_IDENTIFIER@50..62 + 0: IDENT@50..62 "CONTENT_SLOT" [] [] + 2: R_BRACK@62..63 "]" [] [] + 1: COLON@63..65 ":" [] [Whitespace(" ")] + 2: JS_IDENTIFIER_EXPRESSION@65..67 + 0: JS_REFERENCE_IDENTIFIER@65..67 + 0: IDENT@65..67 "i" [] [Whitespace(" ")] + 2: R_CURLY@67..68 "}" [] [] + 2: R_PAREN@68..69 ")" [] [] + 2: R_PAREN@69..70 ")" [] [] + 3: COLON@70..75 ":" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] + 4: JS_ARROW_FUNCTION_EXPRESSION@75..105 + 0: (empty) + 1: (empty) + 2: JS_IDENTIFIER_BINDING@75..77 + 0: IDENT@75..77 "i" [] [Whitespace(" ")] + 3: (empty) + 4: FAT_ARROW@77..80 "=>" [] [Whitespace(" ")] + 5: JS_CALL_EXPRESSION@80..105 + 0: JS_IDENTIFIER_EXPRESSION@80..92 + 0: JS_REFERENCE_IDENTIFIER@80..92 + 0: IDENT@80..92 "wrapSlotExpr" [] [] + 1: (empty) + 2: (empty) + 3: JS_CALL_ARGUMENTS@92..105 + 0: L_PAREN@92..93 "(" [] [] + 1: JS_CALL_ARGUMENT_LIST@93..104 + 0: JS_COMPUTED_MEMBER_EXPRESSION@93..104 + 0: JS_IDENTIFIER_EXPRESSION@93..101 + 0: JS_REFERENCE_IDENTIFIER@93..101 + 0: IDENT@93..101 "newExprs" [] [] + 1: (empty) + 2: L_BRACK@101..102 "[" [] [] + 3: JS_IDENTIFIER_EXPRESSION@102..103 + 0: JS_REFERENCE_IDENTIFIER@102..103 + 0: IDENT@102..103 "i" [] [] + 4: R_BRACK@103..104 "]" [] [] + 2: R_PAREN@104..105 ")" [] [] + 1: SEMICOLON@105..106 ";" [] [] + 4: EOF@106..107 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_async_destructured_arrow_return_type.ts b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_async_destructured_arrow_return_type.ts new file mode 100644 index 000000000000..4718ad89c928 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_async_destructured_arrow_return_type.ts @@ -0,0 +1,7 @@ +const queryAuditLog = async ({ + startDate, + endDate, + jobId, + src, + type, +}: Filter): Promise => {}; diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_async_destructured_arrow_return_type.ts.snap b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_async_destructured_arrow_return_type.ts.snap new file mode 100644 index 000000000000..07f9928664b6 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_async_destructured_arrow_return_type.ts.snap @@ -0,0 +1,237 @@ +--- +source: crates/biome_js_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```ts +const queryAuditLog = async ({ + startDate, + endDate, + jobId, + src, + type, +}: Filter): Promise => {}; + +``` + + +## AST + +``` +JsModule { + bom_token: missing (optional), + interpreter_token: missing (optional), + directives: JsDirectiveList [], + items: JsModuleItemList [ + JsVariableStatement { + declaration: JsVariableDeclaration { + await_token: missing (optional), + kind: CONST_KW@0..6 "const" [] [Whitespace(" ")], + declarators: JsVariableDeclaratorList [ + JsVariableDeclarator { + id: JsIdentifierBinding { + name_token: IDENT@6..20 "queryAuditLog" [] [Whitespace(" ")], + }, + variable_annotation: missing (optional), + initializer: JsInitializerClause { + eq_token: EQ@20..22 "=" [] [Whitespace(" ")], + expression: JsArrowFunctionExpression { + async_token: ASYNC_KW@22..28 "async" [] [Whitespace(" ")], + type_parameters: missing (optional), + parameters: JsParameters { + l_paren_token: L_PAREN@28..29 "(" [] [], + items: JsParameterList [ + JsFormalParameter { + decorators: JsDecoratorList [], + binding: JsObjectBindingPattern { + l_curly_token: L_CURLY@29..30 "{" [] [], + properties: JsObjectBindingPatternPropertyList [ + JsObjectBindingPatternShorthandProperty { + identifier: JsIdentifierBinding { + name_token: IDENT@30..42 "startDate" [Newline("\n"), Whitespace(" ")] [], + }, + init: missing (optional), + }, + COMMA@42..43 "," [] [], + JsObjectBindingPatternShorthandProperty { + identifier: JsIdentifierBinding { + name_token: IDENT@43..53 "endDate" [Newline("\n"), Whitespace(" ")] [], + }, + init: missing (optional), + }, + COMMA@53..54 "," [] [], + JsObjectBindingPatternShorthandProperty { + identifier: JsIdentifierBinding { + name_token: IDENT@54..62 "jobId" [Newline("\n"), Whitespace(" ")] [], + }, + init: missing (optional), + }, + COMMA@62..63 "," [] [], + JsObjectBindingPatternShorthandProperty { + identifier: JsIdentifierBinding { + name_token: IDENT@63..69 "src" [Newline("\n"), Whitespace(" ")] [], + }, + init: missing (optional), + }, + COMMA@69..70 "," [] [], + JsObjectBindingPatternShorthandProperty { + identifier: JsIdentifierBinding { + name_token: IDENT@70..77 "type" [Newline("\n"), Whitespace(" ")] [], + }, + init: missing (optional), + }, + COMMA@77..78 "," [] [], + ], + r_curly_token: R_CURLY@78..80 "}" [Newline("\n")] [], + }, + question_mark_token: missing (optional), + type_annotation: TsTypeAnnotation { + colon_token: COLON@80..82 ":" [] [Whitespace(" ")], + ty: TsReferenceType { + name: JsReferenceIdentifier { + value_token: IDENT@82..88 "Filter" [] [], + }, + type_arguments: missing (optional), + }, + }, + initializer: missing (optional), + }, + ], + r_paren_token: R_PAREN@88..89 ")" [] [], + }, + return_type_annotation: TsReturnTypeAnnotation { + colon_token: COLON@89..91 ":" [] [Whitespace(" ")], + ty: TsReferenceType { + name: JsReferenceIdentifier { + value_token: IDENT@91..98 "Promise" [] [], + }, + type_arguments: TsTypeArguments { + l_angle_token: L_ANGLE@98..99 "<" [] [], + ts_type_argument_list: TsTypeArgumentList [ + TsArrayType { + element_type: TsReferenceType { + name: JsReferenceIdentifier { + value_token: IDENT@99..109 "DBAuditLog" [] [], + }, + type_arguments: missing (optional), + }, + l_brack_token: L_BRACK@109..110 "[" [] [], + r_brack_token: R_BRACK@110..111 "]" [] [], + }, + ], + r_angle_token: R_ANGLE@111..113 ">" [] [Whitespace(" ")], + }, + }, + }, + fat_arrow_token: FAT_ARROW@113..116 "=>" [] [Whitespace(" ")], + body: JsFunctionBody { + l_curly_token: L_CURLY@116..117 "{" [] [], + directives: JsDirectiveList [], + statements: JsStatementList [], + r_curly_token: R_CURLY@117..118 "}" [] [], + }, + }, + }, + }, + ], + }, + semicolon_token: SEMICOLON@118..119 ";" [] [], + }, + ], + eof_token: EOF@119..120 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: JS_MODULE@0..120 + 0: (empty) + 1: (empty) + 2: JS_DIRECTIVE_LIST@0..0 + 3: JS_MODULE_ITEM_LIST@0..119 + 0: JS_VARIABLE_STATEMENT@0..119 + 0: JS_VARIABLE_DECLARATION@0..118 + 0: (empty) + 1: CONST_KW@0..6 "const" [] [Whitespace(" ")] + 2: JS_VARIABLE_DECLARATOR_LIST@6..118 + 0: JS_VARIABLE_DECLARATOR@6..118 + 0: JS_IDENTIFIER_BINDING@6..20 + 0: IDENT@6..20 "queryAuditLog" [] [Whitespace(" ")] + 1: (empty) + 2: JS_INITIALIZER_CLAUSE@20..118 + 0: EQ@20..22 "=" [] [Whitespace(" ")] + 1: JS_ARROW_FUNCTION_EXPRESSION@22..118 + 0: ASYNC_KW@22..28 "async" [] [Whitespace(" ")] + 1: (empty) + 2: JS_PARAMETERS@28..89 + 0: L_PAREN@28..29 "(" [] [] + 1: JS_PARAMETER_LIST@29..88 + 0: JS_FORMAL_PARAMETER@29..88 + 0: JS_DECORATOR_LIST@29..29 + 1: JS_OBJECT_BINDING_PATTERN@29..80 + 0: L_CURLY@29..30 "{" [] [] + 1: JS_OBJECT_BINDING_PATTERN_PROPERTY_LIST@30..78 + 0: JS_OBJECT_BINDING_PATTERN_SHORTHAND_PROPERTY@30..42 + 0: JS_IDENTIFIER_BINDING@30..42 + 0: IDENT@30..42 "startDate" [Newline("\n"), Whitespace(" ")] [] + 1: (empty) + 1: COMMA@42..43 "," [] [] + 2: JS_OBJECT_BINDING_PATTERN_SHORTHAND_PROPERTY@43..53 + 0: JS_IDENTIFIER_BINDING@43..53 + 0: IDENT@43..53 "endDate" [Newline("\n"), Whitespace(" ")] [] + 1: (empty) + 3: COMMA@53..54 "," [] [] + 4: JS_OBJECT_BINDING_PATTERN_SHORTHAND_PROPERTY@54..62 + 0: JS_IDENTIFIER_BINDING@54..62 + 0: IDENT@54..62 "jobId" [Newline("\n"), Whitespace(" ")] [] + 1: (empty) + 5: COMMA@62..63 "," [] [] + 6: JS_OBJECT_BINDING_PATTERN_SHORTHAND_PROPERTY@63..69 + 0: JS_IDENTIFIER_BINDING@63..69 + 0: IDENT@63..69 "src" [Newline("\n"), Whitespace(" ")] [] + 1: (empty) + 7: COMMA@69..70 "," [] [] + 8: JS_OBJECT_BINDING_PATTERN_SHORTHAND_PROPERTY@70..77 + 0: JS_IDENTIFIER_BINDING@70..77 + 0: IDENT@70..77 "type" [Newline("\n"), Whitespace(" ")] [] + 1: (empty) + 9: COMMA@77..78 "," [] [] + 2: R_CURLY@78..80 "}" [Newline("\n")] [] + 2: (empty) + 3: TS_TYPE_ANNOTATION@80..88 + 0: COLON@80..82 ":" [] [Whitespace(" ")] + 1: TS_REFERENCE_TYPE@82..88 + 0: JS_REFERENCE_IDENTIFIER@82..88 + 0: IDENT@82..88 "Filter" [] [] + 1: (empty) + 4: (empty) + 2: R_PAREN@88..89 ")" [] [] + 3: TS_RETURN_TYPE_ANNOTATION@89..113 + 0: COLON@89..91 ":" [] [Whitespace(" ")] + 1: TS_REFERENCE_TYPE@91..113 + 0: JS_REFERENCE_IDENTIFIER@91..98 + 0: IDENT@91..98 "Promise" [] [] + 1: TS_TYPE_ARGUMENTS@98..113 + 0: L_ANGLE@98..99 "<" [] [] + 1: TS_TYPE_ARGUMENT_LIST@99..111 + 0: TS_ARRAY_TYPE@99..111 + 0: TS_REFERENCE_TYPE@99..109 + 0: JS_REFERENCE_IDENTIFIER@99..109 + 0: IDENT@99..109 "DBAuditLog" [] [] + 1: (empty) + 1: L_BRACK@109..110 "[" [] [] + 2: R_BRACK@110..111 "]" [] [] + 2: R_ANGLE@111..113 ">" [] [Whitespace(" ")] + 4: FAT_ARROW@113..116 "=>" [] [Whitespace(" ")] + 5: JS_FUNCTION_BODY@116..118 + 0: L_CURLY@116..117 "{" [] [] + 1: JS_DIRECTIVE_LIST@117..117 + 2: JS_STATEMENT_LIST@117..117 + 3: R_CURLY@117..118 "}" [] [] + 1: SEMICOLON@118..119 ";" [] [] + 4: EOF@119..120 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_destructured_arrow_return_type_in_consequent.ts b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_destructured_arrow_return_type_in_consequent.ts new file mode 100644 index 000000000000..e35e72166816 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_destructured_arrow_return_type_in_consequent.ts @@ -0,0 +1,3 @@ +const slotFn = isFirstMount + ? ({ item }: Filter): Slot => item + : other; diff --git a/crates/biome_js_parser/tests/js_test_suite/ok/conditional_destructured_arrow_return_type_in_consequent.ts.snap b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_destructured_arrow_return_type_in_consequent.ts.snap new file mode 100644 index 000000000000..c404434bf726 --- /dev/null +++ b/crates/biome_js_parser/tests/js_test_suite/ok/conditional_destructured_arrow_return_type_in_consequent.ts.snap @@ -0,0 +1,178 @@ +--- +source: crates/biome_js_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```ts +const slotFn = isFirstMount + ? ({ item }: Filter): Slot => item + : other; + +``` + + +## AST + +``` +JsModule { + bom_token: missing (optional), + interpreter_token: missing (optional), + directives: JsDirectiveList [], + items: JsModuleItemList [ + JsVariableStatement { + declaration: JsVariableDeclaration { + await_token: missing (optional), + kind: CONST_KW@0..6 "const" [] [Whitespace(" ")], + declarators: JsVariableDeclaratorList [ + JsVariableDeclarator { + id: JsIdentifierBinding { + name_token: IDENT@6..13 "slotFn" [] [Whitespace(" ")], + }, + variable_annotation: missing (optional), + initializer: JsInitializerClause { + eq_token: EQ@13..15 "=" [] [Whitespace(" ")], + expression: JsConditionalExpression { + test: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@15..27 "isFirstMount" [] [], + }, + }, + question_mark_token: QUESTION@27..32 "?" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + consequent: JsArrowFunctionExpression { + async_token: missing (optional), + type_parameters: missing (optional), + parameters: JsParameters { + l_paren_token: L_PAREN@32..33 "(" [] [], + items: JsParameterList [ + JsFormalParameter { + decorators: JsDecoratorList [], + binding: JsObjectBindingPattern { + l_curly_token: L_CURLY@33..35 "{" [] [Whitespace(" ")], + properties: JsObjectBindingPatternPropertyList [ + JsObjectBindingPatternShorthandProperty { + identifier: JsIdentifierBinding { + name_token: IDENT@35..40 "item" [] [Whitespace(" ")], + }, + init: missing (optional), + }, + ], + r_curly_token: R_CURLY@40..41 "}" [] [], + }, + question_mark_token: missing (optional), + type_annotation: TsTypeAnnotation { + colon_token: COLON@41..43 ":" [] [Whitespace(" ")], + ty: TsReferenceType { + name: JsReferenceIdentifier { + value_token: IDENT@43..49 "Filter" [] [], + }, + type_arguments: missing (optional), + }, + }, + initializer: missing (optional), + }, + ], + r_paren_token: R_PAREN@49..50 ")" [] [], + }, + return_type_annotation: TsReturnTypeAnnotation { + colon_token: COLON@50..52 ":" [] [Whitespace(" ")], + ty: TsReferenceType { + name: JsReferenceIdentifier { + value_token: IDENT@52..57 "Slot" [] [Whitespace(" ")], + }, + type_arguments: missing (optional), + }, + }, + fat_arrow_token: FAT_ARROW@57..60 "=>" [] [Whitespace(" ")], + body: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@60..64 "item" [] [], + }, + }, + }, + colon_token: COLON@64..69 ":" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternate: JsIdentifierExpression { + name: JsReferenceIdentifier { + value_token: IDENT@69..74 "other" [] [], + }, + }, + }, + }, + }, + ], + }, + semicolon_token: SEMICOLON@74..75 ";" [] [], + }, + ], + eof_token: EOF@75..76 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: JS_MODULE@0..76 + 0: (empty) + 1: (empty) + 2: JS_DIRECTIVE_LIST@0..0 + 3: JS_MODULE_ITEM_LIST@0..75 + 0: JS_VARIABLE_STATEMENT@0..75 + 0: JS_VARIABLE_DECLARATION@0..74 + 0: (empty) + 1: CONST_KW@0..6 "const" [] [Whitespace(" ")] + 2: JS_VARIABLE_DECLARATOR_LIST@6..74 + 0: JS_VARIABLE_DECLARATOR@6..74 + 0: JS_IDENTIFIER_BINDING@6..13 + 0: IDENT@6..13 "slotFn" [] [Whitespace(" ")] + 1: (empty) + 2: JS_INITIALIZER_CLAUSE@13..74 + 0: EQ@13..15 "=" [] [Whitespace(" ")] + 1: JS_CONDITIONAL_EXPRESSION@15..74 + 0: JS_IDENTIFIER_EXPRESSION@15..27 + 0: JS_REFERENCE_IDENTIFIER@15..27 + 0: IDENT@15..27 "isFirstMount" [] [] + 1: QUESTION@27..32 "?" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] + 2: JS_ARROW_FUNCTION_EXPRESSION@32..64 + 0: (empty) + 1: (empty) + 2: JS_PARAMETERS@32..50 + 0: L_PAREN@32..33 "(" [] [] + 1: JS_PARAMETER_LIST@33..49 + 0: JS_FORMAL_PARAMETER@33..49 + 0: JS_DECORATOR_LIST@33..33 + 1: JS_OBJECT_BINDING_PATTERN@33..41 + 0: L_CURLY@33..35 "{" [] [Whitespace(" ")] + 1: JS_OBJECT_BINDING_PATTERN_PROPERTY_LIST@35..40 + 0: JS_OBJECT_BINDING_PATTERN_SHORTHAND_PROPERTY@35..40 + 0: JS_IDENTIFIER_BINDING@35..40 + 0: IDENT@35..40 "item" [] [Whitespace(" ")] + 1: (empty) + 2: R_CURLY@40..41 "}" [] [] + 2: (empty) + 3: TS_TYPE_ANNOTATION@41..49 + 0: COLON@41..43 ":" [] [Whitespace(" ")] + 1: TS_REFERENCE_TYPE@43..49 + 0: JS_REFERENCE_IDENTIFIER@43..49 + 0: IDENT@43..49 "Filter" [] [] + 1: (empty) + 4: (empty) + 2: R_PAREN@49..50 ")" [] [] + 3: TS_RETURN_TYPE_ANNOTATION@50..57 + 0: COLON@50..52 ":" [] [Whitespace(" ")] + 1: TS_REFERENCE_TYPE@52..57 + 0: JS_REFERENCE_IDENTIFIER@52..57 + 0: IDENT@52..57 "Slot" [] [Whitespace(" ")] + 1: (empty) + 4: FAT_ARROW@57..60 "=>" [] [Whitespace(" ")] + 5: JS_IDENTIFIER_EXPRESSION@60..64 + 0: JS_REFERENCE_IDENTIFIER@60..64 + 0: IDENT@60..64 "item" [] [] + 3: COLON@64..69 ":" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] + 4: JS_IDENTIFIER_EXPRESSION@69..74 + 0: JS_REFERENCE_IDENTIFIER@69..74 + 0: IDENT@69..74 "other" [] [] + 1: SEMICOLON@74..75 ";" [] [] + 4: EOF@75..76 "" [Newline("\n")] [] + +```