aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/reorder_fields.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers/reorder_fields.rs')
-rw-r--r--crates/assists/src/handlers/reorder_fields.rs220
1 files changed, 220 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/reorder_fields.rs b/crates/assists/src/handlers/reorder_fields.rs
new file mode 100644
index 000000000..527f457a7
--- /dev/null
+++ b/crates/assists/src/handlers/reorder_fields.rs
@@ -0,0 +1,220 @@
1use itertools::Itertools;
2use rustc_hash::FxHashMap;
3
4use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
5use ide_db::RootDatabase;
6use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7
8use 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 = <|>Foo {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//
25pub(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
29fn 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 return None;
42 }
43
44 let target = record.syntax().text_range();
45 acc.add(
46 AssistId("reorder_fields", AssistKind::RefactorRewrite),
47 "Reorder record fields",
48 target,
49 |edit| {
50 for (old, new) in fields.iter().zip(&sorted_fields) {
51 algo::diff(old, new).into_text_edit(edit.text_edit_builder());
52 }
53 },
54 )
55}
56
57fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
58 match node.kind() {
59 RECORD_EXPR => vec![RECORD_EXPR_FIELD],
60 RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT],
61 _ => vec![],
62 }
63}
64
65fn get_field_name(node: &SyntaxNode) -> String {
66 let res = match_ast! {
67 match node {
68 ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
69 ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()),
70 _ => None,
71 }
72 };
73 res.unwrap_or_default()
74}
75
76fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> {
77 let kinds = get_fields_kind(record);
78 record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect()
79}
80
81fn sorted_by_rank(
82 fields: &[SyntaxNode],
83 get_rank: impl Fn(&SyntaxNode) -> usize,
84) -> Vec<SyntaxNode> {
85 fields.iter().cloned().sorted_by_key(get_rank).collect()
86}
87
88fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
89 match sema.resolve_path(path) {
90 Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
91 _ => None,
92 }
93}
94
95fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
96 Some(
97 struct_definition(path, &ctx.sema)?
98 .fields(ctx.db())
99 .iter()
100 .enumerate()
101 .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
102 .collect(),
103 )
104}
105
106#[cfg(test)]
107mod tests {
108 use crate::tests::{check_assist, check_assist_not_applicable};
109
110 use super::*;
111
112 #[test]
113 fn not_applicable_if_sorted() {
114 check_assist_not_applicable(
115 reorder_fields,
116 r#"
117 struct Foo {
118 foo: i32,
119 bar: i32,
120 }
121
122 const test: Foo = <|>Foo { foo: 0, bar: 0 };
123 "#,
124 )
125 }
126
127 #[test]
128 fn trivial_empty_fields() {
129 check_assist_not_applicable(
130 reorder_fields,
131 r#"
132 struct Foo {};
133 const test: Foo = <|>Foo {}
134 "#,
135 )
136 }
137
138 #[test]
139 fn reorder_struct_fields() {
140 check_assist(
141 reorder_fields,
142 r#"
143 struct Foo {foo: i32, bar: i32};
144 const test: Foo = <|>Foo {bar: 0, foo: 1}
145 "#,
146 r#"
147 struct Foo {foo: i32, bar: i32};
148 const test: Foo = Foo {foo: 1, bar: 0}
149 "#,
150 )
151 }
152
153 #[test]
154 fn reorder_struct_pattern() {
155 check_assist(
156 reorder_fields,
157 r#"
158 struct Foo { foo: i64, bar: i64, baz: i64 }
159
160 fn f(f: Foo) -> {
161 match f {
162 <|>Foo { baz: 0, ref mut bar, .. } => (),
163 _ => ()
164 }
165 }
166 "#,
167 r#"
168 struct Foo { foo: i64, bar: i64, baz: i64 }
169
170 fn f(f: Foo) -> {
171 match f {
172 Foo { ref mut bar, baz: 0, .. } => (),
173 _ => ()
174 }
175 }
176 "#,
177 )
178 }
179
180 #[test]
181 fn reorder_with_extra_field() {
182 check_assist(
183 reorder_fields,
184 r#"
185 struct Foo {
186 foo: String,
187 bar: String,
188 }
189
190 impl Foo {
191 fn new() -> Foo {
192 let foo = String::new();
193 <|>Foo {
194 bar: foo.clone(),
195 extra: "Extra field",
196 foo,
197 }
198 }
199 }
200 "#,
201 r#"
202 struct Foo {
203 foo: String,
204 bar: String,
205 }
206
207 impl Foo {
208 fn new() -> Foo {
209 let foo = String::new();
210 Foo {
211 foo,
212 bar: foo.clone(),
213 extra: "Extra field",
214 }
215 }
216 }
217 "#,
218 )
219 }
220}