From fc34403018079ea053f26d0a31b7517053c7dd8c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov <aleksey.kladov@gmail.com> Date: Thu, 13 Aug 2020 17:33:38 +0200 Subject: Rename ra_assists -> assists --- crates/assists/src/handlers/reorder_fields.rs | 220 ++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 crates/assists/src/handlers/reorder_fields.rs (limited to 'crates/assists/src/handlers/reorder_fields.rs') diff --git a/crates/assists/src/handlers/reorder_fields.rs b/crates/assists/src/handlers/reorder_fields.rs new file mode 100644 index 000000000..527f457a7 --- /dev/null +++ b/crates/assists/src/handlers/reorder_fields.rs @@ -0,0 +1,220 @@ +use itertools::Itertools; +use rustc_hash::FxHashMap; + +use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; +use ide_db::RootDatabase; +use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: reorder_fields +// +// Reorder the fields of record literals and record patterns in the same order as in +// the definition. +// +// ``` +// struct Foo {foo: i32, bar: i32}; +// const test: Foo = <|>Foo {bar: 0, foo: 1} +// ``` +// -> +// ``` +// struct Foo {foo: i32, bar: i32}; +// const test: Foo = Foo {foo: 1, bar: 0} +// ``` +// +pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx)) +} + +fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let record = ctx.find_node_at_offset::<R>()?; + let path = record.syntax().children().find_map(ast::Path::cast)?; + + let ranks = compute_fields_ranks(&path, &ctx)?; + + let fields = get_fields(&record.syntax()); + let sorted_fields = sorted_by_rank(&fields, |node| { + *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value()) + }); + + if sorted_fields == fields { + return None; + } + + let target = record.syntax().text_range(); + acc.add( + AssistId("reorder_fields", AssistKind::RefactorRewrite), + "Reorder record fields", + target, + |edit| { + for (old, new) in fields.iter().zip(&sorted_fields) { + algo::diff(old, new).into_text_edit(edit.text_edit_builder()); + } + }, + ) +} + +fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> { + match node.kind() { + RECORD_EXPR => vec![RECORD_EXPR_FIELD], + RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT], + _ => vec![], + } +} + +fn get_field_name(node: &SyntaxNode) -> String { + let res = match_ast! { + match node { + ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()), + ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()), + _ => None, + } + }; + res.unwrap_or_default() +} + +fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> { + let kinds = get_fields_kind(record); + record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect() +} + +fn sorted_by_rank( + fields: &[SyntaxNode], + get_rank: impl Fn(&SyntaxNode) -> usize, +) -> Vec<SyntaxNode> { + fields.iter().cloned().sorted_by_key(get_rank).collect() +} + +fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> { + match sema.resolve_path(path) { + Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s), + _ => None, + } +} + +fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { + Some( + struct_definition(path, &ctx.sema)? + .fields(ctx.db()) + .iter() + .enumerate() + .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx)) + .collect(), + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn not_applicable_if_sorted() { + check_assist_not_applicable( + reorder_fields, + r#" + struct Foo { + foo: i32, + bar: i32, + } + + const test: Foo = <|>Foo { foo: 0, bar: 0 }; + "#, + ) + } + + #[test] + fn trivial_empty_fields() { + check_assist_not_applicable( + reorder_fields, + r#" + struct Foo {}; + const test: Foo = <|>Foo {} + "#, + ) + } + + #[test] + fn reorder_struct_fields() { + check_assist( + reorder_fields, + r#" + struct Foo {foo: i32, bar: i32}; + const test: Foo = <|>Foo {bar: 0, foo: 1} + "#, + r#" + struct Foo {foo: i32, bar: i32}; + const test: Foo = Foo {foo: 1, bar: 0} + "#, + ) + } + + #[test] + fn reorder_struct_pattern() { + check_assist( + reorder_fields, + r#" + struct Foo { foo: i64, bar: i64, baz: i64 } + + fn f(f: Foo) -> { + match f { + <|>Foo { baz: 0, ref mut bar, .. } => (), + _ => () + } + } + "#, + r#" + struct Foo { foo: i64, bar: i64, baz: i64 } + + fn f(f: Foo) -> { + match f { + Foo { ref mut bar, baz: 0, .. } => (), + _ => () + } + } + "#, + ) + } + + #[test] + fn reorder_with_extra_field() { + check_assist( + reorder_fields, + r#" + struct Foo { + foo: String, + bar: String, + } + + impl Foo { + fn new() -> Foo { + let foo = String::new(); + <|>Foo { + bar: foo.clone(), + extra: "Extra field", + foo, + } + } + } + "#, + r#" + struct Foo { + foo: String, + bar: String, + } + + impl Foo { + fn new() -> Foo { + let foo = String::new(); + Foo { + foo, + bar: foo.clone(), + extra: "Extra field", + } + } + } + "#, + ) + } +} -- cgit v1.2.3