ide_assists/handlers/
sort_items.rs

1use std::cmp::Ordering;
2
3use itertools::Itertools;
4
5use syntax::{
6    AstNode, SyntaxNode,
7    ast::{self, HasName},
8};
9
10use crate::{AssistContext, AssistId, Assists, utils::get_methods};
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// ```
84pub(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(struct_ast) = ctx.find_node_at_offset::<ast::Struct>() {
91        add_sort_field_list_assist(acc, struct_ast.field_list())
92    } else if let Some(union_ast) = ctx.find_node_at_offset::<ast::Union>() {
93        add_sort_fields_assist(acc, union_ast.record_field_list()?)
94    } else if let Some(variant_ast) = ctx.find_node_at_offset::<ast::Variant>() {
95        add_sort_field_list_assist(acc, variant_ast.field_list())
96    } else if let Some(enum_struct_variant_ast) = ctx.find_node_at_offset::<ast::RecordFieldList>()
97    {
98        // should be above enum and below struct
99        add_sort_fields_assist(acc, enum_struct_variant_ast)
100    } else if let Some(enum_ast) = ctx.find_node_at_offset::<ast::Enum>() {
101        add_sort_variants_assist(acc, enum_ast.variant_list()?)
102    } else if let Some(trait_ast) = ctx.find_node_at_offset::<ast::Trait>() {
103        add_sort_methods_assist(acc, ctx, trait_ast.assoc_item_list()?)
104    } else if let Some(impl_ast) = ctx.find_node_at_offset::<ast::Impl>() {
105        add_sort_methods_assist(acc, ctx, impl_ast.assoc_item_list()?)
106    } else {
107        None
108    }
109}
110
111trait AddRewrite {
112    fn add_rewrite<T: AstNode>(
113        &mut self,
114        label: &str,
115        old: Vec<T>,
116        new: Vec<T>,
117        target: &SyntaxNode,
118    ) -> Option<()>;
119}
120
121impl 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: &SyntaxNode,
128    ) -> Option<()> {
129        self.add(AssistId::refactor_rewrite("sort_items"), label, target.text_range(), |builder| {
130            let mut editor = builder.make_editor(target);
131
132            old.into_iter()
133                .zip(new)
134                .for_each(|(old, new)| editor.replace(old.syntax(), new.syntax()));
135
136            builder.add_file_edits(builder.file_id, editor)
137        })
138    }
139}
140
141fn add_sort_field_list_assist(acc: &mut Assists, field_list: Option<ast::FieldList>) -> Option<()> {
142    match field_list {
143        Some(ast::FieldList::RecordFieldList(it)) => add_sort_fields_assist(acc, it),
144        _ => {
145            cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
146            None
147        }
148    }
149}
150
151fn add_sort_methods_assist(
152    acc: &mut Assists,
153    ctx: &AssistContext<'_>,
154    item_list: ast::AssocItemList,
155) -> Option<()> {
156    let selection = ctx.selection_trimmed();
157
158    // ignore assist if the selection intersects with an associated item.
159    if item_list.assoc_items().any(|item| item.syntax().text_range().intersect(selection).is_some())
160    {
161        return None;
162    }
163
164    let methods = get_methods(&item_list);
165    let sorted = sort_by_name(&methods);
166
167    if methods == sorted {
168        cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
169        return None;
170    }
171
172    acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax())
173}
174
175fn add_sort_fields_assist(
176    acc: &mut Assists,
177    record_field_list: ast::RecordFieldList,
178) -> Option<()> {
179    let fields: Vec<_> = record_field_list.fields().collect();
180    let sorted = sort_by_name(&fields);
181
182    if fields == sorted {
183        cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
184        return None;
185    }
186
187    acc.add_rewrite("Sort fields alphabetically", fields, sorted, record_field_list.syntax())
188}
189
190fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) -> Option<()> {
191    let variants: Vec<_> = variant_list.variants().collect();
192    let sorted = sort_by_name(&variants);
193
194    if variants == sorted {
195        cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
196        return None;
197    }
198
199    acc.add_rewrite("Sort variants alphabetically", variants, sorted, variant_list.syntax())
200}
201
202fn sort_by_name<T: HasName + Clone>(initial: &[T]) -> Vec<T> {
203    initial
204        .iter()
205        .cloned()
206        .sorted_by(|a, b| match (a.name(), b.name()) {
207            (Some(a), Some(b)) => Ord::cmp(&a.to_string(), &b.to_string()),
208
209            // unexpected, but just in case
210            (None, None) => Ordering::Equal,
211            (None, Some(_)) => Ordering::Less,
212            (Some(_), None) => Ordering::Greater,
213        })
214        .collect()
215}
216
217#[cfg(test)]
218mod tests {
219    use crate::tests::{check_assist, check_assist_not_applicable};
220
221    use super::*;
222
223    #[test]
224    fn not_applicable_if_selection_in_fn_body() {
225        check_assist_not_applicable(
226            sort_items,
227            r#"
228struct S;
229impl S {
230    fn func2() {
231        $0 bar $0
232    }
233    fn func() {}
234}
235        "#,
236        )
237    }
238
239    #[test]
240    fn not_applicable_if_selection_at_associated_const() {
241        check_assist_not_applicable(
242            sort_items,
243            r#"
244struct S;
245impl S {
246    fn func2() {}
247    fn func() {}
248    const C: () = $0()$0;
249}
250        "#,
251        )
252    }
253
254    #[test]
255    fn not_applicable_if_selection_overlaps_nodes() {
256        check_assist_not_applicable(
257            sort_items,
258            r#"
259struct S;
260impl $0S {
261    fn$0 func2() {}
262    fn func() {}
263}
264        "#,
265        )
266    }
267
268    #[test]
269    fn not_applicable_if_no_selection() {
270        cov_mark::check!(not_applicable_if_no_selection);
271
272        check_assist_not_applicable(
273            sort_items,
274            r#"
275t$0rait Bar {
276    fn b();
277    fn a();
278}
279        "#,
280        )
281    }
282
283    #[test]
284    fn not_applicable_if_selection_in_trait_fn_body() {
285        check_assist_not_applicable(
286            sort_items,
287            r#"
288trait Bar {
289    fn b() {
290        $0 hello $0
291    }
292    fn a();
293}
294        "#,
295        )
296    }
297
298    #[test]
299    fn not_applicable_if_trait_empty() {
300        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
301
302        check_assist_not_applicable(
303            sort_items,
304            r#"
305t$0rait Bar$0 {
306}
307        "#,
308        )
309    }
310
311    #[test]
312    fn not_applicable_if_impl_empty() {
313        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
314
315        check_assist_not_applicable(
316            sort_items,
317            r#"
318struct Bar;
319$0impl Bar$0 {
320}
321        "#,
322        )
323    }
324
325    #[test]
326    fn not_applicable_if_struct_empty() {
327        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
328
329        check_assist_not_applicable(
330            sort_items,
331            r#"
332$0struct Bar$0 ;
333        "#,
334        )
335    }
336
337    #[test]
338    fn not_applicable_if_struct_empty2() {
339        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
340
341        check_assist_not_applicable(
342            sort_items,
343            r#"
344$0struct Bar$0 { };
345        "#,
346        )
347    }
348
349    #[test]
350    fn not_applicable_if_enum_empty() {
351        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
352
353        check_assist_not_applicable(
354            sort_items,
355            r#"
356$0enum ZeroVariants$0 {};
357        "#,
358        )
359    }
360
361    #[test]
362    fn not_applicable_if_trait_sorted() {
363        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
364
365        check_assist_not_applicable(
366            sort_items,
367            r#"
368t$0rait Bar$0 {
369    fn a() {}
370    fn b() {}
371    fn c() {}
372}
373        "#,
374        )
375    }
376
377    #[test]
378    fn not_applicable_if_impl_sorted() {
379        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
380
381        check_assist_not_applicable(
382            sort_items,
383            r#"
384struct Bar;
385$0impl Bar$0 {
386    fn a() {}
387    fn b() {}
388    fn c() {}
389}
390        "#,
391        )
392    }
393
394    #[test]
395    fn not_applicable_if_struct_sorted() {
396        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
397
398        check_assist_not_applicable(
399            sort_items,
400            r#"
401$0struct Bar$0 {
402    a: u32,
403    b: u8,
404    c: u64,
405}
406        "#,
407        )
408    }
409
410    #[test]
411    fn not_applicable_if_union_sorted() {
412        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
413
414        check_assist_not_applicable(
415            sort_items,
416            r#"
417$0union Bar$0 {
418    a: u32,
419    b: u8,
420    c: u64,
421}
422        "#,
423        )
424    }
425
426    #[test]
427    fn not_applicable_if_enum_sorted() {
428        cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
429
430        check_assist_not_applicable(
431            sort_items,
432            r#"
433$0enum Bar$0 {
434    a,
435    b,
436    c,
437}
438        "#,
439        )
440    }
441
442    #[test]
443    fn sort_trait() {
444        check_assist(
445            sort_items,
446            r#"
447$0trait Bar$0 {
448    fn a() {
449
450    }
451
452    // comment for c
453    fn c() {}
454    fn z() {}
455    fn b() {}
456}
457        "#,
458            r#"
459trait Bar {
460    fn a() {
461
462    }
463
464    fn b() {}
465    // comment for c
466    fn c() {}
467    fn z() {}
468}
469        "#,
470        )
471    }
472
473    #[test]
474    fn sort_impl() {
475        check_assist(
476            sort_items,
477            r#"
478struct Bar;
479$0impl Bar$0 {
480    fn c() {}
481    fn a() {}
482    /// long
483    /// doc
484    /// comment
485    fn z() {}
486    fn d() {}
487}
488        "#,
489            r#"
490struct Bar;
491impl Bar {
492    fn a() {}
493    fn c() {}
494    fn d() {}
495    /// long
496    /// doc
497    /// comment
498    fn z() {}
499}
500        "#,
501        )
502    }
503
504    #[test]
505    fn sort_struct() {
506        check_assist(
507            sort_items,
508            r#"
509$0struct Bar$0 {
510    b: u8,
511    a: u32,
512    c: u64,
513}
514        "#,
515            r#"
516struct Bar {
517    a: u32,
518    b: u8,
519    c: u64,
520}
521        "#,
522        )
523    }
524
525    #[test]
526    fn sort_struct_inside_a_function() {
527        check_assist(
528            sort_items,
529            r#"
530fn hello() {
531    $0struct Bar$0 {
532        b: u8,
533        a: u32,
534        c: u64,
535    }
536}
537        "#,
538            r#"
539fn hello() {
540    struct Bar {
541        a: u32,
542        b: u8,
543        c: u64,
544    }
545}
546        "#,
547        )
548    }
549
550    #[test]
551    fn sort_generic_struct_with_lifetime() {
552        check_assist(
553            sort_items,
554            r#"
555$0struct Bar<'a,$0 T> {
556    d: &'a str,
557    b: u8,
558    a: T,
559    c: u64,
560}
561        "#,
562            r#"
563struct Bar<'a, T> {
564    a: T,
565    b: u8,
566    c: u64,
567    d: &'a str,
568}
569        "#,
570        )
571    }
572
573    #[test]
574    fn sort_struct_fields_diff_len() {
575        check_assist(
576            sort_items,
577            r#"
578$0struct Bar $0{
579    aaa: u8,
580    a: usize,
581    b: u8,
582}
583        "#,
584            r#"
585struct Bar {
586    a: usize,
587    aaa: u8,
588    b: u8,
589}
590        "#,
591        )
592    }
593
594    #[test]
595    fn sort_union() {
596        check_assist(
597            sort_items,
598            r#"
599$0union Bar$0 {
600    b: u8,
601    a: u32,
602    c: u64,
603}
604        "#,
605            r#"
606union Bar {
607    a: u32,
608    b: u8,
609    c: u64,
610}
611        "#,
612        )
613    }
614
615    #[test]
616    fn sort_enum() {
617        check_assist(
618            sort_items,
619            r#"
620$0enum Bar $0{
621    d{ first: u32, second: usize},
622    b = 14,
623    a,
624    c(u32, usize),
625}
626        "#,
627            r#"
628enum Bar {
629    a,
630    b = 14,
631    c(u32, usize),
632    d{ first: u32, second: usize},
633}
634        "#,
635        )
636    }
637
638    #[test]
639    fn sort_struct_enum_variant_fields() {
640        check_assist(
641            sort_items,
642            r#"
643enum Bar {
644    d$0{ second: usize, first: u32 }$0,
645    b = 14,
646    a,
647    c(u32, usize),
648}
649        "#,
650            r#"
651enum Bar {
652    d{ first: u32, second: usize },
653    b = 14,
654    a,
655    c(u32, usize),
656}
657        "#,
658        )
659    }
660
661    #[test]
662    fn sort_struct_enum_variant() {
663        check_assist(
664            sort_items,
665            r#"
666enum Bar {
667    $0d$0{ second: usize, first: u32 },
668}
669        "#,
670            r#"
671enum Bar {
672    d{ first: u32, second: usize },
673}
674        "#,
675        )
676    }
677}