use hir::{PathResolution, Semantics}; use ide_db::{FxHashMap, RootDatabase}; use itertools::Itertools; use syntax::{ ast::{self, HasName}, ted, AstNode, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; // Assist: reorder_impl_items // // Reorder the items of an `impl Trait`. The items will be ordered // in the same order as in the trait definition. // // ``` // trait Foo { // type A; // const B: u8; // fn c(); // } // // struct Bar; // $0impl Foo for Bar$0 { // const B: u8 = 17; // fn c() {} // type A = String; // } // ``` // -> // ``` // trait Foo { // type A; // const B: u8; // fn c(); // } // // struct Bar; // impl Foo for Bar { // type A = String; // const B: u8 = 17; // fn c() {} // } // ``` pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let impl_ast = ctx.find_node_at_offset::()?; let items = impl_ast.assoc_item_list()?; // restrict the range // if cursor is in assoc_items, abort let assoc_range = items.syntax().text_range(); let cursor_position = ctx.offset(); if assoc_range.contains_inclusive(cursor_position) { cov_mark::hit!(not_applicable_editing_assoc_items); return None; } let assoc_items = items.assoc_items().collect::>(); let path = impl_ast .trait_() .and_then(|t| match t { ast::Type::PathType(path) => Some(path), _ => None, })? .path()?; let ranks = compute_item_ranks(&path, ctx)?; let sorted: Vec<_> = assoc_items .iter() .cloned() .sorted_by_key(|i| { let name = match i { ast::AssocItem::Const(c) => c.name(), ast::AssocItem::Fn(f) => f.name(), ast::AssocItem::TypeAlias(t) => t.name(), ast::AssocItem::MacroCall(_) => None, }; name.and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value()) }) .collect(); // Don't edit already sorted methods: if assoc_items == sorted { cov_mark::hit!(not_applicable_if_sorted); return None; } let target = items.syntax().text_range(); acc.add( AssistId("reorder_impl_items", AssistKind::RefactorRewrite), "Sort items by trait definition", target, |builder| { let assoc_items = assoc_items.into_iter().map(|item| builder.make_mut(item)).collect::>(); assoc_items .into_iter() .zip(sorted) .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax())); }, ) } fn compute_item_ranks( path: &ast::Path, ctx: &AssistContext<'_>, ) -> Option> { let td = trait_definition(path, &ctx.sema)?; Some( td.items(ctx.db()) .iter() .flat_map(|i| i.name(ctx.db())) .enumerate() .map(|(idx, name)| (name.display(ctx.db()).to_string(), idx)) .collect(), ) } fn trait_definition(path: &ast::Path, sema: &Semantics<'_, RootDatabase>) -> Option { match sema.resolve_path(path)? { PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_), _ => None, } } #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; #[test] fn not_applicable_if_sorted() { cov_mark::check!(not_applicable_if_sorted); check_assist_not_applicable( reorder_impl_items, r#" trait Bar { type T; const C: (); fn a() {} fn z() {} fn b() {} } struct Foo; $0impl Bar for Foo { type T = (); const C: () = (); fn a() {} fn z() {} fn b() {} } "#, ) } #[test] fn reorder_impl_trait_functions() { check_assist( reorder_impl_items, r#" trait Bar { fn a() {} fn c() {} fn b() {} fn d() {} } struct Foo; $0impl Bar for Foo { fn d() {} fn b() {} fn c() {} fn a() {} } "#, r#" trait Bar { fn a() {} fn c() {} fn b() {} fn d() {} } struct Foo; impl Bar for Foo { fn a() {} fn c() {} fn b() {} fn d() {} } "#, ) } #[test] fn not_applicable_if_empty() { check_assist_not_applicable( reorder_impl_items, r#" trait Bar {}; struct Foo; $0impl Bar for Foo {} "#, ) } #[test] fn reorder_impl_trait_items() { check_assist( reorder_impl_items, r#" trait Bar { fn a() {} type T0; fn c() {} const C1: (); fn b() {} type T1; fn d() {} const C0: (); } struct Foo; $0impl Bar for Foo { type T1 = (); fn d() {} fn b() {} fn c() {} const C1: () = (); fn a() {} type T0 = (); const C0: () = (); } "#, r#" trait Bar { fn a() {} type T0; fn c() {} const C1: (); fn b() {} type T1; fn d() {} const C0: (); } struct Foo; impl Bar for Foo { fn a() {} type T0 = (); fn c() {} const C1: () = (); fn b() {} type T1 = (); fn d() {} const C0: () = (); } "#, ) } #[test] fn reorder_impl_trait_items_uneven_ident_lengths() { check_assist( reorder_impl_items, r#" trait Bar { type Foo; type Fooo; } struct Foo; $0impl Bar for Foo { type Fooo = (); type Foo = (); }"#, r#" trait Bar { type Foo; type Fooo; } struct Foo; impl Bar for Foo { type Foo = (); type Fooo = (); }"#, ) } #[test] fn not_applicable_editing_assoc_items() { cov_mark::check!(not_applicable_editing_assoc_items); check_assist_not_applicable( reorder_impl_items, r#" trait Bar { type T; const C: (); fn a() {} fn z() {} fn b() {} } struct Foo; impl Bar for Foo { type T = ();$0 const C: () = (); fn z() {} fn a() {} fn b() {} } "#, ) } }