@@ -6,7 +6,7 @@ use rustc_errors::Applicability;
6
6
use rustc_lexer:: unescape:: { self , EscapeError } ;
7
7
use rustc_lint:: { EarlyContext , EarlyLintPass } ;
8
8
use rustc_parse:: parser;
9
- use rustc_session:: { declare_lint_pass , declare_tool_lint } ;
9
+ use rustc_session:: { declare_tool_lint , impl_lint_pass } ;
10
10
use rustc_span:: symbol:: Symbol ;
11
11
use rustc_span:: { BytePos , Span } ;
12
12
use syntax:: ast:: * ;
@@ -175,7 +175,12 @@ declare_clippy_lint! {
175
175
"writing a literal with a format string"
176
176
}
177
177
178
- declare_lint_pass ! ( Write => [
178
+ #[ derive( Default ) ]
179
+ pub struct Write {
180
+ in_debug_impl : bool ,
181
+ }
182
+
183
+ impl_lint_pass ! ( Write => [
179
184
PRINT_WITH_NEWLINE ,
180
185
PRINTLN_EMPTY_STRING ,
181
186
PRINT_STDOUT ,
@@ -187,10 +192,35 @@ declare_lint_pass!(Write => [
187
192
] ) ;
188
193
189
194
impl EarlyLintPass for Write {
195
+ fn check_item ( & mut self , _: & EarlyContext < ' _ > , item : & Item ) {
196
+ if let ItemKind :: Impl {
197
+ of_trait : Some ( trait_ref) ,
198
+ ..
199
+ } = & item. kind
200
+ {
201
+ let trait_name = trait_ref
202
+ . path
203
+ . segments
204
+ . iter ( )
205
+ . last ( )
206
+ . expect ( "path has at least one segment" )
207
+ . ident
208
+ . name
209
+ . as_str ( ) ;
210
+ if trait_name == "Debug" {
211
+ self . in_debug_impl = true ;
212
+ }
213
+ }
214
+ }
215
+
216
+ fn check_item_post ( & mut self , _: & EarlyContext < ' _ > , _: & Item ) {
217
+ self . in_debug_impl = false ;
218
+ }
219
+
190
220
fn check_mac ( & mut self , cx : & EarlyContext < ' _ > , mac : & Mac ) {
191
221
if mac. path == sym ! ( println) {
192
222
span_lint ( cx, PRINT_STDOUT , mac. span ( ) , "use of `println!`" ) ;
193
- if let ( Some ( fmt_str) , _) = check_tts ( cx, & mac. args . inner_tokens ( ) , false ) {
223
+ if let ( Some ( fmt_str) , _) = self . check_tts ( cx, & mac. args . inner_tokens ( ) , false ) {
194
224
if fmt_str. symbol == Symbol :: intern ( "" ) {
195
225
span_lint_and_sugg (
196
226
cx,
@@ -205,7 +235,7 @@ impl EarlyLintPass for Write {
205
235
}
206
236
} else if mac. path == sym ! ( print) {
207
237
span_lint ( cx, PRINT_STDOUT , mac. span ( ) , "use of `print!`" ) ;
208
- if let ( Some ( fmt_str) , _) = check_tts ( cx, & mac. args . inner_tokens ( ) , false ) {
238
+ if let ( Some ( fmt_str) , _) = self . check_tts ( cx, & mac. args . inner_tokens ( ) , false ) {
209
239
if check_newlines ( & fmt_str) {
210
240
span_lint_and_then (
211
241
cx,
@@ -226,7 +256,7 @@ impl EarlyLintPass for Write {
226
256
}
227
257
}
228
258
} else if mac. path == sym ! ( write) {
229
- if let ( Some ( fmt_str) , _) = check_tts ( cx, & mac. args . inner_tokens ( ) , true ) {
259
+ if let ( Some ( fmt_str) , _) = self . check_tts ( cx, & mac. args . inner_tokens ( ) , true ) {
230
260
if check_newlines ( & fmt_str) {
231
261
span_lint_and_then (
232
262
cx,
@@ -247,7 +277,7 @@ impl EarlyLintPass for Write {
247
277
}
248
278
}
249
279
} else if mac. path == sym ! ( writeln) {
250
- if let ( Some ( fmt_str) , expr) = check_tts ( cx, & mac. args . inner_tokens ( ) , true ) {
280
+ if let ( Some ( fmt_str) , expr) = self . check_tts ( cx, & mac. args . inner_tokens ( ) , true ) {
251
281
if fmt_str. symbol == Symbol :: intern ( "" ) {
252
282
let mut applicability = Applicability :: MachineApplicable ;
253
283
let suggestion = expr. map_or_else (
@@ -273,6 +303,138 @@ impl EarlyLintPass for Write {
273
303
}
274
304
}
275
305
306
+ impl Write {
307
+ /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
308
+ /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
309
+ /// the contents of the string, whether it's a raw string, and the span of the literal in the
310
+ /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
311
+ /// `format_str` should be written to.
312
+ ///
313
+ /// Example:
314
+ ///
315
+ /// Calling this function on
316
+ /// ```rust
317
+ /// # use std::fmt::Write;
318
+ /// # let mut buf = String::new();
319
+ /// # let something = "something";
320
+ /// writeln!(buf, "string to write: {}", something);
321
+ /// ```
322
+ /// will return
323
+ /// ```rust,ignore
324
+ /// (Some("string to write: {}"), Some(buf))
325
+ /// ```
326
+ #[ allow( clippy:: too_many_lines) ]
327
+ fn check_tts < ' a > (
328
+ & self ,
329
+ cx : & EarlyContext < ' a > ,
330
+ tts : & TokenStream ,
331
+ is_write : bool ,
332
+ ) -> ( Option < StrLit > , Option < Expr > ) {
333
+ use fmt_macros:: * ;
334
+ let tts = tts. clone ( ) ;
335
+
336
+ let mut parser = parser:: Parser :: new ( & cx. sess . parse_sess , tts, None , false , false , None ) ;
337
+ let mut expr: Option < Expr > = None ;
338
+ if is_write {
339
+ expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
340
+ Ok ( p) => Some ( p. into_inner ( ) ) ,
341
+ Err ( _) => return ( None , None ) ,
342
+ } ;
343
+ // might be `writeln!(foo)`
344
+ if parser. expect ( & token:: Comma ) . map_err ( |mut err| err. cancel ( ) ) . is_err ( ) {
345
+ return ( None , expr) ;
346
+ }
347
+ }
348
+
349
+ let fmtstr = match parser. parse_str_lit ( ) {
350
+ Ok ( fmtstr) => fmtstr,
351
+ Err ( _) => return ( None , expr) ,
352
+ } ;
353
+ let tmp = fmtstr. symbol . as_str ( ) ;
354
+ let mut args = vec ! [ ] ;
355
+ let mut fmt_parser = Parser :: new ( & tmp, None , Vec :: new ( ) , false ) ;
356
+ while let Some ( piece) = fmt_parser. next ( ) {
357
+ if !fmt_parser. errors . is_empty ( ) {
358
+ return ( None , expr) ;
359
+ }
360
+ if let Piece :: NextArgument ( arg) = piece {
361
+ if !self . in_debug_impl && arg. format . ty == "?" {
362
+ // FIXME: modify rustc's fmt string parser to give us the current span
363
+ span_lint ( cx, USE_DEBUG , parser. prev_span , "use of `Debug`-based formatting" ) ;
364
+ }
365
+ args. push ( arg) ;
366
+ }
367
+ }
368
+ let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL } ;
369
+ let mut idx = 0 ;
370
+ loop {
371
+ const SIMPLE : FormatSpec < ' _ > = FormatSpec {
372
+ fill : None ,
373
+ align : AlignUnknown ,
374
+ flags : 0 ,
375
+ precision : CountImplied ,
376
+ precision_span : None ,
377
+ width : CountImplied ,
378
+ width_span : None ,
379
+ ty : "" ,
380
+ ty_span : None ,
381
+ } ;
382
+ if !parser. eat ( & token:: Comma ) {
383
+ return ( Some ( fmtstr) , expr) ;
384
+ }
385
+ let token_expr = if let Ok ( expr) = parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
386
+ expr
387
+ } else {
388
+ return ( Some ( fmtstr) , None ) ;
389
+ } ;
390
+ match & token_expr. kind {
391
+ ExprKind :: Lit ( _) => {
392
+ let mut all_simple = true ;
393
+ let mut seen = false ;
394
+ for arg in & args {
395
+ match arg. position {
396
+ ArgumentImplicitlyIs ( n) | ArgumentIs ( n) => {
397
+ if n == idx {
398
+ all_simple &= arg. format == SIMPLE ;
399
+ seen = true ;
400
+ }
401
+ } ,
402
+ ArgumentNamed ( _) => { } ,
403
+ }
404
+ }
405
+ if all_simple && seen {
406
+ span_lint ( cx, lint, token_expr. span , "literal with an empty format string" ) ;
407
+ }
408
+ idx += 1 ;
409
+ } ,
410
+ ExprKind :: Assign ( lhs, rhs, _) => {
411
+ if let ExprKind :: Lit ( _) = rhs. kind {
412
+ if let ExprKind :: Path ( _, p) = & lhs. kind {
413
+ let mut all_simple = true ;
414
+ let mut seen = false ;
415
+ for arg in & args {
416
+ match arg. position {
417
+ ArgumentImplicitlyIs ( _) | ArgumentIs ( _) => { } ,
418
+ ArgumentNamed ( name) => {
419
+ if * p == name {
420
+ seen = true ;
421
+ all_simple &= arg. format == SIMPLE ;
422
+ }
423
+ } ,
424
+ }
425
+ }
426
+ if all_simple && seen {
427
+ span_lint ( cx, lint, rhs. span , "literal with an empty format string" ) ;
428
+ }
429
+ }
430
+ }
431
+ } ,
432
+ _ => idx += 1 ,
433
+ }
434
+ }
435
+ }
436
+ }
437
+
276
438
/// Given a format string that ends in a newline and its span, calculates the span of the
277
439
/// newline.
278
440
fn newline_span ( fmtstr : & StrLit ) -> Span {
@@ -296,131 +458,6 @@ fn newline_span(fmtstr: &StrLit) -> Span {
296
458
sp. with_lo ( newline_sp_hi - newline_sp_len) . with_hi ( newline_sp_hi)
297
459
}
298
460
299
- /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
300
- /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
301
- /// the contents of the string, whether it's a raw string, and the span of the literal in the
302
- /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
303
- /// `format_str` should be written to.
304
- ///
305
- /// Example:
306
- ///
307
- /// Calling this function on
308
- /// ```rust
309
- /// # use std::fmt::Write;
310
- /// # let mut buf = String::new();
311
- /// # let something = "something";
312
- /// writeln!(buf, "string to write: {}", something);
313
- /// ```
314
- /// will return
315
- /// ```rust,ignore
316
- /// (Some("string to write: {}"), Some(buf))
317
- /// ```
318
- #[ allow( clippy:: too_many_lines) ]
319
- fn check_tts < ' a > ( cx : & EarlyContext < ' a > , tts : & TokenStream , is_write : bool ) -> ( Option < StrLit > , Option < Expr > ) {
320
- use fmt_macros:: * ;
321
- let tts = tts. clone ( ) ;
322
-
323
- let mut parser = parser:: Parser :: new ( & cx. sess . parse_sess , tts, None , false , false , None ) ;
324
- let mut expr: Option < Expr > = None ;
325
- if is_write {
326
- expr = match parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
327
- Ok ( p) => Some ( p. into_inner ( ) ) ,
328
- Err ( _) => return ( None , None ) ,
329
- } ;
330
- // might be `writeln!(foo)`
331
- if parser. expect ( & token:: Comma ) . map_err ( |mut err| err. cancel ( ) ) . is_err ( ) {
332
- return ( None , expr) ;
333
- }
334
- }
335
-
336
- let fmtstr = match parser. parse_str_lit ( ) {
337
- Ok ( fmtstr) => fmtstr,
338
- Err ( _) => return ( None , expr) ,
339
- } ;
340
- let tmp = fmtstr. symbol . as_str ( ) ;
341
- let mut args = vec ! [ ] ;
342
- let mut fmt_parser = Parser :: new ( & tmp, None , Vec :: new ( ) , false ) ;
343
- while let Some ( piece) = fmt_parser. next ( ) {
344
- if !fmt_parser. errors . is_empty ( ) {
345
- return ( None , expr) ;
346
- }
347
- if let Piece :: NextArgument ( arg) = piece {
348
- if arg. format . ty == "?" {
349
- // FIXME: modify rustc's fmt string parser to give us the current span
350
- span_lint ( cx, USE_DEBUG , parser. prev_span , "use of `Debug`-based formatting" ) ;
351
- }
352
- args. push ( arg) ;
353
- }
354
- }
355
- let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL } ;
356
- let mut idx = 0 ;
357
- loop {
358
- const SIMPLE : FormatSpec < ' _ > = FormatSpec {
359
- fill : None ,
360
- align : AlignUnknown ,
361
- flags : 0 ,
362
- precision : CountImplied ,
363
- precision_span : None ,
364
- width : CountImplied ,
365
- width_span : None ,
366
- ty : "" ,
367
- ty_span : None ,
368
- } ;
369
- if !parser. eat ( & token:: Comma ) {
370
- return ( Some ( fmtstr) , expr) ;
371
- }
372
- let token_expr = if let Ok ( expr) = parser. parse_expr ( ) . map_err ( |mut err| err. cancel ( ) ) {
373
- expr
374
- } else {
375
- return ( Some ( fmtstr) , None ) ;
376
- } ;
377
- match & token_expr. kind {
378
- ExprKind :: Lit ( _) => {
379
- let mut all_simple = true ;
380
- let mut seen = false ;
381
- for arg in & args {
382
- match arg. position {
383
- ArgumentImplicitlyIs ( n) | ArgumentIs ( n) => {
384
- if n == idx {
385
- all_simple &= arg. format == SIMPLE ;
386
- seen = true ;
387
- }
388
- } ,
389
- ArgumentNamed ( _) => { } ,
390
- }
391
- }
392
- if all_simple && seen {
393
- span_lint ( cx, lint, token_expr. span , "literal with an empty format string" ) ;
394
- }
395
- idx += 1 ;
396
- } ,
397
- ExprKind :: Assign ( lhs, rhs, _) => {
398
- if let ExprKind :: Lit ( _) = rhs. kind {
399
- if let ExprKind :: Path ( _, p) = & lhs. kind {
400
- let mut all_simple = true ;
401
- let mut seen = false ;
402
- for arg in & args {
403
- match arg. position {
404
- ArgumentImplicitlyIs ( _) | ArgumentIs ( _) => { } ,
405
- ArgumentNamed ( name) => {
406
- if * p == name {
407
- seen = true ;
408
- all_simple &= arg. format == SIMPLE ;
409
- }
410
- } ,
411
- }
412
- }
413
- if all_simple && seen {
414
- span_lint ( cx, lint, rhs. span , "literal with an empty format string" ) ;
415
- }
416
- }
417
- }
418
- } ,
419
- _ => idx += 1 ,
420
- }
421
- }
422
- }
423
-
424
461
/// Checks if the format string contains a single newline that terminates it.
425
462
///
426
463
/// Literal and escaped newlines are both checked (only literal for raw strings).
0 commit comments