@@ -7,13 +7,14 @@ use std::sync::Arc;
7
7
use rustc_ast:: { LitKind , StrStyle } ;
8
8
use rustc_errors:: Applicability ;
9
9
use rustc_hir:: { BlockCheckMode , Expr , ExprKind , UnsafeSource } ;
10
+ use rustc_lexer:: { LiteralKind , TokenKind , tokenize} ;
10
11
use rustc_lint:: { EarlyContext , LateContext } ;
11
12
use rustc_middle:: ty:: TyCtxt ;
12
13
use rustc_session:: Session ;
13
14
use rustc_span:: source_map:: { SourceMap , original_sp} ;
14
15
use rustc_span:: {
15
- BytePos , DUMMY_SP , FileNameDisplayPreference , Pos , SourceFile , SourceFileAndLine , Span , SpanData , SyntaxContext ,
16
- hygiene,
16
+ BytePos , DUMMY_SP , FileNameDisplayPreference , Pos , RelativeBytePos , SourceFile , SourceFileAndLine , Span , SpanData ,
17
+ SyntaxContext , hygiene,
17
18
} ;
18
19
use std:: borrow:: Cow ;
19
20
use std:: fmt;
@@ -137,25 +138,25 @@ pub trait SpanRangeExt: SpanRange {
137
138
fn map_range (
138
139
self ,
139
140
cx : & impl HasSession ,
140
- f : impl for < ' a > FnOnce ( & ' a str , Range < usize > ) -> Option < Range < usize > > ,
141
+ f : impl for < ' a > FnOnce ( & ' a SourceFile , & ' a str , Range < usize > ) -> Option < Range < usize > > ,
141
142
) -> Option < Range < BytePos > > {
142
143
map_range ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
143
144
}
144
145
145
146
#[ allow( rustdoc:: invalid_rust_codeblocks, reason = "The codeblock is intentionally broken" ) ]
146
- /// Extends the range to include all preceding whitespace characters, unless there
147
- /// are non-whitespace characters left on the same line after `self`.
147
+ /// Extends the range to include all preceding whitespace characters.
148
+ ///
149
+ /// The range will not be expanded if it would cross a line boundary, the line the range would
150
+ /// be extended to ends with a line comment and the text after the range contains a
151
+ /// non-whitespace character on the same line. e.g.
148
152
///
149
- /// This extra condition prevents a problem when removing the '}' in:
150
153
/// ```ignore
151
- /// ( // There was an opening bracket after the parenthesis, which has been removed
152
- /// // This is a comment
153
- /// })
154
+ /// ( // Some comment
155
+ /// foo)
154
156
/// ```
155
- /// Removing the whitespaces, including the linefeed, before the '}', would put the
156
- /// closing parenthesis at the end of the `// This is a comment` line, which would
157
- /// make it part of the comment as well. In this case, it is best to keep the span
158
- /// on the '}' alone.
157
+ ///
158
+ /// When the range points to `foo`, suggesting to remove the range after it's been extended will
159
+ /// cause the `)` to be placed inside the line comment as `( // Some comment)`.
159
160
fn with_leading_whitespace ( self , cx : & impl HasSession ) -> Range < BytePos > {
160
161
with_leading_whitespace ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
161
162
}
@@ -254,11 +255,11 @@ fn with_source_text_and_range<T>(
254
255
fn map_range (
255
256
sm : & SourceMap ,
256
257
sp : Range < BytePos > ,
257
- f : impl for < ' a > FnOnce ( & ' a str , Range < usize > ) -> Option < Range < usize > > ,
258
+ f : impl for < ' a > FnOnce ( & ' a SourceFile , & ' a str , Range < usize > ) -> Option < Range < usize > > ,
258
259
) -> Option < Range < BytePos > > {
259
260
if let Some ( src) = get_source_range ( sm, sp. clone ( ) )
260
261
&& let Some ( text) = & src. sf . src
261
- && let Some ( range) = f ( text, src. range . clone ( ) )
262
+ && let Some ( range) = f ( & src . sf , text, src. range . clone ( ) )
262
263
{
263
264
debug_assert ! (
264
265
range. start <= text. len( ) && range. end <= text. len( ) ,
@@ -275,20 +276,57 @@ fn map_range(
275
276
}
276
277
}
277
278
279
+ fn ends_with_line_comment_or_broken ( text : & str ) -> bool {
280
+ let Some ( last) = tokenize ( text) . last ( ) else {
281
+ return false ;
282
+ } ;
283
+ match last. kind {
284
+ // Will give the wrong result on text like `" // "` where the first quote ends a string
285
+ // started earlier. The only workaround is to lex the whole file which we don't really want
286
+ // to do.
287
+ TokenKind :: LineComment { .. } | TokenKind :: BlockComment { terminated : false , .. } => true ,
288
+ TokenKind :: Literal { kind, .. } => matches ! (
289
+ kind,
290
+ LiteralKind :: Byte { terminated: false }
291
+ | LiteralKind :: ByteStr { terminated: false }
292
+ | LiteralKind :: CStr { terminated: false }
293
+ | LiteralKind :: Char { terminated: false }
294
+ | LiteralKind :: RawByteStr { n_hashes: None }
295
+ | LiteralKind :: RawCStr { n_hashes: None }
296
+ | LiteralKind :: RawStr { n_hashes: None }
297
+ ) ,
298
+ _ => false ,
299
+ }
300
+ }
301
+
302
+ fn with_leading_whitespace_inner ( lines : & [ RelativeBytePos ] , src : & str , range : Range < usize > ) -> Option < usize > {
303
+ debug_assert ! ( lines. is_empty( ) || lines[ 0 ] . to_u32( ) == 0 ) ;
304
+
305
+ let start = src. get ( ..range. start ) ?. trim_end ( ) ;
306
+ let next_line = lines. partition_point ( |& pos| pos. to_usize ( ) <= start. len ( ) ) ;
307
+ if let Some ( line_end) = lines. get ( next_line)
308
+ && line_end. to_usize ( ) <= range. start
309
+ && let prev_start = lines. get ( next_line - 1 ) . map_or ( 0 , |& x| x. to_usize ( ) )
310
+ && ends_with_line_comment_or_broken ( & start[ prev_start..] )
311
+ && let next_line = lines. partition_point ( |& pos| pos. to_usize ( ) < range. end )
312
+ && let next_start = lines. get ( next_line) . map_or ( src. len ( ) , |& x| x. to_usize ( ) )
313
+ && tokenize ( src. get ( range. end ..next_start) ?) . any ( |t| !matches ! ( t. kind, TokenKind :: Whitespace ) )
314
+ {
315
+ Some ( range. start )
316
+ } else {
317
+ Some ( start. len ( ) )
318
+ }
319
+ }
320
+
278
321
fn with_leading_whitespace ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
279
- map_range ( sm, sp, |src, range| {
280
- let non_blank_after = src. len ( ) - src. get ( range. end ..) ?. trim_start ( ) . len ( ) ;
281
- if src. get ( range. end ..non_blank_after) ?. contains ( [ '\r' , '\n' ] ) {
282
- Some ( src. get ( ..range. start ) ?. trim_end ( ) . len ( ) ..range. end )
283
- } else {
284
- Some ( range)
285
- }
322
+ map_range ( sm, sp. clone ( ) , |sf, src, range| {
323
+ Some ( with_leading_whitespace_inner ( sf. lines ( ) , src, range. clone ( ) ) ?..range. end )
286
324
} )
287
- . unwrap ( )
325
+ . unwrap_or ( sp )
288
326
}
289
327
290
328
fn trim_start ( sm : & SourceMap , sp : Range < BytePos > ) -> Range < BytePos > {
291
- map_range ( sm, sp. clone ( ) , |src, range| {
329
+ map_range ( sm, sp. clone ( ) , |_ , src, range| {
292
330
let src = src. get ( range. clone ( ) ) ?;
293
331
Some ( range. start + ( src. len ( ) - src. trim_start ( ) . len ( ) ) ..range. end )
294
332
} )
0 commit comments