Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 2b20a05

Browse files
committed
Add "Sort items by trait definition"
1 parent d7ed351 commit 2b20a05

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
use hir::{PathResolution, Semantics};
2+
use ide_db::{FxHashMap, RootDatabase};
3+
use itertools::Itertools;
4+
use syntax::{
5+
ast::{self, HasName},
6+
ted, AstNode,
7+
};
8+
9+
use crate::{AssistContext, AssistId, AssistKind, 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 {
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+
// ```
45+
pub(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+
let assoc_items = items.assoc_items().collect::<Vec<_>>();
49+
50+
// If all items are either function or macro calls, then reorder_impl assist can be used
51+
if assoc_items.iter().all(|i| matches!(i, ast::AssocItem::Fn(_) | ast::AssocItem::MacroCall(_)))
52+
{
53+
cov_mark::hit!(not_applicable_if_all_functions);
54+
return None;
55+
}
56+
57+
let path = impl_ast
58+
.trait_()
59+
.and_then(|t| match t {
60+
ast::Type::PathType(path) => Some(path),
61+
_ => None,
62+
})?
63+
.path()?;
64+
65+
let ranks = compute_item_ranks(&path, ctx)?;
66+
let sorted: Vec<_> = assoc_items
67+
.iter()
68+
.cloned()
69+
.sorted_by_key(|i| {
70+
let name = match i {
71+
ast::AssocItem::Const(c) => c.name(),
72+
ast::AssocItem::Fn(f) => f.name(),
73+
ast::AssocItem::TypeAlias(t) => t.name(),
74+
ast::AssocItem::MacroCall(_) => None,
75+
};
76+
77+
name.and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value())
78+
})
79+
.collect();
80+
81+
// Don't edit already sorted methods:
82+
if assoc_items == sorted {
83+
cov_mark::hit!(not_applicable_if_sorted);
84+
return None;
85+
}
86+
87+
let target = items.syntax().text_range();
88+
acc.add(
89+
AssistId("reorder_impl_items", AssistKind::RefactorRewrite),
90+
"Sort items by trait definition",
91+
target,
92+
|builder| {
93+
let assoc_items =
94+
assoc_items.into_iter().map(|item| builder.make_mut(item)).collect::<Vec<_>>();
95+
assoc_items
96+
.into_iter()
97+
.zip(sorted)
98+
.for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax()));
99+
},
100+
)
101+
}
102+
103+
fn compute_item_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
104+
let td = trait_definition(path, &ctx.sema)?;
105+
106+
Some(
107+
td.items(ctx.db())
108+
.iter()
109+
.flat_map(|i| i.name(ctx.db()))
110+
.enumerate()
111+
.map(|(idx, name)| (name.to_string(), idx))
112+
.collect(),
113+
)
114+
}
115+
116+
fn trait_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<hir::Trait> {
117+
match sema.resolve_path(path)? {
118+
PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_),
119+
_ => None,
120+
}
121+
}
122+
123+
#[cfg(test)]
124+
mod tests {
125+
use crate::tests::{check_assist, check_assist_not_applicable};
126+
127+
use super::*;
128+
129+
#[test]
130+
fn not_applicable_if_sorted() {
131+
cov_mark::check!(not_applicable_if_sorted);
132+
check_assist_not_applicable(
133+
reorder_impl_items,
134+
r#"
135+
trait Bar {
136+
type T;
137+
const C: ();
138+
fn a() {}
139+
fn z() {}
140+
fn b() {}
141+
}
142+
struct Foo;
143+
$0impl Bar for Foo {
144+
type T = ();
145+
const C: () = ();
146+
fn a() {}
147+
fn z() {}
148+
fn b() {}
149+
}
150+
"#,
151+
)
152+
}
153+
154+
#[test]
155+
fn not_applicable_if_all_functions() {
156+
cov_mark::check!(not_applicable_if_all_functions);
157+
check_assist_not_applicable(
158+
reorder_impl_items,
159+
r#"
160+
trait Bar {
161+
fn a() {}
162+
fn z() {}
163+
fn b() {}
164+
}
165+
struct Foo;
166+
$0impl Bar for Foo {
167+
fn a() {}
168+
fn z() {}
169+
fn b() {}
170+
}
171+
"#,
172+
)
173+
}
174+
175+
#[test]
176+
fn not_applicable_if_empty() {
177+
check_assist_not_applicable(
178+
reorder_impl_items,
179+
r#"
180+
trait Bar {};
181+
struct Foo;
182+
$0impl Bar for Foo {}
183+
"#,
184+
)
185+
}
186+
187+
#[test]
188+
fn reorder_impl_trait_items() {
189+
check_assist(
190+
reorder_impl_items,
191+
r#"
192+
trait Bar {
193+
fn a() {}
194+
type T0;
195+
fn c() {}
196+
const C1: ();
197+
fn b() {}
198+
type T1;
199+
fn d() {}
200+
const C0: ();
201+
}
202+
203+
struct Foo;
204+
$0impl Bar for Foo {
205+
type T1 = ();
206+
fn d() {}
207+
fn b() {}
208+
fn c() {}
209+
const C1: () = ();
210+
fn a() {}
211+
type T0 = ();
212+
const C0: () = ();
213+
}
214+
"#,
215+
r#"
216+
trait Bar {
217+
fn a() {}
218+
type T0;
219+
fn c() {}
220+
const C1: ();
221+
fn b() {}
222+
type T1;
223+
fn d() {}
224+
const C0: ();
225+
}
226+
227+
struct Foo;
228+
impl Bar for Foo {
229+
fn a() {}
230+
type T0 = ();
231+
fn c() {}
232+
const C1: () = ();
233+
fn b() {}
234+
type T1 = ();
235+
fn d() {}
236+
const C0: () = ();
237+
}
238+
"#,
239+
)
240+
}
241+
242+
#[test]
243+
fn reorder_impl_trait_items_uneven_ident_lengths() {
244+
check_assist(
245+
reorder_impl_items,
246+
r#"
247+
trait Bar {
248+
type Foo;
249+
type Fooo;
250+
}
251+
252+
struct Foo;
253+
impl Bar for Foo {
254+
type Fooo = ();
255+
type Foo = ();$0
256+
}"#,
257+
r#"
258+
trait Bar {
259+
type Foo;
260+
type Fooo;
261+
}
262+
263+
struct Foo;
264+
impl Bar for Foo {
265+
type Foo = ();
266+
type Fooo = ();
267+
}"#,
268+
)
269+
}
270+
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ mod handlers {
170170
mod remove_unused_param;
171171
mod reorder_fields;
172172
mod reorder_impl;
173+
mod reorder_impl_items;
173174
mod replace_try_expr_with_match;
174175
mod replace_derive_with_manual_impl;
175176
mod replace_if_let_with_match;
@@ -257,6 +258,7 @@ mod handlers {
257258
remove_unused_param::remove_unused_param,
258259
reorder_fields::reorder_fields,
259260
reorder_impl::reorder_impl,
261+
reorder_impl_items::reorder_impl_items,
260262
replace_try_expr_with_match::replace_try_expr_with_match,
261263
replace_derive_with_manual_impl::replace_derive_with_manual_impl,
262264
replace_if_let_with_match::replace_if_let_with_match,

crates/ide-assists/src/tests/generated.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,41 @@ impl Foo for Bar {
17711771
)
17721772
}
17731773

1774+
#[test]
1775+
fn doctest_reorder_impl_items() {
1776+
check_doc_test(
1777+
"reorder_impl_items",
1778+
r#####"
1779+
trait Foo {
1780+
type A;
1781+
const B: u8;
1782+
fn c();
1783+
}
1784+
1785+
struct Bar;
1786+
$0impl Foo for Bar {
1787+
const B: u8 = 17;
1788+
fn c() {}
1789+
type A = String;
1790+
}
1791+
"#####,
1792+
r#####"
1793+
trait Foo {
1794+
type A;
1795+
const B: u8;
1796+
fn c();
1797+
}
1798+
1799+
struct Bar;
1800+
impl Foo for Bar {
1801+
type A = String;
1802+
const B: u8 = 17;
1803+
fn c() {}
1804+
}
1805+
"#####,
1806+
)
1807+
}
1808+
17741809
#[test]
17751810
fn doctest_replace_char_with_string() {
17761811
check_doc_test(

0 commit comments

Comments
 (0)