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