ide_assists/handlers/
reorder_impl_items.rs1use 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
11pub(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 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 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}