diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-04-11 19:40:46 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-04-11 19:40:46 +0100 |
commit | fd06fe7b13045185ab4e630b0044aa9d8bbcdf8a (patch) | |
tree | 1481f3fb3e65902b36571ad403fabf5e5940aa56 /crates/ra_assists/src/handlers/reorder_fields.rs | |
parent | 1a1c09ed3e3a34c0a8750f98ece9ad85595395d2 (diff) | |
parent | d9089245fefdc4179c1d61839e78131c5e5a5a45 (diff) |
Merge #3925
3925: Implement assist "Reorder field names" r=matklad a=geoffreycopin
This PR implements the "Reorder record fields" assist as discussed in issue #3821 .
Adding a `RecordFieldPat` variant to the `Pat` enum seemed like the easiest way to handle the `RecordPat` children as a single sequence of elements, maybe there is a better way ?
Co-authored-by: Geoffrey Copin <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/handlers/reorder_fields.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/reorder_fields.rs | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs new file mode 100644 index 000000000..692dd1315 --- /dev/null +++ b/crates/ra_assists/src/handlers/reorder_fields.rs | |||
@@ -0,0 +1,230 @@ | |||
1 | use std::collections::HashMap; | ||
2 | |||
3 | use itertools::Itertools; | ||
4 | |||
5 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; | ||
6 | use ra_ide_db::RootDatabase; | ||
7 | use ra_syntax::{ | ||
8 | algo, ast, | ||
9 | ast::{Name, Path, RecordLit, RecordPat}, | ||
10 | AstNode, SyntaxKind, SyntaxNode, | ||
11 | }; | ||
12 | |||
13 | use crate::{ | ||
14 | assist_ctx::{Assist, AssistCtx}, | ||
15 | AssistId, | ||
16 | }; | ||
17 | use ra_syntax::ast::{Expr, NameRef}; | ||
18 | |||
19 | // Assist: reorder_fields | ||
20 | // | ||
21 | // Reorder the fields of record literals and record patterns in the same order as in | ||
22 | // the definition. | ||
23 | // | ||
24 | // ``` | ||
25 | // struct Foo {foo: i32, bar: i32}; | ||
26 | // const test: Foo = <|>Foo {bar: 0, foo: 1} | ||
27 | // ``` | ||
28 | // -> | ||
29 | // ``` | ||
30 | // struct Foo {foo: i32, bar: i32}; | ||
31 | // const test: Foo = Foo {foo: 1, bar: 0} | ||
32 | // ``` | ||
33 | // | ||
34 | pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> { | ||
35 | reorder::<RecordLit>(ctx.clone()).or_else(|| reorder::<RecordPat>(ctx)) | ||
36 | } | ||
37 | |||
38 | fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> { | ||
39 | let record = ctx.find_node_at_offset::<R>()?; | ||
40 | let path = record.syntax().children().find_map(Path::cast)?; | ||
41 | |||
42 | let ranks = compute_fields_ranks(&path, &ctx)?; | ||
43 | |||
44 | let fields = get_fields(&record.syntax()); | ||
45 | let sorted_fields = sorted_by_rank(&fields, |node| { | ||
46 | *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value()) | ||
47 | }); | ||
48 | |||
49 | if sorted_fields == fields { | ||
50 | return None; | ||
51 | } | ||
52 | |||
53 | ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| { | ||
54 | for (old, new) in fields.iter().zip(&sorted_fields) { | ||
55 | algo::diff(old, new).into_text_edit(edit.text_edit_builder()); | ||
56 | } | ||
57 | edit.target(record.syntax().text_range()) | ||
58 | }) | ||
59 | } | ||
60 | |||
61 | fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> { | ||
62 | use SyntaxKind::*; | ||
63 | match node.kind() { | ||
64 | RECORD_LIT => vec![RECORD_FIELD], | ||
65 | RECORD_PAT => vec![RECORD_FIELD_PAT, BIND_PAT], | ||
66 | _ => vec![], | ||
67 | } | ||
68 | } | ||
69 | |||
70 | fn get_field_name(node: &SyntaxNode) -> String { | ||
71 | use SyntaxKind::*; | ||
72 | match node.kind() { | ||
73 | RECORD_FIELD => { | ||
74 | if let Some(name) = node.children().find_map(NameRef::cast) { | ||
75 | return name.to_string(); | ||
76 | } | ||
77 | node.children().find_map(Expr::cast).map(|expr| expr.to_string()).unwrap_or_default() | ||
78 | } | ||
79 | BIND_PAT | RECORD_FIELD_PAT => { | ||
80 | node.children().find_map(Name::cast).map(|n| n.to_string()).unwrap_or_default() | ||
81 | } | ||
82 | _ => String::new(), | ||
83 | } | ||
84 | } | ||
85 | |||
86 | fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> { | ||
87 | let kinds = get_fields_kind(record); | ||
88 | record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect() | ||
89 | } | ||
90 | |||
91 | fn sorted_by_rank( | ||
92 | fields: &[SyntaxNode], | ||
93 | get_rank: impl Fn(&SyntaxNode) -> usize, | ||
94 | ) -> Vec<SyntaxNode> { | ||
95 | fields.iter().cloned().sorted_by_key(get_rank).collect() | ||
96 | } | ||
97 | |||
98 | fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> { | ||
99 | match sema.resolve_path(path) { | ||
100 | Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s), | ||
101 | _ => None, | ||
102 | } | ||
103 | } | ||
104 | |||
105 | fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> { | ||
106 | Some( | ||
107 | struct_definition(path, ctx.sema)? | ||
108 | .fields(ctx.db) | ||
109 | .iter() | ||
110 | .enumerate() | ||
111 | .map(|(idx, field)| (field.name(ctx.db).to_string(), idx)) | ||
112 | .collect(), | ||
113 | ) | ||
114 | } | ||
115 | |||
116 | #[cfg(test)] | ||
117 | mod tests { | ||
118 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
119 | |||
120 | use super::*; | ||
121 | |||
122 | #[test] | ||
123 | fn not_applicable_if_sorted() { | ||
124 | check_assist_not_applicable( | ||
125 | reorder_fields, | ||
126 | r#" | ||
127 | struct Foo { | ||
128 | foo: i32, | ||
129 | bar: i32, | ||
130 | } | ||
131 | |||
132 | const test: Foo = <|>Foo { foo: 0, bar: 0 }; | ||
133 | "#, | ||
134 | ) | ||
135 | } | ||
136 | |||
137 | #[test] | ||
138 | fn trivial_empty_fields() { | ||
139 | check_assist_not_applicable( | ||
140 | reorder_fields, | ||
141 | r#" | ||
142 | struct Foo {}; | ||
143 | const test: Foo = <|>Foo {} | ||
144 | "#, | ||
145 | ) | ||
146 | } | ||
147 | |||
148 | #[test] | ||
149 | fn reorder_struct_fields() { | ||
150 | check_assist( | ||
151 | reorder_fields, | ||
152 | r#" | ||
153 | struct Foo {foo: i32, bar: i32}; | ||
154 | const test: Foo = <|>Foo {bar: 0, foo: 1} | ||
155 | "#, | ||
156 | r#" | ||
157 | struct Foo {foo: i32, bar: i32}; | ||
158 | const test: Foo = <|>Foo {foo: 1, bar: 0} | ||
159 | "#, | ||
160 | ) | ||
161 | } | ||
162 | |||
163 | #[test] | ||
164 | fn reorder_struct_pattern() { | ||
165 | check_assist( | ||
166 | reorder_fields, | ||
167 | r#" | ||
168 | struct Foo { foo: i64, bar: i64, baz: i64 } | ||
169 | |||
170 | fn f(f: Foo) -> { | ||
171 | match f { | ||
172 | <|>Foo { baz: 0, ref mut bar, .. } => (), | ||
173 | _ => () | ||
174 | } | ||
175 | } | ||
176 | "#, | ||
177 | r#" | ||
178 | struct Foo { foo: i64, bar: i64, baz: i64 } | ||
179 | |||
180 | fn f(f: Foo) -> { | ||
181 | match f { | ||
182 | <|>Foo { ref mut bar, baz: 0, .. } => (), | ||
183 | _ => () | ||
184 | } | ||
185 | } | ||
186 | "#, | ||
187 | ) | ||
188 | } | ||
189 | |||
190 | #[test] | ||
191 | fn reorder_with_extra_field() { | ||
192 | check_assist( | ||
193 | reorder_fields, | ||
194 | r#" | ||
195 | struct Foo { | ||
196 | foo: String, | ||
197 | bar: String, | ||
198 | } | ||
199 | |||
200 | impl Foo { | ||
201 | fn new() -> Foo { | ||
202 | let foo = String::new(); | ||
203 | <|>Foo { | ||
204 | bar: foo.clone(), | ||
205 | extra: "Extra field", | ||
206 | foo, | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | "#, | ||
211 | r#" | ||
212 | struct Foo { | ||
213 | foo: String, | ||
214 | bar: String, | ||
215 | } | ||
216 | |||
217 | impl Foo { | ||
218 | fn new() -> Foo { | ||
219 | let foo = String::new(); | ||
220 | <|>Foo { | ||
221 | foo, | ||
222 | bar: foo.clone(), | ||
223 | extra: "Extra field", | ||
224 | } | ||
225 | } | ||
226 | } | ||
227 | "#, | ||
228 | ) | ||
229 | } | ||
230 | } | ||