Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/reorder_fields.rs')
-rw-r--r--crates/ide-assists/src/handlers/reorder_fields.rs209
1 files changed, 209 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 0000000000..35c4cb6d64
--- /dev/null
+++ b/crates/ide-assists/src/handlers/reorder_fields.rs
@@ -0,0 +1,209 @@
+use either::Either;
+use ide_db::FxHashMap;
+use itertools::Itertools;
+use syntax::{ast, ted, AstNode};
+
+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 = $0Foo {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<()> {
+ let record = ctx
+ .find_node_at_offset::<ast::RecordExpr>()
+ .map(Either::Left)
+ .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(Either::Right))?;
+
+ let path = record.as_ref().either(|it| it.path(), |it| it.path())?;
+ let ranks = compute_fields_ranks(&path, ctx)?;
+ let get_rank_of_field =
+ |of: Option<_>| *ranks.get(&of.unwrap_or_default()).unwrap_or(&usize::MAX);
+
+ let field_list = match &record {
+ Either::Left(it) => Either::Left(it.record_expr_field_list()?),
+ Either::Right(it) => Either::Right(it.record_pat_field_list()?),
+ };
+ let fields = match field_list {
+ Either::Left(it) => Either::Left((
+ it.fields()
+ .sorted_unstable_by_key(|field| {
+ get_rank_of_field(field.field_name().map(|it| it.to_string()))
+ })
+ .collect::<Vec<_>>(),
+ it,
+ )),
+ Either::Right(it) => Either::Right((
+ it.fields()
+ .sorted_unstable_by_key(|field| {
+ get_rank_of_field(field.field_name().map(|it| it.to_string()))
+ })
+ .collect::<Vec<_>>(),
+ it,
+ )),
+ };
+
+ let is_sorted = fields.as_ref().either(
+ |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b),
+ |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b),
+ );
+ if is_sorted {
+ cov_mark::hit!(reorder_sorted_fields);
+ return None;
+ }
+ let target = record.as_ref().either(AstNode::syntax, AstNode::syntax).text_range();
+ acc.add(
+ AssistId("reorder_fields", AssistKind::RefactorRewrite),
+ "Reorder record fields",
+ target,
+ |builder| match fields {
+ Either::Left((sorted, field_list)) => {
+ replace(builder.make_mut(field_list).fields(), sorted)
+ }
+ Either::Right((sorted, field_list)) => {
+ replace(builder.make_mut(field_list).fields(), sorted)
+ }
+ },
+ )
+}
+
+fn replace<T: AstNode + PartialEq>(
+ fields: impl Iterator<Item = T>,
+ sorted_fields: impl IntoIterator<Item = T>,
+) {
+ fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
+ ted::replace(field.syntax(), sorted_field.syntax().clone_for_update())
+ });
+}
+
+fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
+ let strukt = match ctx.sema.resolve_path(path) {
+ Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Struct(it)))) => it,
+ _ => return None,
+ };
+
+ let res = strukt
+ .fields(ctx.db())
+ .into_iter()
+ .enumerate()
+ .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
+ .collect();
+
+ Some(res)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn reorder_sorted_fields() {
+ cov_mark::check!(reorder_sorted_fields);
+ check_assist_not_applicable(
+ reorder_fields,
+ r#"
+struct Foo { foo: i32, bar: i32 }
+const test: Foo = $0Foo { foo: 0, bar: 0 };
+"#,
+ )
+ }
+
+ #[test]
+ fn trivial_empty_fields() {
+ check_assist_not_applicable(
+ reorder_fields,
+ r#"
+struct Foo {}
+const test: Foo = $0Foo {};
+"#,
+ )
+ }
+
+ #[test]
+ fn reorder_struct_fields() {
+ check_assist(
+ reorder_fields,
+ r#"
+struct Foo { foo: i32, bar: i32 }
+const test: Foo = $0Foo { 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 {
+ $0Foo { 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();
+ $0Foo {
+ 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",
+ }
+ }
+}
+"#,
+ )
+ }
+}