aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorGeoffrey Copin <[email protected]>2020-04-09 23:35:43 +0100
committerGeoffrey Copin <[email protected]>2020-04-09 23:57:03 +0100
commit730a927c5e6b382690e88f482a03701636242a2c (patch)
treee148e93832b9b7c1ddedaaa4c5d10f80d07abec6 /crates
parent176f7f61175bc433c56083a758bd7a28a8ae31f8 (diff)
Implement assist "Reorder field names"
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs207
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_hir_def/src/body/lower.rs3
-rw-r--r--crates/ra_syntax/src/ast/generated/nodes.rs8
4 files changed, 218 insertions, 2 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}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 5ba5254fd..a00136da1 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -129,6 +129,7 @@ mod handlers {
129 mod replace_unwrap_with_match; 129 mod replace_unwrap_with_match;
130 mod split_import; 130 mod split_import;
131 mod add_from_impl_for_enum; 131 mod add_from_impl_for_enum;
132 mod reorder_fields;
132 133
133 pub(crate) fn all() -> &'static [AssistHandler] { 134 pub(crate) fn all() -> &'static [AssistHandler] {
134 &[ 135 &[
@@ -170,6 +171,7 @@ mod handlers {
170 // These are manually sorted for better priorities 171 // These are manually sorted for better priorities
171 add_missing_impl_members::add_missing_impl_members, 172 add_missing_impl_members::add_missing_impl_members,
172 add_missing_impl_members::add_missing_default_members, 173 add_missing_impl_members::add_missing_default_members,
174 reorder_fields::reorder_fields,
173 ] 175 ]
174 } 176 }
175} 177}
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index b0d71eb3d..80492b733 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -689,9 +689,10 @@ impl ExprCollector<'_> {
689 Pat::Missing 689 Pat::Missing
690 } 690 }
691 } 691 }
692
693 // FIXME: implement 692 // FIXME: implement
694 ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing, 693 ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
694 // FIXME: implement
695 ast::Pat::RecordFieldPat(_) => Pat::Missing,
695 }; 696 };
696 let ptr = AstPtr::new(&pat); 697 let ptr = AstPtr::new(&pat);
697 self.alloc_pat(pattern, Either::Left(ptr)) 698 self.alloc_pat(pattern, Either::Left(ptr))
diff --git a/crates/ra_syntax/src/ast/generated/nodes.rs b/crates/ra_syntax/src/ast/generated/nodes.rs
index 20f663046..79b225622 100644
--- a/crates/ra_syntax/src/ast/generated/nodes.rs
+++ b/crates/ra_syntax/src/ast/generated/nodes.rs
@@ -3256,6 +3256,7 @@ pub enum Pat {
3256 RangePat(RangePat), 3256 RangePat(RangePat),
3257 LiteralPat(LiteralPat), 3257 LiteralPat(LiteralPat),
3258 MacroPat(MacroPat), 3258 MacroPat(MacroPat),
3259 RecordFieldPat(RecordFieldPat),
3259} 3260}
3260impl From<OrPat> for Pat { 3261impl From<OrPat> for Pat {
3261 fn from(node: OrPat) -> Pat { Pat::OrPat(node) } 3262 fn from(node: OrPat) -> Pat { Pat::OrPat(node) }
@@ -3302,12 +3303,15 @@ impl From<LiteralPat> for Pat {
3302impl From<MacroPat> for Pat { 3303impl From<MacroPat> for Pat {
3303 fn from(node: MacroPat) -> Pat { Pat::MacroPat(node) } 3304 fn from(node: MacroPat) -> Pat { Pat::MacroPat(node) }
3304} 3305}
3306impl From<RecordFieldPat> for Pat {
3307 fn from(node: RecordFieldPat) -> Pat { Pat::RecordFieldPat(node) }
3308}
3305impl AstNode for Pat { 3309impl AstNode for Pat {
3306 fn can_cast(kind: SyntaxKind) -> bool { 3310 fn can_cast(kind: SyntaxKind) -> bool {
3307 match kind { 3311 match kind {
3308 OR_PAT | PAREN_PAT | REF_PAT | BOX_PAT | BIND_PAT | PLACEHOLDER_PAT | DOT_DOT_PAT 3312 OR_PAT | PAREN_PAT | REF_PAT | BOX_PAT | BIND_PAT | PLACEHOLDER_PAT | DOT_DOT_PAT
3309 | PATH_PAT | RECORD_PAT | TUPLE_STRUCT_PAT | TUPLE_PAT | SLICE_PAT | RANGE_PAT 3313 | PATH_PAT | RECORD_PAT | TUPLE_STRUCT_PAT | TUPLE_PAT | SLICE_PAT | RANGE_PAT
3310 | LITERAL_PAT | MACRO_PAT => true, 3314 | LITERAL_PAT | MACRO_PAT | RECORD_FIELD_PAT => true,
3311 _ => false, 3315 _ => false,
3312 } 3316 }
3313 } 3317 }
@@ -3328,6 +3332,7 @@ impl AstNode for Pat {
3328 RANGE_PAT => Pat::RangePat(RangePat { syntax }), 3332 RANGE_PAT => Pat::RangePat(RangePat { syntax }),
3329 LITERAL_PAT => Pat::LiteralPat(LiteralPat { syntax }), 3333 LITERAL_PAT => Pat::LiteralPat(LiteralPat { syntax }),
3330 MACRO_PAT => Pat::MacroPat(MacroPat { syntax }), 3334 MACRO_PAT => Pat::MacroPat(MacroPat { syntax }),
3335 RECORD_FIELD_PAT => Pat::RecordFieldPat(RecordFieldPat { syntax }),
3331 _ => return None, 3336 _ => return None,
3332 }; 3337 };
3333 Some(res) 3338 Some(res)
@@ -3349,6 +3354,7 @@ impl AstNode for Pat {
3349 Pat::RangePat(it) => &it.syntax, 3354 Pat::RangePat(it) => &it.syntax,
3350 Pat::LiteralPat(it) => &it.syntax, 3355 Pat::LiteralPat(it) => &it.syntax,
3351 Pat::MacroPat(it) => &it.syntax, 3356 Pat::MacroPat(it) => &it.syntax,
3357 Pat::RecordFieldPat(it) => &it.syntax,
3352 } 3358 }
3353 } 3359 }
3354} 3360}