diff options
| author | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2024-09-18 00:09:34 +0200 |
|---|---|---|
| committer | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2024-11-19 15:44:19 +0100 |
| commit | cd7cec9066f35e2fd661a69830fd57864b41ea44 (patch) | |
| tree | 8e971c922cf686f28e585115d285636bc29e6823 | |
| parent | 53994bda923a81d1c12d3e32dab3c208beaaca63 (diff) | |
| download | rust-cd7cec9066f35e2fd661a69830fd57864b41ea44.tar.gz rust-cd7cec9066f35e2fd661a69830fd57864b41ea44.zip | |
Add new `literal_string_with_formatting_arg` lint
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | clippy_lints/src/declared_lints.rs | 1 | ||||
| -rw-r--r-- | clippy_lints/src/lib.rs | 3 | ||||
| -rw-r--r-- | clippy_lints/src/literal_string_with_formatting_arg.rs | 101 |
4 files changed, 106 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index dd3124ee9a3..3a82bfce82b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5639,6 +5639,7 @@ Released 2018-09-13 [`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok [`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist [`lint_groups_priority`]: https://rust-lang.github.io/rust-clippy/master/index.html#lint_groups_priority +[`literal_string_with_formatting_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#literal_string_with_formatting_arg [`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes [`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug [`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index dff60f76b74..a3f89b3f70c 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -276,6 +276,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::literal_representation::MISTYPED_LITERAL_SUFFIXES_INFO, crate::literal_representation::UNREADABLE_LITERAL_INFO, crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO, + crate::literal_string_with_formatting_arg::LITERAL_STRING_WITH_FORMATTING_ARG_INFO, crate::loops::EMPTY_LOOP_INFO, crate::loops::EXPLICIT_COUNTER_LOOP_INFO, crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9064df25ac..44e36b99b5d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -49,6 +49,7 @@ extern crate rustc_lexer; extern crate rustc_lint; extern crate rustc_middle; extern crate rustc_parse; +extern crate rustc_parse_format; extern crate rustc_resolve; extern crate rustc_session; extern crate rustc_span; @@ -196,6 +197,7 @@ mod let_with_type_underscore; mod lifetimes; mod lines_filter_map_ok; mod literal_representation; +mod literal_string_with_formatting_arg; mod loops; mod macro_metavars_in_unsafe; mod macro_use; @@ -959,6 +961,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf))); store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo)); store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)); + store.register_early_pass(|| Box::new(literal_string_with_formatting_arg::LiteralStringWithFormattingArg)); store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); diff --git a/clippy_lints/src/literal_string_with_formatting_arg.rs b/clippy_lints/src/literal_string_with_formatting_arg.rs new file mode 100644 index 00000000000..e72f0de90c6 --- /dev/null +++ b/clippy_lints/src/literal_string_with_formatting_arg.rs @@ -0,0 +1,101 @@ +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_ast::token::LitKind; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_parse_format::{ParseMode, Parser, Piece}; +use rustc_session::declare_lint_pass; +use rustc_span::BytePos; + +use clippy_utils::diagnostics::span_lint; + +declare_clippy_lint! { + /// ### What it does + /// Checks if string literals have formatting arguments outside of macros + /// using them (like `format!`). + /// + /// ### Why is this bad? + /// It will likely not generate the expected content. + /// + /// ### Example + /// ```no_run + /// let x: Option<usize> = None; + /// let y = "hello"; + /// x.expect("{y:?}"); + /// ``` + /// Use instead: + /// ```no_run + /// let x: Option<usize> = None; + /// let y = "hello"; + /// x.expect(&format!("{y:?}")); + /// ``` + #[clippy::version = "1.83.0"] + pub LITERAL_STRING_WITH_FORMATTING_ARG, + suspicious, + "Checks if string literals have formatting arguments" +} + +declare_lint_pass!(LiteralStringWithFormattingArg => [LITERAL_STRING_WITH_FORMATTING_ARG]); + +impl EarlyLintPass for LiteralStringWithFormattingArg { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Lit(lit) = expr.kind { + let add = match lit.kind { + LitKind::Str => 1, + LitKind::StrRaw(nb) => nb as usize + 2, + _ => return, + }; + let fmt_str = lit.symbol.as_str(); + let lo = expr.span.lo(); + let mut current = fmt_str; + let mut diff_len = 0; + + let mut parser = Parser::new(current, None, None, false, ParseMode::Format); + let mut spans = Vec::new(); + while let Some(piece) = parser.next() { + if let Some(error) = parser.errors.last() { + // We simply ignore the errors and move after them. + if error.span.end >= current.len() { + break; + } + current = ¤t[error.span.end + 1..]; + diff_len = fmt_str.len() - current.len(); + parser = Parser::new(current, None, None, false, ParseMode::Format); + } else if let Piece::NextArgument(arg) = piece { + let mut pos = arg.position_span; + pos.start += diff_len; + pos.end += diff_len; + + let start = fmt_str[..pos.start].rfind('{').unwrap_or(pos.start); + // If this is a unicode character escape, we don't want to lint. + if start > 1 && fmt_str[..start].ends_with("\\u") { + continue; + } + + let mut end = fmt_str[pos.end..].find('}').map_or(pos.end, |found| found + pos.end); + if fmt_str[start..end].contains(':') { + end += 1; + } + spans.push( + expr.span + .with_hi(lo + BytePos((start + add) as _)) + .with_lo(lo + BytePos((end + add) as _)), + ); + } + } + if spans.len() == 1 { + span_lint( + cx, + LITERAL_STRING_WITH_FORMATTING_ARG, + spans, + "this looks like a formatting argument but it is not part of a formatting macro", + ); + } else if spans.len() > 1 { + span_lint( + cx, + LITERAL_STRING_WITH_FORMATTING_ARG, + spans, + "these look like formatting arguments but are not part of a formatting macro", + ); + } + } + } +} |
