| 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::<ast::Impl>()?; |
| 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::<Vec<_>>(); |
| |
| 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::<Vec<_>>(); |
| 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<FxHashMap<String, usize>> { |
| 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<hir::Trait> { |
| 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() {} |
| } |
| "#, |
| ) |
| } |
| } |