Skip to content

Commit fd06fe7

Browse files
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]>
2 parents 1a1c09e + d908924 commit fd06fe7

File tree

5 files changed

+262
-1
lines changed

5 files changed

+262
-1
lines changed

crates/ra_assists/src/doc_tests/generated.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,21 @@ impl Walrus {
606606
)
607607
}
608608

609+
#[test]
610+
fn doctest_reorder_fields() {
611+
check(
612+
"reorder_fields",
613+
r#####"
614+
struct Foo {foo: i32, bar: i32};
615+
const test: Foo = <|>Foo {bar: 0, foo: 1}
616+
"#####,
617+
r#####"
618+
struct Foo {foo: i32, bar: i32};
619+
const test: Foo = Foo {foo: 1, bar: 0}
620+
"#####,
621+
)
622+
}
623+
609624
#[test]
610625
fn doctest_replace_if_let_with_match() {
611626
check(
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

crates/ra_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ mod handlers {
129129
mod replace_unwrap_with_match;
130130
mod split_import;
131131
mod add_from_impl_for_enum;
132+
mod reorder_fields;
132133

133134
pub(crate) fn all() -> &'static [AssistHandler] {
134135
&[
@@ -170,6 +171,7 @@ mod handlers {
170171
// These are manually sorted for better priorities
171172
add_missing_impl_members::add_missing_impl_members,
172173
add_missing_impl_members::add_missing_default_members,
174+
reorder_fields::reorder_fields,
173175
]
174176
}
175177
}

crates/ra_hir_def/src/body/lower.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,6 @@ impl ExprCollector<'_> {
665665
Pat::Missing
666666
}
667667
}
668-
669668
// FIXME: implement
670669
ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
671670
};

docs/user/assists.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,21 @@ impl Walrus {
582582
}
583583
```
584584

585+
## `reorder_fields`
586+
587+
Reorder the fields of record literals and record patterns in the same order as in
588+
the definition.
589+
590+
```rust
591+
// BEFORE
592+
struct Foo {foo: i32, bar: i32};
593+
const test: Foo =Foo {bar: 0, foo: 1}
594+
595+
// AFTER
596+
struct Foo {foo: i32, bar: i32};
597+
const test: Foo = Foo {foo: 1, bar: 0}
598+
```
599+
585600
## `replace_if_let_with_match`
586601

587602
Replaces `if let` with an else branch with a `match` expression.

0 commit comments

Comments
 (0)