@@ -3,7 +3,6 @@ use std::cmp::Ordering;
3
3
4
4
use itertools:: { EitherOrBoth , Itertools } ;
5
5
use parser:: T ;
6
- use stdx:: is_upper_snake_case;
7
6
use syntax:: {
8
7
Direction , SyntaxElement , algo,
9
8
ast:: {
@@ -543,12 +542,13 @@ fn use_tree_cmp_bin_search(lhs: &ast::UseTree, rhs: &ast::UseTree) -> Ordering {
543
542
}
544
543
}
545
544
546
- /// Orders use trees following `rustfmt`'s algorithm for ordering imports, which is `self`, `super`
547
- /// and `crate` first, then identifier imports with lowercase ones first and upper snake case
548
- /// (e.g. UPPER_SNAKE_CASE) ones last, then glob imports, and at last list imports.
545
+ /// Orders use trees following `rustfmt`'s version sorting algorithm for ordering imports.
549
546
///
550
- /// Example: `foo::{self, baz, foo, Baz, Qux, FOO_BAZ, *, {Bar}}`
551
- /// Ref: <https://github.com/rust-lang/rustfmt/blob/6356fca675bd756d71f5c123cd053d17b16c573e/src/imports.rs#L83-L86>.
547
+ /// Example: `foo::{self, Baz, FOO_BAZ, Qux, baz, foo, *, {Bar}}`
548
+ ///
549
+ /// Ref:
550
+ /// - <https://doc.rust-lang.org/style-guide/index.html#sorting>
551
+ /// - <https://doc.rust-lang.org/edition-guide/rust-2024/rustfmt.html>
552
552
pub ( super ) fn use_tree_cmp ( a : & ast:: UseTree , b : & ast:: UseTree ) -> Ordering {
553
553
let a_is_simple_path = a. is_simple_path ( ) && a. rename ( ) . is_none ( ) ;
554
554
let b_is_simple_path = b. is_simple_path ( ) && b. rename ( ) . is_none ( ) ;
@@ -613,26 +613,9 @@ fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
613
613
( Some ( _) , None ) => Ordering :: Greater ,
614
614
( None , Some ( _) ) => Ordering :: Less ,
615
615
( Some ( a_name) , Some ( b_name) ) => {
616
- // snake_case < UpperCamelCase < UPPER_SNAKE_CASE
617
616
let a_text = a_name. as_str ( ) . trim_start_matches ( "r#" ) ;
618
617
let b_text = b_name. as_str ( ) . trim_start_matches ( "r#" ) ;
619
- if a_text. starts_with ( char:: is_lowercase)
620
- && b_text. starts_with ( char:: is_uppercase)
621
- {
622
- return Ordering :: Less ;
623
- }
624
- if a_text. starts_with ( char:: is_uppercase)
625
- && b_text. starts_with ( char:: is_lowercase)
626
- {
627
- return Ordering :: Greater ;
628
- }
629
- if !is_upper_snake_case ( a_text) && is_upper_snake_case ( b_text) {
630
- return Ordering :: Less ;
631
- }
632
- if is_upper_snake_case ( a_text) && !is_upper_snake_case ( b_text) {
633
- return Ordering :: Greater ;
634
- }
635
- a_text. cmp ( b_text)
618
+ version_sort:: version_sort ( a_text, b_text)
636
619
}
637
620
}
638
621
}
@@ -740,3 +723,189 @@ fn remove_subtree_if_only_self(use_tree: &ast::UseTree) {
740
723
_ => ( ) ,
741
724
}
742
725
}
726
+
727
+ // Taken from rustfmt
728
+ // https://github.com/rust-lang/rustfmt/blob/0332da01486508710f2a542111e40513bfb215aa/src/sort.rs
729
+ mod version_sort {
730
+ // Original rustfmt code contains some clippy lints.
731
+ // Suppress them to minimize changes from upstream.
732
+ #![ allow( clippy:: all) ]
733
+
734
+ use std:: cmp:: Ordering ;
735
+
736
+ use itertools:: { EitherOrBoth , Itertools } ;
737
+
738
+ struct VersionChunkIter < ' a > {
739
+ ident : & ' a str ,
740
+ start : usize ,
741
+ }
742
+
743
+ impl < ' a > VersionChunkIter < ' a > {
744
+ pub ( crate ) fn new ( ident : & ' a str ) -> Self {
745
+ Self { ident, start : 0 }
746
+ }
747
+
748
+ fn parse_numeric_chunk (
749
+ & mut self ,
750
+ mut chars : std:: str:: CharIndices < ' a > ,
751
+ ) -> Option < VersionChunk < ' a > > {
752
+ let mut end = self . start ;
753
+ let mut is_end_of_chunk = false ;
754
+
755
+ while let Some ( ( idx, c) ) = chars. next ( ) {
756
+ end = self . start + idx;
757
+
758
+ if c. is_ascii_digit ( ) {
759
+ continue ;
760
+ }
761
+
762
+ is_end_of_chunk = true ;
763
+ break ;
764
+ }
765
+
766
+ let source = if is_end_of_chunk {
767
+ let value = & self . ident [ self . start ..end] ;
768
+ self . start = end;
769
+ value
770
+ } else {
771
+ let value = & self . ident [ self . start ..] ;
772
+ self . start = self . ident . len ( ) ;
773
+ value
774
+ } ;
775
+
776
+ let zeros = source. chars ( ) . take_while ( |c| * c == '0' ) . count ( ) ;
777
+ let value = source. parse :: < usize > ( ) . ok ( ) ?;
778
+
779
+ Some ( VersionChunk :: Number { value, zeros, source } )
780
+ }
781
+
782
+ fn parse_str_chunk (
783
+ & mut self ,
784
+ mut chars : std:: str:: CharIndices < ' a > ,
785
+ ) -> Option < VersionChunk < ' a > > {
786
+ let mut end = self . start ;
787
+ let mut is_end_of_chunk = false ;
788
+
789
+ while let Some ( ( idx, c) ) = chars. next ( ) {
790
+ end = self . start + idx;
791
+
792
+ if c == '_' {
793
+ is_end_of_chunk = true ;
794
+ break ;
795
+ }
796
+
797
+ if !c. is_ascii_digit ( ) {
798
+ continue ;
799
+ }
800
+
801
+ is_end_of_chunk = true ;
802
+ break ;
803
+ }
804
+
805
+ let source = if is_end_of_chunk {
806
+ let value = & self . ident [ self . start ..end] ;
807
+ self . start = end;
808
+ value
809
+ } else {
810
+ let value = & self . ident [ self . start ..] ;
811
+ self . start = self . ident . len ( ) ;
812
+ value
813
+ } ;
814
+
815
+ Some ( VersionChunk :: Str ( source) )
816
+ }
817
+ }
818
+
819
+ impl < ' a > Iterator for VersionChunkIter < ' a > {
820
+ type Item = VersionChunk < ' a > ;
821
+
822
+ fn next ( & mut self ) -> Option < Self :: Item > {
823
+ let mut chars = self . ident [ self . start ..] . char_indices ( ) ;
824
+ let ( _, next) = chars. next ( ) ?;
825
+
826
+ if next == '_' {
827
+ self . start = self . start + next. len_utf8 ( ) ;
828
+ return Some ( VersionChunk :: Underscore ) ;
829
+ }
830
+
831
+ if next. is_ascii_digit ( ) {
832
+ return self . parse_numeric_chunk ( chars) ;
833
+ }
834
+
835
+ self . parse_str_chunk ( chars)
836
+ }
837
+ }
838
+
839
+ /// Represents a chunk in the version-sort algorithm
840
+ #[ derive( Debug , PartialEq , Eq ) ]
841
+ enum VersionChunk < ' a > {
842
+ /// A single `_` in an identifier. Underscores are sorted before all other characters.
843
+ Underscore ,
844
+ /// A &str chunk in the version sort.
845
+ Str ( & ' a str ) ,
846
+ /// A numeric chunk in the version sort. Keeps track of the numeric value and leading zeros.
847
+ Number { value : usize , zeros : usize , source : & ' a str } ,
848
+ }
849
+
850
+ /// Determine which side of the version-sort comparison had more leading zeros.
851
+ #[ derive( Debug , PartialEq , Eq ) ]
852
+ enum MoreLeadingZeros {
853
+ Left ,
854
+ Right ,
855
+ Equal ,
856
+ }
857
+
858
+ pub ( super ) fn version_sort ( a : & str , b : & str ) -> Ordering {
859
+ let iter_a = VersionChunkIter :: new ( a) ;
860
+ let iter_b = VersionChunkIter :: new ( b) ;
861
+ let mut more_leading_zeros = MoreLeadingZeros :: Equal ;
862
+
863
+ for either_or_both in iter_a. zip_longest ( iter_b) {
864
+ match either_or_both {
865
+ EitherOrBoth :: Left ( _) => return std:: cmp:: Ordering :: Greater ,
866
+ EitherOrBoth :: Right ( _) => return std:: cmp:: Ordering :: Less ,
867
+ EitherOrBoth :: Both ( a, b) => match ( a, b) {
868
+ ( VersionChunk :: Underscore , VersionChunk :: Underscore ) => {
869
+ continue ;
870
+ }
871
+ ( VersionChunk :: Underscore , _) => return std:: cmp:: Ordering :: Less ,
872
+ ( _, VersionChunk :: Underscore ) => return std:: cmp:: Ordering :: Greater ,
873
+ ( VersionChunk :: Str ( ca) , VersionChunk :: Str ( cb) )
874
+ | ( VersionChunk :: Str ( ca) , VersionChunk :: Number { source : cb, .. } )
875
+ | ( VersionChunk :: Number { source : ca, .. } , VersionChunk :: Str ( cb) ) => {
876
+ match ca. cmp ( & cb) {
877
+ std:: cmp:: Ordering :: Equal => {
878
+ continue ;
879
+ }
880
+ order @ _ => return order,
881
+ }
882
+ }
883
+ (
884
+ VersionChunk :: Number { value : va, zeros : lza, .. } ,
885
+ VersionChunk :: Number { value : vb, zeros : lzb, .. } ,
886
+ ) => match va. cmp ( & vb) {
887
+ std:: cmp:: Ordering :: Equal => {
888
+ if lza == lzb {
889
+ continue ;
890
+ }
891
+
892
+ if more_leading_zeros == MoreLeadingZeros :: Equal && lza > lzb {
893
+ more_leading_zeros = MoreLeadingZeros :: Left ;
894
+ } else if more_leading_zeros == MoreLeadingZeros :: Equal && lza < lzb {
895
+ more_leading_zeros = MoreLeadingZeros :: Right ;
896
+ }
897
+ continue ;
898
+ }
899
+ order @ _ => return order,
900
+ } ,
901
+ } ,
902
+ }
903
+ }
904
+
905
+ match more_leading_zeros {
906
+ MoreLeadingZeros :: Equal => std:: cmp:: Ordering :: Equal ,
907
+ MoreLeadingZeros :: Left => std:: cmp:: Ordering :: Less ,
908
+ MoreLeadingZeros :: Right => std:: cmp:: Ordering :: Greater ,
909
+ }
910
+ }
911
+ }
0 commit comments