1 use itertools::Itertools;
2 use rustc_hash::FxHashMap;
4 use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
5 use ide_db::RootDatabase;
6 use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
9 use crate::{AssistContext, AssistId, AssistKind, Assists};
11 // Assist: reorder_fields
13 // Reorder the fields of record literals and record patterns in the same order as in
17 // struct Foo {foo: i32, bar: i32};
18 // const test: Foo = $0Foo {bar: 0, foo: 1}
22 // struct Foo {foo: i32, bar: i32};
23 // const test: Foo = Foo {foo: 1, bar: 0}
26 pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
30 fn 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)?;
34 let ranks = compute_fields_ranks(&path, &ctx)?;
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())
41 if sorted_fields == fields {
42 mark::hit!(reorder_sorted_fields);
46 let target = record.syntax().text_range();
48 AssistId("reorder_fields", AssistKind::RefactorRewrite),
49 "Reorder record fields",
52 let mut rewriter = algo::SyntaxRewriter::default();
53 for (old, new) in fields.iter().zip(&sorted_fields) {
54 rewriter.replace(old, new);
56 edit.rewrite(rewriter);
61 fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
63 RECORD_EXPR => vec![RECORD_EXPR_FIELD],
64 RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT],
69 fn get_field_name(node: &SyntaxNode) -> String {
70 let res = match_ast! {
72 ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
73 ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()),
77 res.unwrap_or_default()
80 fn 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()
86 fields: &[SyntaxNode],
87 get_rank: impl Fn(&SyntaxNode) -> usize,
88 ) -> Vec<SyntaxNode> {
89 fields.iter().cloned().sorted_by_key(get_rank).collect()
92 fn 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),
99 fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
101 struct_definition(path, &ctx.sema)?
105 .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
112 use test_utils::mark;
114 use crate::tests::{check_assist, check_assist_not_applicable};
119 fn reorder_sorted_fields() {
120 mark::check!(reorder_sorted_fields);
121 check_assist_not_applicable(
129 const test: Foo = $0Foo { foo: 0, bar: 0 };
135 fn trivial_empty_fields() {
136 check_assist_not_applicable(
140 const test: Foo = $0Foo {}
146 fn reorder_struct_fields() {
150 struct Foo {foo: i32, bar: i32};
151 const test: Foo = $0Foo {bar: 0, foo: 1}
154 struct Foo {foo: i32, bar: i32};
155 const test: Foo = Foo {foo: 1, bar: 0}
161 fn reorder_struct_pattern() {
165 struct Foo { foo: i64, bar: i64, baz: i64 }
169 $0Foo { baz: 0, ref mut bar, .. } => (),
175 struct Foo { foo: i64, bar: i64, baz: i64 }
179 Foo { ref mut bar, baz: 0, .. } => (),
188 fn reorder_with_extra_field() {
199 let foo = String::new();
202 extra: "Extra field",
216 let foo = String::new();
220 extra: "Extra field",