|
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 |
| -use rustc_span::BytePos; |
| 7 | +use rustc_span::{BytePos, Span}; |
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,65 @@ 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 | +fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, spans: &[(Span, Option<String>)]) { |
| 41 | + if !spans.is_empty() |
| 42 | + && let Some(mir) = enclosing_mir(cx.tcx, expr.hir_id) |
| 43 | + { |
| 44 | + let spans = spans |
| 45 | + .iter() |
| 46 | + .filter_map(|(span, name)| { |
| 47 | + if let Some(name) = name { |
| 48 | + // We need to check that the name is a local. |
| 49 | + if !mir |
| 50 | + .var_debug_info |
| 51 | + .iter() |
| 52 | + .any(|local| !local.source_info.span.from_expansion() && local.name.as_str() == name) |
| 53 | + { |
| 54 | + return None; |
| 55 | + } |
| 56 | + } |
| 57 | + Some(*span) |
| 58 | + }) |
| 59 | + .collect::<Vec<_>>(); |
| 60 | + match spans.len() { |
| 61 | + 0 => {}, |
| 62 | + 1 => { |
| 63 | + span_lint( |
| 64 | + cx, |
| 65 | + LITERAL_STRING_WITH_FORMATTING_ARGS, |
| 66 | + spans, |
| 67 | + "this looks like a formatting argument but it is not part of a formatting macro", |
| 68 | + ); |
| 69 | + }, |
| 70 | + _ => { |
| 71 | + span_lint( |
| 72 | + cx, |
| 73 | + LITERAL_STRING_WITH_FORMATTING_ARGS, |
| 74 | + spans, |
| 75 | + "these look like formatting arguments but are not part of a formatting macro", |
| 76 | + ); |
| 77 | + }, |
| 78 | + } |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +impl LateLintPass<'_> for LiteralStringWithFormattingArg { |
| 83 | + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { |
40 | 84 | if expr.span.from_expansion() {
|
41 | 85 | return;
|
42 | 86 | }
|
43 | 87 | 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, |
| 88 | + let (add, symbol) = match lit.node { |
| 89 | + LitKind::Str(symbol, style) => { |
| 90 | + let add = match style { |
| 91 | + StrStyle::Cooked => 1, |
| 92 | + StrStyle::Raw(nb) => nb as usize + 2, |
| 93 | + }; |
| 94 | + (add, symbol) |
| 95 | + }, |
47 | 96 | _ => return,
|
48 | 97 | };
|
49 |
| - let fmt_str = lit.symbol.as_str(); |
| 98 | + let fmt_str = symbol.as_str(); |
50 | 99 | let lo = expr.span.lo();
|
51 | 100 | let mut current = fmt_str;
|
52 | 101 | let mut diff_len = 0;
|
@@ -74,40 +123,37 @@ impl EarlyLintPass for LiteralStringWithFormattingArg {
|
74 | 123 | }
|
75 | 124 |
|
76 | 125 | if fmt_str[start + 1..].trim_start().starts_with('}') {
|
77 |
| - // For now, we ignore `{}`. |
| 126 | + // We ignore `{}`. |
78 | 127 | continue;
|
79 | 128 | }
|
80 | 129 |
|
81 | 130 | let end = fmt_str[start + 1..]
|
82 | 131 | .find('}')
|
83 | 132 | .map_or(pos.end, |found| start + 1 + found)
|
84 | 133 | + 1;
|
85 |
| - spans.push( |
| 134 | + let ident_start = start + 1; |
| 135 | + let colon_pos = fmt_str[ident_start..end].find(':'); |
| 136 | + let ident_end = colon_pos.unwrap_or(end - 1); |
| 137 | + let mut name = None; |
| 138 | + if ident_start < ident_end |
| 139 | + && let arg = &fmt_str[ident_start..ident_end] |
| 140 | + && !arg.is_empty() |
| 141 | + && is_ident(arg) |
| 142 | + { |
| 143 | + name = Some(arg.to_string()); |
| 144 | + } else if colon_pos.is_none() { |
| 145 | + // Not a `{:?}`. |
| 146 | + continue; |
| 147 | + } |
| 148 | + spans.push(( |
86 | 149 | expr.span
|
87 | 150 | .with_hi(lo + BytePos((start + add).try_into().unwrap()))
|
88 | 151 | .with_lo(lo + BytePos((end + add).try_into().unwrap())),
|
89 |
| - ); |
| 152 | + name, |
| 153 | + )); |
90 | 154 | }
|
91 | 155 | }
|
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 |
| - }, |
110 |
| - } |
| 156 | + emit_lint(cx, expr, &spans); |
111 | 157 | }
|
112 | 158 | }
|
113 | 159 | }
|
0 commit comments