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