@@ -7,13 +7,14 @@ use std::sync::Arc;
77use rustc_ast:: { LitKind , StrStyle } ;
88use rustc_errors:: Applicability ;
99use rustc_hir:: { BlockCheckMode , Expr , ExprKind , UnsafeSource } ;
10+ use rustc_lexer:: { LiteralKind , TokenKind , tokenize} ;
1011use rustc_lint:: { EarlyContext , LateContext } ;
1112use rustc_middle:: ty:: TyCtxt ;
1213use rustc_session:: Session ;
1314use rustc_span:: source_map:: { SourceMap , original_sp} ;
1415use 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,
1718} ;
1819use std:: borrow:: Cow ;
1920use std:: fmt;
@@ -137,25 +138,25 @@ pub trait SpanRangeExt: SpanRange {
137138 fn map_range (
138139 self ,
139140 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 > > ,
141142 ) -> Option < Range < BytePos > > {
142143 map_range ( cx. sess ( ) . source_map ( ) , self . into_range ( ) , f)
143144 }
144145
145146 #[ 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.
148152 ///
149- /// This extra condition prevents a problem when removing the '}' in:
150153 /// ```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)
154156 /// ```
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)`.
159160 fn with_leading_whitespace ( self , cx : & impl HasSession ) -> Range < BytePos > {
160161 with_leading_whitespace ( cx. sess ( ) . source_map ( ) , self . into_range ( ) )
161162 }
@@ -254,11 +255,11 @@ fn with_source_text_and_range<T>(
254255fn map_range (
255256 sm : & SourceMap ,
256257 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 > > ,
258259) -> Option < Range < BytePos > > {
259260 if let Some ( src) = get_source_range ( sm, sp. clone ( ) )
260261 && 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 ( ) )
262263 {
263264 debug_assert ! (
264265 range. start <= text. len( ) && range. end <= text. len( ) ,
@@ -275,20 +276,57 @@ fn map_range(
275276 }
276277}
277278
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+
278321fn 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 )
286324 } )
287- . unwrap ( )
325+ . unwrap_or ( sp )
288326}
289327
290328fn 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| {
292330 let src = src. get ( range. clone ( ) ) ?;
293331 Some ( range. start + ( src. len ( ) - src. trim_start ( ) . len ( ) ) ..range. end )
294332 } )
0 commit comments