11use clippy_utils:: diagnostics:: { span_lint, span_lint_and_then} ;
2- use clippy_utils:: macros:: { root_macro_call_first_node, FormatArgsExpn , MacroCall } ;
2+ use clippy_utils:: macros:: {
3+ find_format_args, format_arg_removal_span, populate_ast_format_args, root_macro_call_first_node, MacroCall ,
4+ } ;
35use clippy_utils:: source:: { expand_past_previous_comma, snippet_opt} ;
46use clippy_utils:: { is_in_cfg_test, is_in_test_function} ;
5- use rustc_ast:: LitKind ;
7+ use rustc_ast:: token:: LitKind ;
8+ use rustc_ast:: { FormatArgPosition , FormatArgs , FormatArgsPiece , FormatOptions , FormatPlaceholder , FormatTrait } ;
69use rustc_errors:: Applicability ;
7- use rustc_hir:: { Expr , ExprKind , HirIdMap , Impl , Item , ItemKind } ;
8- use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
10+ use rustc_hir:: { Expr , Impl , Item , ItemKind } ;
11+ use rustc_lint:: { EarlyLintPass , LateContext , LateLintPass , LintContext } ;
912use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
1013use rustc_span:: { sym, BytePos } ;
1114
@@ -257,6 +260,12 @@ impl_lint_pass!(Write => [
257260 WRITE_LITERAL ,
258261] ) ;
259262
263+ impl EarlyLintPass for Write {
264+ fn check_expr ( & mut self , _: & rustc_lint:: EarlyContext < ' _ > , expr : & rustc_ast:: Expr ) {
265+ populate_ast_format_args ( expr) ;
266+ }
267+ }
268+
260269impl < ' tcx > LateLintPass < ' tcx > for Write {
261270 fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
262271 if is_debug_impl ( cx, item) {
@@ -297,34 +306,40 @@ impl<'tcx> LateLintPass<'tcx> for Write {
297306 _ => return ,
298307 }
299308
300- let Some ( format_args) = FormatArgsExpn :: find_nested ( cx, expr, macro_call. expn ) else { return } ;
301-
302- // ignore `writeln!(w)` and `write!(v, some_macro!())`
303- if format_args. format_string . span . from_expansion ( ) {
304- return ;
305- }
309+ find_format_args ( cx, expr, macro_call. expn , |format_args| {
310+ // ignore `writeln!(w)` and `write!(v, some_macro!())`
311+ if format_args. span . from_expansion ( ) {
312+ return ;
313+ }
306314
307- match diag_name {
308- sym:: print_macro | sym:: eprint_macro | sym:: write_macro => {
309- check_newline ( cx, & format_args, & macro_call, name) ;
310- } ,
311- sym:: println_macro | sym:: eprintln_macro | sym:: writeln_macro => {
312- check_empty_string ( cx, & format_args, & macro_call, name) ;
313- } ,
314- _ => { } ,
315- }
315+ match diag_name {
316+ sym:: print_macro | sym:: eprint_macro | sym:: write_macro => {
317+ check_newline ( cx, format_args, & macro_call, name) ;
318+ } ,
319+ sym:: println_macro | sym:: eprintln_macro | sym:: writeln_macro => {
320+ check_empty_string ( cx, format_args, & macro_call, name) ;
321+ } ,
322+ _ => { } ,
323+ }
316324
317- check_literal ( cx, & format_args, name) ;
325+ check_literal ( cx, format_args, name) ;
318326
319- if !self . in_debug_impl {
320- for arg in & format_args. args {
321- if arg. format . r#trait == sym:: Debug {
322- span_lint ( cx, USE_DEBUG , arg. span , "use of `Debug`-based formatting" ) ;
327+ if !self . in_debug_impl {
328+ for piece in & format_args. template {
329+ if let & FormatArgsPiece :: Placeholder ( FormatPlaceholder {
330+ span : Some ( span) ,
331+ format_trait : FormatTrait :: Debug ,
332+ ..
333+ } ) = piece
334+ {
335+ span_lint ( cx, USE_DEBUG , span, "use of `Debug`-based formatting" ) ;
336+ }
323337 }
324338 }
325- }
339+ } ) ;
326340 }
327341}
342+
328343fn is_debug_impl ( cx : & LateContext < ' _ > , item : & Item < ' _ > ) -> bool {
329344 if let ItemKind :: Impl ( Impl { of_trait : Some ( trait_ref) , .. } ) = & item. kind
330345 && let Some ( trait_id) = trait_ref. trait_def_id ( )
@@ -335,27 +350,28 @@ fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
335350 }
336351}
337352
338- fn check_newline ( cx : & LateContext < ' _ > , format_args : & FormatArgsExpn < ' _ > , macro_call : & MacroCall , name : & str ) {
339- let format_string_parts = & format_args. format_string . parts ;
340- let mut format_string_span = format_args. format_string . span ;
341-
342- let Some ( last) = format_string_parts. last ( ) else { return } ;
353+ fn check_newline ( cx : & LateContext < ' _ > , format_args : & FormatArgs , macro_call : & MacroCall , name : & str ) {
354+ let Some ( FormatArgsPiece :: Literal ( last) ) = format_args. template . last ( ) else { return } ;
343355
344356 let count_vertical_whitespace = || {
345- format_string_parts
357+ format_args
358+ . template
346359 . iter ( )
347- . flat_map ( |part| part. as_str ( ) . chars ( ) )
360+ . filter_map ( |piece| match piece {
361+ FormatArgsPiece :: Literal ( literal) => Some ( literal) ,
362+ FormatArgsPiece :: Placeholder ( _) => None ,
363+ } )
364+ . flat_map ( |literal| literal. as_str ( ) . chars ( ) )
348365 . filter ( |ch| matches ! ( ch, '\r' | '\n' ) )
349366 . count ( )
350367 } ;
351368
352369 if last. as_str ( ) . ends_with ( '\n' )
353370 // ignore format strings with other internal vertical whitespace
354371 && count_vertical_whitespace ( ) == 1
355-
356- // ignore trailing arguments: `print!("Issue\n{}", 1265);`
357- && format_string_parts. len ( ) > format_args. args . len ( )
358372 {
373+ let mut format_string_span = format_args. span ;
374+
359375 let lint = if name == "write" {
360376 format_string_span = expand_past_previous_comma ( cx, format_string_span) ;
361377
@@ -373,7 +389,7 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
373389 let name_span = cx. sess ( ) . source_map ( ) . span_until_char ( macro_call. span , '!' ) ;
374390 let Some ( format_snippet) = snippet_opt ( cx, format_string_span) else { return } ;
375391
376- if format_string_parts . len ( ) == 1 && last. as_str ( ) == "\n " {
392+ if format_args . template . len ( ) == 1 && last. as_str ( ) == "\n " {
377393 // print!("\n"), write!(f, "\n")
378394
379395 diag. multipart_suggestion (
@@ -398,11 +414,12 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
398414 }
399415}
400416
401- fn check_empty_string ( cx : & LateContext < ' _ > , format_args : & FormatArgsExpn < ' _ > , macro_call : & MacroCall , name : & str ) {
402- if let [ part] = & format_args. format_string . parts [ ..]
403- && let mut span = format_args. format_string . span
404- && part. as_str ( ) == "\n "
417+ fn check_empty_string ( cx : & LateContext < ' _ > , format_args : & FormatArgs , macro_call : & MacroCall , name : & str ) {
418+ if let [ FormatArgsPiece :: Literal ( literal) ] = & format_args. template [ ..]
419+ && literal. as_str ( ) == "\n "
405420 {
421+ let mut span = format_args. span ;
422+
406423 let lint = if name == "writeln" {
407424 span = expand_past_previous_comma ( cx, span) ;
408425
@@ -428,33 +445,43 @@ fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, ma
428445 }
429446}
430447
431- fn check_literal ( cx : & LateContext < ' _ > , format_args : & FormatArgsExpn < ' _ > , name : & str ) {
432- let mut counts = HirIdMap :: < usize > :: default ( ) ;
433- for param in format_args. params ( ) {
434- * counts. entry ( param. value . hir_id ) . or_default ( ) += 1 ;
448+ fn check_literal ( cx : & LateContext < ' _ > , format_args : & FormatArgs , name : & str ) {
449+ let arg_index = |argument : & FormatArgPosition | argument. index . unwrap_or_else ( |pos| pos) ;
450+
451+ let mut counts = vec ! [ 0u32 ; format_args. arguments. all_args( ) . len( ) ] ;
452+ for piece in & format_args. template {
453+ if let FormatArgsPiece :: Placeholder ( placeholder) = piece {
454+ counts[ arg_index ( & placeholder. argument ) ] += 1 ;
455+ }
435456 }
436457
437- for arg in & format_args. args {
438- let value = arg. param . value ;
439-
440- if counts[ & value. hir_id ] == 1
441- && arg. format . is_default ( )
442- && let ExprKind :: Lit ( lit) = & value. kind
443- && !value. span . from_expansion ( )
444- && let Some ( value_string) = snippet_opt ( cx, value. span )
445- {
446- let ( replacement, replace_raw) = match lit. node {
447- LitKind :: Str ( ..) => extract_str_literal ( & value_string) ,
448- LitKind :: Char ( ch) => (
449- match ch {
450- '"' => "\\ \" " ,
451- '\'' => "'" ,
458+ for piece in & format_args. template {
459+ if let FormatArgsPiece :: Placeholder ( FormatPlaceholder {
460+ argument,
461+ span : Some ( placeholder_span) ,
462+ format_trait : FormatTrait :: Display ,
463+ format_options,
464+ } ) = piece
465+ && * format_options == FormatOptions :: default ( )
466+ && let index = arg_index ( argument)
467+ && counts[ index] == 1
468+ && let Some ( arg) = format_args. arguments . by_index ( index)
469+ && let rustc_ast:: ExprKind :: Lit ( lit) = & arg. expr . kind
470+ && !arg. expr . span . from_expansion ( )
471+ && let Some ( value_string) = snippet_opt ( cx, arg. expr . span )
472+ {
473+ let ( replacement, replace_raw) = match lit. kind {
474+ LitKind :: Str | LitKind :: StrRaw ( _) => extract_str_literal ( & value_string) ,
475+ LitKind :: Char => (
476+ match lit. symbol . as_str ( ) {
477+ "\" " => "\\ \" " ,
478+ "\\ '" => "'" ,
452479 _ => & value_string[ 1 ..value_string. len ( ) - 1 ] ,
453480 }
454481 . to_string ( ) ,
455482 false ,
456483 ) ,
457- LitKind :: Bool ( b ) => ( b . to_string ( ) , false ) ,
484+ LitKind :: Bool => ( lit . symbol . to_string ( ) , false ) ,
458485 _ => continue ,
459486 } ;
460487
@@ -464,7 +491,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
464491 PRINT_LITERAL
465492 } ;
466493
467- let format_string_is_raw = format_args. format_string . style . is_some ( ) ;
494+ let Some ( format_string_snippet) = snippet_opt ( cx, format_args. span ) else { continue } ;
495+ let format_string_is_raw = format_string_snippet. starts_with ( 'r' ) ;
496+
468497 let replacement = match ( format_string_is_raw, replace_raw) {
469498 ( false , false ) => Some ( replacement) ,
470499 ( false , true ) => Some ( replacement. replace ( '"' , "\\ \" " ) . replace ( '\\' , "\\ \\ " ) ) ,
@@ -485,23 +514,24 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
485514 span_lint_and_then (
486515 cx,
487516 lint,
488- value . span ,
517+ arg . expr . span ,
489518 "literal with an empty format string" ,
490519 |diag| {
491520 if let Some ( replacement) = replacement
492521 // `format!("{}", "a")`, `format!("{named}", named = "b")
493522 // ~~~~~ ~~~~~~~~~~~~~
494- && let Some ( value_span ) = format_args . value_with_prev_comma_span ( value . hir_id )
523+ && let Some ( removal_span ) = format_arg_removal_span ( format_args , index )
495524 {
496525 let replacement = replacement. replace ( '{' , "{{" ) . replace ( '}' , "}}" ) ;
497526 diag. multipart_suggestion (
498527 "try this" ,
499- vec ! [ ( arg . span , replacement) , ( value_span , String :: new( ) ) ] ,
528+ vec ! [ ( * placeholder_span , replacement) , ( removal_span , String :: new( ) ) ] ,
500529 Applicability :: MachineApplicable ,
501530 ) ;
502531 }
503532 } ,
504533 ) ;
534+
505535 }
506536 }
507537}
0 commit comments