@@ -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 > {
@@ -97,6 +103,16 @@ pub trait SpanRangeExt: SpanRange {
97103 with_source_text_and_range ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
98104 }
99105
106+ /// Checks if the referenced source text satisfies the given predicate. Returns `false` if the
107+ /// source text cannot be retrieved.
108+ fn check_source_text_with_range (
109+ self ,
110+ cx : & impl LintContext ,
111+ pred : impl for < ' a > FnOnce ( & ' a str , Range < usize > ) -> bool ,
112+ ) -> bool {
113+ self . with_source_text_and_range ( cx, pred) . unwrap_or ( false )
114+ }
115+
100116 /// Calls the given function with the both the text of the source file and the referenced range,
101117 /// and creates a new span with the returned range. Returns `None` if the source text cannot be
102118 /// retrieved, or no result is returned.
@@ -110,11 +126,29 @@ pub trait SpanRangeExt: SpanRange {
110126 map_range ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
111127 }
112128
129+ /// Calls the given function with the both the text of the source file and the referenced range,
130+ /// and creates a new span from the result. Returns `None` if the source text cannot be
131+ /// retrieved, or no result is returned.
132+ ///
133+ /// The new range must reside within the same source file.
134+ fn map_range_as_pos_len (
135+ self ,
136+ cx : & impl LintContext ,
137+ f : impl for < ' a > FnOnce ( & ' a str , Range < usize > ) -> Option < ( usize , usize ) > ,
138+ ) -> Option < Range < BytePos > > {
139+ map_range_as_pos_len ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
140+ }
141+
113142 /// Extends the range to include all preceding whitespace characters.
114143 fn with_leading_whitespace ( self , cx : & impl LintContext ) -> Range < BytePos > {
115144 with_leading_whitespace ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
116145 }
117146
147+ /// Extends the range to include all trailing whitespace characters.
148+ fn with_trailing_whitespace ( self , cx : & impl LintContext ) -> Range < BytePos > {
149+ with_trailing_whitespace ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
150+ }
151+
118152 /// Trims the leading whitespace from the range.
119153 fn trim_start ( self , cx : & impl LintContext ) -> Range < BytePos > {
120154 trim_start ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
@@ -139,7 +173,7 @@ fn get_source_text(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange
139173 if !Lrc :: ptr_eq ( & start. sf , & end. sf ) || start. pos > end. pos {
140174 return None ;
141175 }
142- let range = start. pos . to_usize ( ) ..end. pos . to_usize ( ) ;
176+ let range = RelativeBytePos ( start. pos . 0 ) ..RelativeBytePos ( end. pos . 0 ) ;
143177 Some ( SourceFileRange { sf : start. sf , range } )
144178}
145179
@@ -161,12 +195,31 @@ fn with_source_text_and_range<T>(
161195 if let Some ( src) = get_source_text ( sm, sp)
162196 && let Some ( text) = & src. sf . src
163197 {
164- Some ( f ( text, src. range ) )
198+ Some ( f ( text, src. usize_range ( ) ) )
165199 } else {
166200 None
167201 }
168202}
169203
204+ fn with_line_indent < T > ( sm : & SourceMap , sp : Range < BytePos > , f : impl for < ' a > FnOnce ( & ' a str ) -> T ) -> T {
205+ let src = get_source_text ( sm, sp) ;
206+ let indent = if let Some ( src) = & src
207+ && let Some ( line) = src. sf . lookup_line ( src. range . start )
208+ && let Some ( start) = src. sf . lines ( ) . get ( line)
209+ && let Some ( text) = src. sf . src . as_ref ( )
210+ {
211+ let text = if let Some ( end) = src. sf . lines ( ) . get ( line + 1 ) {
212+ & text[ start. to_usize ( ) ..end. to_usize ( ) ]
213+ } else {
214+ & text[ start. to_usize ( ) ..]
215+ } ;
216+ & text[ ..text. len ( ) - text. trim_start ( ) . len ( ) ]
217+ } else {
218+ ""
219+ } ;
220+ f ( indent)
221+ }
222+
170223#[ expect( clippy:: cast_possible_truncation) ]
171224fn map_range (
172225 sm : & SourceMap ,
@@ -175,7 +228,7 @@ fn map_range(
175228) -> Option < Range < BytePos > > {
176229 if let Some ( src) = get_source_text ( sm, sp. clone ( ) )
177230 && let Some ( text) = & src. sf . src
178- && let Some ( range) = f ( text, src. range . clone ( ) )
231+ && let Some ( range) = f ( text, src. usize_range ( ) )
179232 {
180233 debug_assert ! (
181234 range. start <= text. len( ) && range. end <= text. len( ) ,
@@ -184,21 +237,54 @@ fn map_range(
184237 text. len( ) ,
185238 ) ;
186239 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 ) ;
240+ let dstart = ( range. start as u32 ) . wrapping_sub ( src. range . start . 0 ) ;
241+ let dend = ( range. end as u32 ) . wrapping_sub ( src. range . start . 0 ) ;
189242 Some ( BytePos ( sp. start . 0 . wrapping_add ( dstart) ) ..BytePos ( sp. start . 0 . wrapping_add ( dend) ) )
190243 } else {
191244 None
192245 }
193246}
194247
248+ #[ expect( clippy:: cast_possible_truncation) ]
249+ fn map_range_as_pos_len (
250+ sm : & SourceMap ,
251+ sp : Range < BytePos > ,
252+ f : impl for < ' a > FnOnce ( & ' a str , Range < usize > ) -> Option < ( usize , usize ) > ,
253+ ) -> Option < Range < BytePos > > {
254+ if let Some ( src) = get_source_text ( sm, sp. clone ( ) )
255+ && let Some ( text) = & src. sf . src
256+ && let Some ( ( pos, len) ) = f ( text, src. usize_range ( ) )
257+ {
258+ debug_assert ! (
259+ pos + len <= text. len( ) ,
260+ "Range `{:?}` is outside the source file (file `{}`, length `{}`)" ,
261+ pos..pos + len,
262+ src. sf. name. display( FileNameDisplayPreference :: Local ) ,
263+ text. len( ) ,
264+ ) ;
265+ let delta = ( pos as u32 ) . wrapping_sub ( src. range . start . 0 ) ;
266+ let pos = sp. start . 0 . wrapping_add ( delta) ;
267+ Some ( BytePos ( pos) ..BytePos ( pos + len as u32 ) )
268+ } else {
269+ None
270+ }
271+ }
272+
195273fn with_leading_whitespace ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
196274 map_range ( sm, sp. clone ( ) , |src, range| {
197275 Some ( src. get ( ..range. start ) ?. trim_end ( ) . len ( ) ..range. end )
198276 } )
199277 . unwrap_or ( sp)
200278}
201279
280+ fn with_trailing_whitespace ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
281+ map_range ( sm, sp. clone ( ) , |src, range| {
282+ let tail = src. get ( range. end ..) ?;
283+ Some ( range. start ..range. end + ( tail. len ( ) - tail. trim_start ( ) . len ( ) ) )
284+ } )
285+ . unwrap_or ( sp)
286+ }
287+
202288fn trim_start ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
203289 map_range ( sm, sp. clone ( ) , |src, range| {
204290 let src = src. get ( range. clone ( ) ) ?;
@@ -216,13 +302,18 @@ fn write_source_text_to(sm: &SourceMap, sp: Range<BytePos>, dst: &mut impl fmt::
216302
217303pub struct SourceFileRange {
218304 pub sf : Lrc < SourceFile > ,
219- pub range : Range < usize > ,
305+ pub range : Range < RelativeBytePos > ,
220306}
221307impl SourceFileRange {
222308 /// Attempts to get the text from the source file. This can fail if the source text isn't
223309 /// loaded.
224310 pub fn as_str ( & self ) -> Option < & str > {
225- self . sf . src . as_ref ( ) . and_then ( |x| x. get ( self . range . clone ( ) ) )
311+ self . sf . src . as_ref ( ) . and_then ( |x| x. get ( self . usize_range ( ) ) )
312+ }
313+
314+ /// Gets the range of the source text as a `usize` range.
315+ pub fn usize_range ( & self ) -> Range < usize > {
316+ self . range . start . 0 as usize ..self . range . end . 0 as usize
226317 }
227318}
228319
0 commit comments