aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-04-11 19:40:46 +0100
committerGitHub <[email protected]>2020-04-11 19:40:46 +0100
commitfd06fe7b13045185ab4e630b0044aa9d8bbcdf8a (patch)
tree1481f3fb3e65902b36571ad403fabf5e5940aa56
parent1a1c09ed3e3a34c0a8750f98ece9ad85595395d2 (diff)
parentd9089245fefdc4179c1d61839e78131c5e5a5a45 (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]>
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs15
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs230
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_hir_def/src/body/lower.rs1
-rw-r--r--docs/user/assists.md15
5 files changed, 262 insertions, 1 deletions
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 24392836c..b63b4d81a 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -607,6 +607,21 @@ impl Walrus {
607} 607}
608 608
609#[test] 609#[test]
610fn doctest_reorder_fields() {
611 check(
612 "reorder_fields",
613 r#####"
614struct Foo {foo: i32, bar: i32};
615const test: Foo = <|>Foo {bar: 0, foo: 1}
616"#####,
617 r#####"
618struct Foo {foo: i32, bar: i32};
619const test: Foo = Foo {foo: 1, bar: 0}
620"#####,
621 )
622}
623
624#[test]
610fn doctest_replace_if_let_with_match() { 625fn doctest_replace_if_let_with_match() {
611 check( 626 check(
612 "replace_if_let_with_match", 627 "replace_if_let_with_match",
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}
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 cc2532e88..c057dc8f2 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -665,7 +665,6 @@ impl ExprCollector<'_> {
665 Pat::Missing 665 Pat::Missing
666 } 666 }
667 } 667 }
668
669 // FIXME: implement 668 // FIXME: implement
670 ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing, 669 ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
671 }; 670 };
diff --git a/docs/user/assists.md b/docs/user/assists.md
index cc42169d8..1d9510423 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -582,6 +582,21 @@ impl Walrus {
582} 582}
583``` 583```
584 584
585## `reorder_fields`
586
587Reorder the fields of record literals and record patterns in the same order as in
588the definition.
589
590```rust
591// BEFORE
592struct Foo {foo: i32, bar: i32};
593const test: Foo = ┃Foo {bar: 0, foo: 1}
594
595// AFTER
596struct Foo {foo: i32, bar: i32};
597const test: Foo = Foo {foo: 1, bar: 0}
598```
599
585## `replace_if_let_with_match` 600## `replace_if_let_with_match`
586 601
587Replaces `if let` with an else branch with a `match` expression. 602Replaces `if let` with an else branch with a `match` expression.