1- use syntax:: { algo:: non_trivia_sibling, Direction , SyntaxKind , T } ;
1+ use ide_db:: base_db:: SourceDatabase ;
2+ use syntax:: TextSize ;
3+ use syntax:: {
4+ algo:: non_trivia_sibling, ast, AstNode , Direction , SyntaxKind , SyntaxToken , TextRange , T ,
5+ } ;
26
37use crate :: { AssistContext , AssistId , AssistKind , Assists } ;
48
@@ -21,6 +25,8 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
2125 let comma = ctx. find_token_syntax_at_offset ( T ! [ , ] ) ?;
2226 let prev = non_trivia_sibling ( comma. clone ( ) . into ( ) , Direction :: Prev ) ?;
2327 let next = non_trivia_sibling ( comma. clone ( ) . into ( ) , Direction :: Next ) ?;
28+ let ( mut prev_text, mut next_text) = ( prev. to_string ( ) , next. to_string ( ) ) ;
29+ let ( mut prev_range, mut next_range) = ( prev. text_range ( ) , next. text_range ( ) ) ;
2430
2531 // Don't apply a "flip" in case of a last comma
2632 // that typically comes before punctuation
@@ -34,17 +40,55 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
3440 return None ;
3541 }
3642
43+ if let Some ( parent) = comma. parent ( ) . and_then ( ast:: TokenTree :: cast) {
44+ // An attribute. It often contains a path followed by a token tree (e.g. `align(2)`), so we have
45+ // to be smarter.
46+ let prev_start =
47+ match comma. siblings_with_tokens ( Direction :: Prev ) . skip ( 1 ) . find ( |it| it. kind ( ) == T ! [ , ] )
48+ {
49+ Some ( it) => position_after_token ( it. as_token ( ) . unwrap ( ) ) ,
50+ None => position_after_token ( & parent. left_delimiter_token ( ) ?) ,
51+ } ;
52+ let prev_end = prev. text_range ( ) . end ( ) ;
53+ let next_start = next. text_range ( ) . start ( ) ;
54+ let next_end =
55+ match comma. siblings_with_tokens ( Direction :: Next ) . skip ( 1 ) . find ( |it| it. kind ( ) == T ! [ , ] )
56+ {
57+ Some ( it) => position_before_token ( it. as_token ( ) . unwrap ( ) ) ,
58+ None => position_before_token ( & parent. right_delimiter_token ( ) ?) ,
59+ } ;
60+ prev_range = TextRange :: new ( prev_start, prev_end) ;
61+ next_range = TextRange :: new ( next_start, next_end) ;
62+ let file_text = ctx. db ( ) . file_text ( ctx. file_id ( ) . file_id ( ) ) ;
63+ prev_text = file_text[ prev_range] . to_owned ( ) ;
64+ next_text = file_text[ next_range] . to_owned ( ) ;
65+ }
66+
3767 acc. add (
3868 AssistId ( "flip_comma" , AssistKind :: RefactorRewrite ) ,
3969 "Flip comma" ,
4070 comma. text_range ( ) ,
4171 |edit| {
42- edit. replace ( prev . text_range ( ) , next . to_string ( ) ) ;
43- edit. replace ( next . text_range ( ) , prev . to_string ( ) ) ;
72+ edit. replace ( prev_range , next_text ) ;
73+ edit. replace ( next_range , prev_text ) ;
4474 } ,
4575 )
4676}
4777
78+ fn position_before_token ( token : & SyntaxToken ) -> TextSize {
79+ match non_trivia_sibling ( token. clone ( ) . into ( ) , Direction :: Prev ) {
80+ Some ( prev_token) => prev_token. text_range ( ) . end ( ) ,
81+ None => token. text_range ( ) . start ( ) ,
82+ }
83+ }
84+
85+ fn position_after_token ( token : & SyntaxToken ) -> TextSize {
86+ match non_trivia_sibling ( token. clone ( ) . into ( ) , Direction :: Next ) {
87+ Some ( prev_token) => prev_token. text_range ( ) . start ( ) ,
88+ None => token. text_range ( ) . end ( ) ,
89+ }
90+ }
91+
4892#[ cfg( test) ]
4993mod tests {
5094 use super :: * ;
@@ -89,4 +133,18 @@ mod tests {
89133 // See https://github.com/rust-lang/rust-analyzer/issues/7693
90134 check_assist_not_applicable ( flip_comma, r#"bar!(a,$0 b)"# ) ;
91135 }
136+
137+ #[ test]
138+ fn flip_comma_attribute ( ) {
139+ check_assist (
140+ flip_comma,
141+ r#"#[repr(align(2),$0 C)] struct Foo;"# ,
142+ r#"#[repr(C, align(2))] struct Foo;"# ,
143+ ) ;
144+ check_assist (
145+ flip_comma,
146+ r#"#[foo(bar, baz(1 + 1),$0 qux, other)] struct Foo;"# ,
147+ r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"# ,
148+ ) ;
149+ }
92150}
0 commit comments