ide_assists/handlers/
reorder_impl_items.rs

1use hir::{PathResolution, Semantics};
2use ide_db::{FxHashMap, RootDatabase};
3use itertools::Itertools;
4use syntax::{
5    AstNode, SyntaxElement,
6    ast::{self, HasName},
7};
8
9use crate::{AssistContext, AssistId, Assists};
10
11// Assist: reorder_impl_items
12//
13// Reorder the items of an `impl Trait`. The items will be ordered
14// in the same order as in the trait definition.
15//
16// ```
17// trait Foo {
18//     type A;
19//     const B: u8;
20//     fn c();
21// }
22//
23// struct Bar;
24// $0impl Foo for Bar$0 {
25//     const B: u8 = 17;
26//     fn c() {}
27//     type A = String;
28// }
29// ```
30// ->
31// ```
32// trait Foo {
33//     type A;
34//     const B: u8;
35//     fn c();
36// }
37//
38// struct Bar;
39// impl Foo for Bar {
40//     type A = String;
41//     const B: u8 = 17;
42//     fn c() {}
43// }
44// ```
45pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
46    let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
47    let items = impl_ast.assoc_item_list()?;
48
49    let parent_node = match ctx.covering_element() {
50        SyntaxElement::Node(n) => n,
51        SyntaxElement::Token(t) => t.parent()?,
52    };
53
54    // restrict the range
55    // if cursor is in assoc_items, abort
56    let assoc_range = items.syntax().text_range();
57    let cursor_position = ctx.offset();
58    if assoc_range.contains_inclusive(cursor_position) {
59        cov_mark::hit!(not_applicable_editing_assoc_items);
60        return None;
61    }
62
63    let assoc_items = items.assoc_items().collect::<Vec<_>>();
64
65    let path = impl_ast
66        .trait_()
67        .and_then(|t| match t {
68            ast::Type::PathType(path) => Some(path),
69            _ => None,
70        })?
71        .path()?;
72
73    let ranks = compute_item_ranks(&path, ctx)?;
74    let sorted: Vec<_> = assoc_items
75        .iter()
76        .cloned()
77        .sorted_by_key(|i| {
78            let name = match i {
79                ast::AssocItem::Const(c) => c.name(),
80                ast::AssocItem::Fn(f) => f.name(),
81                ast::AssocItem::TypeAlias(t) => t.name(),
82                ast::AssocItem::MacroCall(_) => None,
83            };
84
85            name.and_then(|n| ranks.get(n.text().as_str().trim_start_matches("r#")).copied())
86                .unwrap_or(usize::MAX)
87        })
88        .collect();
89
90    // Don't edit already sorted methods:
91    if assoc_items == sorted {
92        cov_mark::hit!(not_applicable_if_sorted);
93        return None;
94    }
95
96    let target = items.syntax().text_range();
97    acc.add(
98        AssistId::refactor_rewrite("reorder_impl_items"),
99        "Sort items by trait definition",
100        target,
101        |builder| {
102            let mut editor = builder.make_editor(&parent_node);
103
104            assoc_items
105                .into_iter()
106                .zip(sorted)
107                .for_each(|(old, new)| editor.replace(old.syntax(), new.syntax()));
108
109            builder.add_file_edits(ctx.vfs_file_id(), editor);
110        },
111    )
112}
113
114fn compute_item_ranks(
115    path: &ast::Path,
116    ctx: &AssistContext<'_>,
117) -> Option<FxHashMap<String, usize>> {
118    let td = trait_definition(path, &ctx.sema)?;
119
120    Some(
121        td.items(ctx.db())
122            .iter()
123            .flat_map(|i| i.name(ctx.db()))
124            .enumerate()
125            .map(|(idx, name)| (name.as_str().to_owned(), idx))
126            .collect(),
127    )
128}
129
130fn trait_definition(path: &ast::Path, sema: &Semantics<'_, RootDatabase>) -> Option<hir::Trait> {
131    match sema.resolve_path(path)? {
132        PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_),
133        _ => None,
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use crate::tests::{check_assist, check_assist_not_applicable};
140
141    use super::*;
142
143    #[test]
144    fn not_applicable_if_sorted() {
145        cov_mark::check!(not_applicable_if_sorted);
146        check_assist_not_applicable(
147            reorder_impl_items,
148            r#"
149trait Bar {
150    type T;
151    const C: ();
152    fn a() {}
153    fn z() {}
154    fn b() {}
155}
156struct Foo;
157$0impl Bar for Foo {
158    type T = ();
159    const C: () = ();
160    fn a() {}
161    fn z() {}
162    fn b() {}
163}
164        "#,
165        )
166    }
167
168    #[test]
169    fn reorder_impl_trait_functions() {
170        check_assist(
171            reorder_impl_items,
172            r#"
173trait Bar {
174    fn a() {}
175    fn c() {}
176    fn b() {}
177    fn d() {}
178}
179
180struct Foo;
181$0impl Bar for Foo {
182    fn d() {}
183    fn b() {}
184    fn c() {}
185    fn a() {}
186}
187"#,
188            r#"
189trait Bar {
190    fn a() {}
191    fn c() {}
192    fn b() {}
193    fn d() {}
194}
195
196struct Foo;
197impl Bar for Foo {
198    fn a() {}
199    fn c() {}
200    fn b() {}
201    fn d() {}
202}
203"#,
204        )
205    }
206
207    #[test]
208    fn not_applicable_if_empty() {
209        check_assist_not_applicable(
210            reorder_impl_items,
211            r#"
212trait Bar {};
213struct Foo;
214$0impl Bar for Foo {}
215        "#,
216        )
217    }
218
219    #[test]
220    fn reorder_impl_trait_items() {
221        check_assist(
222            reorder_impl_items,
223            r#"
224trait Bar {
225    fn a() {}
226    type T0;
227    fn c() {}
228    const C1: ();
229    fn b() {}
230    type T1;
231    fn d() {}
232    const C0: ();
233}
234
235struct Foo;
236$0impl Bar for Foo {
237    type T1 = ();
238    fn d() {}
239    fn b() {}
240    fn c() {}
241    const C1: () = ();
242    fn a() {}
243    type T0 = ();
244    const C0: () = ();
245}
246        "#,
247            r#"
248trait Bar {
249    fn a() {}
250    type T0;
251    fn c() {}
252    const C1: ();
253    fn b() {}
254    type T1;
255    fn d() {}
256    const C0: ();
257}
258
259struct Foo;
260impl Bar for Foo {
261    fn a() {}
262    type T0 = ();
263    fn c() {}
264    const C1: () = ();
265    fn b() {}
266    type T1 = ();
267    fn d() {}
268    const C0: () = ();
269}
270        "#,
271        )
272    }
273
274    #[test]
275    fn reorder_impl_trait_items_uneven_ident_lengths() {
276        check_assist(
277            reorder_impl_items,
278            r#"
279trait Bar {
280    type Foo;
281    type Fooo;
282}
283
284struct Foo;
285$0impl Bar for Foo {
286    type Fooo = ();
287    type Foo = ();
288}"#,
289            r#"
290trait Bar {
291    type Foo;
292    type Fooo;
293}
294
295struct Foo;
296impl Bar for Foo {
297    type Foo = ();
298    type Fooo = ();
299}"#,
300        )
301    }
302
303    #[test]
304    fn not_applicable_editing_assoc_items() {
305        cov_mark::check!(not_applicable_editing_assoc_items);
306        check_assist_not_applicable(
307            reorder_impl_items,
308            r#"
309trait Bar {
310    type T;
311    const C: ();
312    fn a() {}
313    fn z() {}
314    fn b() {}
315}
316struct Foo;
317impl Bar for Foo {
318    type T = ();$0
319    const C: () = ();
320    fn z() {}
321    fn a() {}
322    fn b() {}
323}
324        "#,
325        )
326    }
327}