1
- use crate :: utils:: { snippet_with_applicability, span_lint, span_lint_and_sugg} ;
1
+ use crate :: utils:: { snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then } ;
2
2
use rustc:: lint:: { EarlyContext , EarlyLintPass , LintArray , LintPass } ;
3
3
use rustc:: { declare_lint_pass, declare_tool_lint} ;
4
4
use rustc_errors:: Applicability ;
5
5
use std:: borrow:: Cow ;
6
6
use syntax:: ast:: * ;
7
7
use syntax:: parse:: { parser, token} ;
8
- use syntax:: tokenstream:: { TokenStream , TokenTree } ;
9
- use syntax_pos:: symbol:: Symbol ;
8
+ use syntax:: tokenstream:: TokenStream ;
9
+ use syntax_pos:: { symbol:: Symbol , BytePos , Span } ;
10
10
11
11
declare_clippy_lint ! {
12
12
/// **What it does:** This lint warns when you use `println!("")` to
@@ -184,8 +184,8 @@ impl EarlyLintPass for Write {
184
184
fn check_mac ( & mut self , cx : & EarlyContext < ' _ > , mac : & Mac ) {
185
185
if mac. node . path == sym ! ( println) {
186
186
span_lint ( cx, PRINT_STDOUT , mac. span , "use of `println!`" ) ;
187
- if let Some ( fmtstr ) = check_tts ( cx, & mac. node . tts , false ) . 0 {
188
- if fmtstr == "" {
187
+ if let ( Some ( fmt_str ) , _ ) = check_tts ( cx, & mac. node . tts , false ) {
188
+ if fmt_str . contents . is_empty ( ) {
189
189
span_lint_and_sugg (
190
190
cx,
191
191
PRINTLN_EMPTY_STRING ,
@@ -199,35 +199,52 @@ impl EarlyLintPass for Write {
199
199
}
200
200
} else if mac. node . path == sym ! ( print) {
201
201
span_lint ( cx, PRINT_STDOUT , mac. span , "use of `print!`" ) ;
202
- if let ( Some ( fmtstr ) , _, is_raw ) = check_tts ( cx, & mac. node . tts , false ) {
203
- if check_newlines ( & fmtstr , is_raw ) {
204
- span_lint (
202
+ if let ( Some ( fmt_str ) , _) = check_tts ( cx, & mac. node . tts , false ) {
203
+ if check_newlines ( & fmt_str ) {
204
+ span_lint_and_then (
205
205
cx,
206
206
PRINT_WITH_NEWLINE ,
207
207
mac. span ,
208
- "using `print!()` with a format string that ends in a \
209
- single newline, consider using `println!()` instead",
208
+ "using `print!()` with a format string that ends in a single newline" ,
209
+ |err| {
210
+ err. multipart_suggestion (
211
+ "use `println!` instead" ,
212
+ vec ! [
213
+ ( mac. node. path. span, String :: from( "println" ) ) ,
214
+ ( fmt_str. newline_span( ) , String :: new( ) ) ,
215
+ ] ,
216
+ Applicability :: MachineApplicable ,
217
+ ) ;
218
+ } ,
210
219
) ;
211
220
}
212
221
}
213
222
} else if mac. node . path == sym ! ( write) {
214
- if let ( Some ( fmtstr ) , _, is_raw ) = check_tts ( cx, & mac. node . tts , true ) {
215
- if check_newlines ( & fmtstr , is_raw ) {
216
- span_lint (
223
+ if let ( Some ( fmt_str ) , _) = check_tts ( cx, & mac. node . tts , true ) {
224
+ if check_newlines ( & fmt_str ) {
225
+ span_lint_and_then (
217
226
cx,
218
227
WRITE_WITH_NEWLINE ,
219
228
mac. span ,
220
- "using `write!()` with a format string that ends in a \
221
- single newline, consider using `writeln!()` instead",
222
- ) ;
229
+ "using `write!()` with a format string that ends in a single newline" ,
230
+ |err| {
231
+ err. multipart_suggestion (
232
+ "use `writeln!()` instead" ,
233
+ vec ! [
234
+ ( mac. node. path. span, String :: from( "writeln" ) ) ,
235
+ ( fmt_str. newline_span( ) , String :: new( ) ) ,
236
+ ] ,
237
+ Applicability :: MachineApplicable ,
238
+ ) ;
239
+ } ,
240
+ )
223
241
}
224
242
}
225
243
} else if mac. node . path == sym ! ( writeln) {
226
- let check_tts = check_tts ( cx, & mac. node . tts , true ) ;
227
- if let Some ( fmtstr) = check_tts. 0 {
228
- if fmtstr == "" {
244
+ if let ( Some ( fmt_str) , expr) = check_tts ( cx, & mac. node . tts , true ) {
245
+ if fmt_str. contents . is_empty ( ) {
229
246
let mut applicability = Applicability :: MachineApplicable ;
230
- let suggestion = check_tts . 1 . map_or_else (
247
+ let suggestion = expr . map_or_else (
231
248
move || {
232
249
applicability = Applicability :: HasPlaceholders ;
233
250
Cow :: Borrowed ( "v" )
@@ -250,10 +267,44 @@ impl EarlyLintPass for Write {
250
267
}
251
268
}
252
269
270
+ /// The arguments of a `print[ln]!` or `write[ln]!` invocation.
271
+ struct FmtStr {
272
+ /// The contents of the format string (inside the quotes).
273
+ contents : String ,
274
+ style : StrStyle ,
275
+ /// The span of the format string, including quotes, the raw marker, and any raw hashes.
276
+ span : Span ,
277
+ }
278
+
279
+ impl FmtStr {
280
+ /// Given a format string that ends in a newline and its span, calculates the span of the
281
+ /// newline.
282
+ fn newline_span ( & self ) -> Span {
283
+ let sp = self . span ;
284
+
285
+ let newline_sp_hi = sp. hi ( )
286
+ - match self . style {
287
+ StrStyle :: Cooked => BytePos ( 1 ) ,
288
+ StrStyle :: Raw ( hashes) => BytePos ( ( 1 + hashes) . into ( ) ) ,
289
+ } ;
290
+
291
+ let newline_sp_len = if self . contents . ends_with ( '\n' ) {
292
+ BytePos ( 1 )
293
+ } else if self . contents . ends_with ( r"\n" ) {
294
+ BytePos ( 2 )
295
+ } else {
296
+ panic ! ( "expected format string to contain a newline" ) ;
297
+ } ;
298
+
299
+ sp. with_lo ( newline_sp_hi - newline_sp_len) . with_hi ( newline_sp_hi)
300
+ }
301
+ }
302
+
253
303
/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
254
- /// options and a bool. The first part of the tuple is `format_str` of the macros. The second part
255
- /// of the tuple is in the `write[ln]!` case the expression the `format_str` should be written to.
256
- /// The final part is a boolean flag indicating if the string is a raw string.
304
+ /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
305
+ /// the contents of the string, whether it's a raw string, and the span of the literal in the
306
+ /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
307
+ /// `format_str` should be written to.
257
308
///
258
309
/// Example:
259
310
///
@@ -266,49 +317,36 @@ impl EarlyLintPass for Write {
266
317
/// ```
267
318
/// will return
268
319
/// ```rust,ignore
269
- /// (Some("string to write: {}"), Some(buf), false )
320
+ /// (Some("string to write: {}"), Some(buf))
270
321
/// ```
271
- fn check_tts < ' a > ( cx : & EarlyContext < ' a > , tts : & TokenStream , is_write : bool ) -> ( Option < String > , Option < Expr > , bool ) {
322
+ fn check_tts < ' a > ( cx : & EarlyContext < ' a > , tts : & TokenStream , is_write : bool ) -> ( Option < FmtStr > , Option < Expr > ) {
272
323
use fmt_macros:: * ;
273
324
let tts = tts. clone ( ) ;
274
- let mut is_raw = false ;
275
- if let TokenStream ( Some ( tokens) ) = & tts {
276
- for token in tokens. iter ( ) {
277
- if let ( TokenTree :: Token ( _, token:: Token :: Literal ( lit) ) , _) = token {
278
- match lit. kind {
279
- token:: Str => break ,
280
- token:: StrRaw ( _) => {
281
- is_raw = true ;
282
- break ;
283
- } ,
284
- _ => { } ,
285
- }
286
- }
287
- }
288
- }
325
+
289
326
let mut parser = parser:: Parser :: new ( & cx. sess . parse_sess , tts, None , false , false , None ) ;
290
327
let mut expr: Option < Expr > = None ;
291
328
if is_write {
292
329
expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
293
330
Ok ( p) => Some ( p. into_inner ( ) ) ,
294
- Err ( _) => return ( None , None , is_raw ) ,
331
+ Err ( _) => return ( None , None ) ,
295
332
} ;
296
333
// might be `writeln!(foo)`
297
334
if parser. expect ( & token:: Comma ) . map_err ( |mut err| err. cancel ( ) ) . is_err ( ) {
298
- return ( None , expr, is_raw ) ;
335
+ return ( None , expr) ;
299
336
}
300
337
}
301
338
302
- let fmtstr = match parser. parse_str ( ) . map_err ( |mut err| err. cancel ( ) ) {
303
- Ok ( token ) => token . 0 . to_string ( ) ,
304
- Err ( _) => return ( None , expr, is_raw ) ,
339
+ let ( fmtstr, fmtstyle ) = match parser. parse_str ( ) . map_err ( |mut err| err. cancel ( ) ) {
340
+ Ok ( ( fmtstr , fmtstyle ) ) => ( fmtstr . to_string ( ) , fmtstyle ) ,
341
+ Err ( _) => return ( None , expr) ,
305
342
} ;
343
+ let fmtspan = parser. prev_span ;
306
344
let tmp = fmtstr. clone ( ) ;
307
345
let mut args = vec ! [ ] ;
308
346
let mut fmt_parser = Parser :: new ( & tmp, None , Vec :: new ( ) , false ) ;
309
347
while let Some ( piece) = fmt_parser. next ( ) {
310
348
if !fmt_parser. errors . is_empty ( ) {
311
- return ( None , expr, is_raw ) ;
349
+ return ( None , expr) ;
312
350
}
313
351
if let Piece :: NextArgument ( arg) = piece {
314
352
if arg. format . ty == "?" {
@@ -330,11 +368,26 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
330
368
ty : "" ,
331
369
} ;
332
370
if !parser. eat ( & token:: Comma ) {
333
- return ( Some ( fmtstr) , expr, is_raw) ;
371
+ return (
372
+ Some ( FmtStr {
373
+ contents : fmtstr,
374
+ style : fmtstyle,
375
+ span : fmtspan,
376
+ } ) ,
377
+ expr,
378
+ ) ;
334
379
}
335
- let token_expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
336
- Ok ( expr) => expr,
337
- Err ( _) => return ( Some ( fmtstr) , None , is_raw) ,
380
+ let token_expr = if let Ok ( expr) = parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
381
+ expr
382
+ } else {
383
+ return (
384
+ Some ( FmtStr {
385
+ contents : fmtstr,
386
+ style : fmtstyle,
387
+ span : fmtspan,
388
+ } ) ,
389
+ None ,
390
+ ) ;
338
391
} ;
339
392
match & token_expr. node {
340
393
ExprKind :: Lit ( _) => {
@@ -383,12 +436,15 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
383
436
}
384
437
}
385
438
386
- // Checks if `s` constains a single newline that terminates it
387
- // Literal and escaped newlines are both checked (only literal for raw strings)
388
- fn check_newlines ( s : & str , is_raw : bool ) -> bool {
439
+ /// Checks if the format string constains a single newline that terminates it.
440
+ ///
441
+ /// Literal and escaped newlines are both checked (only literal for raw strings).
442
+ fn check_newlines ( fmt_str : & FmtStr ) -> bool {
443
+ let s = & fmt_str. contents ;
444
+
389
445
if s. ends_with ( '\n' ) {
390
446
return true ;
391
- } else if is_raw {
447
+ } else if let StrStyle :: Raw ( _ ) = fmt_str . style {
392
448
return false ;
393
449
}
394
450
0 commit comments