]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/reorder_fields.rs
7526: Rename crate assists to ide_assists.
[rust.git] / crates / ide_assists / src / handlers / reorder_fields.rs
1 use itertools::Itertools;
2 use rustc_hash::FxHashMap;
3
4 use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
5 use ide_db::RootDatabase;
6 use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7 use test_utils::mark;
8
9 use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11 // Assist: reorder_fields
12 //
13 // Reorder the fields of record literals and record patterns in the same order as in
14 // the definition.
15 //
16 // ```
17 // struct Foo {foo: i32, bar: i32};
18 // const test: Foo = $0Foo {bar: 0, foo: 1}
19 // ```
20 // ->
21 // ```
22 // struct Foo {foo: i32, bar: i32};
23 // const test: Foo = Foo {foo: 1, bar: 0}
24 // ```
25 //
26 pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27     reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
28 }
29
30 fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31     let record = ctx.find_node_at_offset::<R>()?;
32     let path = record.syntax().children().find_map(ast::Path::cast)?;
33
34     let ranks = compute_fields_ranks(&path, &ctx)?;
35
36     let fields = get_fields(&record.syntax());
37     let sorted_fields = sorted_by_rank(&fields, |node| {
38         *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())
39     });
40
41     if sorted_fields == fields {
42         mark::hit!(reorder_sorted_fields);
43         return None;
44     }
45
46     let target = record.syntax().text_range();
47     acc.add(
48         AssistId("reorder_fields", AssistKind::RefactorRewrite),
49         "Reorder record fields",
50         target,
51         |edit| {
52             let mut rewriter = algo::SyntaxRewriter::default();
53             for (old, new) in fields.iter().zip(&sorted_fields) {
54                 rewriter.replace(old, new);
55             }
56             edit.rewrite(rewriter);
57         },
58     )
59 }
60
61 fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
62     match node.kind() {
63         RECORD_EXPR => vec![RECORD_EXPR_FIELD],
64         RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT],
65         _ => vec![],
66     }
67 }
68
69 fn get_field_name(node: &SyntaxNode) -> String {
70     let res = match_ast! {
71         match node {
72             ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
73             ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()),
74             _ => None,
75         }
76     };
77     res.unwrap_or_default()
78 }
79
80 fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> {
81     let kinds = get_fields_kind(record);
82     record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect()
83 }
84
85 fn sorted_by_rank(
86     fields: &[SyntaxNode],
87     get_rank: impl Fn(&SyntaxNode) -> usize,
88 ) -> Vec<SyntaxNode> {
89     fields.iter().cloned().sorted_by_key(get_rank).collect()
90 }
91
92 fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
93     match sema.resolve_path(path) {
94         Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
95         _ => None,
96     }
97 }
98
99 fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
100     Some(
101         struct_definition(path, &ctx.sema)?
102             .fields(ctx.db())
103             .iter()
104             .enumerate()
105             .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
106             .collect(),
107     )
108 }
109
110 #[cfg(test)]
111 mod tests {
112     use test_utils::mark;
113
114     use crate::tests::{check_assist, check_assist_not_applicable};
115
116     use super::*;
117
118     #[test]
119     fn reorder_sorted_fields() {
120         mark::check!(reorder_sorted_fields);
121         check_assist_not_applicable(
122             reorder_fields,
123             r#"
124 struct Foo {
125     foo: i32,
126     bar: i32,
127 }
128
129 const test: Foo = $0Foo { foo: 0, bar: 0 };
130 "#,
131         )
132     }
133
134     #[test]
135     fn trivial_empty_fields() {
136         check_assist_not_applicable(
137             reorder_fields,
138             r#"
139 struct Foo {};
140 const test: Foo = $0Foo {}
141 "#,
142         )
143     }
144
145     #[test]
146     fn reorder_struct_fields() {
147         check_assist(
148             reorder_fields,
149             r#"
150 struct Foo {foo: i32, bar: i32};
151 const test: Foo = $0Foo {bar: 0, foo: 1}
152 "#,
153             r#"
154 struct Foo {foo: i32, bar: i32};
155 const test: Foo = Foo {foo: 1, bar: 0}
156 "#,
157         )
158     }
159
160     #[test]
161     fn reorder_struct_pattern() {
162         check_assist(
163             reorder_fields,
164             r#"
165 struct Foo { foo: i64, bar: i64, baz: i64 }
166
167 fn f(f: Foo) -> {
168     match f {
169         $0Foo { baz: 0, ref mut bar, .. } => (),
170         _ => ()
171     }
172 }
173 "#,
174             r#"
175 struct Foo { foo: i64, bar: i64, baz: i64 }
176
177 fn f(f: Foo) -> {
178     match f {
179         Foo { ref mut bar, baz: 0, .. } => (),
180         _ => ()
181     }
182 }
183 "#,
184         )
185     }
186
187     #[test]
188     fn reorder_with_extra_field() {
189         check_assist(
190             reorder_fields,
191             r#"
192 struct Foo {
193     foo: String,
194     bar: String,
195 }
196
197 impl Foo {
198     fn new() -> Foo {
199         let foo = String::new();
200         $0Foo {
201             bar: foo.clone(),
202             extra: "Extra field",
203             foo,
204         }
205     }
206 }
207 "#,
208             r#"
209 struct Foo {
210     foo: String,
211     bar: String,
212 }
213
214 impl Foo {
215     fn new() -> Foo {
216         let foo = String::new();
217         Foo {
218             foo,
219             bar: foo.clone(),
220             extra: "Extra field",
221         }
222     }
223 }
224 "#,
225         )
226     }
227 }