1use std::cmp::Ordering;
3
4use itertools::{EitherOrBoth, Itertools};
5use parser::T;
6use syntax::{
7 Direction, SyntaxElement, ToSmolStr, algo,
8 ast::{
9 self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind, edit_in_place::Removable,
10 make,
11 },
12 ted::{self, Position},
13};
14
15use crate::syntax_helpers::node_ext::vis_eq;
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19pub enum MergeBehavior {
20 Crate,
22 Module,
24 One,
27}
28
29impl MergeBehavior {
30 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
31 match self {
32 MergeBehavior::Crate | MergeBehavior::One => true,
33 MergeBehavior::Module => {
35 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
36 }
37 }
38 }
39}
40
41pub fn try_merge_imports(
44 lhs: &ast::Use,
45 rhs: &ast::Use,
46 merge_behavior: MergeBehavior,
47) -> Option<ast::Use> {
48 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
50 return None;
51 }
52 if !eq_attrs(lhs.attrs(), rhs.attrs()) {
53 return None;
54 }
55
56 let lhs = lhs.clone_subtree().clone_for_update();
57 let rhs = rhs.clone_subtree().clone_for_update();
58 let lhs_tree = lhs.use_tree()?;
59 let rhs_tree = rhs.use_tree()?;
60 try_merge_trees_mut(&lhs_tree, &rhs_tree, merge_behavior)?;
61
62 try_normalize_use_tree_mut(&lhs_tree, merge_behavior.into());
64
65 Some(lhs)
66}
67
68pub fn try_merge_trees(
71 lhs: &ast::UseTree,
72 rhs: &ast::UseTree,
73 merge: MergeBehavior,
74) -> Option<ast::UseTree> {
75 let lhs = lhs.clone_subtree().clone_for_update();
76 let rhs = rhs.clone_subtree().clone_for_update();
77 try_merge_trees_mut(&lhs, &rhs, merge)?;
78
79 try_normalize_use_tree_mut(&lhs, merge.into());
81
82 Some(lhs)
83}
84
85fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
86 if merge == MergeBehavior::One {
87 lhs.wrap_in_tree_list();
88 rhs.wrap_in_tree_list();
89 } else {
90 let lhs_path = lhs.path()?;
91 let rhs_path = rhs.path()?;
92
93 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
94 if lhs.is_simple_path()
95 && rhs.is_simple_path()
96 && lhs_path == lhs_prefix
97 && rhs_path == rhs_prefix
98 {
99 let lhs_name = lhs.rename().and_then(|lhs_name| lhs_name.name());
102 let rhs_name = rhs.rename().and_then(|rhs_name| rhs_name.name());
103 if lhs_name != rhs_name {
104 return None;
105 }
106
107 ted::replace(lhs.syntax(), rhs.syntax());
108 return Some(());
110 } else {
111 lhs.split_prefix(&lhs_prefix);
112 rhs.split_prefix(&rhs_prefix);
113 }
114 }
115 recursive_merge(lhs, rhs, merge)
116}
117
118#[must_use]
120fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
121 let mut use_trees: Vec<ast::UseTree> = lhs
122 .use_tree_list()
123 .into_iter()
124 .flat_map(|list| list.use_trees())
125 .map(|tree| merge.is_tree_allowed(&tree).then_some(tree))
128 .collect::<Option<_>>()?;
129 use_trees.sort_unstable_by(use_tree_cmp);
132 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
133 if !merge.is_tree_allowed(&rhs_t) {
134 return None;
135 }
136
137 match use_trees.binary_search_by(|lhs_t| use_tree_cmp_bin_search(lhs_t, &rhs_t)) {
138 Ok(idx) => {
139 let lhs_t = &mut use_trees[idx];
140 let lhs_path = lhs_t.path()?;
141 let rhs_path = rhs_t.path()?;
142 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
143 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
144 let tree_is_self = |tree: &ast::UseTree| {
145 tree.path().as_ref().map(path_is_self).unwrap_or(false)
146 };
147 let tree_contains_self = |tree: &ast::UseTree| {
152 tree.use_tree_list()
153 .map(|tree_list| tree_list.use_trees().any(|it| tree_is_self(&it)))
154 .or_else(|| tree.star_token().map(|_| false))
157 };
158
159 if lhs_t.rename().and_then(|x| x.underscore_token()).is_some() {
160 ted::replace(lhs_t.syntax(), rhs_t.syntax());
161 *lhs_t = rhs_t;
162 continue;
163 }
164
165 match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) {
166 (Some(true), None) => {
167 remove_subtree_if_only_self(lhs_t);
168 continue;
169 }
170 (None, Some(true)) => {
171 ted::replace(lhs_t.syntax(), rhs_t.syntax());
172 *lhs_t = rhs_t;
173 remove_subtree_if_only_self(lhs_t);
174 continue;
175 }
176 _ => (),
177 }
178
179 if lhs_t.is_simple_path() && rhs_t.is_simple_path() {
180 continue;
181 }
182 }
183 lhs_t.split_prefix(&lhs_prefix);
184 rhs_t.split_prefix(&rhs_prefix);
185 recursive_merge(lhs_t, &rhs_t, merge)?;
186 }
187 Err(_)
188 if merge == MergeBehavior::Module
189 && !use_trees.is_empty()
190 && rhs_t.use_tree_list().is_some() =>
191 {
192 return None;
193 }
194 Err(insert_idx) => {
195 use_trees.insert(insert_idx, rhs_t.clone());
196 lhs.get_or_create_use_tree_list().add_use_tree(rhs_t);
200 }
201 }
202 }
203 Some(())
204}
205
206#[derive(Copy, Clone, Debug, PartialEq, Eq)]
208pub enum NormalizationStyle {
209 Default,
216 One,
223}
224
225impl From<MergeBehavior> for NormalizationStyle {
226 fn from(mb: MergeBehavior) -> Self {
227 match mb {
228 MergeBehavior::One => NormalizationStyle::One,
229 _ => NormalizationStyle::Default,
230 }
231 }
232}
233
234pub fn try_normalize_import(use_item: &ast::Use, style: NormalizationStyle) -> Option<ast::Use> {
254 let use_item = use_item.clone_subtree().clone_for_update();
255 try_normalize_use_tree_mut(&use_item.use_tree()?, style)?;
256 Some(use_item)
257}
258
259fn try_normalize_use_tree_mut(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
260 if style == NormalizationStyle::One {
261 let mut modified = false;
262 modified |= use_tree.wrap_in_tree_list().is_some();
263 modified |= recursive_normalize(use_tree, style).is_some();
264 if !modified {
265 return None;
267 }
268 } else {
269 recursive_normalize(use_tree, NormalizationStyle::Default)?;
270 }
271 Some(())
272}
273
274fn recursive_normalize(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
276 let use_tree_list = use_tree.use_tree_list()?;
277 let merge_subtree_into_parent_tree = |single_subtree: &ast::UseTree| {
278 let subtree_is_only_self = single_subtree.path().as_ref().is_some_and(path_is_self);
279
280 let merged_path = match (use_tree.path(), single_subtree.path()) {
281 _ if subtree_is_only_self => None,
285
286 (None, None) => None,
287 (Some(outer), None) => Some(outer),
288 (None, Some(inner)) => Some(inner),
289 (Some(outer), Some(inner)) => Some(make::path_concat(outer, inner).clone_for_update()),
290 };
291
292 if merged_path.is_some()
293 || single_subtree.use_tree_list().is_some()
294 || single_subtree.star_token().is_some()
295 {
296 ted::remove_all_iter(use_tree.syntax().children_with_tokens());
297 if let Some(path) = merged_path {
298 ted::insert_raw(Position::first_child_of(use_tree.syntax()), path.syntax());
299 if single_subtree.use_tree_list().is_some() || single_subtree.star_token().is_some()
300 {
301 ted::insert_raw(
302 Position::last_child_of(use_tree.syntax()),
303 make::token(T![::]),
304 );
305 }
306 }
307 if let Some(inner_use_tree_list) = single_subtree.use_tree_list() {
308 ted::insert_raw(
309 Position::last_child_of(use_tree.syntax()),
310 inner_use_tree_list.syntax(),
311 );
312 } else if single_subtree.star_token().is_some() {
313 ted::insert_raw(Position::last_child_of(use_tree.syntax()), make::token(T![*]));
314 } else if let Some(rename) = single_subtree.rename() {
315 ted::insert_raw(
316 Position::last_child_of(use_tree.syntax()),
317 make::tokens::single_space(),
318 );
319 ted::insert_raw(Position::last_child_of(use_tree.syntax()), rename.syntax());
320 }
321 Some(())
322 } else {
323 None
325 }
326 };
327 let one_style_tree_list = |subtree: &ast::UseTree| match (
328 subtree.path().is_none() && subtree.star_token().is_none() && subtree.rename().is_none(),
329 subtree.use_tree_list(),
330 ) {
331 (true, tree_list) => tree_list,
332 _ => None,
333 };
334 let add_element_to_list = |elem: SyntaxElement, elements: &mut Vec<SyntaxElement>| {
335 if !elements.is_empty() {
336 elements.push(make::token(T![,]).into());
337 elements.push(make::tokens::single_space().into());
338 }
339 elements.push(elem);
340 };
341 if let Some((single_subtree,)) = use_tree_list.use_trees().collect_tuple() {
342 if style == NormalizationStyle::One {
343 recursive_normalize(&single_subtree, NormalizationStyle::Default)?;
345 } else {
346 merge_subtree_into_parent_tree(&single_subtree)?;
349 recursive_normalize(use_tree, style);
350 }
351 } else {
352 let mut modified = false;
354
355 for subtree in use_tree_list.use_trees() {
357 if let Some(one_tree_list) = one_style_tree_list(&subtree) {
358 let mut elements = Vec::new();
359 let mut one_tree_list_iter = one_tree_list.use_trees();
360 let mut prev_skipped = Vec::new();
361 loop {
362 let mut prev_skipped_iter = prev_skipped.into_iter();
363 let mut curr_skipped = Vec::new();
364
365 while let Some(sub_sub_tree) =
366 one_tree_list_iter.next().or(prev_skipped_iter.next())
367 {
368 if let Some(sub_one_tree_list) = one_style_tree_list(&sub_sub_tree) {
369 curr_skipped.extend(sub_one_tree_list.use_trees());
370 } else {
371 modified |=
372 recursive_normalize(&sub_sub_tree, NormalizationStyle::Default)
373 .is_some();
374 add_element_to_list(
375 sub_sub_tree.syntax().clone().into(),
376 &mut elements,
377 );
378 }
379 }
380
381 if curr_skipped.is_empty() {
382 break;
384 }
385 prev_skipped = curr_skipped;
386 }
387
388 if elements.is_empty() {
391 subtree.remove();
392 } else {
393 ted::replace_with_many(subtree.syntax(), elements);
394 }
395 let _ = modified;
397 modified = true;
398 } else {
399 modified |= recursive_normalize(&subtree, NormalizationStyle::Default).is_some();
400 }
401 }
402
403 let mut tree_list_iter = use_tree_list.use_trees();
405 let mut anchor = tree_list_iter.next()?;
406 let mut prev_skipped = Vec::new();
407 loop {
408 let mut has_merged = false;
409 let mut prev_skipped_iter = prev_skipped.into_iter();
410 let mut next_anchor = None;
411 let mut curr_skipped = Vec::new();
412
413 while let Some(candidate) = tree_list_iter.next().or(prev_skipped_iter.next()) {
414 let result = try_merge_trees_mut(&anchor, &candidate, MergeBehavior::Crate);
415 if result.is_some() {
416 candidate.remove();
418 has_merged = true;
419 } else if next_anchor.is_none() {
420 next_anchor = Some(candidate);
421 } else {
422 curr_skipped.push(candidate);
423 }
424 }
425
426 if has_merged {
427 recursive_normalize(&anchor, NormalizationStyle::Default);
429 modified = true;
430 }
431
432 let (Some(next_anchor), true) = (next_anchor, !curr_skipped.is_empty()) else {
433 break;
435 };
436
437 anchor = next_anchor;
439 prev_skipped = curr_skipped;
440 }
441
442 let mut subtrees: Vec<_> = use_tree_list.use_trees().collect();
443 if subtrees.len() == 1 && style != NormalizationStyle::One {
446 modified |= merge_subtree_into_parent_tree(&subtrees[0]).is_some();
447 }
448 if subtrees.len() > 1 {
450 let mut did_sort = false;
451 subtrees.sort_unstable_by(|a, b| {
452 let order = use_tree_cmp_bin_search(a, b);
453 if !did_sort && order == Ordering::Less {
454 did_sort = true;
455 }
456 order
457 });
458 if did_sort {
459 let start = use_tree_list
460 .l_curly_token()
461 .and_then(|l_curly| algo::non_trivia_sibling(l_curly.into(), Direction::Next))
462 .filter(|it| it.kind() != T!['}']);
463 let end = use_tree_list
464 .r_curly_token()
465 .and_then(|r_curly| algo::non_trivia_sibling(r_curly.into(), Direction::Prev))
466 .filter(|it| it.kind() != T!['{']);
467 if let Some((start, end)) = start.zip(end) {
468 let mut elements = Vec::new();
470 for subtree in subtrees {
471 add_element_to_list(subtree.syntax().clone().into(), &mut elements);
472 }
473 ted::replace_all(start..=end, elements);
474 } else {
475 let new_use_tree_list = make::use_tree_list(subtrees).clone_for_update();
476 ted::replace(use_tree_list.syntax(), new_use_tree_list.syntax());
477 }
478 modified = true;
479 }
480 }
481
482 if !modified {
483 return None;
485 }
486 }
487 Some(())
488}
489
490pub fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
492 let mut res = None;
493 let mut lhs_curr = lhs.first_qualifier_or_self();
494 let mut rhs_curr = rhs.first_qualifier_or_self();
495 loop {
496 match (lhs_curr.segment(), rhs_curr.segment()) {
497 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
498 _ => break res,
499 }
500 res = Some((lhs_curr.clone(), rhs_curr.clone()));
501
502 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
503 Some((lhs, rhs)) => {
504 lhs_curr = lhs;
505 rhs_curr = rhs;
506 }
507 _ => break res,
508 }
509 }
510}
511
512fn use_tree_cmp_bin_search(lhs: &ast::UseTree, rhs: &ast::UseTree) -> Ordering {
514 let lhs_is_simple_path = lhs.is_simple_path() && lhs.rename().is_none();
515 let rhs_is_simple_path = rhs.is_simple_path() && rhs.rename().is_none();
516 match (
517 lhs.path().as_ref().and_then(ast::Path::first_segment),
518 rhs.path().as_ref().and_then(ast::Path::first_segment),
519 ) {
520 (None, None) => match (lhs_is_simple_path, rhs_is_simple_path) {
521 (true, true) => Ordering::Equal,
522 (true, false) => Ordering::Less,
523 (false, true) => Ordering::Greater,
524 (false, false) => use_tree_cmp_by_tree_list_glob_or_alias(lhs, rhs, false),
525 },
526 (Some(_), None) if !rhs_is_simple_path => Ordering::Less,
527 (Some(_), None) => Ordering::Greater,
528 (None, Some(_)) if !lhs_is_simple_path => Ordering::Greater,
529 (None, Some(_)) => Ordering::Less,
530 (Some(a), Some(b)) => path_segment_cmp(&a, &b),
531 }
532}
533
534pub(super) fn use_tree_cmp(a: &ast::UseTree, b: &ast::UseTree) -> Ordering {
542 let a_is_simple_path = a.is_simple_path() && a.rename().is_none();
543 let b_is_simple_path = b.is_simple_path() && b.rename().is_none();
544 match (a.path(), b.path()) {
545 (None, None) => match (a_is_simple_path, b_is_simple_path) {
546 (true, true) => Ordering::Equal,
547 (true, false) => Ordering::Less,
548 (false, true) => Ordering::Greater,
549 (false, false) => use_tree_cmp_by_tree_list_glob_or_alias(a, b, true),
550 },
551 (Some(_), None) if !b_is_simple_path => Ordering::Less,
552 (Some(_), None) => Ordering::Greater,
553 (None, Some(_)) if !a_is_simple_path => Ordering::Greater,
554 (None, Some(_)) => Ordering::Less,
555 (Some(a_path), Some(b_path)) => {
556 a_path
559 .segments()
560 .zip_longest(b_path.segments())
561 .find_map(|zipped| match zipped {
562 EitherOrBoth::Both(a_segment, b_segment) => {
563 match path_segment_cmp(&a_segment, &b_segment) {
564 Ordering::Equal => None,
565 ord => Some(ord),
566 }
567 }
568 EitherOrBoth::Left(_) if b_is_simple_path => Some(Ordering::Greater),
569 EitherOrBoth::Left(_) => Some(Ordering::Less),
570 EitherOrBoth::Right(_) if a_is_simple_path => Some(Ordering::Less),
571 EitherOrBoth::Right(_) => Some(Ordering::Greater),
572 })
573 .unwrap_or_else(|| use_tree_cmp_by_tree_list_glob_or_alias(a, b, true))
574 }
575 }
576}
577
578fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
579 match (a.kind(), b.kind()) {
580 (None, None) => Ordering::Equal,
581 (Some(_), None) => Ordering::Greater,
582 (None, Some(_)) => Ordering::Less,
583 (Some(PathSegmentKind::SelfKw), Some(PathSegmentKind::SelfKw)) => Ordering::Equal,
585 (Some(PathSegmentKind::SelfKw), _) => Ordering::Less,
586 (_, Some(PathSegmentKind::SelfKw)) => Ordering::Greater,
587 (Some(PathSegmentKind::SuperKw), Some(PathSegmentKind::SuperKw)) => Ordering::Equal,
589 (Some(PathSegmentKind::SuperKw), _) => Ordering::Less,
590 (_, Some(PathSegmentKind::SuperKw)) => Ordering::Greater,
591 (Some(PathSegmentKind::CrateKw), Some(PathSegmentKind::CrateKw)) => Ordering::Equal,
593 (Some(PathSegmentKind::CrateKw), _) => Ordering::Less,
594 (_, Some(PathSegmentKind::CrateKw)) => Ordering::Greater,
595 _ => {
597 match (
598 a.name_ref().as_ref().map(ast::NameRef::text),
599 b.name_ref().as_ref().map(ast::NameRef::text),
600 ) {
601 (None, None) => Ordering::Equal,
602 (Some(_), None) => Ordering::Greater,
603 (None, Some(_)) => Ordering::Less,
604 (Some(a_name), Some(b_name)) => {
605 let a_text = a_name.as_str().trim_start_matches("r#");
606 let b_text = b_name.as_str().trim_start_matches("r#");
607 version_sort::version_sort(a_text, b_text)
608 }
609 }
610 }
611 }
612}
613
614fn use_tree_cmp_by_tree_list_glob_or_alias(
622 a: &ast::UseTree,
623 b: &ast::UseTree,
624 strict: bool,
625) -> Ordering {
626 let cmp_by_glob_or_alias = || match (a.star_token().is_some(), b.star_token().is_some()) {
627 (true, false) => Ordering::Greater,
628 (false, true) => Ordering::Less,
629 _ => match (a.rename(), b.rename()) {
630 (None, None) => Ordering::Equal,
631 (Some(_), None) => Ordering::Greater,
632 (None, Some(_)) => Ordering::Less,
633 (Some(a_rename), Some(b_rename)) => a_rename
634 .name()
635 .as_ref()
636 .map(ast::Name::text)
637 .as_ref()
638 .map_or("_", |a_name| a_name.as_str().trim_start_matches("r#"))
639 .cmp(
640 b_rename
641 .name()
642 .as_ref()
643 .map(ast::Name::text)
644 .as_ref()
645 .map_or("_", |b_name| b_name.as_str().trim_start_matches("r#")),
646 ),
647 },
648 };
649
650 match (a.use_tree_list(), b.use_tree_list()) {
651 (Some(_), None) => Ordering::Greater,
652 (None, Some(_)) => Ordering::Less,
653 (Some(a_list), Some(b_list)) if strict => a_list
654 .use_trees()
655 .zip_longest(b_list.use_trees())
656 .find_map(|zipped| match zipped {
657 EitherOrBoth::Both(a_tree, b_tree) => match use_tree_cmp(&a_tree, &b_tree) {
658 Ordering::Equal => None,
659 ord => Some(ord),
660 },
661 EitherOrBoth::Left(_) => Some(Ordering::Greater),
662 EitherOrBoth::Right(_) => Some(Ordering::Less),
663 })
664 .unwrap_or_else(cmp_by_glob_or_alias),
665 _ => cmp_by_glob_or_alias(),
666 }
667}
668
669pub fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
670 match (vis0, vis1) {
671 (None, None) => true,
672 (Some(vis0), Some(vis1)) => vis_eq(&vis0, &vis1),
673 _ => false,
674 }
675}
676
677pub fn eq_attrs(
678 attrs0: impl Iterator<Item = ast::Attr>,
679 attrs1: impl Iterator<Item = ast::Attr>,
680) -> bool {
681 let mut attrs0: Vec<_> = attrs0.map(|attr| attr.syntax().text().to_smolstr()).collect();
682 let mut attrs1: Vec<_> = attrs1.map(|attr| attr.syntax().text().to_smolstr()).collect();
683 attrs0.sort_unstable();
684 attrs1.sort_unstable();
685
686 attrs0 == attrs1
687}
688
689fn path_is_self(path: &ast::Path) -> bool {
690 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
691}
692
693fn path_len(path: ast::Path) -> usize {
694 path.segments().count()
695}
696
697fn get_single_subtree(use_tree: &ast::UseTree) -> Option<ast::UseTree> {
698 use_tree
699 .use_tree_list()
700 .and_then(|tree_list| tree_list.use_trees().collect_tuple())
701 .map(|(single_subtree,)| single_subtree)
702}
703
704fn remove_subtree_if_only_self(use_tree: &ast::UseTree) {
705 let Some(single_subtree) = get_single_subtree(use_tree) else { return };
706 match (use_tree.path(), single_subtree.path()) {
707 (Some(_), Some(inner)) if path_is_self(&inner) => {
708 ted::remove_all_iter(single_subtree.syntax().children_with_tokens());
709 }
710 _ => (),
711 }
712}
713
714mod version_sort {
717 #![allow(clippy::all)]
720
721 use std::cmp::Ordering;
722
723 use itertools::{EitherOrBoth, Itertools};
724
725 struct VersionChunkIter<'a> {
726 ident: &'a str,
727 start: usize,
728 }
729
730 impl<'a> VersionChunkIter<'a> {
731 pub(crate) fn new(ident: &'a str) -> Self {
732 Self { ident, start: 0 }
733 }
734
735 fn parse_numeric_chunk(
736 &mut self,
737 mut chars: std::str::CharIndices<'a>,
738 ) -> Option<VersionChunk<'a>> {
739 let mut end = self.start;
740 let mut is_end_of_chunk = false;
741
742 while let Some((idx, c)) = chars.next() {
743 end = self.start + idx;
744
745 if c.is_ascii_digit() {
746 continue;
747 }
748
749 is_end_of_chunk = true;
750 break;
751 }
752
753 let source = if is_end_of_chunk {
754 let value = &self.ident[self.start..end];
755 self.start = end;
756 value
757 } else {
758 let value = &self.ident[self.start..];
759 self.start = self.ident.len();
760 value
761 };
762
763 let zeros = source.chars().take_while(|c| *c == '0').count();
764 let value = source.parse::<usize>().ok()?;
765
766 Some(VersionChunk::Number { value, zeros, source })
767 }
768
769 fn parse_str_chunk(
770 &mut self,
771 mut chars: std::str::CharIndices<'a>,
772 ) -> Option<VersionChunk<'a>> {
773 let mut end = self.start;
774 let mut is_end_of_chunk = false;
775
776 while let Some((idx, c)) = chars.next() {
777 end = self.start + idx;
778
779 if c == '_' {
780 is_end_of_chunk = true;
781 break;
782 }
783
784 if !c.is_ascii_digit() {
785 continue;
786 }
787
788 is_end_of_chunk = true;
789 break;
790 }
791
792 let source = if is_end_of_chunk {
793 let value = &self.ident[self.start..end];
794 self.start = end;
795 value
796 } else {
797 let value = &self.ident[self.start..];
798 self.start = self.ident.len();
799 value
800 };
801
802 Some(VersionChunk::Str(source))
803 }
804 }
805
806 impl<'a> Iterator for VersionChunkIter<'a> {
807 type Item = VersionChunk<'a>;
808
809 fn next(&mut self) -> Option<Self::Item> {
810 let mut chars = self.ident[self.start..].char_indices();
811 let (_, next) = chars.next()?;
812
813 if next == '_' {
814 self.start = self.start + next.len_utf8();
815 return Some(VersionChunk::Underscore);
816 }
817
818 if next.is_ascii_digit() {
819 return self.parse_numeric_chunk(chars);
820 }
821
822 self.parse_str_chunk(chars)
823 }
824 }
825
826 #[derive(Debug, PartialEq, Eq)]
828 enum VersionChunk<'a> {
829 Underscore,
831 Str(&'a str),
833 Number { value: usize, zeros: usize, source: &'a str },
835 }
836
837 #[derive(Debug, PartialEq, Eq)]
839 enum MoreLeadingZeros {
840 Left,
841 Right,
842 Equal,
843 }
844
845 pub(super) fn version_sort(a: &str, b: &str) -> Ordering {
846 let iter_a = VersionChunkIter::new(a);
847 let iter_b = VersionChunkIter::new(b);
848 let mut more_leading_zeros = MoreLeadingZeros::Equal;
849
850 for either_or_both in iter_a.zip_longest(iter_b) {
851 match either_or_both {
852 EitherOrBoth::Left(_) => return std::cmp::Ordering::Greater,
853 EitherOrBoth::Right(_) => return std::cmp::Ordering::Less,
854 EitherOrBoth::Both(a, b) => match (a, b) {
855 (VersionChunk::Underscore, VersionChunk::Underscore) => {
856 continue;
857 }
858 (VersionChunk::Underscore, _) => return std::cmp::Ordering::Less,
859 (_, VersionChunk::Underscore) => return std::cmp::Ordering::Greater,
860 (VersionChunk::Str(ca), VersionChunk::Str(cb))
861 | (VersionChunk::Str(ca), VersionChunk::Number { source: cb, .. })
862 | (VersionChunk::Number { source: ca, .. }, VersionChunk::Str(cb)) => {
863 match ca.cmp(&cb) {
864 std::cmp::Ordering::Equal => {
865 continue;
866 }
867 order @ _ => return order,
868 }
869 }
870 (
871 VersionChunk::Number { value: va, zeros: lza, .. },
872 VersionChunk::Number { value: vb, zeros: lzb, .. },
873 ) => match va.cmp(&vb) {
874 std::cmp::Ordering::Equal => {
875 if lza == lzb {
876 continue;
877 }
878
879 if more_leading_zeros == MoreLeadingZeros::Equal && lza > lzb {
880 more_leading_zeros = MoreLeadingZeros::Left;
881 } else if more_leading_zeros == MoreLeadingZeros::Equal && lza < lzb {
882 more_leading_zeros = MoreLeadingZeros::Right;
883 }
884 continue;
885 }
886 order @ _ => return order,
887 },
888 },
889 }
890 }
891
892 match more_leading_zeros {
893 MoreLeadingZeros::Equal => std::cmp::Ordering::Equal,
894 MoreLeadingZeros::Left => std::cmp::Ordering::Less,
895 MoreLeadingZeros::Right => std::cmp::Ordering::Greater,
896 }
897 }
898}