@@ -86,10 +86,10 @@ pub fn format_node<'src>(ps: &mut ParserState<'src>, node: prism::Node<'src>) {
8686
8787 if is_last_call_in_chain && has_block {
8888 ps. breakable_call_chain_of ( MultilineHandling :: Prism ( false ) , |ps| {
89- format_call_node ( ps, call_node, false , is_last_call_in_chain) ;
89+ format_call_node ( ps, call_node, false , is_last_call_in_chain, false ) ;
9090 } ) ;
9191 } else {
92- format_call_node ( ps, call_node, false , is_last_call_in_chain)
92+ format_call_node ( ps, call_node, false , is_last_call_in_chain, false )
9393 }
9494 }
9595 Node :: CallOperatorWriteNode { .. } => {
@@ -1711,6 +1711,7 @@ fn format_call_node<'src>(
17111711 call_node : prism:: CallNode < ' src > ,
17121712 skip_receiver : bool ,
17131713 is_final_call_in_chain : bool ,
1714+ skip_attr_write_value : bool ,
17141715) {
17151716 let method_name = const_to_str ( call_node. name ( ) ) ;
17161717 let end_offset = call_node. location ( ) . end_offset ( ) ;
@@ -1785,13 +1786,15 @@ fn format_call_node<'src>(
17851786 ) ;
17861787 ps. with_start_of_line ( false , |ps| format_node ( ps, last_arg) ) ;
17871788 } else if call_node. is_attribute_write ( ) {
1788- ps. emit_ident ( " = " ) ;
1789- let value = arguments
1790- . arguments ( )
1791- . iter ( )
1792- . next ( )
1793- . expect ( "Attribute writes must have a value" ) ;
1794- ps. with_start_of_line ( false , |ps| format_node ( ps, value) ) ;
1789+ if !skip_attr_write_value {
1790+ ps. emit_ident ( " = " ) ;
1791+ let value = arguments
1792+ . arguments ( )
1793+ . iter ( )
1794+ . next ( )
1795+ . expect ( "Attribute writes must have a value" ) ;
1796+ ps. with_start_of_line ( false , |ps| format_node ( ps, value) ) ;
1797+ }
17951798 } else {
17961799 let should_use_parens = use_parens_for_call_node (
17971800 ps,
@@ -2108,41 +2111,66 @@ fn format_call_chain<'src>(ps: &mut ParserState<'src>, call_node: ruby_prism::Ca
21082111 ps. with_start_of_line ( false , |ps| {
21092112 let segments = split_node_into_call_chains ( call_node. as_node ( ) ) ;
21102113
2111- let is_attribute_write = segments
2112- . last ( )
2113- . and_then ( |seg| seg. last ( ) )
2114- . and_then ( |n| n. as_call_node ( ) )
2115- . map ( |c| c. is_attribute_write ( ) )
2116- . unwrap_or ( false ) ;
2117-
2118- format_call_chain_segments ( ps, segments, is_attribute_write) ;
2114+ format_call_chain_segments ( ps, segments) ;
21192115 } ) ;
21202116}
21212117
21222118fn format_call_chain_segments < ' src > (
21232119 ps : & mut ParserState < ' src > ,
21242120 mut segments : Vec < Vec < prism:: Node < ' src > > > ,
2125- is_attr_write : bool ,
21262121) {
21272122 if let Some ( current) = segments. pop ( ) {
21282123 let has_inner = !segments. is_empty ( ) ;
21292124
21302125 let ( chain_elements, trailing_arefs) = extract_trailing_arefs ( current) ;
21312126
2127+ // Render the right-hand side of the write in its own breakable, so that each side can
2128+ // break independently of each other
2129+ let trailing_attr_write_value = chain_elements. last ( ) . and_then ( |last| {
2130+ last. as_call_node ( ) . and_then ( |call| {
2131+ let method_name = const_to_str ( call. name ( ) ) ;
2132+ // arefs are handled in format_call_node
2133+ if call. is_attribute_write ( ) && method_name != "[]=" {
2134+ call. arguments ( ) . and_then ( |args| {
2135+ debug_assert ! (
2136+ args. arguments( ) . len( ) == 1 ,
2137+ "Expected attr_write to have exactly one argument"
2138+ ) ;
2139+
2140+ args. arguments ( ) . iter ( ) . next ( )
2141+ } )
2142+ } else {
2143+ None
2144+ }
2145+ } )
2146+ } ) ;
2147+
21322148 let is_user_multilined = call_chain_elements_are_user_multilined ( ps, & chain_elements) ;
21332149
21342150 ps. breakable_call_chain_of ( MultilineHandling :: Prism ( is_user_multilined) , |ps| {
21352151 // Recurse and format previous segments inside this breakable
2136- // Inner segments are never attribute writes
2137- format_call_chain_segments ( ps, segments, false ) ;
2152+ format_call_chain_segments ( ps, segments) ;
21382153
2139- format_call_body ( ps, chain_elements, is_attr_write, has_inner) ;
2154+ format_call_body (
2155+ ps,
2156+ chain_elements,
2157+ has_inner,
2158+ trailing_attr_write_value. is_some ( ) ,
2159+ ) ;
21402160 } ) ;
21412161
2162+ // Attr_write values are formatted after the breakable so they break independently
2163+ if let Some ( value) = trailing_attr_write_value {
2164+ ps. emit_space ( ) ;
2165+ ps. emit_op ( "=" . to_string ( ) ) ;
2166+ ps. emit_space ( ) ;
2167+ ps. with_start_of_line ( false , |ps| format_node ( ps, value) ) ;
2168+ }
2169+
21422170 // Trailing arefs are formatted after the breakable
21432171 for aref in trailing_arefs {
21442172 let aref = aref. as_call_node ( ) . unwrap ( ) ;
2145- format_call_node ( ps, aref, true , false ) ;
2173+ format_call_node ( ps, aref, true , false , false ) ;
21462174 }
21472175 }
21482176}
@@ -2173,8 +2201,8 @@ fn extract_trailing_arefs<'src>(
21732201fn format_call_body < ' src > (
21742202 ps : & mut ParserState < ' src > ,
21752203 mut call_chain_elements : Vec < prism:: Node < ' src > > ,
2176- is_attr_write : bool ,
21772204 is_continuation : bool ,
2205+ skip_final_attr_write_value : bool ,
21782206) {
21792207 if call_chain_elements. is_empty ( ) {
21802208 return ;
@@ -2196,7 +2224,7 @@ fn format_call_body<'src>(
21962224 && call_node. call_operator_loc ( ) . is_none ( )
21972225 {
21982226 call_chain_elements. remove ( 0 ) ;
2199- format_call_node ( ps, call_node, true , false ) ;
2227+ format_call_node ( ps, call_node, true , false , false ) ;
22002228 continue ;
22012229 }
22022230 break ;
@@ -2209,16 +2237,15 @@ fn format_call_body<'src>(
22092237 ps. render_heredocs ( true ) ;
22102238 }
22112239
2212- // Attribute writes like `self.foo = bar` should have both sides
2213- // rendered separately so they can break independently
2240+ // Attribute writes like `self.foo = bar` format the LHS without call chain indent
2241+ // since there's only one element.
22142242 if !is_continuation
22152243 && call_chain_elements. len ( ) == 1
22162244 && let Some ( attr_write) = call_chain_elements
22172245 . first ( )
22182246 . and_then ( |n| n. as_call_node ( ) )
22192247 . filter ( |c| c. is_attribute_write ( ) )
22202248 {
2221- // Format `.attr_name = ` directly without call chain indent
22222249 let call_operator = attr_write. call_operator_loc ( ) . map ( |loc| loc_to_str ( loc) ) ;
22232250 if let Some ( call_operator) = call_operator {
22242251 match call_operator {
@@ -2232,14 +2259,9 @@ fn format_call_body<'src>(
22322259 ps. at_offset ( start_loc_for_call_node_in_chain ( & attr_write) ) ;
22332260 ps. shift_comments ( ) ;
22342261
2235- // Format just the attribute name and ` = `, then the value separately
2236- format_call_node ( ps, attr_write, true , true ) ;
2262+ format_call_node ( ps, attr_write, true , true , skip_final_attr_write_value) ;
22372263 } else if !call_chain_elements. is_empty ( ) {
2238- // attr_writes are not wrapped in a breakable, so they
2239- // cannot emit abstract token types
2240- if !is_attr_write {
2241- ps. start_indent_for_call_chain ( ) ;
2242- }
2264+ ps. start_indent_for_call_chain ( ) ;
22432265
22442266 ps. with_start_of_line ( false , |ps| {
22452267 let call_chain_element_count = call_chain_elements. len ( ) ;
@@ -2251,7 +2273,7 @@ fn format_call_body<'src>(
22512273 // it may be None in the case of arefs, e.g. foo[bar]
22522274 let call_operator = element. call_operator_loc ( ) . map ( |loc| loc_to_str ( loc) ) ;
22532275 if let Some ( call_operator) = call_operator {
2254- if call_operator != "::" && !is_attr_write {
2276+ if call_operator != "::" {
22552277 ps. emit_collapsing_newline ( ) ;
22562278 ps. emit_soft_indent ( ) ;
22572279 }
@@ -2270,12 +2292,12 @@ fn format_call_body<'src>(
22702292 ps. at_offset ( start_loc_for_call_node_in_chain ( & element) ) ;
22712293 ps. shift_comments ( ) ;
22722294
2273- format_call_node ( ps, element, true , is_final_call) ;
2295+ let skip_value =
2296+ is_final_call && skip_final_attr_write_value && element. is_attribute_write ( ) ;
2297+ format_call_node ( ps, element, true , is_final_call, skip_value) ;
22742298 }
22752299 } ) ;
2276- if !is_attr_write {
2277- ps. end_indent_for_call_chain ( ) ;
2278- }
2300+ ps. end_indent_for_call_chain ( ) ;
22792301 }
22802302}
22812303
@@ -4207,7 +4229,7 @@ fn format_match_write_node<'src>(
42074229 ps : & mut ParserState < ' src > ,
42084230 match_write_node : prism:: MatchWriteNode < ' src > ,
42094231) {
4210- format_call_node ( ps, match_write_node. call ( ) , false , true ) ;
4232+ format_call_node ( ps, match_write_node. call ( ) , false , true , false ) ;
42114233}
42124234
42134235fn format_multi_target_node < ' src > (
0 commit comments