Skip to content

Commit 811d4db

Browse files
committed
perf(formatter): optimize grouping logic for call arguments
1 parent a6b6ef8 commit 811d4db

File tree

1 file changed

+101
-76
lines changed

1 file changed

+101
-76
lines changed

crates/oxc_formatter/src/write/call_arguments.rs

Lines changed: 101 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -264,53 +264,115 @@ pub fn arguments_grouped_layout(
264264
args: &[Argument],
265265
f: &Formatter<'_, '_>,
266266
) -> Option<GroupedCallArgumentLayout> {
267-
if should_group_first_argument(call_like_span, args, f) {
268-
Some(GroupedCallArgumentLayout::GroupedFirstArgument)
269-
} else if should_group_last_argument(call_like_span, args, f) {
270-
Some(GroupedCallArgumentLayout::GroupedLastArgument)
267+
// For exactly 2 arguments, we need to check both grouping strategies.
268+
// To avoid redundant `can_group_expression_argument` calls, we handle this case specially.
269+
if args.len() == 2 {
270+
let [first, second] = args else { unreachable!("args.len() == 2 guarantees two elements") };
271+
let first = first.as_expression()?;
272+
let second = second.as_expression()?;
273+
274+
// Call `can_group_expression_argument` only once for the second argument
275+
let second_can_group = can_group_expression_argument(second, f);
276+
let second_can_group_fn = || second_can_group;
277+
278+
// Check if we should group the last argument (second)
279+
if should_group_last_argument_impl(
280+
call_like_span,
281+
Some(first),
282+
second,
283+
second_can_group_fn,
284+
f,
285+
) {
286+
return Some(GroupedCallArgumentLayout::GroupedLastArgument);
287+
}
288+
289+
// Check if we should group the first argument instead
290+
// Reuse the already-computed `second_can_group` value
291+
should_group_first_argument_impl(call_like_span, first, second, second_can_group, f)
292+
.then_some(GroupedCallArgumentLayout::GroupedFirstArgument)
271293
} else {
272-
None
294+
// For other cases (not exactly 2 arguments), only check last argument grouping
295+
should_group_last_argument(call_like_span, args, f)
296+
.then_some(GroupedCallArgumentLayout::GroupedLastArgument)
273297
}
274298
}
275299

276300
/// Checks if the first argument requires grouping
277-
fn should_group_first_argument(
301+
fn should_group_first_argument_impl(
278302
call_like_span: Span,
279-
args: &[Argument],
303+
first: &Expression,
304+
second: &Expression,
305+
second_can_group: bool,
280306
f: &Formatter<'_, '_>,
281307
) -> bool {
282-
let mut iter = args.iter();
283-
match (iter.next().and_then(|a| a.as_expression()), iter.next().and_then(|a| a.as_expression()))
284-
{
285-
(Some(first), Some(second)) if iter.next().is_none() => {
286-
match &first {
287-
Expression::FunctionExpression(_) => {}
288-
// Arrow expressions that are a plain expression or are a chain
289-
// don't get grouped as the first argument, since they'll either
290-
// fit entirely on the line or break fully. Only a single arrow
291-
// with a block body can be grouped to collapse the braces.
292-
Expression::ArrowFunctionExpression(arrow) => {
293-
if arrow.expression {
294-
return false;
295-
}
296-
}
297-
_ => return false,
308+
match first {
309+
Expression::FunctionExpression(_) => {}
310+
// Arrow expressions that are a plain expression or are a chain
311+
// don't get grouped as the first argument, since they'll either
312+
// fit entirely on the line or break fully. Only a single arrow
313+
// with a block body can be grouped to collapse the braces.
314+
Expression::ArrowFunctionExpression(arrow) => {
315+
if arrow.expression {
316+
return false;
298317
}
318+
}
319+
_ => return false,
320+
}
321+
322+
if matches!(
323+
second,
324+
Expression::ArrowFunctionExpression(_)
325+
| Expression::FunctionExpression(_)
326+
| Expression::ConditionalExpression(_)
327+
) {
328+
return false;
329+
}
299330

300-
if matches!(
301-
second,
302-
Expression::ArrowFunctionExpression(_)
303-
| Expression::FunctionExpression(_)
304-
| Expression::ConditionalExpression(_)
305-
) {
331+
!f.comments().has_comment(call_like_span.start, first.span(), second.span().start)
332+
&& !second_can_group
333+
&& is_relatively_short_argument(second)
334+
}
335+
336+
/// Core logic for checking if the last argument should be grouped.
337+
/// Takes the penultimate argument as an Expression for the 2-argument case,
338+
/// or extracts it from the arguments array for other cases.
339+
fn should_group_last_argument_impl(
340+
call_like_span: Span,
341+
penultimate: Option<&Expression>,
342+
last: &Expression,
343+
last_can_group_fn: impl FnOnce() -> bool,
344+
f: &Formatter<'_, '_>,
345+
) -> bool {
346+
// Check if penultimate and last are the same type (both Object or both Array)
347+
if let Some(penultimate) = penultimate
348+
&& matches!(
349+
(penultimate, last),
350+
(Expression::ObjectExpression(_), Expression::ObjectExpression(_))
351+
| (Expression::ArrayExpression(_), Expression::ArrayExpression(_))
352+
)
353+
{
354+
return false;
355+
}
356+
357+
let previous_span = penultimate.map_or(call_like_span.start, |e| e.span().end);
358+
if f.comments().has_comment(previous_span, last.span(), call_like_span.end) {
359+
return false;
360+
}
361+
362+
if !last_can_group_fn() {
363+
return false;
364+
}
365+
366+
match last {
367+
Expression::ArrayExpression(array) if penultimate.is_some() => {
368+
// Not for `useEffect`
369+
if matches!(penultimate, Some(Expression::ArrowFunctionExpression(_))) {
306370
return false;
307371
}
308372

309-
!f.comments().has_comment(call_like_span.start, first.span(), second.span().start)
310-
&& !can_group_expression_argument(second, f)
311-
&& is_relatively_short_argument(second)
373+
!can_concisely_print_array_list(array.span, &array.elements, f)
312374
}
313-
_ => false,
375+
_ => true,
314376
}
315377
}
316378

@@ -321,50 +383,13 @@ fn should_group_last_argument(
321383
f: &Formatter<'_, '_>,
322384
) -> bool {
323385
let mut iter = args.iter();
324-
let last = iter.next_back();
325-
326-
match last.and_then(|arg| arg.as_expression()) {
327-
Some(last) => {
328-
let penultimate = iter.next_back();
329-
if let Some(penultimate) = &penultimate
330-
&& matches!(
331-
(penultimate, last),
332-
(Argument::ObjectExpression(_), Expression::ObjectExpression(_))
333-
| (Argument::ArrayExpression(_), Expression::ArrayExpression(_))
334-
)
335-
{
336-
return false;
337-
}
338-
339-
let previous_span = penultimate.map_or(call_like_span.start, |a| a.span().end);
340-
if f.comments().has_comment(previous_span, last.span(), call_like_span.end) {
341-
return false;
342-
}
343-
344-
if !can_group_expression_argument(last, f) {
345-
return false;
346-
}
347-
348-
match last {
349-
Expression::ArrayExpression(array) if args.len() > 1 => {
350-
// Not for `useEffect`
351-
if args.len() == 2
352-
&& matches!(penultimate, Some(Argument::ArrowFunctionExpression(_)))
353-
{
354-
return false;
355-
}
356-
357-
if can_concisely_print_array_list(array.span, &array.elements, f) {
358-
return false;
359-
}
386+
let Some(last) = iter.next_back().unwrap().as_expression() else {
387+
return false;
388+
};
360389

361-
true
362-
}
363-
_ => true,
364-
}
365-
}
366-
_ => false,
367-
}
390+
let penultimate = iter.next_back().and_then(|arg| arg.as_expression());
391+
let last_can_group_fn = || can_group_expression_argument(last, f);
392+
should_group_last_argument_impl(call_like_span, penultimate, last, last_can_group_fn, f)
368393
}
369394

370395
/// Check if `ty` is a relatively simple type annotation, allowing a few

0 commit comments

Comments
 (0)