]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/reorder_fields.rs
Merge #11481
[rust.git] / crates / ide_assists / src / handlers / reorder_fields.rs
1 use either::Either;
2 use itertools::Itertools;
3 use rustc_hash::FxHashMap;
4
5 use syntax::{ast, ted, AstNode};
6
7 use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9 // Assist: reorder_fields
10 //
11 // Reorder the fields of record literals and record patterns in the same order as in
12 // the definition.
13 //
14 // ```
15 // struct Foo {foo: i32, bar: i32};
16 // const test: Foo = $0Foo {bar: 0, foo: 1}
17 // ```
18 // ->
19 // ```
20 // struct Foo {foo: i32, bar: i32};
21 // const test: Foo = Foo {foo: 1, bar: 0}
22 // ```
23 pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24     let record = ctx
25         .find_node_at_offset::<ast::RecordExpr>()
26         .map(Either::Left)
27         .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(Either::Right))?;
28
29     let path = record.as_ref().either(|it| it.path(), |it| it.path())?;
30     let ranks = compute_fields_ranks(&path, ctx)?;
31     let get_rank_of_field =
32         |of: Option<_>| *ranks.get(&of.unwrap_or_default()).unwrap_or(&usize::MAX);
33
34     let field_list = match &record {
35         Either::Left(it) => Either::Left(it.record_expr_field_list()?),
36         Either::Right(it) => Either::Right(it.record_pat_field_list()?),
37     };
38     let fields = match field_list {
39         Either::Left(it) => Either::Left((
40             it.fields()
41                 .sorted_unstable_by_key(|field| {
42                     get_rank_of_field(field.field_name().map(|it| it.to_string()))
43                 })
44                 .collect::<Vec<_>>(),
45             it,
46         )),
47         Either::Right(it) => Either::Right((
48             it.fields()
49                 .sorted_unstable_by_key(|field| {
50                     get_rank_of_field(field.field_name().map(|it| it.to_string()))
51                 })
52                 .collect::<Vec<_>>(),
53             it,
54         )),
55     };
56
57     let is_sorted = fields.as_ref().either(
58         |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b),
59         |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b),
60     );
61     if is_sorted {
62         cov_mark::hit!(reorder_sorted_fields);
63         return None;
64     }
65     let target = record.as_ref().either(AstNode::syntax, AstNode::syntax).text_range();
66     acc.add(
67         AssistId("reorder_fields", AssistKind::RefactorRewrite),
68         "Reorder record fields",
69         target,
70         |builder| match fields {
71             Either::Left((sorted, field_list)) => {
72                 replace(builder.make_mut(field_list).fields(), sorted)
73             }
74             Either::Right((sorted, field_list)) => {
75                 replace(builder.make_mut(field_list).fields(), sorted)
76             }
77         },
78     )
79 }
80
81 fn replace<T: AstNode + PartialEq>(
82     fields: impl Iterator<Item = T>,
83     sorted_fields: impl IntoIterator<Item = T>,
84 ) {
85     fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
86         ted::replace(field.syntax(), sorted_field.syntax().clone_for_update())
87     });
88 }
89
90 fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
91     let strukt = match ctx.sema.resolve_path(path) {
92         Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Struct(it)))) => it,
93         _ => return None,
94     };
95
96     let res = strukt
97         .fields(ctx.db())
98         .into_iter()
99         .enumerate()
100         .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
101         .collect();
102
103     Some(res)
104 }
105
106 #[cfg(test)]
107 mod tests {
108     use crate::tests::{check_assist, check_assist_not_applicable};
109
110     use super::*;
111
112     #[test]
113     fn reorder_sorted_fields() {
114         cov_mark::check!(reorder_sorted_fields);
115         check_assist_not_applicable(
116             reorder_fields,
117             r#"
118 struct Foo { foo: i32, bar: i32 }
119 const test: Foo = $0Foo { foo: 0, bar: 0 };
120 "#,
121         )
122     }
123
124     #[test]
125     fn trivial_empty_fields() {
126         check_assist_not_applicable(
127             reorder_fields,
128             r#"
129 struct Foo {}
130 const test: Foo = $0Foo {};
131 "#,
132         )
133     }
134
135     #[test]
136     fn reorder_struct_fields() {
137         check_assist(
138             reorder_fields,
139             r#"
140 struct Foo { foo: i32, bar: i32 }
141 const test: Foo = $0Foo { bar: 0, foo: 1 };
142 "#,
143             r#"
144 struct Foo { foo: i32, bar: i32 }
145 const test: Foo = Foo { foo: 1, bar: 0 };
146 "#,
147         )
148     }
149     #[test]
150     fn reorder_struct_pattern() {
151         check_assist(
152             reorder_fields,
153             r#"
154 struct Foo { foo: i64, bar: i64, baz: i64 }
155
156 fn f(f: Foo) -> {
157     match f {
158         $0Foo { baz: 0, ref mut bar, .. } => (),
159         _ => ()
160     }
161 }
162 "#,
163             r#"
164 struct Foo { foo: i64, bar: i64, baz: i64 }
165
166 fn f(f: Foo) -> {
167     match f {
168         Foo { ref mut bar, baz: 0, .. } => (),
169         _ => ()
170     }
171 }
172 "#,
173         )
174     }
175
176     #[test]
177     fn reorder_with_extra_field() {
178         check_assist(
179             reorder_fields,
180             r#"
181 struct Foo { foo: String, bar: String }
182
183 impl Foo {
184     fn new() -> Foo {
185         let foo = String::new();
186         $0Foo {
187             bar: foo.clone(),
188             extra: "Extra field",
189             foo,
190         }
191     }
192 }
193 "#,
194             r#"
195 struct Foo { foo: String, bar: String }
196
197 impl Foo {
198     fn new() -> Foo {
199         let foo = String::new();
200         Foo {
201             foo,
202             bar: foo.clone(),
203             extra: "Extra field",
204         }
205     }
206 }
207 "#,
208         )
209     }
210 }