aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/reorder_fields.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/reorder_fields.rs')
-rw-r--r--crates/ide_assists/src/handlers/reorder_fields.rs227
1 files changed, 227 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs
new file mode 100644
index 000000000..fba7d6ddb
--- /dev/null
+++ b/crates/ide_assists/src/handlers/reorder_fields.rs
@@ -0,0 +1,227 @@
1use itertools::Itertools;
2use rustc_hash::FxHashMap;
3
4use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
5use ide_db::RootDatabase;
6use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: reorder_fields
12//
13// Reorder the fields of record literals and record patterns in the same order as in
14// the definition.
15//
16// ```
17// struct Foo {foo: i32, bar: i32};
18// const test: Foo = $0Foo {bar: 0, foo: 1}
19// ```
20// ->
21// ```
22// struct Foo {foo: i32, bar: i32};
23// const test: Foo = Foo {foo: 1, bar: 0}
24// ```
25//
26pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
28}
29
30fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 let record = ctx.find_node_at_offset::<R>()?;
32 let path = record.syntax().children().find_map(ast::Path::cast)?;
33
34 let ranks = compute_fields_ranks(&path, &ctx)?;
35
36 let fields = get_fields(&record.syntax());
37 let sorted_fields = sorted_by_rank(&fields, |node| {
38 *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())
39 });
40
41 if sorted_fields == fields {
42 mark::hit!(reorder_sorted_fields);
43 return None;
44 }
45
46 let target = record.syntax().text_range();
47 acc.add(
48 AssistId("reorder_fields", AssistKind::RefactorRewrite),
49 "Reorder record fields",
50 target,
51 |edit| {
52 let mut rewriter = algo::SyntaxRewriter::default();
53 for (old, new) in fields.iter().zip(&sorted_fields) {
54 rewriter.replace(old, new);
55 }
56 edit.rewrite(rewriter);
57 },
58 )
59}
60
61fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
62 match node.kind() {
63 RECORD_EXPR => vec![RECORD_EXPR_FIELD],
64 RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT],
65 _ => vec![],
66 }
67}
68
69fn get_field_name(node: &SyntaxNode) -> String {
70 let res = match_ast! {
71 match node {
72 ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
73 ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()),
74 _ => None,
75 }
76 };
77 res.unwrap_or_default()
78}
79
80fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> {
81 let kinds = get_fields_kind(record);
82 record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect()
83}
84
85fn sorted_by_rank(
86 fields: &[SyntaxNode],
87 get_rank: impl Fn(&SyntaxNode) -> usize,
88) -> Vec<SyntaxNode> {
89 fields.iter().cloned().sorted_by_key(get_rank).collect()
90}
91
92fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
93 match sema.resolve_path(path) {
94 Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
95 _ => None,
96 }
97}
98
99fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
100 Some(
101 struct_definition(path, &ctx.sema)?
102 .fields(ctx.db())
103 .iter()
104 .enumerate()
105 .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
106 .collect(),
107 )
108}
109
110#[cfg(test)]
111mod tests {
112 use test_utils::mark;
113
114 use crate::tests::{check_assist, check_assist_not_applicable};
115
116 use super::*;
117
118 #[test]
119 fn reorder_sorted_fields() {
120 mark::check!(reorder_sorted_fields);
121 check_assist_not_applicable(
122 reorder_fields,
123 r#"
124struct Foo {
125 foo: i32,
126 bar: i32,
127}
128
129const test: Foo = $0Foo { foo: 0, bar: 0 };
130"#,
131 )
132 }
133
134 #[test]
135 fn trivial_empty_fields() {
136 check_assist_not_applicable(
137 reorder_fields,
138 r#"
139struct Foo {};
140const test: Foo = $0Foo {}
141"#,
142 )
143 }
144
145 #[test]
146 fn reorder_struct_fields() {
147 check_assist(
148 reorder_fields,
149 r#"
150struct Foo {foo: i32, bar: i32};
151const test: Foo = $0Foo {bar: 0, foo: 1}
152"#,
153 r#"
154struct Foo {foo: i32, bar: i32};
155const test: Foo = Foo {foo: 1, bar: 0}
156"#,
157 )
158 }
159
160 #[test]
161 fn reorder_struct_pattern() {
162 check_assist(
163 reorder_fields,
164 r#"
165struct Foo { foo: i64, bar: i64, baz: i64 }
166
167fn f(f: Foo) -> {
168 match f {
169 $0Foo { baz: 0, ref mut bar, .. } => (),
170 _ => ()
171 }
172}
173"#,
174 r#"
175struct Foo { foo: i64, bar: i64, baz: i64 }
176
177fn f(f: Foo) -> {
178 match f {
179 Foo { ref mut bar, baz: 0, .. } => (),
180 _ => ()
181 }
182}
183"#,
184 )
185 }
186
187 #[test]
188 fn reorder_with_extra_field() {
189 check_assist(
190 reorder_fields,
191 r#"
192struct Foo {
193 foo: String,
194 bar: String,
195}
196
197impl Foo {
198 fn new() -> Foo {
199 let foo = String::new();
200 $0Foo {
201 bar: foo.clone(),
202 extra: "Extra field",
203 foo,
204 }
205 }
206}
207"#,
208 r#"
209struct Foo {
210 foo: String,
211 bar: String,
212}
213
214impl Foo {
215 fn new() -> Foo {
216 let foo = String::new();
217 Foo {
218 foo,
219 bar: foo.clone(),
220 extra: "Extra field",
221 }
222 }
223}
224"#,
225 )
226 }
227}