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