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