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.rs207
1 files changed, 207 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..c17c1288c
--- /dev/null
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -0,0 +1,207 @@
1use std::collections::HashMap;
2
3use itertools::Itertools;
4
5use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
6use ra_ide_db::RootDatabase;
7use ra_syntax::ast::{Name, Pat};
8use ra_syntax::{
9 ast,
10 ast::{Path, RecordField, RecordLit, RecordPat},
11 AstNode,
12};
13
14use crate::{
15 assist_ctx::{Assist, AssistCtx},
16 AssistId,
17};
18
19pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> {
20 reorder_struct(ctx.clone()).or_else(|| reorder_struct_pat(ctx))
21}
22
23fn reorder_struct(ctx: AssistCtx) -> Option<Assist> {
24 let record: RecordLit = ctx.find_node_at_offset()?;
25 reorder(ctx, &record, &record.path()?, field_name)
26}
27
28fn field_name(r: &RecordField) -> String {
29 r.name_ref()
30 .map(|name| name.syntax().text())
31 .unwrap_or_else(|| r.expr().unwrap().syntax().text())
32 .to_string()
33}
34
35fn reorder_struct_pat(ctx: AssistCtx) -> Option<Assist> {
36 let record: RecordPat = ctx.find_node_at_offset()?;
37 reorder(ctx, &record, &record.path()?, field_pat_name)
38}
39
40fn field_pat_name(field: &Pat) -> String {
41 field.syntax().children().find_map(Name::cast).map(|n| n.to_string()).unwrap_or_default()
42}
43
44fn reorder<R: AstNode, F: AstNode + Eq + Clone>(
45 ctx: AssistCtx,
46 record: &R,
47 path: &Path,
48 field_name: fn(&F) -> String,
49) -> Option<Assist> {
50 let ranks = compute_fields_ranks(path, &ctx)?;
51 let fields: Vec<F> = get_fields(record);
52 let sorted_fields: Vec<F> =
53 sort_by_rank(&fields, |f| *ranks.get(&field_name(f)).unwrap_or(&usize::max_value()));
54
55 if sorted_fields == fields {
56 return None;
57 }
58
59 ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| {
60 for (old, new) in fields.into_iter().zip(sorted_fields) {
61 edit.replace_ast(old, new);
62 }
63 edit.target(record.syntax().text_range())
64 })
65}
66
67fn get_fields<R: AstNode, F: AstNode>(record: &R) -> Vec<F> {
68 record.syntax().children().flat_map(|n1| n1.children()).filter_map(|n3| F::cast(n3)).collect()
69}
70
71fn sort_by_rank<F: AstNode + Clone>(fields: &[F], get_rank: impl FnMut(&F) -> usize) -> Vec<F> {
72 fields.iter().cloned().sorted_by_key(get_rank).collect()
73}
74
75fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
76 match sema.resolve_path(path) {
77 Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
78 _ => None,
79 }
80}
81
82fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> {
83 Some(
84 struct_definition(path, ctx.sema)?
85 .fields(ctx.db)
86 .iter()
87 .enumerate()
88 .map(|(idx, field)| (field.name(ctx.db).to_string(), idx))
89 .collect(),
90 )
91}
92
93#[cfg(test)]
94mod tests {
95 use crate::helpers::{check_assist, check_assist_not_applicable};
96
97 use super::*;
98
99 #[test]
100 fn not_applicable_if_sorted() {
101 check_assist_not_applicable(
102 reorder_fields,
103 r#"
104 struct Foo {
105 foo: i32,
106 bar: i32,
107 }
108
109 const test: Foo = <|>Foo { foo: 0, bar: 0 };
110 "#,
111 )
112 }
113
114 #[test]
115 fn trivial_empty_fields() {
116 check_assist_not_applicable(
117 reorder_fields,
118 r#"
119 struct Foo {};
120 const test: Foo = <|>Foo {}
121 "#,
122 )
123 }
124
125 #[test]
126 fn reorder_struct_fields() {
127 check_assist(
128 reorder_fields,
129 r#"
130 struct Foo {foo: i32, bar: i32};
131 const test: Foo = <|>Foo {bar: 0, foo: 1}
132 "#,
133 r#"
134 struct Foo {foo: i32, bar: i32};
135 const test: Foo = <|>Foo {foo: 1, bar: 0}
136 "#,
137 )
138 }
139
140 #[test]
141 fn reorder_struct_pattern() {
142 check_assist(
143 reorder_fields,
144 r#"
145 struct Foo { foo: i64, bar: i64, baz: i64 }
146
147 fn f(f: Foo) -> {
148 match f {
149 <|>Foo { baz: 0, ref mut bar, .. } => (),
150 _ => ()
151 }
152 }
153 "#,
154 r#"
155 struct Foo { foo: i64, bar: i64, baz: i64 }
156
157 fn f(f: Foo) -> {
158 match f {
159 <|>Foo { ref mut bar, baz: 0, .. } => (),
160 _ => ()
161 }
162 }
163 "#,
164 )
165 }
166
167 #[test]
168 fn reorder_with_extra_field() {
169 check_assist(
170 reorder_fields,
171 r#"
172 struct Foo {
173 foo: String,
174 bar: String,
175 }
176
177 impl Foo {
178 fn new() -> Foo {
179 let foo = String::new();
180 <|>Foo {
181 bar: foo.clone(),
182 extra: "Extra field",
183 foo,
184 }
185 }
186 }
187 "#,
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 <|>Foo {
198 foo,
199 bar: foo.clone(),
200 extra: "Extra field",
201 }
202 }
203 }
204 "#,
205 )
206 }
207}