|
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