Skip to content

Commit 1f966e9

Browse files
authored
Add new literal_string_with_formatting_args lint (rust-lang#13410)
Fixes rust-lang#10195. changelog: Added new [`literal_string_with_formatting_args`] `pedantic` lint [rust-lang#13410](rust-lang/rust-clippy#13410)
2 parents 650e0c8 + cfc6444 commit 1f966e9

20 files changed

+308
-31
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5685,6 +5685,7 @@ Released 2018-09-13
56855685
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
56865686
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
56875687
[`lint_groups_priority`]: https://rust-lang.github.io/rust-clippy/master/index.html#lint_groups_priority
5688+
[`literal_string_with_formatting_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#literal_string_with_formatting_args
56885689
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
56895690
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
56905691
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
277277
crate::literal_representation::MISTYPED_LITERAL_SUFFIXES_INFO,
278278
crate::literal_representation::UNREADABLE_LITERAL_INFO,
279279
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
280+
crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO,
280281
crate::loops::EMPTY_LOOP_INFO,
281282
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
282283
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,

clippy_lints/src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
clippy::missing_docs_in_private_items,
1818
clippy::must_use_candidate,
1919
rustc::diagnostic_outside_of_impl,
20-
rustc::untranslatable_diagnostic
20+
rustc::untranslatable_diagnostic,
21+
clippy::literal_string_with_formatting_args
2122
)]
2223
#![warn(
2324
trivial_casts,
@@ -49,6 +50,7 @@ extern crate rustc_lexer;
4950
extern crate rustc_lint;
5051
extern crate rustc_middle;
5152
extern crate rustc_parse;
53+
extern crate rustc_parse_format;
5254
extern crate rustc_resolve;
5355
extern crate rustc_session;
5456
extern crate rustc_span;
@@ -196,6 +198,7 @@ mod let_with_type_underscore;
196198
mod lifetimes;
197199
mod lines_filter_map_ok;
198200
mod literal_representation;
201+
mod literal_string_with_formatting_args;
199202
mod loops;
200203
mod macro_metavars_in_unsafe;
201204
mod macro_use;
@@ -959,6 +962,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
959962
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
960963
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
961964
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
965+
store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg));
962966
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
963967
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
964968
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use rustc_ast::{LitKind, StrStyle};
2+
use rustc_hir::{Expr, ExprKind};
3+
use rustc_lexer::is_ident;
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_parse_format::{ParseMode, Parser, Piece};
6+
use rustc_session::declare_lint_pass;
7+
use rustc_span::{BytePos, Span};
8+
9+
use clippy_utils::diagnostics::span_lint;
10+
use clippy_utils::mir::enclosing_mir;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks if string literals have formatting arguments outside of macros
15+
/// using them (like `format!`).
16+
///
17+
/// ### Why is this bad?
18+
/// It will likely not generate the expected content.
19+
///
20+
/// ### Example
21+
/// ```no_run
22+
/// let x: Option<usize> = None;
23+
/// let y = "hello";
24+
/// x.expect("{y:?}");
25+
/// ```
26+
/// Use instead:
27+
/// ```no_run
28+
/// let x: Option<usize> = None;
29+
/// let y = "hello";
30+
/// x.expect(&format!("{y:?}"));
31+
/// ```
32+
#[clippy::version = "1.83.0"]
33+
pub LITERAL_STRING_WITH_FORMATTING_ARGS,
34+
suspicious,
35+
"Checks if string literals have formatting arguments"
36+
}
37+
38+
declare_lint_pass!(LiteralStringWithFormattingArg => [LITERAL_STRING_WITH_FORMATTING_ARGS]);
39+
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<'_>) {
84+
if expr.span.from_expansion() {
85+
return;
86+
}
87+
if let ExprKind::Lit(lit) = expr.kind {
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+
},
96+
_ => return,
97+
};
98+
let fmt_str = symbol.as_str();
99+
let lo = expr.span.lo();
100+
let mut current = fmt_str;
101+
let mut diff_len = 0;
102+
103+
let mut parser = Parser::new(current, None, None, false, ParseMode::Format);
104+
let mut spans = Vec::new();
105+
while let Some(piece) = parser.next() {
106+
if let Some(error) = parser.errors.last() {
107+
// We simply ignore the errors and move after them.
108+
if error.span.end >= current.len() {
109+
break;
110+
}
111+
current = &current[error.span.end + 1..];
112+
diff_len = fmt_str.len() - current.len();
113+
parser = Parser::new(current, None, None, false, ParseMode::Format);
114+
} else if let Piece::NextArgument(arg) = piece {
115+
let mut pos = arg.position_span;
116+
pos.start += diff_len;
117+
pos.end += diff_len;
118+
119+
let start = fmt_str[..pos.start].rfind('{').unwrap_or(pos.start);
120+
// If this is a unicode character escape, we don't want to lint.
121+
if start > 1 && fmt_str[..start].ends_with("\\u") {
122+
continue;
123+
}
124+
125+
if fmt_str[start + 1..].trim_start().starts_with('}') {
126+
// We ignore `{}`.
127+
continue;
128+
}
129+
130+
let end = fmt_str[start + 1..]
131+
.find('}')
132+
.map_or(pos.end, |found| start + 1 + found)
133+
+ 1;
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((
149+
expr.span
150+
.with_hi(lo + BytePos((start + add).try_into().unwrap()))
151+
.with_lo(lo + BytePos((end + add).try_into().unwrap())),
152+
name,
153+
));
154+
}
155+
}
156+
emit_lint(cx, expr, &spans);
157+
}
158+
}
159+
}

lintcheck/src/main.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
#![allow(
1818
clippy::collapsible_else_if,
1919
clippy::needless_borrows_for_generic_args,
20-
clippy::module_name_repetitions
20+
clippy::module_name_repetitions,
21+
clippy::literal_string_with_formatting_args
2122
)]
2223

2324
mod config;

tests/ui-toml/large_include_file/large_include_file.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::large_include_file)]
2+
#![allow(clippy::literal_string_with_formatting_args)]
23

34
// Good
45
const GOOD_INCLUDE_BYTES: &[u8; 68] = include_bytes!("../../ui/author.rs");

tests/ui-toml/large_include_file/large_include_file.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: attempted to include a large file
2-
--> tests/ui-toml/large_include_file/large_include_file.rs:13:43
2+
--> tests/ui-toml/large_include_file/large_include_file.rs:14:43
33
|
44
LL | const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt");
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -9,15 +9,15 @@ LL | const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt");
99
= help: to override `-D warnings` add `#[allow(clippy::large_include_file)]`
1010

1111
error: attempted to include a large file
12-
--> tests/ui-toml/large_include_file/large_include_file.rs:15:35
12+
--> tests/ui-toml/large_include_file/large_include_file.rs:16:35
1313
|
1414
LL | const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt");
1515
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1616
|
1717
= note: the configuration allows a maximum size of 600 bytes
1818

1919
error: attempted to include a large file
20-
--> tests/ui-toml/large_include_file/large_include_file.rs:18:1
20+
--> tests/ui-toml/large_include_file/large_include_file.rs:19:1
2121
|
2222
LL | #[doc = include_str!("too_big.txt")]
2323
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/ui/auxiliary/proc_macro_derive.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![allow(incomplete_features)]
33
#![allow(clippy::field_reassign_with_default)]
44
#![allow(clippy::eq_op)]
5+
#![allow(clippy::literal_string_with_formatting_args)]
56

67
extern crate proc_macro;
78

tests/ui/format.fixed

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::needless_borrow,
77
clippy::uninlined_format_args,
88
clippy::needless_raw_string_hashes,
9-
clippy::useless_vec
9+
clippy::useless_vec,
10+
clippy::literal_string_with_formatting_args
1011
)]
1112

1213
struct Foo(pub String);

tests/ui/format.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::needless_borrow,
77
clippy::uninlined_format_args,
88
clippy::needless_raw_string_hashes,
9-
clippy::useless_vec
9+
clippy::useless_vec,
10+
clippy::literal_string_with_formatting_args
1011
)]
1112

1213
struct Foo(pub String);

tests/ui/format.stderr

+15-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: useless use of `format!`
2-
--> tests/ui/format.rs:19:5
2+
--> tests/ui/format.rs:20:5
33
|
44
LL | format!("foo");
55
| ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()`
@@ -8,19 +8,19 @@ LL | format!("foo");
88
= help: to override `-D warnings` add `#[allow(clippy::useless_format)]`
99

1010
error: useless use of `format!`
11-
--> tests/ui/format.rs:20:5
11+
--> tests/ui/format.rs:21:5
1212
|
1313
LL | format!("{{}}");
1414
| ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{}".to_string()`
1515

1616
error: useless use of `format!`
17-
--> tests/ui/format.rs:21:5
17+
--> tests/ui/format.rs:22:5
1818
|
1919
LL | format!("{{}} abc {{}}");
2020
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{} abc {}".to_string()`
2121

2222
error: useless use of `format!`
23-
--> tests/ui/format.rs:22:5
23+
--> tests/ui/format.rs:23:5
2424
|
2525
LL | / format!(
2626
LL | | r##"foo {{}}
@@ -35,67 +35,67 @@ LL ~ " bar"##.to_string();
3535
|
3636

3737
error: useless use of `format!`
38-
--> tests/ui/format.rs:27:13
38+
--> tests/ui/format.rs:28:13
3939
|
4040
LL | let _ = format!("");
4141
| ^^^^^^^^^^^ help: consider using `String::new()`: `String::new()`
4242

4343
error: useless use of `format!`
44-
--> tests/ui/format.rs:29:5
44+
--> tests/ui/format.rs:30:5
4545
|
4646
LL | format!("{}", "foo");
4747
| ^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()`
4848

4949
error: useless use of `format!`
50-
--> tests/ui/format.rs:37:5
50+
--> tests/ui/format.rs:38:5
5151
|
5252
LL | format!("{}", arg);
5353
| ^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string()`
5454

5555
error: useless use of `format!`
56-
--> tests/ui/format.rs:67:5
56+
--> tests/ui/format.rs:68:5
5757
|
5858
LL | format!("{}", 42.to_string());
5959
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `42.to_string()`
6060

6161
error: useless use of `format!`
62-
--> tests/ui/format.rs:69:5
62+
--> tests/ui/format.rs:70:5
6363
|
6464
LL | format!("{}", x.display().to_string());
6565
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.display().to_string()`
6666

6767
error: useless use of `format!`
68-
--> tests/ui/format.rs:73:18
68+
--> tests/ui/format.rs:74:18
6969
|
7070
LL | let _ = Some(format!("{}", a + "bar"));
7171
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `a + "bar"`
7272

7373
error: useless use of `format!`
74-
--> tests/ui/format.rs:77:22
74+
--> tests/ui/format.rs:78:22
7575
|
7676
LL | let _s: String = format!("{}", &*v.join("\n"));
7777
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `(&*v.join("\n")).to_string()`
7878

7979
error: useless use of `format!`
80-
--> tests/ui/format.rs:83:13
80+
--> tests/ui/format.rs:84:13
8181
|
8282
LL | let _ = format!("{x}");
8383
| ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()`
8484

8585
error: useless use of `format!`
86-
--> tests/ui/format.rs:85:13
86+
--> tests/ui/format.rs:86:13
8787
|
8888
LL | let _ = format!("{y}", y = x);
8989
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()`
9090

9191
error: useless use of `format!`
92-
--> tests/ui/format.rs:89:13
92+
--> tests/ui/format.rs:90:13
9393
|
9494
LL | let _ = format!("{abc}");
9595
| ^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `abc.to_string()`
9696

9797
error: useless use of `format!`
98-
--> tests/ui/format.rs:91:13
98+
--> tests/ui/format.rs:92:13
9999
|
100100
LL | let _ = format!("{xx}");
101101
| ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `xx.to_string()`

0 commit comments

Comments
 (0)