Skip to content

Commit cfc6444

Browse files
Limit literal_string_with_formatting_args to known variables if no formatting argument is passed
1 parent 607a3f6 commit cfc6444

File tree

7 files changed

+127
-74
lines changed

7 files changed

+127
-74
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
962962
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
963963
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
964964
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
965-
store.register_early_pass(|| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg));
965+
store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg));
966966
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
967967
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
968968
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));

clippy_lints/src/literal_string_with_formatting_args.rs

Lines changed: 78 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
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};
45
use rustc_parse_format::{ParseMode, Parser, Piece};
56
use rustc_session::declare_lint_pass;
6-
use rustc_span::BytePos;
7+
use rustc_span::{BytePos, Span};
78

89
use clippy_utils::diagnostics::span_lint;
10+
use clippy_utils::mir::enclosing_mir;
911

1012
declare_clippy_lint! {
1113
/// ### What it does
@@ -35,18 +37,65 @@ declare_clippy_lint! {
3537

3638
declare_lint_pass!(LiteralStringWithFormattingArg => [LITERAL_STRING_WITH_FORMATTING_ARGS]);
3739

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<'_>) {
4084
if expr.span.from_expansion() {
4185
return;
4286
}
4387
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+
},
4796
_ => return,
4897
};
49-
let fmt_str = lit.symbol.as_str();
98+
let fmt_str = symbol.as_str();
5099
let lo = expr.span.lo();
51100
let mut current = fmt_str;
52101
let mut diff_len = 0;
@@ -74,40 +123,37 @@ impl EarlyLintPass for LiteralStringWithFormattingArg {
74123
}
75124

76125
if fmt_str[start + 1..].trim_start().starts_with('}') {
77-
// For now, we ignore `{}`.
126+
// We ignore `{}`.
78127
continue;
79128
}
80129

81130
let end = fmt_str[start + 1..]
82131
.find('}')
83132
.map_or(pos.end, |found| start + 1 + found)
84133
+ 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((
86149
expr.span
87150
.with_hi(lo + BytePos((start + add).try_into().unwrap()))
88151
.with_lo(lo + BytePos((end + add).try_into().unwrap())),
89-
);
152+
name,
153+
));
90154
}
91155
}
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);
111157
}
112158
}
113159
}

lintcheck/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
clippy::collapsible_else_if,
1919
clippy::needless_borrows_for_generic_args,
2020
clippy::module_name_repetitions,
21-
clippy::literal_string_with_formatting_arg
21+
clippy::literal_string_with_formatting_args
2222
)]
2323

2424
mod config;

tests/ui/literal_string_with_formatting_arg.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ fn main() {
1919
// Should not lint!
2020
format!("{y:?}");
2121
println!("{y:?}");
22-
x.expect(" {} "); // For now we ignore `{}` to limit false positives.
23-
x.expect(" { } "); // For now we ignore `{}` to limit false positives.
22+
x.expect(" {} "); // We ignore `{}` to limit false positives.
23+
x.expect(" { } "); // We ignore `{}` to limit false positives.
2424
x.expect("{{y} {x");
2525
x.expect("{{y:?}");
26+
x.expect(" {0}"); // If it only contains an integer, we ignore it.
27+
x.expect(r##" {x:?} "##); // `x` doesn't exist so we shoud not lint
2628
x.expect("{y:...}");
2729
let _ = "fn main {\n\
2830
}";
Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,71 @@
11
error: this looks like a formatting argument but it is not part of a formatting macro
2-
--> tests/ui/literal_string_with_formatting_args.rs:7:15
2+
--> tests/ui/literal_string_with_formatting_arg.rs:7:15
33
|
44
LL | x.expect("{y} {}");
55
| ^^^
66
|
7-
= note: `-D clippy::literal-string-with-formatting-arg` implied by `-D warnings`
7+
= note: `-D clippy::literal-string-with-formatting-args` implied by `-D warnings`
88
= help: to override `-D warnings` add `#[allow(clippy::literal_string_with_formatting_args)]`
99

1010
error: this looks like a formatting argument but it is not part of a formatting macro
11-
--> tests/ui/literal_string_with_formatting_args.rs:8:16
11+
--> tests/ui/literal_string_with_formatting_arg.rs:8:16
1212
|
1313
LL | x.expect(" {y} bla");
1414
| ^^^
1515

1616
error: this looks like a formatting argument but it is not part of a formatting macro
17-
--> tests/ui/literal_string_with_formatting_args.rs:9:15
17+
--> tests/ui/literal_string_with_formatting_arg.rs:9:15
1818
|
1919
LL | x.expect("{:?}");
2020
| ^^^^
2121

2222
error: this looks like a formatting argument but it is not part of a formatting macro
23-
--> tests/ui/literal_string_with_formatting_args.rs:10:15
23+
--> tests/ui/literal_string_with_formatting_arg.rs:10:15
2424
|
2525
LL | x.expect("{y:?}");
2626
| ^^^^^
2727

2828
error: these look like formatting arguments but are not part of a formatting macro
29-
--> tests/ui/literal_string_with_formatting_args.rs:11:16
29+
--> tests/ui/literal_string_with_formatting_arg.rs:11:16
3030
|
3131
LL | x.expect(" {y:?} {y:?} ");
3232
| ^^^^^ ^^^^^
3333

3434
error: this looks like a formatting argument but it is not part of a formatting macro
35-
--> tests/ui/literal_string_with_formatting_args.rs:12:23
35+
--> tests/ui/literal_string_with_formatting_arg.rs:12:23
3636
|
3737
LL | x.expect(" {y:..} {y:?} ");
3838
| ^^^^^
3939

4040
error: these look like formatting arguments but are not part of a formatting macro
41-
--> tests/ui/literal_string_with_formatting_args.rs:13:16
41+
--> tests/ui/literal_string_with_formatting_arg.rs:13:16
4242
|
4343
LL | x.expect(r"{y:?} {y:?} ");
4444
| ^^^^^ ^^^^^
4545

4646
error: this looks like a formatting argument but it is not part of a formatting macro
47-
--> tests/ui/literal_string_with_formatting_args.rs:14:16
47+
--> tests/ui/literal_string_with_formatting_arg.rs:14:16
4848
|
4949
LL | x.expect(r"{y:?} y:?}");
5050
| ^^^^^
5151

5252
error: these look like formatting arguments but are not part of a formatting macro
53-
--> tests/ui/literal_string_with_formatting_args.rs:15:19
53+
--> tests/ui/literal_string_with_formatting_arg.rs:15:19
5454
|
5555
LL | x.expect(r##" {y:?} {y:?} "##);
5656
| ^^^^^ ^^^^^
5757

5858
error: this looks like a formatting argument but it is not part of a formatting macro
59-
--> tests/ui/literal_string_with_formatting_args.rs:17:18
59+
--> tests/ui/literal_string_with_formatting_arg.rs:17:18
6060
|
6161
LL | x.expect("———{:?}");
6262
| ^^^^
6363

64-
error: aborting due to 10 previous errors
64+
error: this looks like a formatting argument but it is not part of a formatting macro
65+
--> tests/ui/literal_string_with_formatting_arg.rs:27:19
66+
|
67+
LL | x.expect(r##" {x:?} "##); // `x` doesn't exist so we shoud not lint
68+
| ^^^^^
69+
70+
error: aborting due to 11 previous errors
6571

tests/ui/regex.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
clippy::needless_raw_strings,
44
clippy::needless_raw_string_hashes,
55
clippy::needless_borrow,
6-
clippy::needless_borrows_for_generic_args,
7-
clippy::literal_string_with_formatting_args
6+
clippy::needless_borrows_for_generic_args
87
)]
98
#![warn(clippy::invalid_regex, clippy::trivial_regex, clippy::regex_creation_in_loops)]
109

0 commit comments

Comments
 (0)