]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/reorder_impl.rs
Merge #8641
[rust.git] / crates / ide_assists / src / handlers / reorder_impl.rs
1 use itertools::Itertools;
2 use rustc_hash::FxHashMap;
3
4 use hir::{PathResolution, Semantics};
5 use ide_db::RootDatabase;
6 use syntax::{
7     ast::{self, NameOwner},
8     ted, AstNode,
9 };
10
11 use crate::{AssistContext, AssistId, AssistKind, Assists};
12
13 // Assist: reorder_impl
14 //
15 // Reorder the methods of an `impl Trait`. The methods will be ordered
16 // in the same order as in the trait definition.
17 //
18 // ```
19 // trait Foo {
20 //     fn a() {}
21 //     fn b() {}
22 //     fn c() {}
23 // }
24 //
25 // struct Bar;
26 // $0impl Foo for Bar {
27 //     fn b() {}
28 //     fn c() {}
29 //     fn a() {}
30 // }
31 // ```
32 // ->
33 // ```
34 // trait Foo {
35 //     fn a() {}
36 //     fn b() {}
37 //     fn c() {}
38 // }
39 //
40 // struct Bar;
41 // impl Foo for Bar {
42 //     fn a() {}
43 //     fn b() {}
44 //     fn c() {}
45 // }
46 // ```
47 //
48 pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
49     let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
50     let items = impl_ast.assoc_item_list()?;
51     let methods = get_methods(&items);
52
53     let path = impl_ast
54         .trait_()
55         .and_then(|t| match t {
56             ast::Type::PathType(path) => Some(path),
57             _ => None,
58         })?
59         .path()?;
60
61     let ranks = compute_method_ranks(&path, ctx)?;
62     let sorted: Vec<_> = methods
63         .iter()
64         .cloned()
65         .sorted_by_key(|f| {
66             f.name().and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value())
67         })
68         .collect();
69
70     // Don't edit already sorted methods:
71     if methods == sorted {
72         cov_mark::hit!(not_applicable_if_sorted);
73         return None;
74     }
75
76     let target = items.syntax().text_range();
77     acc.add(
78         AssistId("reorder_impl", AssistKind::RefactorRewrite),
79         "Sort methods",
80         target,
81         |builder| {
82             methods.into_iter().zip(sorted).for_each(|(old, new)| {
83                 ted::replace(builder.make_ast_mut(old).syntax(), new.clone_for_update().syntax())
84             });
85         },
86     )
87 }
88
89 fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
90     let td = trait_definition(path, &ctx.sema)?;
91
92     Some(
93         td.items(ctx.db())
94             .iter()
95             .flat_map(|i| match i {
96                 hir::AssocItem::Function(f) => Some(f),
97                 _ => None,
98             })
99             .enumerate()
100             .map(|(idx, func)| (func.name(ctx.db()).to_string(), idx))
101             .collect(),
102     )
103 }
104
105 fn trait_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<hir::Trait> {
106     match sema.resolve_path(path)? {
107         PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_),
108         _ => None,
109     }
110 }
111
112 fn get_methods(items: &ast::AssocItemList) -> Vec<ast::Fn> {
113     items
114         .assoc_items()
115         .flat_map(|i| match i {
116             ast::AssocItem::Fn(f) => Some(f),
117             _ => None,
118         })
119         .filter(|f| f.name().is_some())
120         .collect()
121 }
122
123 #[cfg(test)]
124 mod tests {
125     use crate::tests::{check_assist, check_assist_not_applicable};
126
127     use super::*;
128
129     #[test]
130     fn not_applicable_if_sorted() {
131         cov_mark::check!(not_applicable_if_sorted);
132         check_assist_not_applicable(
133             reorder_impl,
134             r#"
135 trait Bar {
136     fn a() {}
137     fn z() {}
138     fn b() {}
139 }
140 struct Foo;
141 $0impl Bar for Foo {
142     fn a() {}
143     fn z() {}
144     fn b() {}
145 }
146         "#,
147         )
148     }
149
150     #[test]
151     fn not_applicable_if_empty() {
152         check_assist_not_applicable(
153             reorder_impl,
154             r#"
155 trait Bar {};
156 struct Foo;
157 $0impl Bar for Foo {}
158         "#,
159         )
160     }
161
162     #[test]
163     fn reorder_impl_trait_methods() {
164         check_assist(
165             reorder_impl,
166             r#"
167 trait Bar {
168     fn a() {}
169     fn c() {}
170     fn b() {}
171     fn d() {}
172 }
173
174 struct Foo;
175 $0impl Bar for Foo {
176     fn d() {}
177     fn b() {}
178     fn c() {}
179     fn a() {}
180 }
181         "#,
182             r#"
183 trait Bar {
184     fn a() {}
185     fn c() {}
186     fn b() {}
187     fn d() {}
188 }
189
190 struct Foo;
191 impl Bar for Foo {
192     fn a() {}
193     fn c() {}
194     fn b() {}
195     fn d() {}
196 }
197         "#,
198         )
199     }
200 }