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
12pub(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 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 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 (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}