Skip to content

Commit dc8f125

Browse files
Add new literal_string_with_formatting_arg lint
1 parent 903293b commit dc8f125

File tree

4 files changed

+106
-0
lines changed

4 files changed

+106
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5603,6 +5603,7 @@ Released 2018-09-13
56035603
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
56045604
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
56055605
[`lint_groups_priority`]: https://rust-lang.github.io/rust-clippy/master/index.html#lint_groups_priority
5606+
[`literal_string_with_formatting_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#literal_string_with_formatting_arg
56065607
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
56075608
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
56085609
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
272272
crate::literal_representation::MISTYPED_LITERAL_SUFFIXES_INFO,
273273
crate::literal_representation::UNREADABLE_LITERAL_INFO,
274274
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
275+
crate::literal_string_with_formatting_arg::LITERAL_STRING_WITH_FORMATTING_ARG_INFO,
275276
crate::loops::EMPTY_LOOP_INFO,
276277
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
277278
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ extern crate rustc_lexer;
5151
extern crate rustc_lint;
5252
extern crate rustc_middle;
5353
extern crate rustc_parse;
54+
extern crate rustc_parse_format;
5455
extern crate rustc_resolve;
5556
extern crate rustc_session;
5657
extern crate rustc_span;
@@ -195,6 +196,7 @@ mod let_with_type_underscore;
195196
mod lifetimes;
196197
mod lines_filter_map_ok;
197198
mod literal_representation;
199+
mod literal_string_with_formatting_arg;
198200
mod loops;
199201
mod macro_metavars_in_unsafe;
200202
mod macro_use;
@@ -942,5 +944,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
942944
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
943945
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
944946
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
947+
store.register_early_pass(|| Box::new(literal_string_with_formatting_arg::LiteralStringWithFormattingArg));
945948
// add lints here, do not remove this comment, it's used in `new_lint`
946949
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use rustc_ast::ast::{Expr, ExprKind};
2+
use rustc_ast::token::LitKind;
3+
use rustc_lint::{EarlyContext, EarlyLintPass};
4+
use rustc_parse_format::{ParseMode, Parser, Piece};
5+
use rustc_session::declare_lint_pass;
6+
use rustc_span::BytePos;
7+
8+
use clippy_utils::diagnostics::span_lint;
9+
10+
declare_clippy_lint! {
11+
/// ### What it does
12+
/// Checks if string literals have formatting arguments outside of macros
13+
/// using them (like `format!`).
14+
///
15+
/// ### Why is this bad?
16+
/// It will likely not generate the expected content.
17+
///
18+
/// ### Example
19+
/// ```no_run
20+
/// let x: Option<usize> = None;
21+
/// let y = "hello";
22+
/// x.expect("{y:?}");
23+
/// ```
24+
/// Use instead:
25+
/// ```no_run
26+
/// let x: Option<usize> = None;
27+
/// let y = "hello";
28+
/// x.expect(&format!("{y:?}"));
29+
/// ```
30+
#[clippy::version = "1.83.0"]
31+
pub LITERAL_STRING_WITH_FORMATTING_ARG,
32+
suspicious,
33+
"Checks if string literals have formatting arguments"
34+
}
35+
36+
declare_lint_pass!(LiteralStringWithFormattingArg => [LITERAL_STRING_WITH_FORMATTING_ARG]);
37+
38+
impl EarlyLintPass for LiteralStringWithFormattingArg {
39+
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
40+
if let ExprKind::Lit(lit) = expr.kind {
41+
let add = match lit.kind {
42+
LitKind::Str => 1,
43+
LitKind::StrRaw(nb) => nb as usize + 2,
44+
_ => return,
45+
};
46+
let fmt_str = lit.symbol.as_str();
47+
let lo = expr.span.lo();
48+
let mut current = fmt_str;
49+
let mut diff_len = 0;
50+
51+
let mut parser = Parser::new(current, None, None, false, ParseMode::Format);
52+
let mut spans = Vec::new();
53+
while let Some(piece) = parser.next() {
54+
if let Some(error) = parser.errors.last() {
55+
// We simply ignore the errors and move after them.
56+
current = &current[error.span.end + 1..];
57+
diff_len = fmt_str.len() - current.len();
58+
parser = Parser::new(current, None, None, false, ParseMode::Format);
59+
} else if let Piece::NextArgument(arg) = piece {
60+
let mut pos = arg.position_span;
61+
pos.start += diff_len;
62+
pos.end += diff_len;
63+
64+
let start = fmt_str[..pos.start].rfind('{').unwrap_or(pos.start);
65+
// If this is a unicode character escape, we don't want to lint.
66+
if start > 1 && fmt_str[start - 2..].starts_with("\\u{") {
67+
continue;
68+
}
69+
70+
let mut end = fmt_str[pos.end..]
71+
.find('}')
72+
.map(|found| found + pos.end)
73+
.unwrap_or(pos.end);
74+
if fmt_str[start..end].contains(':') {
75+
end += 1;
76+
}
77+
spans.push(
78+
expr.span
79+
.with_hi(lo + BytePos((start + add) as _))
80+
.with_lo(lo + BytePos((end + add) as _)),
81+
);
82+
}
83+
}
84+
if spans.len() == 1 {
85+
span_lint(
86+
cx,
87+
LITERAL_STRING_WITH_FORMATTING_ARG,
88+
spans,
89+
"this is a formatting argument but it is not part of a formatting macro",
90+
);
91+
} else if spans.len() > 1 {
92+
span_lint(
93+
cx,
94+
LITERAL_STRING_WITH_FORMATTING_ARG,
95+
spans,
96+
"these are formatting arguments but are not part of a formatting macro",
97+
);
98+
}
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)