@@ -7,7 +7,9 @@ use crate::{
77 ast_nodes:: { AstNode , AstNodes } ,
88 format_args,
99 formatter:: {
10- Comments , FormatElement , Formatter , SourceText , VecBuffer , format_element,
10+ Comments , FormatElement , Formatter , SourceText , VecBuffer ,
11+ buffer:: RemoveSoftLinesBuffer ,
12+ format_element,
1113 prelude:: {
1214 FormatElements , Tag , empty_line, expand_parent, format_once, format_with, group,
1315 soft_block_indent, soft_line_break_or_space, space,
@@ -634,7 +636,13 @@ fn write_grouped_arguments<'a>(
634636 let is_grouped_argument = ( group_layout. is_grouped_first ( ) && index == 0 )
635637 || ( group_layout. is_grouped_last ( ) && index == last_index) ;
636638
637- let format_argument = format_once ( |f| {
639+ // We have to get the lines before the argument has been formatted, because it relies on
640+ // the comments before the argument. After formatting, the comments might marked as printed,
641+ // which would lead to a wrong line count.
642+ let lines_before = f. source_text ( ) . get_lines_before ( argument. span ( ) , f. comments ( ) ) ;
643+ let comma = ( last_index != index) . then_some ( "," ) ;
644+
645+ let interned = f. intern ( & format_once ( |f| {
638646 if is_grouped_argument {
639647 match argument. as_ast_nodes ( ) {
640648 AstNodes :: Function ( function)
@@ -643,40 +651,40 @@ fn write_grouped_arguments<'a>(
643651 || function_has_only_simple_parameters ( & function. params ) ) =>
644652 {
645653 has_cached = true ;
646- return FormatFunction :: new_with_options (
647- function,
648- FormatFunctionOptions {
649- cache_mode : FunctionCacheMode :: Cache ,
650- ..FormatFunctionOptions :: default ( )
651- } ,
652- )
653- . fmt ( f) ;
654+ return write ! (
655+ f,
656+ [
657+ FormatFunction :: new_with_options(
658+ function,
659+ FormatFunctionOptions {
660+ cache_mode: FunctionCacheMode :: Cache ,
661+ ..FormatFunctionOptions :: default ( )
662+ } ,
663+ ) ,
664+ comma
665+ ]
666+ ) ;
654667 }
655668 AstNodes :: ArrowFunctionExpression ( arrow) => {
656669 has_cached = true ;
657- return FormatJsArrowFunctionExpression :: new_with_options (
658- arrow,
659- FormatJsArrowFunctionExpressionOptions {
660- cache_mode : FunctionCacheMode :: Cache ,
661- ..FormatJsArrowFunctionExpressionOptions :: default ( )
662- } ,
663- )
664- . fmt ( f) ;
670+ return write ! (
671+ f,
672+ [
673+ FormatJsArrowFunctionExpression :: new_with_options(
674+ arrow,
675+ FormatJsArrowFunctionExpressionOptions {
676+ cache_mode: FunctionCacheMode :: Cache ,
677+ ..FormatJsArrowFunctionExpressionOptions :: default ( )
678+ } ,
679+ ) ,
680+ comma
681+ ]
682+ ) ;
665683 }
666684 _ => { }
667685 }
668686 }
669- argument. fmt ( f) ;
670- } ) ;
671-
672- // We have to get the lines before the argument has been formatted, because it relies on
673- // the comments before the argument. After formatting, the comments might marked as printed,
674- // which would lead to a wrong line count.
675- let lines_before = f. source_text ( ) . get_lines_before ( argument. span ( ) , f. comments ( ) ) ;
676-
677- let interned = f. intern ( & format_with ( |f| {
678- format_argument. fmt ( f) ;
679- write ! ( f, ( last_index != index) . then_some( "," ) ) ;
687+ write ! ( f, [ argument, comma] ) ;
680688 } ) ) ;
681689
682690 let break_type =
@@ -715,46 +723,60 @@ fn write_grouped_arguments<'a>(
715723 // as first or last argument.
716724 let mut grouped = elements;
717725 if has_cached {
718- match group_layout {
726+ let ( argument , grouped_element ) = match group_layout {
719727 GroupedCallArgumentLayout :: GroupedFirstArgument => {
720- let argument = node. first ( ) . unwrap ( ) ;
721- let interned = f. intern ( & format_with ( |f| {
722- FormatGroupedFirstArgument { argument } . fmt ( f) ;
723- write ! ( f, ( last_index != 0 ) . then_some( "," ) ) ;
724- } ) ) ;
725-
726- // Turns out, using the grouped layout isn't a good fit because some parameters of the
727- // grouped function or arrow expression break. In that case, fall back to the all args expanded
728- // formatting.
729- // This back tracking is required because testing if the grouped argument breaks would also return `true`
730- // if any content of the function body breaks. But, as far as this is concerned, it's only interested if
731- // any content in the signature breaks.
732- // TODO: should figure out
733- // if matches!(interned, Err(FormatError::PoorLayout)) {
734- // return format_all_elements_broken_out(node, grouped.into_iter(), true, f);
735- // }
736-
737- grouped. first_mut ( ) . unwrap ( ) . 0 = interned;
728+ ( node. first ( ) . unwrap ( ) , & mut grouped. first_mut ( ) . unwrap ( ) . 0 )
738729 }
739730 GroupedCallArgumentLayout :: GroupedLastArgument => {
740- let argument = node. last ( ) . unwrap ( ) ;
741- let interned = f. intern ( & format_once ( |f| {
742- FormatGroupedLastArgument { argument, is_only : only_one_argument } . fmt ( f) ;
743- } ) ) ;
744-
745- // Turns out, using the grouped layout isn't a good fit because some parameters of the
746- // grouped function or arrow expression break. In that case, fall back to the all args expanded
747- // formatting.
748- // This back tracking is required because testing if the grouped argument breaks would also return `true`
749- // if any content of the function body breaks. But, as far as this is concerned, it's only interested if
750- // any content in the signature breaks.
751- // TODO: should figure out
752- // if matches!(interned, Err(FormatError::PoorLayout)) {
753- // return format_all_elements_broken_out(node, grouped.into_iter(), true, f);
754- // }
755-
756- grouped. last_mut ( ) . unwrap ( ) . 0 = interned;
731+ ( node. last ( ) . unwrap ( ) , & mut grouped. last_mut ( ) . unwrap ( ) . 0 )
757732 }
733+ } ;
734+
735+ let function_params = match argument. as_ast_nodes ( ) {
736+ AstNodes :: ArrowFunctionExpression ( arrow) => Some ( & arrow. params ) ,
737+ AstNodes :: Function ( function) => Some ( & function. params ) ,
738+ _ => None ,
739+ } ;
740+
741+ // Turns out, using the grouped layout isn't a good fit because some parameters of the
742+ // grouped function or arrow expression break. In that case, fall back to the all args expanded
743+ // formatting.
744+ // This back tracking is required because testing if the grouped argument breaks would also return `true`
745+ // if any content of the function body breaks. But, as far as this is concerned, it's only interested if
746+ // any content in the signature breaks.
747+ //
748+ // <https://github.com/biomejs/biome/blob/98ca2ae9f3b9b25a14d63b243223583aba6e4907/crates/biome_js_formatter/src/js/expressions/call_arguments.rs#L466-L482>
749+ if let Some ( params) = function_params {
750+ let Some ( cached_element) = f. context ( ) . get_cached_element ( params. as_ref ( ) ) else {
751+ unreachable ! (
752+ "The parameters should have already been formatted and cached in the `FormatFunction` or `FormatJsArrowFunctionExpression`"
753+ ) ;
754+ } ;
755+
756+ // Remove soft lines from the cached parameters and check if they would break.
757+ // If they break even without soft lines, we need to use the expanded layout.
758+ let interned = f. intern ( & format_once ( |f| {
759+ RemoveSoftLinesBuffer :: new ( f) . write_element ( cached_element) ;
760+ } ) ) ;
761+
762+ if let Some ( interned) = interned {
763+ if interned. will_break ( ) {
764+ return format_all_elements_broken_out ( node, grouped. into_iter ( ) , true , f) ;
765+ }
766+
767+ // No break; it should print the element without soft lines.
768+ // It would be used in the `FormatFunction` or `FormatJsArrowFunctionExpression`.
769+ f. context_mut ( ) . cache_element ( params. as_ref ( ) , interned) ;
770+ }
771+ }
772+
773+ * grouped_element = if group_layout. is_grouped_first ( ) {
774+ f. intern ( & format_with ( |f| {
775+ FormatGroupedFirstArgument { argument } . fmt ( f) ;
776+ write ! ( f, ( last_index != 0 ) . then_some( "," ) ) ;
777+ } ) )
778+ } else {
779+ f. intern ( & FormatGroupedLastArgument { argument, is_only : only_one_argument } )
758780 }
759781 }
760782
0 commit comments