]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/reorder_impl.rs
Merge #8882
[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             let methods = methods.into_iter().map(|fn_| builder.make_mut(fn_)).collect::<Vec<_>>();
83             methods
84                 .into_iter()
85                 .zip(sorted)
86                 .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax()));
87         },
88     )
89 }
90
91 fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
92     let td = trait_definition(path, &ctx.sema)?;
93
94     Some(
95         td.items(ctx.db())
96             .iter()
97             .flat_map(|i| match i {
98                 hir::AssocItem::Function(f) => Some(f),
99                 _ => None,
100             })
101             .enumerate()
102             .map(|(idx, func)| (func.name(ctx.db()).to_string(), idx))
103             .collect(),
104     )
105 }
106
107 fn trait_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<hir::Trait> {
108     match sema.resolve_path(path)? {
109         PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_),
110         _ => None,
111     }
112 }
113
114 fn get_methods(items: &ast::AssocItemList) -> Vec<ast::Fn> {
115     items
116         .assoc_items()
117         .flat_map(|i| match i {
118             ast::AssocItem::Fn(f) => Some(f),
119             _ => None,
120         })
121         .filter(|f| f.name().is_some())
122         .collect()
123 }
124
125 #[cfg(test)]
126 mod tests {
127     use crate::tests::{check_assist, check_assist_not_applicable};
128
129     use super::*;
130
131     #[test]
132     fn not_applicable_if_sorted() {
133         cov_mark::check!(not_applicable_if_sorted);
134         check_assist_not_applicable(
135             reorder_impl,
136             r#"
137 trait Bar {
138     fn a() {}
139     fn z() {}
140     fn b() {}
141 }
142 struct Foo;
143 $0impl Bar for Foo {
144     fn a() {}
145     fn z() {}
146     fn b() {}
147 }
148         "#,
149         )
150     }
151
152     #[test]
153     fn not_applicable_if_empty() {
154         check_assist_not_applicable(
155             reorder_impl,
156             r#"
157 trait Bar {};
158 struct Foo;
159 $0impl Bar for Foo {}
160         "#,
161         )
162     }
163
164     #[test]
165     fn reorder_impl_trait_functions() {
166         check_assist(
167             reorder_impl,
168             r#"
169 trait Bar {
170     fn a() {}
171     fn c() {}
172     fn b() {}
173     fn d() {}
174 }
175
176 struct Foo;
177 $0impl Bar for Foo {
178     fn d() {}
179     fn b() {}
180     fn c() {}
181     fn a() {}
182 }
183         "#,
184             r#"
185 trait Bar {
186     fn a() {}
187     fn c() {}
188     fn b() {}
189     fn d() {}
190 }
191
192 struct Foo;
193 impl Bar for Foo {
194     fn a() {}
195     fn c() {}
196     fn b() {}
197     fn d() {}
198 }
199         "#,
200         )
201     }
202
203     #[test]
204     fn reorder_impl_trait_methods_uneven_ident_lengths() {
205         check_assist(
206             reorder_impl,
207             r#"
208 trait Bar {
209     fn foo(&mut self) {}
210     fn fooo(&mut self) {}
211 }
212
213 struct Foo;
214 impl Bar for Foo {
215     fn fooo(&mut self) {}
216     fn foo(&mut self) {$0}
217 }"#,
218             r#"
219 trait Bar {
220     fn foo(&mut self) {}
221     fn fooo(&mut self) {}
222 }
223
224 struct Foo;
225 impl Bar for Foo {
226     fn foo(&mut self) {}
227     fn fooo(&mut self) {}
228 }"#,
229         )
230     }
231 }