@@ -10,8 +10,8 @@ use rustc_lint::{LateContext, LintContext};
10
10
use rustc_session:: Session ;
11
11
use rustc_span:: source_map:: { original_sp, SourceMap } ;
12
12
use 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 ,
15
15
} ;
16
16
use std:: borrow:: Cow ;
17
17
use std:: fmt;
@@ -75,6 +75,12 @@ pub trait SpanRangeExt: SpanRange {
75
75
get_source_text ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
76
76
}
77
77
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
+
78
84
/// Calls the given function with the source text referenced and returns the value. Returns
79
85
/// `None` if the source text cannot be retrieved.
80
86
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 {
97
103
with_source_text_and_range ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
98
104
}
99
105
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
+
100
116
/// Calls the given function with the both the text of the source file and the referenced range,
101
117
/// and creates a new span with the returned range. Returns `None` if the source text cannot be
102
118
/// retrieved, or no result is returned.
@@ -110,11 +126,29 @@ pub trait SpanRangeExt: SpanRange {
110
126
map_range ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
111
127
}
112
128
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
+
113
142
/// Extends the range to include all preceding whitespace characters.
114
143
fn with_leading_whitespace ( self , cx : & impl LintContext ) -> Range < BytePos > {
115
144
with_leading_whitespace ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
116
145
}
117
146
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
+
118
152
/// Trims the leading whitespace from the range.
119
153
fn trim_start ( self , cx : & impl LintContext ) -> Range < BytePos > {
120
154
trim_start ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
@@ -139,7 +173,7 @@ fn get_source_text(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange
139
173
if !Lrc :: ptr_eq ( & start. sf , & end. sf ) || start. pos > end. pos {
140
174
return None ;
141
175
}
142
- let range = start. pos . to_usize ( ) ..end. pos . to_usize ( ) ;
176
+ let range = RelativeBytePos ( start. pos . 0 ) ..RelativeBytePos ( end. pos . 0 ) ;
143
177
Some ( SourceFileRange { sf : start. sf , range } )
144
178
}
145
179
@@ -161,12 +195,31 @@ fn with_source_text_and_range<T>(
161
195
if let Some ( src) = get_source_text ( sm, sp)
162
196
&& let Some ( text) = & src. sf . src
163
197
{
164
- Some ( f ( text, src. range ) )
198
+ Some ( f ( text, src. usize_range ( ) ) )
165
199
} else {
166
200
None
167
201
}
168
202
}
169
203
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
+
170
223
#[ expect( clippy:: cast_possible_truncation) ]
171
224
fn map_range (
172
225
sm : & SourceMap ,
@@ -175,7 +228,7 @@ fn map_range(
175
228
) -> Option < Range < BytePos > > {
176
229
if let Some ( src) = get_source_text ( sm, sp. clone ( ) )
177
230
&& let Some ( text) = & src. sf . src
178
- && let Some ( range) = f ( text, src. range . clone ( ) )
231
+ && let Some ( range) = f ( text, src. usize_range ( ) )
179
232
{
180
233
debug_assert ! (
181
234
range. start <= text. len( ) && range. end <= text. len( ) ,
@@ -184,21 +237,54 @@ fn map_range(
184
237
text. len( ) ,
185
238
) ;
186
239
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 ) ;
189
242
Some ( BytePos ( sp. start . 0 . wrapping_add ( dstart) ) ..BytePos ( sp. start . 0 . wrapping_add ( dend) ) )
190
243
} else {
191
244
None
192
245
}
193
246
}
194
247
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
+
195
273
fn with_leading_whitespace ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
196
274
map_range ( sm, sp. clone ( ) , |src, range| {
197
275
Some ( src. get ( ..range. start ) ?. trim_end ( ) . len ( ) ..range. end )
198
276
} )
199
277
. unwrap_or ( sp)
200
278
}
201
279
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
+
202
288
fn trim_start ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
203
289
map_range ( sm, sp. clone ( ) , |src, range| {
204
290
let src = src. get ( range. clone ( ) ) ?;
@@ -216,13 +302,18 @@ fn write_source_text_to(sm: &SourceMap, sp: Range<BytePos>, dst: &mut impl fmt::
216
302
217
303
pub struct SourceFileRange {
218
304
pub sf : Lrc < SourceFile > ,
219
- pub range : Range < usize > ,
305
+ pub range : Range < RelativeBytePos > ,
220
306
}
221
307
impl SourceFileRange {
222
308
/// Attempts to get the text from the source file. This can fail if the source text isn't
223
309
/// loaded.
224
310
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
226
317
}
227
318
}
228
319
0 commit comments