aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/reorder_fields.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/reorder_fields.rs')
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs230
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 @@
1use std::collections::HashMap;
2
3use itertools::Itertools;
4
5use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
6use ra_ide_db::RootDatabase;
7use ra_syntax::{
8 algo, ast,
9 ast::{Name, Path, RecordLit, RecordPat},
10 AstNode, SyntaxKind, SyntaxNode,
11};
12
13use crate::{
14 assist_ctx::{Assist, AssistCtx},
15 AssistId,
16};
17use 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//
34pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> {
35 reorder::<RecordLit>(ctx.clone()).or_else(|| reorder::<RecordPat>(ctx))
36}
37
38fn 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
61fn 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
70fn 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
86fn 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
91fn 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
98fn 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
105fn 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)]
117mod 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}