@@ -3,8 +3,8 @@ use std::cmp::Ordering;
3
3
4
4
use itertools:: { EitherOrBoth , Itertools } ;
5
5
use syntax:: {
6
- ast:: { self , AstNode , HasAttrs , HasVisibility , PathSegmentKind } ,
7
- ted,
6
+ ast:: { self , AstNode , HasAttrs , HasName , HasVisibility , PathSegmentKind } ,
7
+ ted, TokenText ,
8
8
} ;
9
9
10
10
use crate :: syntax_helpers:: node_ext:: vis_eq;
@@ -97,7 +97,7 @@ fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior)
97
97
// same as a `filter` op).
98
98
. map ( |tree| merge. is_tree_allowed ( & tree) . then_some ( tree) )
99
99
. collect :: < Option < _ > > ( ) ?;
100
- use_trees. sort_unstable_by ( |a, b| path_cmp_for_sort ( a . path ( ) , b. path ( ) ) ) ;
100
+ use_trees. sort_unstable_by ( |a, b| use_tree_cmp ( a , b) ) ;
101
101
for rhs_t in rhs. use_tree_list ( ) . into_iter ( ) . flat_map ( |list| list. use_trees ( ) ) {
102
102
if !merge. is_tree_allowed ( & rhs_t) {
103
103
return None ;
@@ -190,25 +190,6 @@ pub fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast
190
190
}
191
191
}
192
192
193
- /// Orders paths in the following way:
194
- /// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
195
- // FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
196
- // which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
197
- // Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
198
- fn path_cmp_for_sort ( a : Option < ast:: Path > , b : Option < ast:: Path > ) -> Ordering {
199
- match ( a, b) {
200
- ( None , None ) => Ordering :: Equal ,
201
- ( None , Some ( _) ) => Ordering :: Less ,
202
- ( Some ( _) , None ) => Ordering :: Greater ,
203
- ( Some ( ref a) , Some ( ref b) ) => match ( path_is_self ( a) , path_is_self ( b) ) {
204
- ( true , true ) => Ordering :: Equal ,
205
- ( true , false ) => Ordering :: Less ,
206
- ( false , true ) => Ordering :: Greater ,
207
- ( false , false ) => path_cmp_short ( a, b) ,
208
- } ,
209
- }
210
- }
211
-
212
193
/// Path comparison func for binary searching for merging.
213
194
fn path_cmp_bin_search ( lhs : Option < ast:: Path > , rhs : Option < & ast:: Path > ) -> Ordering {
214
195
match ( lhs. as_ref ( ) . and_then ( ast:: Path :: first_segment) , rhs. and_then ( ast:: Path :: first_segment) )
@@ -220,59 +201,141 @@ fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<&ast::Path>) -> Order
220
201
}
221
202
}
222
203
223
- /// Short circuiting comparison, if both paths are equal until one of them ends they are considered
224
- /// equal
225
- fn path_cmp_short ( a : & ast:: Path , b : & ast:: Path ) -> Ordering {
226
- let a = a. segments ( ) ;
227
- let b = b. segments ( ) ;
228
- // cmp_by would be useful for us here but that is currently unstable
229
- // cmp doesn't work due the lifetimes on text's return type
230
- a. zip ( b)
231
- . find_map ( |( a, b) | match path_segment_cmp ( & a, & b) {
232
- Ordering :: Equal => None ,
233
- ord => Some ( ord) ,
234
- } )
235
- . unwrap_or ( Ordering :: Equal )
236
- }
237
-
238
- /// Compares two paths, if one ends earlier than the other the has_tl parameters decide which is
239
- /// greater as a path that has a tree list should be greater, while one that just ends without
240
- /// a tree list should be considered less.
241
- pub ( super ) fn use_tree_path_cmp (
242
- a : & ast:: Path ,
243
- a_has_tl : bool ,
244
- b : & ast:: Path ,
245
- b_has_tl : bool ,
246
- ) -> Ordering {
247
- let a_segments = a. segments ( ) ;
248
- let b_segments = b. segments ( ) ;
249
- // cmp_by would be useful for us here but that is currently unstable
250
- // cmp doesn't work due the lifetimes on text's return type
251
- a_segments
252
- . zip_longest ( b_segments)
253
- . find_map ( |zipped| match zipped {
254
- EitherOrBoth :: Both ( ref a, ref b) => match path_segment_cmp ( a, b) {
255
- Ordering :: Equal => None ,
256
- ord => Some ( ord) ,
257
- } ,
258
- EitherOrBoth :: Left ( _) if !b_has_tl => Some ( Ordering :: Greater ) ,
259
- EitherOrBoth :: Left ( _) => Some ( Ordering :: Less ) ,
260
- EitherOrBoth :: Right ( _) if !a_has_tl => Some ( Ordering :: Less ) ,
261
- EitherOrBoth :: Right ( _) => Some ( Ordering :: Greater ) ,
262
- } )
263
- . unwrap_or ( Ordering :: Equal )
204
+ /// Orders use trees following `rustfmt`'s algorithm for ordering imports, which is `self`, `super` and `crate` first,
205
+ /// then identifier imports with lowercase ones first and upper snake case (e.g. UPPER_SNAKE_CASE) ones last,
206
+ /// then glob imports, and at last list imports.
207
+ ///
208
+ /// Example foo::{self, foo, baz, Baz, Qux, FOO_BAZ, *, {Bar}}
209
+ /// Ref: <https://github.com/rust-lang/rustfmt/blob/6356fca675bd756d71f5c123cd053d17b16c573e/src/imports.rs#L83-L86>.
210
+ pub ( super ) fn use_tree_cmp ( a : & ast:: UseTree , b : & ast:: UseTree ) -> Ordering {
211
+ let tiebreak_by_glob_or_alias = || match ( a. star_token ( ) . is_some ( ) , b. star_token ( ) . is_some ( ) ) {
212
+ ( true , false ) => Ordering :: Greater ,
213
+ ( false , true ) => Ordering :: Less ,
214
+ _ => match ( a. rename ( ) , b. rename ( ) ) {
215
+ ( None , None ) => Ordering :: Equal ,
216
+ ( Some ( _) , None ) => Ordering :: Greater ,
217
+ ( None , Some ( _) ) => Ordering :: Less ,
218
+ ( Some ( a_rename) , Some ( b_rename) ) => a_rename
219
+ . name ( )
220
+ . as_ref ( )
221
+ . map ( ast:: Name :: text)
222
+ . as_ref ( )
223
+ . map_or ( "_" , TokenText :: as_str)
224
+ . cmp (
225
+ b_rename
226
+ . name ( )
227
+ . as_ref ( )
228
+ . map ( ast:: Name :: text)
229
+ . as_ref ( )
230
+ . map_or ( "_" , TokenText :: as_str) ,
231
+ ) ,
232
+ } ,
233
+ } ;
234
+ let tiebreak_by_tree_list_glob_or_alias = || match ( a. use_tree_list ( ) , b. use_tree_list ( ) ) {
235
+ ( None , None ) => tiebreak_by_glob_or_alias ( ) ,
236
+ ( Some ( _) , None ) => Ordering :: Greater ,
237
+ ( None , Some ( _) ) => Ordering :: Less ,
238
+ ( Some ( a_list) , Some ( b_list) ) => a_list
239
+ . use_trees ( )
240
+ . zip_longest ( b_list. use_trees ( ) )
241
+ . find_map ( |zipped| match zipped {
242
+ EitherOrBoth :: Both ( ref a_tree, ref b_tree) => match use_tree_cmp ( a_tree, b_tree) {
243
+ Ordering :: Equal => None ,
244
+ ord => Some ( ord) ,
245
+ } ,
246
+ EitherOrBoth :: Left ( _) => Some ( Ordering :: Greater ) ,
247
+ EitherOrBoth :: Right ( _) => Some ( Ordering :: Less ) ,
248
+ } )
249
+ . unwrap_or_else ( tiebreak_by_glob_or_alias) ,
250
+ } ;
251
+ match ( a. path ( ) , b. path ( ) ) {
252
+ ( None , None ) => match ( a. is_simple_path ( ) , b. is_simple_path ( ) ) {
253
+ ( true , true ) => Ordering :: Equal ,
254
+ ( true , false ) => Ordering :: Less ,
255
+ ( false , true ) => Ordering :: Greater ,
256
+ ( false , false ) => tiebreak_by_tree_list_glob_or_alias ( ) ,
257
+ } ,
258
+ ( Some ( _) , None ) if !b. is_simple_path ( ) => Ordering :: Less ,
259
+ ( Some ( _) , None ) => Ordering :: Greater ,
260
+ ( None , Some ( _) ) if !a. is_simple_path ( ) => Ordering :: Greater ,
261
+ ( None , Some ( _) ) => Ordering :: Less ,
262
+ ( Some ( ref a_path) , Some ( ref b_path) ) => {
263
+ // cmp_by would be useful for us here but that is currently unstable
264
+ // cmp doesn't work due the lifetimes on text's return type
265
+ a_path
266
+ . segments ( )
267
+ . zip_longest ( b_path. segments ( ) )
268
+ . find_map ( |zipped| match zipped {
269
+ EitherOrBoth :: Both ( ref a_segment, ref b_segment) => {
270
+ match path_segment_cmp ( a_segment, b_segment) {
271
+ Ordering :: Equal => None ,
272
+ ord => Some ( ord) ,
273
+ }
274
+ }
275
+ EitherOrBoth :: Left ( _) if b. is_simple_path ( ) => Some ( Ordering :: Greater ) ,
276
+ EitherOrBoth :: Left ( _) => Some ( Ordering :: Less ) ,
277
+ EitherOrBoth :: Right ( _) if a. is_simple_path ( ) => Some ( Ordering :: Less ) ,
278
+ EitherOrBoth :: Right ( _) => Some ( Ordering :: Greater ) ,
279
+ } )
280
+ . unwrap_or_else ( tiebreak_by_tree_list_glob_or_alias)
281
+ }
282
+ }
264
283
}
265
284
266
285
fn path_segment_cmp ( a : & ast:: PathSegment , b : & ast:: PathSegment ) -> Ordering {
267
- let a = a. kind ( ) . and_then ( |kind| match kind {
268
- PathSegmentKind :: Name ( name_ref) => Some ( name_ref) ,
269
- _ => None ,
270
- } ) ;
271
- let b = b. kind ( ) . and_then ( |kind| match kind {
272
- PathSegmentKind :: Name ( name_ref) => Some ( name_ref) ,
273
- _ => None ,
274
- } ) ;
275
- a. as_ref ( ) . map ( ast:: NameRef :: text) . cmp ( & b. as_ref ( ) . map ( ast:: NameRef :: text) )
286
+ match ( a. kind ( ) , b. kind ( ) ) {
287
+ ( None , None ) => Ordering :: Equal ,
288
+ ( Some ( _) , None ) => Ordering :: Greater ,
289
+ ( None , Some ( _) ) => Ordering :: Less ,
290
+ // self
291
+ ( Some ( PathSegmentKind :: SelfKw ) , Some ( PathSegmentKind :: SelfKw ) ) => Ordering :: Equal ,
292
+ ( Some ( PathSegmentKind :: SelfKw ) , _) => Ordering :: Less ,
293
+ ( _, Some ( PathSegmentKind :: SelfKw ) ) => Ordering :: Greater ,
294
+ // super
295
+ ( Some ( PathSegmentKind :: SuperKw ) , Some ( PathSegmentKind :: SuperKw ) ) => Ordering :: Equal ,
296
+ ( Some ( PathSegmentKind :: SuperKw ) , _) => Ordering :: Less ,
297
+ ( _, Some ( PathSegmentKind :: SuperKw ) ) => Ordering :: Greater ,
298
+ // crate
299
+ ( Some ( PathSegmentKind :: CrateKw ) , Some ( PathSegmentKind :: CrateKw ) ) => Ordering :: Equal ,
300
+ ( Some ( PathSegmentKind :: CrateKw ) , _) => Ordering :: Less ,
301
+ ( _, Some ( PathSegmentKind :: CrateKw ) ) => Ordering :: Greater ,
302
+ // identifiers (everything else is treated as an identifier).
303
+ _ => {
304
+ match (
305
+ a. name_ref ( ) . as_ref ( ) . map ( ast:: NameRef :: text) ,
306
+ b. name_ref ( ) . as_ref ( ) . map ( ast:: NameRef :: text) ,
307
+ ) {
308
+ ( None , None ) => Ordering :: Equal ,
309
+ ( Some ( _) , None ) => Ordering :: Greater ,
310
+ ( None , Some ( _) ) => Ordering :: Less ,
311
+ ( Some ( a_name) , Some ( b_name) ) => {
312
+ // snake_case < CamelCase < UPPER_SNAKE_CASE
313
+ let a_text = a_name. as_str ( ) ;
314
+ let b_text = b_name. as_str ( ) ;
315
+ fn is_upper_snake_case ( s : & str ) -> bool {
316
+ s. chars ( ) . all ( |c| c. is_uppercase ( ) || c == '_' || c. is_numeric ( ) )
317
+ }
318
+ if a_text. starts_with ( char:: is_lowercase)
319
+ && b_text. starts_with ( char:: is_uppercase)
320
+ {
321
+ return Ordering :: Less ;
322
+ }
323
+ if a_text. starts_with ( char:: is_uppercase)
324
+ && b_text. starts_with ( char:: is_lowercase)
325
+ {
326
+ return Ordering :: Greater ;
327
+ }
328
+ if !is_upper_snake_case ( a_text) && is_upper_snake_case ( b_text) {
329
+ return Ordering :: Less ;
330
+ }
331
+ if is_upper_snake_case ( a_text) && !is_upper_snake_case ( b_text) {
332
+ return Ordering :: Greater ;
333
+ }
334
+ a_text. cmp ( & b_text)
335
+ }
336
+ }
337
+ }
338
+ }
276
339
}
277
340
278
341
pub fn eq_visibility ( vis0 : Option < ast:: Visibility > , vis1 : Option < ast:: Visibility > ) -> bool {
0 commit comments