@@ -10,8 +10,8 @@ use rustc_lint::{LateContext, LintContext};
1010use rustc_session:: Session ;
1111use rustc_span:: source_map:: { original_sp, SourceMap } ;
1212use rustc_span:: {
13- hygiene, BytePos , FileNameDisplayPreference , Pos , SourceFile , SourceFileAndLine , Span , SpanData , SyntaxContext ,
14- DUMMY_SP ,
13+ hygiene, BytePos , FileNameDisplayPreference , Pos , RelativeBytePos , SourceFile , SourceFileAndLine , Span , SpanData ,
14+ SyntaxContext , DUMMY_SP ,
1515} ;
1616use std:: borrow:: Cow ;
1717use std:: fmt;
@@ -75,6 +75,12 @@ pub trait SpanRangeExt: SpanRange {
7575 get_source_text ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
7676 }
7777
78+ /// Calls the given function with the indent of the referenced line and returns the value.
79+ /// Passes an empty string if the indent cannot be determined.
80+ fn with_line_indent < T > ( self , cx : & impl LintContext , f : impl for < ' a > FnOnce ( & ' a str ) -> T ) -> T {
81+ with_line_indent ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
82+ }
83+
7884 /// Calls the given function with the source text referenced and returns the value. Returns
7985 /// `None` if the source text cannot be retrieved.
8086 fn with_source_text < T > ( self , cx : & impl LintContext , f : impl for < ' a > FnOnce ( & ' a str ) -> T ) -> Option < T > {
@@ -110,11 +116,29 @@ pub trait SpanRangeExt: SpanRange {
110116 map_range ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
111117 }
112118
119+ /// Calls the given function with the both the text of the source file and the referenced range,
120+ /// and creates a new span from the result. Returns `None` if the source text cannot be
121+ /// retrieved, or no result is returned.
122+ ///
123+ /// The new range must reside within the same source file.
124+ fn map_range_as_pos_len (
125+ self ,
126+ cx : & impl LintContext ,
127+ f : impl for < ' a > FnOnce ( & ' a str , Range < usize > ) -> Option < ( usize , usize ) > ,
128+ ) -> Option < Range < BytePos > > {
129+ map_range_as_pos_len ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
130+ }
131+
113132 /// Extends the range to include all preceding whitespace characters.
114133 fn with_leading_whitespace ( self , cx : & impl LintContext ) -> Range < BytePos > {
115134 with_leading_whitespace ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
116135 }
117136
137+ /// Extends the range to include all trailing whitespace characters.
138+ fn with_trailing_whitespace ( self , cx : & impl LintContext ) -> Range < BytePos > {
139+ with_trailing_whitespace ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
140+ }
141+
118142 /// Trims the leading whitespace from the range.
119143 fn trim_start ( self , cx : & impl LintContext ) -> Range < BytePos > {
120144 trim_start ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
@@ -139,7 +163,7 @@ fn get_source_text(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange
139163 if !Lrc :: ptr_eq ( & start. sf , & end. sf ) || start. pos > end. pos {
140164 return None ;
141165 }
142- let range = start. pos . to_usize ( ) ..end. pos . to_usize ( ) ;
166+ let range = RelativeBytePos ( start. pos . 0 ) ..RelativeBytePos ( end. pos . 0 ) ;
143167 Some ( SourceFileRange { sf : start. sf , range } )
144168}
145169
@@ -161,12 +185,31 @@ fn with_source_text_and_range<T>(
161185 if let Some ( src) = get_source_text ( sm, sp)
162186 && let Some ( text) = & src. sf . src
163187 {
164- Some ( f ( text, src. range ) )
188+ Some ( f ( text, src. usize_range ( ) ) )
165189 } else {
166190 None
167191 }
168192}
169193
194+ fn with_line_indent < T > ( sm : & SourceMap , sp : Range < BytePos > , f : impl for < ' a > FnOnce ( & ' a str ) -> T ) -> T {
195+ let src = get_source_text ( sm, sp) ;
196+ let indent = if let Some ( src) = & src
197+ && let Some ( line) = src. sf . lookup_line ( src. range . start )
198+ && let Some ( start) = src. sf . lines ( ) . get ( line)
199+ && let Some ( text) = src. sf . src . as_ref ( )
200+ {
201+ let text = if let Some ( end) = src. sf . lines ( ) . get ( line + 1 ) {
202+ & text[ start. to_usize ( ) ..end. to_usize ( ) ]
203+ } else {
204+ & text[ start. to_usize ( ) ..]
205+ } ;
206+ & text[ ..text. len ( ) - text. trim_start ( ) . len ( ) ]
207+ } else {
208+ ""
209+ } ;
210+ f ( indent)
211+ }
212+
170213#[ expect( clippy:: cast_possible_truncation) ]
171214fn map_range (
172215 sm : & SourceMap ,
@@ -175,7 +218,7 @@ fn map_range(
175218) -> Option < Range < BytePos > > {
176219 if let Some ( src) = get_source_text ( sm, sp. clone ( ) )
177220 && let Some ( text) = & src. sf . src
178- && let Some ( range) = f ( text, src. range . clone ( ) )
221+ && let Some ( range) = f ( text, src. usize_range ( ) )
179222 {
180223 debug_assert ! (
181224 range. start <= text. len( ) && range. end <= text. len( ) ,
@@ -184,21 +227,54 @@ fn map_range(
184227 text. len( ) ,
185228 ) ;
186229 debug_assert ! ( range. start <= range. end, "Range `{range:?}` has overlapping bounds" ) ;
187- let dstart = ( range. start as u32 ) . wrapping_sub ( src. range . start as u32 ) ;
188- let dend = ( range. end as u32 ) . wrapping_sub ( src. range . start as u32 ) ;
230+ let dstart = ( range. start as u32 ) . wrapping_sub ( src. range . start . 0 ) ;
231+ let dend = ( range. end as u32 ) . wrapping_sub ( src. range . start . 0 ) ;
189232 Some ( BytePos ( sp. start . 0 . wrapping_add ( dstart) ) ..BytePos ( sp. start . 0 . wrapping_add ( dend) ) )
190233 } else {
191234 None
192235 }
193236}
194237
238+ #[ expect( clippy:: cast_possible_truncation) ]
239+ fn map_range_as_pos_len (
240+ sm : & SourceMap ,
241+ sp : Range < BytePos > ,
242+ f : impl for < ' a > FnOnce ( & ' a str , Range < usize > ) -> Option < ( usize , usize ) > ,
243+ ) -> Option < Range < BytePos > > {
244+ if let Some ( src) = get_source_text ( sm, sp. clone ( ) )
245+ && let Some ( text) = & src. sf . src
246+ && let Some ( ( pos, len) ) = f ( text, src. usize_range ( ) )
247+ {
248+ debug_assert ! (
249+ pos + len <= text. len( ) ,
250+ "Range `{:?}` is outside the source file (file `{}`, length `{}`)" ,
251+ pos..pos + len,
252+ src. sf. name. display( FileNameDisplayPreference :: Local ) ,
253+ text. len( ) ,
254+ ) ;
255+ let delta = ( pos as u32 ) . wrapping_sub ( src. range . start . 0 ) ;
256+ let pos = sp. start . 0 . wrapping_add ( delta) ;
257+ Some ( BytePos ( pos) ..BytePos ( pos + len as u32 ) )
258+ } else {
259+ None
260+ }
261+ }
262+
195263fn with_leading_whitespace ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
196264 map_range ( sm, sp. clone ( ) , |src, range| {
197265 Some ( src. get ( ..range. start ) ?. trim_end ( ) . len ( ) ..range. end )
198266 } )
199267 . unwrap_or ( sp)
200268}
201269
270+ fn with_trailing_whitespace ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
271+ map_range ( sm, sp. clone ( ) , |src, range| {
272+ let tail = src. get ( range. end ..) ?;
273+ Some ( range. start ..range. end + ( tail. len ( ) - tail. trim_start ( ) . len ( ) ) )
274+ } )
275+ . unwrap_or ( sp)
276+ }
277+
202278fn trim_start ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
203279 map_range ( sm, sp. clone ( ) , |src, range| {
204280 let src = src. get ( range. clone ( ) ) ?;
@@ -216,13 +292,18 @@ fn write_source_text_to(sm: &SourceMap, sp: Range<BytePos>, dst: &mut impl fmt::
216292
217293pub struct SourceFileRange {
218294 pub sf : Lrc < SourceFile > ,
219- pub range : Range < usize > ,
295+ pub range : Range < RelativeBytePos > ,
220296}
221297impl SourceFileRange {
222298 /// Attempts to get the text from the source file. This can fail if the source text isn't
223299 /// loaded.
224300 pub fn as_str ( & self ) -> Option < & str > {
225- self . sf . src . as_ref ( ) . and_then ( |x| x. get ( self . range . clone ( ) ) )
301+ self . sf . src . as_ref ( ) . and_then ( |x| x. get ( self . usize_range ( ) ) )
302+ }
303+
304+ /// Gets the range of the source text as a `usize` range.
305+ pub fn usize_range ( & self ) -> Range < usize > {
306+ self . range . start . 0 as usize ..self . range . end . 0 as usize
226307 }
227308}
228309
0 commit comments