|
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,66 @@ 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 |
| 122 | + .iter() |
| 123 | + .filter_map(|(span, name)| { |
| 124 | + if let Some(name) = name { |
| 125 | + // We need to check that the name is a local. |
| 126 | + if !mir.var_debug_info.iter().any(|local| local.name.as_str() == name) { |
| 127 | + return None; |
| 128 | + } |
| 129 | + } |
| 130 | + Some(*span) |
| 131 | + }) |
| 132 | + .collect::<Vec<_>>(); |
| 133 | + match spans.len() { |
| 134 | + 0 => {}, |
| 135 | + 1 => { |
| 136 | + span_lint( |
| 137 | + cx, |
| 138 | + LITERAL_STRING_WITH_FORMATTING_ARGS, |
| 139 | + spans, |
| 140 | + "this looks like a formatting argument but it is not part of a formatting macro", |
| 141 | + ); |
| 142 | + }, |
| 143 | + _ => { |
| 144 | + span_lint( |
| 145 | + cx, |
| 146 | + LITERAL_STRING_WITH_FORMATTING_ARGS, |
| 147 | + spans, |
| 148 | + "these look like formatting arguments but are not part of a formatting macro", |
| 149 | + ); |
| 150 | + }, |
| 151 | + } |
110 | 152 | } |
111 | 153 | } |
112 | 154 | } |
|
0 commit comments