@@ -9,7 +9,7 @@ use syntax::{
99 algo,
1010 ast:: {
1111 self , edit_in_place:: Removable , make, AstNode , HasAttrs , HasModuleItem , HasVisibility ,
12- PathSegmentKind , UseTree ,
12+ PathSegmentKind ,
1313 } ,
1414 ted, Direction , NodeOrToken , SyntaxKind , SyntaxNode ,
1515} ;
@@ -26,14 +26,18 @@ pub use hir::PrefixKind;
2626/// How imports should be grouped into use statements.
2727#[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
2828pub enum ImportGranularity {
29- /// Do not change the granularity of any imports and preserve the original structure written by the developer.
29+ /// Do not change the granularity of any imports and preserve the original structure written
30+ /// by the developer.
3031 Preserve ,
3132 /// Merge imports from the same crate into a single use statement.
3233 Crate ,
3334 /// Merge imports from the same module into a single use statement.
3435 Module ,
3536 /// Flatten imports so that each has its own use statement.
3637 Item ,
38+ /// Merge all imports into a single use statement as long as they have the same visibility
39+ /// and attributes.
40+ One ,
3741}
3842
3943#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
@@ -167,7 +171,7 @@ pub fn insert_use_as_alias(scope: &ImportScope, path: ast::Path, cfg: &InsertUse
167171 . tree ( )
168172 . syntax ( )
169173 . descendants ( )
170- . find_map ( UseTree :: cast)
174+ . find_map ( ast :: UseTree :: cast)
171175 . expect ( "Failed to make ast node `Rename`" ) ;
172176 let alias = node. rename ( ) ;
173177
@@ -184,6 +188,7 @@ fn insert_use_with_alias_option(
184188 let mut mb = match cfg. granularity {
185189 ImportGranularity :: Crate => Some ( MergeBehavior :: Crate ) ,
186190 ImportGranularity :: Module => Some ( MergeBehavior :: Module ) ,
191+ ImportGranularity :: One => Some ( MergeBehavior :: One ) ,
187192 ImportGranularity :: Item | ImportGranularity :: Preserve => None ,
188193 } ;
189194 if !cfg. enforce_granularity {
@@ -195,11 +200,16 @@ fn insert_use_with_alias_option(
195200 ImportGranularityGuess :: ModuleOrItem => mb. and ( Some ( MergeBehavior :: Module ) ) ,
196201 ImportGranularityGuess :: Crate => Some ( MergeBehavior :: Crate ) ,
197202 ImportGranularityGuess :: CrateOrModule => mb. or ( Some ( MergeBehavior :: Crate ) ) ,
203+ ImportGranularityGuess :: One => Some ( MergeBehavior :: One ) ,
198204 } ;
199205 }
200206
201- let use_item =
202- make:: use_ ( None , make:: use_tree ( path. clone ( ) , None , alias, false ) ) . clone_for_update ( ) ;
207+ let mut use_tree = make:: use_tree ( path. clone ( ) , None , alias, false ) ;
208+ if mb == Some ( MergeBehavior :: One ) && use_tree. path ( ) . is_some ( ) {
209+ use_tree = use_tree. clone_for_update ( ) ;
210+ use_tree. wrap_in_tree_list ( ) ;
211+ }
212+ let use_item = make:: use_ ( None , use_tree) . clone_for_update ( ) ;
203213
204214 // merge into existing imports if possible
205215 if let Some ( mb) = mb {
@@ -216,7 +226,7 @@ fn insert_use_with_alias_option(
216226
217227 // either we weren't allowed to merge or there is no import that fits the merge conditions
218228 // so look for the place we have to insert to
219- insert_use_ ( scope, & path , cfg. group , use_item ) ;
229+ insert_use_ ( scope, use_item , cfg. group ) ;
220230}
221231
222232pub fn ast_to_remove_for_path_in_use_stmt ( path : & ast:: Path ) -> Option < Box < dyn Removable > > {
@@ -248,15 +258,18 @@ enum ImportGroup {
248258 ThisCrate ,
249259 ThisModule ,
250260 SuperModule ,
261+ One ,
251262}
252263
253264impl ImportGroup {
254- fn new ( path : & ast:: Path ) -> ImportGroup {
255- let default = ImportGroup :: ExternCrate ;
265+ fn new ( use_tree : & ast:: UseTree ) -> ImportGroup {
266+ if use_tree. path ( ) . is_none ( ) && use_tree. use_tree_list ( ) . is_some ( ) {
267+ return ImportGroup :: One ;
268+ }
256269
257- let first_segment = match path. first_segment ( ) {
258- Some ( it ) => it ,
259- None => return default ,
270+ let Some ( first_segment) = use_tree . path ( ) . as_ref ( ) . and_then ( ast :: Path :: first_segment )
271+ else {
272+ return ImportGroup :: ExternCrate ;
260273 } ;
261274
262275 let kind = first_segment. kind ( ) . unwrap_or ( PathSegmentKind :: SelfKw ) ;
@@ -284,6 +297,7 @@ enum ImportGranularityGuess {
284297 ModuleOrItem ,
285298 Crate ,
286299 CrateOrModule ,
300+ One ,
287301}
288302
289303fn guess_granularity_from_scope ( scope : & ImportScope ) -> ImportGranularityGuess {
@@ -303,12 +317,24 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
303317 }
304318 . filter_map ( use_stmt) ;
305319 let mut res = ImportGranularityGuess :: Unknown ;
306- let ( mut prev, mut prev_vis, mut prev_attrs) = match use_stmts. next ( ) {
307- Some ( it) => it,
308- None => return res,
309- } ;
320+ let Some ( ( mut prev, mut prev_vis, mut prev_attrs) ) = use_stmts. next ( ) else { return res } ;
321+
322+ let is_tree_one_style =
323+ |use_tree : & ast:: UseTree | use_tree. path ( ) . is_none ( ) && use_tree. use_tree_list ( ) . is_some ( ) ;
324+ let mut seen_one_style_groups = Vec :: new ( ) ;
325+
310326 loop {
311- if let Some ( use_tree_list) = prev. use_tree_list ( ) {
327+ if is_tree_one_style ( & prev) {
328+ if res != ImportGranularityGuess :: One {
329+ if res != ImportGranularityGuess :: Unknown {
330+ // This scope has a mix of one-style and other style imports.
331+ break ImportGranularityGuess :: Unknown ;
332+ }
333+
334+ res = ImportGranularityGuess :: One ;
335+ seen_one_style_groups. push ( ( prev_vis. clone ( ) , prev_attrs. clone ( ) ) ) ;
336+ }
337+ } else if let Some ( use_tree_list) = prev. use_tree_list ( ) {
312338 if use_tree_list. use_trees ( ) . any ( |tree| tree. use_tree_list ( ) . is_some ( ) ) {
313339 // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
314340 break ImportGranularityGuess :: Crate ;
@@ -318,11 +344,22 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
318344 }
319345 }
320346
321- let ( curr, curr_vis, curr_attrs) = match use_stmts. next ( ) {
322- Some ( it) => it,
323- None => break res,
324- } ;
325- if eq_visibility ( prev_vis, curr_vis. clone ( ) ) && eq_attrs ( prev_attrs, curr_attrs. clone ( ) ) {
347+ let Some ( ( curr, curr_vis, curr_attrs) ) = use_stmts. next ( ) else { break res } ;
348+ if is_tree_one_style ( & curr) {
349+ if res != ImportGranularityGuess :: One
350+ || seen_one_style_groups. iter ( ) . any ( |( prev_vis, prev_attrs) | {
351+ eq_visibility ( prev_vis. clone ( ) , curr_vis. clone ( ) )
352+ && eq_attrs ( prev_attrs. clone ( ) , curr_attrs. clone ( ) )
353+ } )
354+ {
355+ // This scope has either a mix of one-style and other style imports or
356+ // multiple one-style imports with the same visibility and attributes.
357+ break ImportGranularityGuess :: Unknown ;
358+ }
359+ seen_one_style_groups. push ( ( curr_vis. clone ( ) , curr_attrs. clone ( ) ) ) ;
360+ } else if eq_visibility ( prev_vis, curr_vis. clone ( ) )
361+ && eq_attrs ( prev_attrs, curr_attrs. clone ( ) )
362+ {
326363 if let Some ( ( prev_path, curr_path) ) = prev. path ( ) . zip ( curr. path ( ) ) {
327364 if let Some ( ( prev_prefix, _) ) = common_prefix ( & prev_path, & curr_path) {
328365 if prev. use_tree_list ( ) . is_none ( ) && curr. use_tree_list ( ) . is_none ( ) {
@@ -350,39 +387,33 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
350387 }
351388}
352389
353- fn insert_use_ (
354- scope : & ImportScope ,
355- insert_path : & ast:: Path ,
356- group_imports : bool ,
357- use_item : ast:: Use ,
358- ) {
390+ fn insert_use_ ( scope : & ImportScope , use_item : ast:: Use , group_imports : bool ) {
359391 let scope_syntax = scope. as_syntax_node ( ) ;
360392 let insert_use_tree =
361393 use_item. use_tree ( ) . expect ( "`use_item` should have a use tree for `insert_path`" ) ;
362- let group = ImportGroup :: new ( insert_path ) ;
394+ let group = ImportGroup :: new ( & insert_use_tree ) ;
363395 let path_node_iter = scope_syntax
364396 . children ( )
365397 . filter_map ( |node| ast:: Use :: cast ( node. clone ( ) ) . zip ( Some ( node) ) )
366398 . flat_map ( |( use_, node) | {
367399 let tree = use_. use_tree ( ) ?;
368- let path = tree. path ( ) ?;
369- Some ( ( path, tree, node) )
400+ Some ( ( tree, node) )
370401 } ) ;
371402
372403 if group_imports {
373- // Iterator that discards anything thats not in the required grouping
404+ // Iterator that discards anything that's not in the required grouping
374405 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
375406 let group_iter = path_node_iter
376407 . clone ( )
377- . skip_while ( |( path , ..) | ImportGroup :: new ( path ) != group)
378- . take_while ( |( path , ..) | ImportGroup :: new ( path ) == group) ;
408+ . skip_while ( |( use_tree , ..) | ImportGroup :: new ( use_tree ) != group)
409+ . take_while ( |( use_tree , ..) | ImportGroup :: new ( use_tree ) == group) ;
379410
380411 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
381412 let mut last = None ;
382413 // find the element that would come directly after our new import
383- let post_insert: Option < ( _ , _ , SyntaxNode ) > = group_iter
414+ let post_insert: Option < ( _ , SyntaxNode ) > = group_iter
384415 . inspect ( |( .., node) | last = Some ( node. clone ( ) ) )
385- . find ( |( _ , use_tree, _) | use_tree_cmp ( & insert_use_tree, use_tree) != Ordering :: Greater ) ;
416+ . find ( |( use_tree, _) | use_tree_cmp ( & insert_use_tree, use_tree) != Ordering :: Greater ) ;
386417
387418 if let Some ( ( .., node) ) = post_insert {
388419 cov_mark:: hit!( insert_group) ;
@@ -401,7 +432,7 @@ fn insert_use_(
401432 // find the group that comes after where we want to insert
402433 let post_group = path_node_iter
403434 . inspect ( |( .., node) | last = Some ( node. clone ( ) ) )
404- . find ( |( p , ..) | ImportGroup :: new ( p ) > group) ;
435+ . find ( |( use_tree , ..) | ImportGroup :: new ( use_tree ) > group) ;
405436 if let Some ( ( .., node) ) = post_group {
406437 cov_mark:: hit!( insert_group_new_group) ;
407438 ted:: insert ( ted:: Position :: before ( & node) , use_item. syntax ( ) ) ;
@@ -419,7 +450,7 @@ fn insert_use_(
419450 }
420451 } else {
421452 // There exists a group, so append to the end of it
422- if let Some ( ( _, _ , node) ) = path_node_iter. last ( ) {
453+ if let Some ( ( _, node) ) = path_node_iter. last ( ) {
423454 cov_mark:: hit!( insert_no_grouping_last) ;
424455 ted:: insert ( ted:: Position :: after ( node) , use_item. syntax ( ) ) ;
425456 return ;
0 commit comments