2 use itertools::Itertools;
3 use rustc_hash::FxHashMap;
5 use syntax::{ast, ted, AstNode};
7 use crate::{AssistContext, AssistId, AssistKind, Assists};
9 // Assist: reorder_fields
11 // Reorder the fields of record literals and record patterns in the same order as in
15 // struct Foo {foo: i32, bar: i32};
16 // const test: Foo = $0Foo {bar: 0, foo: 1}
20 // struct Foo {foo: i32, bar: i32};
21 // const test: Foo = Foo {foo: 1, bar: 0}
24 pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 .find_node_at_offset::<ast::RecordExpr>()
28 .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(Either::Right))?;
30 let path = record.as_ref().either(|it| it.path(), |it| it.path())?;
31 let ranks = compute_fields_ranks(&path, &ctx)?;
32 let get_rank_of_field =
33 |of: Option<_>| *ranks.get(&of.unwrap_or_default()).unwrap_or(&usize::MAX);
35 let field_list = match &record {
36 Either::Left(it) => Either::Left(it.record_expr_field_list()?),
37 Either::Right(it) => Either::Right(it.record_pat_field_list()?),
39 let fields = match field_list {
40 Either::Left(it) => Either::Left((
42 .sorted_unstable_by_key(|field| {
43 get_rank_of_field(field.field_name().map(|it| it.to_string()))
48 Either::Right(it) => Either::Right((
50 .sorted_unstable_by_key(|field| {
51 get_rank_of_field(field.field_name().map(|it| it.to_string()))
58 let is_sorted = fields.as_ref().either(
59 |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b),
60 |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b),
63 cov_mark::hit!(reorder_sorted_fields);
66 let target = record.as_ref().either(AstNode::syntax, AstNode::syntax).text_range();
68 AssistId("reorder_fields", AssistKind::RefactorRewrite),
69 "Reorder record fields",
71 |builder| match fields {
72 Either::Left((sorted, field_list)) => {
73 replace(builder.make_ast_mut(field_list).fields(), sorted)
75 Either::Right((sorted, field_list)) => {
76 replace(builder.make_ast_mut(field_list).fields(), sorted)
82 fn replace<T: AstNode + PartialEq>(
83 fields: impl Iterator<Item = T>,
84 sorted_fields: impl IntoIterator<Item = T>,
86 fields.zip(sorted_fields).filter(|(field, sorted)| field != sorted).for_each(
87 |(field, sorted_field)| {
88 ted::replace(field.syntax(), sorted_field.syntax().clone_for_update());
93 fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
94 let strukt = match ctx.sema.resolve_path(path) {
95 Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Struct(it)))) => it,
103 .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
111 use crate::tests::{check_assist, check_assist_not_applicable};
116 fn reorder_sorted_fields() {
117 cov_mark::check!(reorder_sorted_fields);
118 check_assist_not_applicable(
121 struct Foo { foo: i32, bar: i32 }
122 const test: Foo = $0Foo { foo: 0, bar: 0 };
128 fn trivial_empty_fields() {
129 check_assist_not_applicable(
133 const test: Foo = $0Foo {};
139 fn reorder_struct_fields() {
143 struct Foo { foo: i32, bar: i32 }
144 const test: Foo = $0Foo { bar: 0, foo: 1 };
147 struct Foo { foo: i32, bar: i32 }
148 const test: Foo = Foo { foo: 1, bar: 0 };
153 fn reorder_struct_pattern() {
157 struct Foo { foo: i64, bar: i64, baz: i64 }
161 $0Foo { baz: 0, ref mut bar, .. } => (),
167 struct Foo { foo: i64, bar: i64, baz: i64 }
171 Foo { ref mut bar, baz: 0, .. } => (),
180 fn reorder_with_extra_field() {
184 struct Foo { foo: String, bar: String }
188 let foo = String::new();
191 extra: "Extra field",
198 struct Foo { foo: String, bar: String }
202 let foo = String::new();
206 extra: "Extra field",