1- use crate :: ir_transform:: sort_imports:: {
2- compute_metadata:: compute_import_metadata,
3- group_config:: GroupName ,
4- options:: SortImportsOptions ,
5- sortable_imports:: { SortSortableImports , SortableImport } ,
6- source_line:: SourceLine ,
1+ use crate :: {
2+ formatter:: format_element:: LineMode ,
3+ ir_transform:: sort_imports:: {
4+ compute_metadata:: compute_import_metadata,
5+ group_config:: GroupName ,
6+ options:: SortImportsOptions ,
7+ sortable_imports:: { SortSortableImports , SortableImport } ,
8+ source_line:: SourceLine ,
9+ } ,
710} ;
811
12+ /// Orphan content (comments/empty lines separated by empty line from the next import).
13+ /// These stay at their original slot position after sorting.
14+ #[ derive( Debug ) ]
15+ pub struct OrphanContent < ' a > {
16+ pub lines : Vec < SourceLine < ' a > > ,
17+ /// The slot position:
18+ /// - `None`: before the first import (leading orphan)
19+ /// - `Some(n)`: after the Nth import (0-indexed)
20+ pub after_slot : Option < usize > ,
21+ }
22+
923#[ derive( Debug ) ]
1024pub enum PartitionedChunk < ' a > {
1125 /// A chunk containing import statements,
@@ -42,39 +56,90 @@ impl<'a> PartitionedChunk<'a> {
4256 matches ! ( self , Self :: Imports ( lines) if lines. is_empty( ) )
4357 }
4458
45- /// Convert this import chunk into a list of sortable import units and trailing lines.
46- /// Returns a tuple of `(sortable_imports, trailing_lines)`.
59+ /// Convert this import chunk into `SortableImport` units with `OrphanContent`.
60+ /// Returns a tuple of `(sortable_imports, orphan_contents, trailing_lines)`.
61+ ///
62+ /// - `sortable_imports`: Import statements with their attached leading lines.
63+ /// - `leading_lines`: Comments directly before this import (no empty line between).
64+ /// - `orphan_contents`: Orphan comments (separated by empty line from next import) with their slot positions.
65+ /// - `after_slot: None` = before first import, `Some(n)` = after slot n
66+ /// - `trailing_lines`: Lines at the end of the chunk after all imports.
4767 #[ must_use]
4868 pub fn into_sorted_import_units (
4969 self ,
5070 groups : & [ Vec < GroupName > ] ,
5171 options : & SortImportsOptions ,
52- ) -> ( Vec < SortableImport < ' a > > , Vec < SourceLine < ' a > > ) {
72+ ) -> ( Vec < SortableImport < ' a > > , Vec < OrphanContent < ' a > > , Vec < SourceLine < ' a > > ) {
5373 let Self :: Imports ( lines) = self else {
5474 unreachable ! (
5575 "`into_import_units()` must be called on `PartitionedChunk::Imports` only."
5676 ) ;
5777 } ;
5878
59- let mut sortable_imports = vec ! [ ] ;
60- let mut current_leading_lines = vec ! [ ] ;
79+ let mut sortable_imports: Vec < SortableImport < ' a > > = vec ! [ ] ;
80+ let mut orphan_contents: Vec < OrphanContent < ' a > > = vec ! [ ] ;
81+
82+ // Comments separated from the next import by empty line.
83+ // These stay at their slot position, not attached to any import.
84+ let mut orphan_pending: Vec < SourceLine < ' a > > = vec ! [ ] ;
85+ // Comments directly before the next import (no empty line between).
86+ // These attach to the next import as leading lines.
87+ let mut current_pending: Vec < SourceLine < ' a > > = vec ! [ ] ;
88+
6189 for line in lines {
6290 match line {
6391 SourceLine :: Import ( _, ref metadata) => {
92+ // Handle orphan content (separated by empty line from this import)
93+ // These stay at their original slot position, not attached to any import.
94+ if !orphan_pending. is_empty ( ) {
95+ // Slot position: None = before first import, Some(n) = after slot n
96+ let after_slot = if sortable_imports. is_empty ( ) {
97+ None
98+ } else {
99+ Some ( sortable_imports. len ( ) - 1 )
100+ } ;
101+ // For leading orphans (None), keep all lines including empty lines
102+ // For other orphans, keep only comments
103+ let lines: Vec < _ > = if after_slot. is_none ( ) {
104+ std:: mem:: take ( & mut orphan_pending)
105+ } else {
106+ std:: mem:: take ( & mut orphan_pending)
107+ . into_iter ( )
108+ . filter_map ( |orphan| {
109+ if let SourceLine :: CommentOnly ( range, _) = orphan {
110+ Some ( SourceLine :: CommentOnly ( range, LineMode :: Hard ) )
111+ } else {
112+ None
113+ }
114+ } )
115+ . collect ( )
116+ } ;
117+ if !lines. is_empty ( ) {
118+ orphan_contents. push ( OrphanContent { lines, after_slot } ) ;
119+ }
120+ }
121+
64122 let is_side_effect = metadata. is_side_effect ;
65123 let ( group_idx, normalized_source, is_ignored) =
66124 compute_import_metadata ( metadata, groups, options) ;
125+
67126 sortable_imports. push ( SortableImport {
68- leading_lines : std:: mem:: take ( & mut current_leading_lines ) ,
127+ leading_lines : std:: mem:: take ( & mut current_pending ) ,
69128 import_line : line,
70129 is_side_effect,
71130 group_idx,
72131 normalized_source,
73132 is_ignored,
74133 } ) ;
75134 }
76- SourceLine :: CommentOnly ( ..) | SourceLine :: Empty => {
77- current_leading_lines. push ( line) ;
135+ SourceLine :: Empty => {
136+ // Empty line separates comments from the next import.
137+ // Move `current_pending` to `orphan_pending`, then add empty line.
138+ orphan_pending. append ( & mut current_pending) ;
139+ orphan_pending. push ( line) ;
140+ }
141+ SourceLine :: CommentOnly ( ..) => {
142+ current_pending. push ( line) ;
78143 }
79144 SourceLine :: Others ( ..) => {
80145 unreachable ! (
@@ -84,12 +149,14 @@ impl<'a> PartitionedChunk<'a> {
84149 }
85150 }
86151
87- // Any remaining comments/lines are trailing
88- let trailing_lines = current_leading_lines;
152+ // Any remaining lines are trailing
153+ // Combine orphan and current pending as trailing lines
154+ orphan_pending. append ( & mut current_pending) ;
155+ let trailing_lines = orphan_pending;
89156
90157 // Let's sort this chunk!
91158 sortable_imports. sort ( options) ;
92159
93- ( sortable_imports, trailing_lines)
160+ ( sortable_imports, orphan_contents , trailing_lines)
94161 }
95162}
0 commit comments