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