]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/sort_items.rs
:arrow_up: rust-analyzer
[rust.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / sort_items.rs
1 use std::cmp::Ordering;
2
3 use itertools::Itertools;
4
5 use syntax::{
6     ast::{self, HasName},
7     ted, AstNode, TextRange,
8 };
9
10 use crate::{utils::get_methods, AssistContext, AssistId, AssistKind, Assists};
11
12 // Assist: sort_items
13 //
14 // Sorts item members alphabetically: fields, enum variants and methods.
15 //
16 // ```
17 // struct $0Foo$0 { second: u32, first: String }
18 // ```
19 // ->
20 // ```
21 // struct Foo { first: String, second: u32 }
22 // ```
23 // ---
24 // ```
25 // trait $0Bar$0 {
26 //     fn second(&self) -> u32;
27 //     fn first(&self) -> String;
28 // }
29 // ```
30 // ->
31 // ```
32 // trait Bar {
33 //     fn first(&self) -> String;
34 //     fn second(&self) -> u32;
35 // }
36 // ```
37 // ---
38 // ```
39 // struct Baz;
40 // impl $0Baz$0 {
41 //     fn second(&self) -> u32;
42 //     fn first(&self) -> String;
43 // }
44 // ```
45 // ->
46 // ```
47 // struct Baz;
48 // impl Baz {
49 //     fn first(&self) -> String;
50 //     fn second(&self) -> u32;
51 // }
52 // ```
53 // ---
54 // There is a difference between sorting enum variants:
55 //
56 // ```
57 // enum $0Animal$0 {
58 //   Dog(String, f64),
59 //   Cat { weight: f64, name: String },
60 // }
61 // ```
62 // ->
63 // ```
64 // enum Animal {
65 //   Cat { weight: f64, name: String },
66 //   Dog(String, f64),
67 // }
68 // ```
69 // and sorting a single enum struct variant:
70 //
71 // ```
72 // enum Animal {
73 //   Dog(String, f64),
74 //   Cat $0{ weight: f64, name: String }$0,
75 // }
76 // ```
77 // ->
78 // ```
79 // enum Animal {
80 //   Dog(String, f64),
81 //   Cat { name: String, weight: f64 },
82 // }
83 // ```
84 pub(crate) fn sort_items(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
85     if ctx.has_empty_selection() {
86         cov_mark::hit!(not_applicable_if_no_selection);
87         return None;
88     }
89
90     if let Some(trait_ast) = ctx.find_node_at_offset::<ast::Trait>() {
91         add_sort_methods_assist(acc, trait_ast.assoc_item_list()?)
92     } else if let Some(impl_ast) = ctx.find_node_at_offset::<ast::Impl>() {
93         add_sort_methods_assist(acc, impl_ast.assoc_item_list()?)
94     } else if let Some(struct_ast) = ctx.find_node_at_offset::<ast::Struct>() {
95         add_sort_field_list_assist(acc, struct_ast.field_list())
96     } else if let Some(union_ast) = ctx.find_node_at_offset::<ast::Union>() {
97         add_sort_fields_assist(acc, union_ast.record_field_list()?)
98     } else if let Some(variant_ast) = ctx.find_node_at_offset::<ast::Variant>() {
99         add_sort_field_list_assist(acc, variant_ast.field_list())
100     } else if let Some(enum_struct_variant_ast) = ctx.find_node_at_offset::<ast::RecordFieldList>()
101     {
102         // should be above enum and below struct
103         add_sort_fields_assist(acc, enum_struct_variant_ast)
104     } else if let Some(enum_ast) = ctx.find_node_at_offset::<ast::Enum>() {
105         add_sort_variants_assist(acc, enum_ast.variant_list()?)
106     } else {
107         None
108     }
109 }
110
111 trait AddRewrite {
112     fn add_rewrite<T: AstNode>(
113         &mut self,
114         label: &str,
115         old: Vec<T>,
116         new: Vec<T>,
117         target: TextRange,
118     ) -> Option<()>;
119 }
120
121 impl AddRewrite for Assists {
122     fn add_rewrite<T: AstNode>(
123         &mut self,
124         label: &str,
125         old: Vec<T>,
126         new: Vec<T>,
127         target: TextRange,
128     ) -> Option<()> {
129         self.add(AssistId("sort_items", AssistKind::RefactorRewrite), label, target, |builder| {
130             let mutable: Vec<T> = old.into_iter().map(|it| builder.make_mut(it)).collect();
131             mutable
132                 .into_iter()
133                 .zip(new)
134                 .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax()));
135         })
136     }
137 }
138
139 fn add_sort_field_list_assist(acc: &mut Assists, field_list: Option<ast::FieldList>) -> Option<()> {
140     match field_list {
141         Some(ast::FieldList::RecordFieldList(it)) => add_sort_fields_assist(acc, it),
142         _ => {
143             cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
144             None
145         }
146     }
147 }
148
149 fn add_sort_methods_assist(acc: &mut Assists, item_list: ast::AssocItemList) -> Option<()> {
150     let methods = get_methods(&item_list);
151     let sorted = sort_by_name(&methods);
152
153     if methods == sorted {
154         cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
155         return None;
156     }
157
158     acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax().text_range())
159 }
160
161 fn add_sort_fields_assist(
162     acc: &mut Assists,
163     record_field_list: ast::RecordFieldList,
164 ) -> Option<()> {
165     let fields: Vec<_> = record_field_list.fields().collect();
166     let sorted = sort_by_name(&fields);
167
168     if fields == sorted {
169         cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
170         return None;
171     }
172
173     acc.add_rewrite(
174         "Sort fields alphabetically",
175         fields,
176         sorted,
177         record_field_list.syntax().text_range(),
178     )
179 }
180
181 fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) -> Option<()> {
182     let variants: Vec<_> = variant_list.variants().collect();
183     let sorted = sort_by_name(&variants);
184
185     if variants == sorted {
186         cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
187         return None;
188     }
189
190     acc.add_rewrite(
191         "Sort variants alphabetically",
192         variants,
193         sorted,
194         variant_list.syntax().text_range(),
195     )
196 }
197
198 fn sort_by_name<T: HasName + Clone>(initial: &[T]) -> Vec<T> {
199     initial
200         .iter()
201         .cloned()
202         .sorted_by(|a, b| match (a.name(), b.name()) {
203             (Some(a), Some(b)) => Ord::cmp(&a.to_string(), &b.to_string()),
204
205             // unexpected, but just in case
206             (None, None) => Ordering::Equal,
207             (None, Some(_)) => Ordering::Less,
208             (Some(_), None) => Ordering::Greater,
209         })
210         .collect()
211 }
212
213 #[cfg(test)]
214 mod tests {
215     use crate::tests::{check_assist, check_assist_not_applicable};
216
217     use super::*;
218
219     #[test]
220     fn not_applicable_if_no_selection() {
221         cov_mark::check!(not_applicable_if_no_selection);
222
223         check_assist_not_applicable(
224             sort_items,
225             r#"
226 t$0rait Bar {
227     fn b();
228     fn a();
229 }
230         "#,
231         )
232     }
233
234     #[test]
235     fn not_applicable_if_trait_empty() {
236         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
237
238         check_assist_not_applicable(
239             sort_items,
240             r#"
241 t$0rait Bar$0 {
242 }
243         "#,
244         )
245     }
246
247     #[test]
248     fn not_applicable_if_impl_empty() {
249         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
250
251         check_assist_not_applicable(
252             sort_items,
253             r#"
254 struct Bar;
255 $0impl Bar$0 {
256 }
257         "#,
258         )
259     }
260
261     #[test]
262     fn not_applicable_if_struct_empty() {
263         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
264
265         check_assist_not_applicable(
266             sort_items,
267             r#"
268 $0struct Bar$0 ;
269         "#,
270         )
271     }
272
273     #[test]
274     fn not_applicable_if_struct_empty2() {
275         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
276
277         check_assist_not_applicable(
278             sort_items,
279             r#"
280 $0struct Bar$0 { };
281         "#,
282         )
283     }
284
285     #[test]
286     fn not_applicable_if_enum_empty() {
287         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
288
289         check_assist_not_applicable(
290             sort_items,
291             r#"
292 $0enum ZeroVariants$0 {};
293         "#,
294         )
295     }
296
297     #[test]
298     fn not_applicable_if_trait_sorted() {
299         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
300
301         check_assist_not_applicable(
302             sort_items,
303             r#"
304 t$0rait Bar$0 {
305     fn a() {}
306     fn b() {}
307     fn c() {}
308 }
309         "#,
310         )
311     }
312
313     #[test]
314     fn not_applicable_if_impl_sorted() {
315         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
316
317         check_assist_not_applicable(
318             sort_items,
319             r#"
320 struct Bar;
321 $0impl Bar$0 {
322     fn a() {}
323     fn b() {}
324     fn c() {}
325 }
326         "#,
327         )
328     }
329
330     #[test]
331     fn not_applicable_if_struct_sorted() {
332         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
333
334         check_assist_not_applicable(
335             sort_items,
336             r#"
337 $0struct Bar$0 {
338     a: u32,
339     b: u8,
340     c: u64,
341 }
342         "#,
343         )
344     }
345
346     #[test]
347     fn not_applicable_if_union_sorted() {
348         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
349
350         check_assist_not_applicable(
351             sort_items,
352             r#"
353 $0union Bar$0 {
354     a: u32,
355     b: u8,
356     c: u64,
357 }
358         "#,
359         )
360     }
361
362     #[test]
363     fn not_applicable_if_enum_sorted() {
364         cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
365
366         check_assist_not_applicable(
367             sort_items,
368             r#"
369 $0enum Bar$0 {
370     a,
371     b,
372     c,
373 }
374         "#,
375         )
376     }
377
378     #[test]
379     fn sort_trait() {
380         check_assist(
381             sort_items,
382             r#"
383 $0trait Bar$0 {
384     fn a() {
385
386     }
387
388     // comment for c
389     fn c() {}
390     fn z() {}
391     fn b() {}
392 }
393         "#,
394             r#"
395 trait Bar {
396     fn a() {
397
398     }
399
400     fn b() {}
401     // comment for c
402     fn c() {}
403     fn z() {}
404 }
405         "#,
406         )
407     }
408
409     #[test]
410     fn sort_impl() {
411         check_assist(
412             sort_items,
413             r#"
414 struct Bar;
415 $0impl Bar$0 {
416     fn c() {}
417     fn a() {}
418     /// long
419     /// doc
420     /// comment
421     fn z() {}
422     fn d() {}
423 }
424         "#,
425             r#"
426 struct Bar;
427 impl Bar {
428     fn a() {}
429     fn c() {}
430     fn d() {}
431     /// long
432     /// doc
433     /// comment
434     fn z() {}
435 }
436         "#,
437         )
438     }
439
440     #[test]
441     fn sort_struct() {
442         check_assist(
443             sort_items,
444             r#"
445 $0struct Bar$0 {
446     b: u8,
447     a: u32,
448     c: u64,
449 }
450         "#,
451             r#"
452 struct Bar {
453     a: u32,
454     b: u8,
455     c: u64,
456 }
457         "#,
458         )
459     }
460
461     #[test]
462     fn sort_generic_struct_with_lifetime() {
463         check_assist(
464             sort_items,
465             r#"
466 $0struct Bar<'a,$0 T> {
467     d: &'a str,
468     b: u8,
469     a: T,
470     c: u64,
471 }
472         "#,
473             r#"
474 struct Bar<'a, T> {
475     a: T,
476     b: u8,
477     c: u64,
478     d: &'a str,
479 }
480         "#,
481         )
482     }
483
484     #[test]
485     fn sort_struct_fields_diff_len() {
486         check_assist(
487             sort_items,
488             r#"
489 $0struct Bar $0{
490     aaa: u8,
491     a: usize,
492     b: u8,
493 }
494         "#,
495             r#"
496 struct Bar {
497     a: usize,
498     aaa: u8,
499     b: u8,
500 }
501         "#,
502         )
503     }
504
505     #[test]
506     fn sort_union() {
507         check_assist(
508             sort_items,
509             r#"
510 $0union Bar$0 {
511     b: u8,
512     a: u32,
513     c: u64,
514 }
515         "#,
516             r#"
517 union Bar {
518     a: u32,
519     b: u8,
520     c: u64,
521 }
522         "#,
523         )
524     }
525
526     #[test]
527     fn sort_enum() {
528         check_assist(
529             sort_items,
530             r#"
531 $0enum Bar $0{
532     d{ first: u32, second: usize},
533     b = 14,
534     a,
535     c(u32, usize),
536 }
537         "#,
538             r#"
539 enum Bar {
540     a,
541     b = 14,
542     c(u32, usize),
543     d{ first: u32, second: usize},
544 }
545         "#,
546         )
547     }
548
549     #[test]
550     fn sort_struct_enum_variant_fields() {
551         check_assist(
552             sort_items,
553             r#"
554 enum Bar {
555     d$0{ second: usize, first: u32 }$0,
556     b = 14,
557     a,
558     c(u32, usize),
559 }
560         "#,
561             r#"
562 enum Bar {
563     d{ first: u32, second: usize },
564     b = 14,
565     a,
566     c(u32, usize),
567 }
568         "#,
569         )
570     }
571
572     #[test]
573     fn sort_struct_enum_variant() {
574         check_assist(
575             sort_items,
576             r#"
577 enum Bar {
578     $0d$0{ second: usize, first: u32 },
579 }
580         "#,
581             r#"
582 enum Bar {
583     d{ first: u32, second: usize },
584 }
585         "#,
586         )
587     }
588 }