Skip to content

Commit cf78793

Browse files
committed
fix(formatter): incorrect check for assignment-like node if the right-hand side is a member chain (#12587)
Previously, we didn't support MemberChain yet, so we missed this check. Now we have added.
1 parent 78c37f0 commit cf78793

File tree

2 files changed

+60
-57
lines changed

2 files changed

+60
-57
lines changed

crates/oxc_formatter/src/utils/assignment_like.rs

Lines changed: 59 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use oxc_span::GetSpan;
55

66
use crate::formatter::prelude::{FormatElements, format_once, line_suffix_boundary};
77
use crate::formatter::{BufferExtensions, VecBuffer};
8+
use crate::utils::member_chain::is_member_call_chain;
89
use crate::write::BinaryLikeExpression;
910
use crate::write::{FormatJsArrowFunctionExpression, FormatJsArrowFunctionExpressionOptions};
1011
use crate::{
@@ -208,7 +209,9 @@ impl<'a> AssignmentLike<'a, '_> {
208209
return layout;
209210
}
210211

211-
if let Some(Expression::CallExpression(call_expression)) = &right_expression {
212+
if let Some(Expression::CallExpression(call_expression)) =
213+
&right_expression.map(AsRef::as_ref)
214+
{
212215
if call_expression
213216
.callee
214217
.get_identifier_reference()
@@ -236,14 +239,15 @@ impl<'a> AssignmentLike<'a, '_> {
236239
// !"123" -> "123"
237240
// void "123" -> "123"
238241
// !!"string"! -> "string"
239-
let right_expression = iter::successors(right_expression, |expression| match expression {
240-
Expression::UnaryExpression(unary) => Some(&unary.argument),
241-
Expression::TSNonNullExpression(assertion) => Some(&assertion.expression),
242-
_ => None,
243-
})
244-
.last();
245-
246-
if matches!(right_expression, Some(Expression::StringLiteral(_))) {
242+
let right_expression =
243+
iter::successors(right_expression, |expression| match expression.as_ast_nodes() {
244+
AstNodes::UnaryExpression(unary) => Some(unary.argument()),
245+
AstNodes::TSNonNullExpression(assertion) => Some(assertion.expression()),
246+
_ => None,
247+
})
248+
.last();
249+
250+
if matches!(right_expression.map(AsRef::as_ref), Some(Expression::StringLiteral(_))) {
247251
return AssignmentLikeLayout::BreakAfterOperator;
248252
}
249253

@@ -258,7 +262,7 @@ impl<'a> AssignmentLike<'a, '_> {
258262

259263
if !left_may_break
260264
&& matches!(
261-
right_expression,
265+
right_expression.map(AsRef::as_ref),
262266
Some(
263267
Expression::ClassExpression(_)
264268
| Expression::TemplateLiteral(_)
@@ -273,12 +277,10 @@ impl<'a> AssignmentLike<'a, '_> {
273277
AssignmentLikeLayout::Fluid
274278
}
275279

276-
fn get_right_expression(&self) -> Option<&Expression<'a>> {
280+
fn get_right_expression(&self) -> Option<&AstNode<'a, Expression<'a>>> {
277281
match self {
278-
AssignmentLike::VariableDeclarator(variable_decorator) => {
279-
variable_decorator.init.as_ref()
280-
}
281-
AssignmentLike::AssignmentExpression(assignment) => Some(&assignment.right),
282+
AssignmentLike::VariableDeclarator(variable_decorator) => variable_decorator.init(),
283+
AssignmentLike::AssignmentExpression(assignment) => Some(assignment.right()),
282284
}
283285
}
284286

@@ -389,7 +391,7 @@ impl<'a> AssignmentLike<'a, '_> {
389391
/// for nodes that belong to TypeScript too.
390392
fn should_break_after_operator(
391393
&self,
392-
right_expression: Option<&Expression<'a>>,
394+
right_expression: Option<&AstNode<'a, Expression<'a>>>,
393395
f: &Formatter<'_, 'a>,
394396
) -> bool {
395397
let comments = f.context().comments();
@@ -454,14 +456,17 @@ impl<'a> AssignmentLike<'a, '_> {
454456
}
455457

456458
/// Checks if the function is entitled to be printed with layout [AssignmentLikeLayout::BreakAfterOperator]
457-
fn should_break_after_operator(right: &Expression, f: &Formatter<'_, '_>) -> bool {
459+
fn should_break_after_operator<'a>(
460+
right: &AstNode<'a, Expression<'a>>,
461+
f: &Formatter<'_, 'a>,
462+
) -> bool {
458463
if f.comments().has_leading_own_line_comments(right.span().start)
459-
&& !matches!(right, Expression::JSXElement(_) | Expression::JSXFragment(_))
464+
&& !matches!(right.as_ref(), Expression::JSXElement(_) | Expression::JSXFragment(_))
460465
{
461466
return true;
462467
}
463468

464-
match right {
469+
match right.as_ref() {
465470
// head is a long chain, meaning that right -> right are both assignment expressions
466471
Expression::AssignmentExpression(assignment) => {
467472
matches!(assignment.right, Expression::AssignmentExpression(_))
@@ -476,14 +481,15 @@ fn should_break_after_operator(right: &Expression, f: &Formatter<'_, '_>) -> boo
476481
Expression::ClassExpression(class) => !class.decorators.is_empty(),
477482

478483
_ => {
479-
let argument = match right {
480-
Expression::AwaitExpression(expression) => Some(&expression.argument),
481-
Expression::YieldExpression(expression) => expression.argument.as_ref(),
482-
Expression::UnaryExpression(expression) => {
483-
match get_last_non_unary_argument(expression) {
484-
Expression::AwaitExpression(expression) => Some(&expression.argument),
485-
Expression::YieldExpression(expression) => expression.argument.as_ref(),
486-
argument => Some(argument),
484+
let argument = match right.as_ast_nodes() {
485+
AstNodes::AwaitExpression(expression) => Some(expression.argument()),
486+
AstNodes::YieldExpression(expression) => expression.argument(),
487+
AstNodes::UnaryExpression(expression) => {
488+
let argument = get_last_non_unary_argument(expression);
489+
match argument.as_ast_nodes() {
490+
AstNodes::AwaitExpression(expression) => Some(expression.argument()),
491+
AstNodes::YieldExpression(expression) => expression.argument(),
492+
_ => Some(argument),
487493
}
488494
}
489495
_ => None,
@@ -499,12 +505,12 @@ fn should_break_after_operator(right: &Expression, f: &Formatter<'_, '_>) -> boo
499505
/// Iterate over unary expression arguments to get last non-unary
500506
/// Example: void !!(await test()) -> returns await as last argument
501507
fn get_last_non_unary_argument<'a, 'b>(
502-
unary_expression: &'b UnaryExpression<'a>,
503-
) -> &'b Expression<'a> {
504-
let mut argument = &unary_expression.argument;
508+
unary_expression: &'b AstNode<'a, UnaryExpression<'a>>,
509+
) -> &'b AstNode<'a, Expression<'a>> {
510+
let mut argument = unary_expression.argument();
505511

506-
while let Expression::UnaryExpression(unary) = argument {
507-
argument = &unary.argument;
512+
while let AstNodes::UnaryExpression(unary) = argument.as_ast_nodes() {
513+
argument = unary.argument();
508514
}
509515

510516
argument
@@ -641,9 +647,9 @@ impl<'a> Format<'a> for WithAssignmentLayout<'a, '_> {
641647
/// A chain that has no calls at all or all of whose calls have no arguments
642648
/// or have only one which [is_short_argument], except for member call chains
643649
/// [Prettier applies]: <https://github.com/prettier/prettier/blob/a043ac0d733c4d53f980aa73807a63fc914f23bd/src/language-js/print/assignment.js#L329>
644-
fn is_poorly_breakable_member_or_call_chain(
645-
expression: &Expression,
646-
f: &Formatter<'_, '_>,
650+
fn is_poorly_breakable_member_or_call_chain<'a>(
651+
expression: &AstNode<'a, Expression<'a>>,
652+
f: &Formatter<'_, 'a>,
647653
) -> bool {
648654
let threshold = f.options().line_width.value() / 4;
649655

@@ -662,23 +668,23 @@ fn is_poorly_breakable_member_or_call_chain(
662668
let mut expression = expression;
663669

664670
loop {
665-
expression = match expression {
666-
Expression::TSNonNullExpression(assertion) => &assertion.expression,
667-
Expression::CallExpression(call_expression) => {
671+
expression = match expression.as_ast_nodes() {
672+
AstNodes::TSNonNullExpression(assertion) => assertion.expression(),
673+
AstNodes::CallExpression(call_expression) => {
668674
is_chain = true;
669-
let callee = &call_expression.callee;
675+
let callee = &call_expression.callee();
670676
call_expressions.push(call_expression);
671677
callee
672678
}
673-
Expression::StaticMemberExpression(node) => {
679+
AstNodes::StaticMemberExpression(node) => {
674680
is_chain = true;
675-
&node.object
681+
node.object()
676682
}
677-
Expression::ComputedMemberExpression(node) => {
683+
AstNodes::ComputedMemberExpression(node) => {
678684
is_chain = true;
679-
&node.object
685+
node.object()
680686
}
681-
Expression::Identifier(_) | Expression::ThisExpression(_) => {
687+
AstNodes::IdentifierReference(_) | AstNodes::ThisExpression(_) => {
682688
is_chain_head_simple = true;
683689
break;
684690
}
@@ -692,16 +698,15 @@ fn is_poorly_breakable_member_or_call_chain(
692698
return false;
693699
}
694700

695-
for call_expression in call_expressions {
696-
// if is_member_call_chain(call_expression.clone(), f.comments(), f.options().tab_width())? {
697-
// return Ok(false);
698-
// }
699-
// TODO: It looks like `is_member_call_chain` is used for checking comments,
700-
// but not sure if the following code is equivalent to the above check.
701-
if f.comments().has_comments_in_span(call_expression.span) {
702-
return false;
703-
}
701+
if call_expressions.is_empty() {
702+
return true;
703+
}
704+
705+
if f.comments().has_comments_in_span(call_expressions[0].span) {
706+
return false;
707+
}
704708

709+
for call_expression in &call_expressions {
705710
let args = &call_expression.arguments;
706711

707712
let is_breakable_call = match args.len() {
@@ -727,7 +732,7 @@ fn is_poorly_breakable_member_or_call_chain(
727732
}
728733
}
729734

730-
true
735+
!is_member_call_chain(call_expressions[0], f)
731736
}
732737

733738
/// This function checks if `JsAnyCallArgument` is short

tasks/prettier_conformance/snapshots/prettier.js.snap.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
js compatibility: 441/699 (63.09%)
1+
js compatibility: 443/699 (63.38%)
22

33
# Failed
44

@@ -145,13 +145,11 @@ js compatibility: 441/699 (63.09%)
145145
| js/line-suffix-boundary/boundary.js | 💥 | 30.43% |
146146
| js/logical_expressions/issue-7024.js | 💥 | 66.67% |
147147
| js/member/conditional.js | 💥 | 0.00% |
148-
| js/method-chain/bracket_0-1.js | 💥 | 0.00% |
149148
| js/method-chain/break-last-member.js | 💥 | 80.56% |
150149
| js/method-chain/comment.js | 💥 | 78.33% |
151150
| js/method-chain/conditional.js | 💥 | 43.14% |
152151
| js/method-chain/first_long.js | 💥 | 96.97% |
153152
| js/method-chain/pr-7889.js | 💥 | 33.33% |
154-
| js/method-chain/square_0.js | 💥 | 53.33% |
155153
| js/new-expression/new_expression.js | 💥 | 55.56% |
156154
| js/no-semi/class.js | 💥✨ | 46.55% |
157155
| js/no-semi/comments.js | 💥✨ | 36.36% |

0 commit comments

Comments
 (0)