| 
1 |  | -use rustc_ast::ast::{Expr, ExprKind};  | 
2 |  | -use rustc_ast::token::LitKind;  | 
3 |  | -use rustc_lint::{EarlyContext, EarlyLintPass};  | 
 | 1 | +use rustc_ast::{LitKind, StrStyle};  | 
 | 2 | +use rustc_hir::{Expr, ExprKind};  | 
 | 3 | +use rustc_lexer::is_ident;  | 
 | 4 | +use rustc_lint::{LateContext, LateLintPass};  | 
4 | 5 | use rustc_parse_format::{ParseMode, Parser, Piece};  | 
5 | 6 | use rustc_session::declare_lint_pass;  | 
6 | 7 | use rustc_span::BytePos;  | 
7 | 8 | 
 
  | 
8 | 9 | use clippy_utils::diagnostics::span_lint;  | 
 | 10 | +use clippy_utils::mir::enclosing_mir;  | 
9 | 11 | 
 
  | 
10 | 12 | declare_clippy_lint! {  | 
11 | 13 |     /// ### What it does  | 
@@ -35,18 +37,23 @@ declare_clippy_lint! {  | 
35 | 37 | 
 
  | 
36 | 38 | declare_lint_pass!(LiteralStringWithFormattingArg => [LITERAL_STRING_WITH_FORMATTING_ARGS]);  | 
37 | 39 | 
 
  | 
38 |  | -impl EarlyLintPass for LiteralStringWithFormattingArg {  | 
39 |  | -    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {  | 
 | 40 | +impl LateLintPass<'_> for LiteralStringWithFormattingArg {  | 
 | 41 | +    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {  | 
40 | 42 |         if expr.span.from_expansion() {  | 
41 | 43 |             return;  | 
42 | 44 |         }  | 
43 | 45 |         if let ExprKind::Lit(lit) = expr.kind {  | 
44 |  | -            let add = match lit.kind {  | 
45 |  | -                LitKind::Str => 1,  | 
46 |  | -                LitKind::StrRaw(nb) => nb as usize + 2,  | 
 | 46 | +            let (add, symbol) = match lit.node {  | 
 | 47 | +                LitKind::Str(symbol, style) => {  | 
 | 48 | +                    let add = match style {  | 
 | 49 | +                        StrStyle::Cooked => 1,  | 
 | 50 | +                        StrStyle::Raw(nb) => nb as usize + 2,  | 
 | 51 | +                    };  | 
 | 52 | +                    (add, symbol)  | 
 | 53 | +                }  | 
47 | 54 |                 _ => return,  | 
48 | 55 |             };  | 
49 |  | -            let fmt_str = lit.symbol.as_str();  | 
 | 56 | +            let fmt_str = symbol.as_str();  | 
50 | 57 |             let lo = expr.span.lo();  | 
51 | 58 |             let mut current = fmt_str;  | 
52 | 59 |             let mut diff_len = 0;  | 
@@ -82,31 +89,63 @@ impl EarlyLintPass for LiteralStringWithFormattingArg {  | 
82 | 89 |                         .find('}')  | 
83 | 90 |                         .map_or(pos.end, |found| start + 1 + found)  | 
84 | 91 |                         + 1;  | 
85 |  | -                    spans.push(  | 
 | 92 | +                    let ident_start = start + 1;  | 
 | 93 | +                    let colon_pos = fmt_str[ident_start..end].find(':');  | 
 | 94 | +                    let ident_end = colon_pos.unwrap_or(end - 1);  | 
 | 95 | +                    let mut name = None;  | 
 | 96 | +                    if ident_start < ident_end  | 
 | 97 | +                        && let arg = &fmt_str[ident_start..ident_end]  | 
 | 98 | +                        && !arg.is_empty()  | 
 | 99 | +                    {  | 
 | 100 | +                        if is_ident(arg) {  | 
 | 101 | +                            name = Some(arg.to_string());  | 
 | 102 | +                        } else if colon_pos.is_none() && !arg.chars().all(|c| c.is_ascii_digit()) {  | 
 | 103 | +                            // Not a `{0}` or a `{0:?}`.  | 
 | 104 | +                            continue;  | 
 | 105 | +                        }  | 
 | 106 | +                    } else if colon_pos.is_none() {  | 
 | 107 | +                        // Not a `{:?}`.  | 
 | 108 | +                        continue;  | 
 | 109 | +                    }  | 
 | 110 | +                    spans.push((  | 
86 | 111 |                         expr.span  | 
87 | 112 |                             .with_hi(lo + BytePos((start + add).try_into().unwrap()))  | 
88 | 113 |                             .with_lo(lo + BytePos((end + add).try_into().unwrap())),  | 
89 |  | -                    );  | 
 | 114 | +                        name,  | 
 | 115 | +                    ));  | 
90 | 116 |                 }  | 
91 | 117 |             }  | 
92 |  | -            match spans.len() {  | 
93 |  | -                0 => {},  | 
94 |  | -                1 => {  | 
95 |  | -                    span_lint(  | 
96 |  | -                        cx,  | 
97 |  | -                        LITERAL_STRING_WITH_FORMATTING_ARGS,  | 
98 |  | -                        spans,  | 
99 |  | -                        "this looks like a formatting argument but it is not part of a formatting macro",  | 
100 |  | -                    );  | 
101 |  | -                },  | 
102 |  | -                _ => {  | 
103 |  | -                    span_lint(  | 
104 |  | -                        cx,  | 
105 |  | -                        LITERAL_STRING_WITH_FORMATTING_ARGS,  | 
106 |  | -                        spans,  | 
107 |  | -                        "these look like formatting arguments but are not part of a formatting macro",  | 
108 |  | -                    );  | 
109 |  | -                },  | 
 | 118 | +            if !spans.is_empty()  | 
 | 119 | +                && let Some(mir) = enclosing_mir(cx.tcx, expr.hir_id)  | 
 | 120 | +            {  | 
 | 121 | +                let spans = spans.iter().filter_map(|(span, name)| {  | 
 | 122 | +                    if let Some(name) = name {  | 
 | 123 | +                        // We need to check that the name is a local.  | 
 | 124 | +                        if !mir.var_debug_info.iter().any(|local| local.name.as_str() == name) {  | 
 | 125 | +                            return None;  | 
 | 126 | +                        }  | 
 | 127 | +                    }  | 
 | 128 | +                    Some(*span)  | 
 | 129 | +                }).collect::<Vec<_>>();  | 
 | 130 | +                match spans.len() {  | 
 | 131 | +                    0 => {}  | 
 | 132 | +                    1 => {  | 
 | 133 | +                        span_lint(  | 
 | 134 | +                            cx,  | 
 | 135 | +                            LITERAL_STRING_WITH_FORMATTING_ARGS,  | 
 | 136 | +                            spans,  | 
 | 137 | +                            "this looks like a formatting argument but it is not part of a formatting macro",  | 
 | 138 | +                        );  | 
 | 139 | +                    }  | 
 | 140 | +                    _ => {  | 
 | 141 | +                        span_lint(  | 
 | 142 | +                            cx,  | 
 | 143 | +                            LITERAL_STRING_WITH_FORMATTING_ARGS,  | 
 | 144 | +                            spans,  | 
 | 145 | +                            "these look like formatting arguments but are not part of a formatting macro",  | 
 | 146 | +                        );  | 
 | 147 | +                    }  | 
 | 148 | +                }  | 
110 | 149 |             }  | 
111 | 150 |         }  | 
112 | 151 |     }  | 
 | 
0 commit comments