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
259pub fn try_normalize_use_tree_mut(
260 use_tree: &ast::UseTree,
261 style: NormalizationStyle,
262) -> Option<()> {
263 if style == NormalizationStyle::One {
264 let mut modified = false;
265 modified |= use_tree.wrap_in_tree_list().is_some();
266 modified |= recursive_normalize(use_tree, style).is_some();
267 if !modified {
268 return None;
270 }
271 } else {
272 recursive_normalize(use_tree, NormalizationStyle::Default)?;
273 }
274 Some(())
275}
276
277fn recursive_normalize(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
279 let use_tree_list = use_tree.use_tree_list()?;
280 let merge_subtree_into_parent_tree = |single_subtree: &ast::UseTree| {
281 let subtree_is_only_self = single_subtree.path().as_ref().is_some_and(path_is_self);
282
283 let merged_path = match (use_tree.path(), single_subtree.path()) {
284 _ if subtree_is_only_self => None,
288
289 (None, None) => None,
290 (Some(outer), None) => Some(outer),
291 (None, Some(inner)) => Some(inner),
292 (Some(outer), Some(inner)) => Some(make::path_concat(outer, inner).clone_for_update()),
293 };
294
295 if merged_path.is_some()
296 || single_subtree.use_tree_list().is_some()
297 || single_subtree.star_token().is_some()
298 {
299 ted::remove_all_iter(use_tree.syntax().children_with_tokens());
300 if let Some(path) = merged_path {
301 ted::insert_raw(Position::first_child_of(use_tree.syntax()), path.syntax());
302 if single_subtree.use_tree_list().is_some() || single_subtree.star_token().is_some()
303 {
304 ted::insert_raw(
305 Position::last_child_of(use_tree.syntax()),
306 make::token(T![::]),
307 );
308 }
309 }
310 if let Some(inner_use_tree_list) = single_subtree.use_tree_list() {
311 ted::insert_raw(
312 Position::last_child_of(use_tree.syntax()),
313 inner_use_tree_list.syntax(),
314 );
315 } else if single_subtree.star_token().is_some() {
316 ted::insert_raw(Position::last_child_of(use_tree.syntax()), make::token(T![*]));
317 } else if let Some(rename) = single_subtree.rename() {
318 ted::insert_raw(
319 Position::last_child_of(use_tree.syntax()),
320 make::tokens::single_space(),
321 );
322 ted::insert_raw(Position::last_child_of(use_tree.syntax()), rename.syntax());
323 }
324 Some(())
325 } else {
326 None
328 }
329 };
330 let one_style_tree_list = |subtree: &ast::UseTree| match (
331 subtree.path().is_none() && subtree.star_token().is_none() && subtree.rename().is_none(),
332 subtree.use_tree_list(),
333 ) {
334 (true, tree_list) => tree_list,
335 _ => None,
336 };
337 let add_element_to_list = |elem: SyntaxElement, elements: &mut Vec<SyntaxElement>| {
338 if !elements.is_empty() {
339 elements.push(make::token(T![,]).into());
340 elements.push(make::tokens::single_space().into());
341 }
342 elements.push(elem);
343 };
344 if let Some((single_subtree,)) = use_tree_list.use_trees().collect_tuple() {
345 if style == NormalizationStyle::One {
346 recursive_normalize(&single_subtree, NormalizationStyle::Default)?;
348 } else {
349 merge_subtree_into_parent_tree(&single_subtree)?;
352 recursive_normalize(use_tree, style);
353 }
354 } else {
355 let mut modified = false;
357
358 for subtree in use_tree_list.use_trees() {
360 if let Some(one_tree_list) = one_style_tree_list(&subtree) {
361 let mut elements = Vec::new();
362 let mut one_tree_list_iter = one_tree_list.use_trees();
363 let mut prev_skipped = Vec::new();
364 loop {
365 let mut prev_skipped_iter = prev_skipped.into_iter();
366 let mut curr_skipped = Vec::new();
367
368 while let Some(sub_sub_tree) =
369 one_tree_list_iter.next().or(prev_skipped_iter.next())
370 {
371 if let Some(sub_one_tree_list) = one_style_tree_list(&sub_sub_tree) {
372 curr_skipped.extend(sub_one_tree_list.use_trees());
373 } else {
374 modified |=
375 recursive_normalize(&sub_sub_tree, NormalizationStyle::Default)
376 .is_some();
377 add_element_to_list(
378 sub_sub_tree.syntax().clone().into(),
379 &mut elements,
380 );
381 }
382 }
383
384 if curr_skipped.is_empty() {
385 break;
387 }
388 prev_skipped = curr_skipped;
389 }
390
391 if elements.is_empty() {
394 subtree.remove();
395 } else {
396 ted::replace_with_many(subtree.syntax(), elements);
397 }
398 let _ = modified;
400 modified = true;
401 } else {
402 modified |= recursive_normalize(&subtree, NormalizationStyle::Default).is_some();
403 }
404 }
405
406 let mut tree_list_iter = use_tree_list.use_trees();
408 let mut anchor = tree_list_iter.next()?;
409 let mut prev_skipped = Vec::new();
410 loop {
411 let mut has_merged = false;
412 let mut prev_skipped_iter = prev_skipped.into_iter();
413 let mut next_anchor = None;
414 let mut curr_skipped = Vec::new();
415
416 while let Some(candidate) = tree_list_iter.next().or(prev_skipped_iter.next()) {
417 let result = try_merge_trees_mut(&anchor, &candidate, MergeBehavior::Crate);
418 if result.is_some() {
419 candidate.remove();
421 has_merged = true;
422 } else if next_anchor.is_none() {
423 next_anchor = Some(candidate);
424 } else {
425 curr_skipped.push(candidate);
426 }
427 }
428
429 if has_merged {
430 recursive_normalize(&anchor, NormalizationStyle::Default);
432 modified = true;
433 }
434
435 let (Some(next_anchor), true) = (next_anchor, !curr_skipped.is_empty()) else {
436 break;
438 };
439
440 anchor = next_anchor;
442 prev_skipped = curr_skipped;
443 }
444
445 let mut subtrees: Vec<_> = use_tree_list.use_trees().collect();
446 if subtrees.len() == 1 && style != NormalizationStyle::One {
449 modified |= merge_subtree_into_parent_tree(&subtrees[0]).is_some();
450 }
451 if subtrees.len() > 1 {
453 let mut did_sort = false;
454 subtrees.sort_unstable_by(|a, b| {
455 let order = use_tree_cmp_bin_search(a, b);
456 if !did_sort && order == Ordering::Less {
457 did_sort = true;
458 }
459 order
460 });
461 if did_sort {
462 let start = use_tree_list
463 .l_curly_token()
464 .and_then(|l_curly| algo::non_trivia_sibling(l_curly.into(), Direction::Next))
465 .filter(|it| it.kind() != T!['}']);
466 let end = use_tree_list
467 .r_curly_token()
468 .and_then(|r_curly| algo::non_trivia_sibling(r_curly.into(), Direction::Prev))
469 .filter(|it| it.kind() != T!['{']);
470 if let Some((start, end)) = start.zip(end) {
471 let mut elements = Vec::new();
473 for subtree in subtrees {
474 add_element_to_list(subtree.syntax().clone().into(), &mut elements);
475 }
476 ted::replace_all(start..=end, elements);
477 } else {
478 let new_use_tree_list = make::use_tree_list(subtrees).clone_for_update();
479 ted::replace(use_tree_list.syntax(), new_use_tree_list.syntax());
480 }
481 modified = true;
482 }
483 }
484
485 if !modified {
486 return None;
488 }
489 }
490 Some(())
491}
492
493pub fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
495 let mut res = None;
496 let mut lhs_curr = lhs.first_qualifier_or_self();
497 let mut rhs_curr = rhs.first_qualifier_or_self();
498 loop {
499 match (lhs_curr.segment(), rhs_curr.segment()) {
500 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
501 _ => break res,
502 }
503 res = Some((lhs_curr.clone(), rhs_curr.clone()));
504
505 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
506 Some((lhs, rhs)) => {
507 lhs_curr = lhs;
508 rhs_curr = rhs;
509 }
510 _ => break res,
511 }
512 }
513}
514
515fn use_tree_cmp_bin_search(lhs: &ast::UseTree, rhs: &ast::UseTree) -> Ordering {
517 let lhs_is_simple_path = lhs.is_simple_path() && lhs.rename().is_none();
518 let rhs_is_simple_path = rhs.is_simple_path() && rhs.rename().is_none();
519 match (
520 lhs.path().as_ref().and_then(ast::Path::first_segment),
521 rhs.path().as_ref().and_then(ast::Path::first_segment),
522 ) {
523 (None, None) => match (lhs_is_simple_path, rhs_is_simple_path) {
524 (true, true) => Ordering::Equal,
525 (true, false) => Ordering::Less,
526 (false, true) => Ordering::Greater,
527 (false, false) => use_tree_cmp_by_tree_list_glob_or_alias(lhs, rhs, false),
528 },
529 (Some(_), None) if !rhs_is_simple_path => Ordering::Less,
530 (Some(_), None) => Ordering::Greater,
531 (None, Some(_)) if !lhs_is_simple_path => Ordering::Greater,
532 (None, Some(_)) => Ordering::Less,
533 (Some(a), Some(b)) => path_segment_cmp(&a, &b),
534 }
535}
536
537pub(super) fn use_tree_cmp(a: &ast::UseTree, b: &ast::UseTree) -> Ordering {
545 let a_is_simple_path = a.is_simple_path() && a.rename().is_none();
546 let b_is_simple_path = b.is_simple_path() && b.rename().is_none();
547 match (a.path(), b.path()) {
548 (None, None) => match (a_is_simple_path, b_is_simple_path) {
549 (true, true) => Ordering::Equal,
550 (true, false) => Ordering::Less,
551 (false, true) => Ordering::Greater,
552 (false, false) => use_tree_cmp_by_tree_list_glob_or_alias(a, b, true),
553 },
554 (Some(_), None) if !b_is_simple_path => Ordering::Less,
555 (Some(_), None) => Ordering::Greater,
556 (None, Some(_)) if !a_is_simple_path => Ordering::Greater,
557 (None, Some(_)) => Ordering::Less,
558 (Some(a_path), Some(b_path)) => {
559 a_path
562 .segments()
563 .zip_longest(b_path.segments())
564 .find_map(|zipped| match zipped {
565 EitherOrBoth::Both(a_segment, b_segment) => {
566 match path_segment_cmp(&a_segment, &b_segment) {
567 Ordering::Equal => None,
568 ord => Some(ord),
569 }
570 }
571 EitherOrBoth::Left(_) if b_is_simple_path => Some(Ordering::Greater),
572 EitherOrBoth::Left(_) => Some(Ordering::Less),
573 EitherOrBoth::Right(_) if a_is_simple_path => Some(Ordering::Less),
574 EitherOrBoth::Right(_) => Some(Ordering::Greater),
575 })
576 .unwrap_or_else(|| use_tree_cmp_by_tree_list_glob_or_alias(a, b, true))
577 }
578 }
579}
580
581fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
582 match (a.kind(), b.kind()) {
583 (None, None) => Ordering::Equal,
584 (Some(_), None) => Ordering::Greater,
585 (None, Some(_)) => Ordering::Less,
586 (Some(PathSegmentKind::SelfKw), Some(PathSegmentKind::SelfKw)) => Ordering::Equal,
588 (Some(PathSegmentKind::SelfKw), _) => Ordering::Less,
589 (_, Some(PathSegmentKind::SelfKw)) => Ordering::Greater,
590 (Some(PathSegmentKind::SuperKw), Some(PathSegmentKind::SuperKw)) => Ordering::Equal,
592 (Some(PathSegmentKind::SuperKw), _) => Ordering::Less,
593 (_, Some(PathSegmentKind::SuperKw)) => Ordering::Greater,
594 (Some(PathSegmentKind::CrateKw), Some(PathSegmentKind::CrateKw)) => Ordering::Equal,
596 (Some(PathSegmentKind::CrateKw), _) => Ordering::Less,
597 (_, Some(PathSegmentKind::CrateKw)) => Ordering::Greater,
598 _ => {
600 match (
601 a.name_ref().as_ref().map(ast::NameRef::text),
602 b.name_ref().as_ref().map(ast::NameRef::text),
603 ) {
604 (None, None) => Ordering::Equal,
605 (Some(_), None) => Ordering::Greater,
606 (None, Some(_)) => Ordering::Less,
607 (Some(a_name), Some(b_name)) => {
608 let a_text = a_name.as_str().trim_start_matches("r#");
609 let b_text = b_name.as_str().trim_start_matches("r#");
610 version_sort::version_sort(a_text, b_text)
611 }
612 }
613 }
614 }
615}
616
617fn use_tree_cmp_by_tree_list_glob_or_alias(
625 a: &ast::UseTree,
626 b: &ast::UseTree,
627 strict: bool,
628) -> Ordering {
629 let cmp_by_glob_or_alias = || match (a.star_token().is_some(), b.star_token().is_some()) {
630 (true, false) => Ordering::Greater,
631 (false, true) => Ordering::Less,
632 _ => match (a.rename(), b.rename()) {
633 (None, None) => Ordering::Equal,
634 (Some(_), None) => Ordering::Greater,
635 (None, Some(_)) => Ordering::Less,
636 (Some(a_rename), Some(b_rename)) => a_rename
637 .name()
638 .as_ref()
639 .map(ast::Name::text)
640 .as_ref()
641 .map_or("_", |a_name| a_name.as_str().trim_start_matches("r#"))
642 .cmp(
643 b_rename
644 .name()
645 .as_ref()
646 .map(ast::Name::text)
647 .as_ref()
648 .map_or("_", |b_name| b_name.as_str().trim_start_matches("r#")),
649 ),
650 },
651 };
652
653 match (a.use_tree_list(), b.use_tree_list()) {
654 (Some(_), None) => Ordering::Greater,
655 (None, Some(_)) => Ordering::Less,
656 (Some(a_list), Some(b_list)) if strict => a_list
657 .use_trees()
658 .zip_longest(b_list.use_trees())
659 .find_map(|zipped| match zipped {
660 EitherOrBoth::Both(a_tree, b_tree) => match use_tree_cmp(&a_tree, &b_tree) {
661 Ordering::Equal => None,
662 ord => Some(ord),
663 },
664 EitherOrBoth::Left(_) => Some(Ordering::Greater),
665 EitherOrBoth::Right(_) => Some(Ordering::Less),
666 })
667 .unwrap_or_else(cmp_by_glob_or_alias),
668 _ => cmp_by_glob_or_alias(),
669 }
670}
671
672pub fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
673 match (vis0, vis1) {
674 (None, None) => true,
675 (Some(vis0), Some(vis1)) => vis_eq(&vis0, &vis1),
676 _ => false,
677 }
678}
679
680pub fn eq_attrs(
681 attrs0: impl Iterator<Item = ast::Attr>,
682 attrs1: impl Iterator<Item = ast::Attr>,
683) -> bool {
684 let mut attrs0: Vec<_> = attrs0.map(|attr| attr.syntax().text().to_smolstr()).collect();
685 let mut attrs1: Vec<_> = attrs1.map(|attr| attr.syntax().text().to_smolstr()).collect();
686 attrs0.sort_unstable();
687 attrs1.sort_unstable();
688
689 attrs0 == attrs1
690}
691
692fn path_is_self(path: &ast::Path) -> bool {
693 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
694}
695
696fn path_len(path: ast::Path) -> usize {
697 path.segments().count()
698}
699
700fn get_single_subtree(use_tree: &ast::UseTree) -> Option<ast::UseTree> {
701 use_tree
702 .use_tree_list()
703 .and_then(|tree_list| tree_list.use_trees().collect_tuple())
704 .map(|(single_subtree,)| single_subtree)
705}
706
707fn remove_subtree_if_only_self(use_tree: &ast::UseTree) {
708 let Some(single_subtree) = get_single_subtree(use_tree) else { return };
709 match (use_tree.path(), single_subtree.path()) {
710 (Some(_), Some(inner)) if path_is_self(&inner) => {
711 ted::remove_all_iter(single_subtree.syntax().children_with_tokens());
712 }
713 _ => (),
714 }
715}
716
717mod version_sort {
720 #![allow(clippy::all)]
723
724 use std::cmp::Ordering;
725
726 use itertools::{EitherOrBoth, Itertools};
727
728 struct VersionChunkIter<'a> {
729 ident: &'a str,
730 start: usize,
731 }
732
733 impl<'a> VersionChunkIter<'a> {
734 pub(crate) fn new(ident: &'a str) -> Self {
735 Self { ident, start: 0 }
736 }
737
738 fn parse_numeric_chunk(
739 &mut self,
740 mut chars: std::str::CharIndices<'a>,
741 ) -> Option<VersionChunk<'a>> {
742 let mut end = self.start;
743 let mut is_end_of_chunk = false;
744
745 while let Some((idx, c)) = chars.next() {
746 end = self.start + idx;
747
748 if c.is_ascii_digit() {
749 continue;
750 }
751
752 is_end_of_chunk = true;
753 break;
754 }
755
756 let source = if is_end_of_chunk {
757 let value = &self.ident[self.start..end];
758 self.start = end;
759 value
760 } else {
761 let value = &self.ident[self.start..];
762 self.start = self.ident.len();
763 value
764 };
765
766 let zeros = source.chars().take_while(|c| *c == '0').count();
767 let value = source.parse::<usize>().ok()?;
768
769 Some(VersionChunk::Number { value, zeros, source })
770 }
771
772 fn parse_str_chunk(
773 &mut self,
774 mut chars: std::str::CharIndices<'a>,
775 ) -> Option<VersionChunk<'a>> {
776 let mut end = self.start;
777 let mut is_end_of_chunk = false;
778
779 while let Some((idx, c)) = chars.next() {
780 end = self.start + idx;
781
782 if c == '_' {
783 is_end_of_chunk = true;
784 break;
785 }
786
787 if !c.is_ascii_digit() {
788 continue;
789 }
790
791 is_end_of_chunk = true;
792 break;
793 }
794
795 let source = if is_end_of_chunk {
796 let value = &self.ident[self.start..end];
797 self.start = end;
798 value
799 } else {
800 let value = &self.ident[self.start..];
801 self.start = self.ident.len();
802 value
803 };
804
805 Some(VersionChunk::Str(source))
806 }
807 }
808
809 impl<'a> Iterator for VersionChunkIter<'a> {
810 type Item = VersionChunk<'a>;
811
812 fn next(&mut self) -> Option<Self::Item> {
813 let mut chars = self.ident[self.start..].char_indices();
814 let (_, next) = chars.next()?;
815
816 if next == '_' {
817 self.start = self.start + next.len_utf8();
818 return Some(VersionChunk::Underscore);
819 }
820
821 if next.is_ascii_digit() {
822 return self.parse_numeric_chunk(chars);
823 }
824
825 self.parse_str_chunk(chars)
826 }
827 }
828
829 #[derive(Debug, PartialEq, Eq)]
831 enum VersionChunk<'a> {
832 Underscore,
834 Str(&'a str),
836 Number { value: usize, zeros: usize, source: &'a str },
838 }
839
840 #[derive(Debug, PartialEq, Eq)]
842 enum MoreLeadingZeros {
843 Left,
844 Right,
845 Equal,
846 }
847
848 pub(super) fn version_sort(a: &str, b: &str) -> Ordering {
849 let iter_a = VersionChunkIter::new(a);
850 let iter_b = VersionChunkIter::new(b);
851 let mut more_leading_zeros = MoreLeadingZeros::Equal;
852
853 for either_or_both in iter_a.zip_longest(iter_b) {
854 match either_or_both {
855 EitherOrBoth::Left(_) => return std::cmp::Ordering::Greater,
856 EitherOrBoth::Right(_) => return std::cmp::Ordering::Less,
857 EitherOrBoth::Both(a, b) => match (a, b) {
858 (VersionChunk::Underscore, VersionChunk::Underscore) => {
859 continue;
860 }
861 (VersionChunk::Underscore, _) => return std::cmp::Ordering::Less,
862 (_, VersionChunk::Underscore) => return std::cmp::Ordering::Greater,
863 (VersionChunk::Str(ca), VersionChunk::Str(cb))
864 | (VersionChunk::Str(ca), VersionChunk::Number { source: cb, .. })
865 | (VersionChunk::Number { source: ca, .. }, VersionChunk::Str(cb)) => {
866 match ca.cmp(&cb) {
867 std::cmp::Ordering::Equal => {
868 continue;
869 }
870 order @ _ => return order,
871 }
872 }
873 (
874 VersionChunk::Number { value: va, zeros: lza, .. },
875 VersionChunk::Number { value: vb, zeros: lzb, .. },
876 ) => match va.cmp(&vb) {
877 std::cmp::Ordering::Equal => {
878 if lza == lzb {
879 continue;
880 }
881
882 if more_leading_zeros == MoreLeadingZeros::Equal && lza > lzb {
883 more_leading_zeros = MoreLeadingZeros::Left;
884 } else if more_leading_zeros == MoreLeadingZeros::Equal && lza < lzb {
885 more_leading_zeros = MoreLeadingZeros::Right;
886 }
887 continue;
888 }
889 order @ _ => return order,
890 },
891 },
892 }
893 }
894
895 match more_leading_zeros {
896 MoreLeadingZeros::Equal => std::cmp::Ordering::Equal,
897 MoreLeadingZeros::Left => std::cmp::Ordering::Less,
898 MoreLeadingZeros::Right => std::cmp::Ordering::Greater,
899 }
900 }
901}