From d02e4c8f6bab0097dc75e7517f795bb57dd57ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Sun, 13 Sep 2020 13:00:45 +0200 Subject: [PATCH] single_char_insert_str: lint using insert_str() on single-char literals and suggest insert() changelog: add single_char_insert_str lint which lints using string.insert_str() with single char literals and suggests string.insert() with a char Fixes #6026 --- clippy_lints/src/methods/mod.rs | 49 ++++++++++++++++++++++++-- clippy_lints/src/utils/paths.rs | 1 + tests/ui/single_char_insert_str.fixed | 18 ++++++++++ tests/ui/single_char_insert_str.rs | 18 ++++++++++ tests/ui/single_char_insert_str.stderr | 40 +++++++++++++++++++++ 5 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 tests/ui/single_char_insert_str.fixed create mode 100644 tests/ui/single_char_insert_str.rs create mode 100644 tests/ui/single_char_insert_str.stderr diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 98e027b6d229..6aa1a066c48e 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1324,8 +1324,8 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Warns when using `push_str` with a single-character string literal, - /// and `push` with a `char` would work fine. + /// **What it does:** Warns when using `push_str` with a single-character string literal + /// where `push` with a `char` would work fine. /// /// **Why is this bad?** It's less clear that we are pushing a single character. /// @@ -1382,6 +1382,28 @@ declare_clippy_lint! { "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation" } +declare_clippy_lint! { + /// **What it does:** Warns when using `insert_str` with a single-character string literal + /// where `insert` with a `char` would work fine. + /// + /// **Why is this bad?** It's less clear that we are inserting a single character. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let mut string = String::new(); + /// string.insert_str(0, "R"); + /// ``` + /// Could be written as + /// ```rust + /// let mut string = String::new(); + /// string.insert(0, 'R'); + /// ``` + pub SINGLE_CHAR_INSERT_STR, + style, + "`insert_str()` used with a single-character string literal as parameter" +} declare_lint_pass!(Methods => [ UNWRAP_USED, EXPECT_USED, @@ -1402,6 +1424,7 @@ declare_lint_pass!(Methods => [ CLONE_DOUBLE_REF, INEFFICIENT_TO_STRING, NEW_RET_NO_SELF, + SINGLE_CHAR_INSERT_STR, SINGLE_CHAR_PATTERN, SINGLE_CHAR_PUSH_STR, SEARCH_IS_SOME, @@ -1532,6 +1555,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if match_def_path(cx, fn_def_id, &paths::PUSH_STR) { lint_single_char_push_string(cx, expr, args); } + if match_def_path(cx, fn_def_id, &paths::INSERT_STR) { + lint_single_char_insert_string(cx, expr, args); + } } match self_ty.kind() { @@ -3314,6 +3340,25 @@ fn lint_single_char_push_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args } } +/// lint for length-1 `str`s as argument for `insert_str` +fn lint_single_char_insert_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[2], &mut applicability) { + let base_string_snippet = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + let pos_arg = snippet(cx, args[1].span, ".."); + let sugg = format!("{}.insert({}, {})", base_string_snippet, pos_arg, extension_string); + span_lint_and_sugg( + cx, + SINGLE_CHAR_INSERT_STR, + expr.span, + "calling `insert_str()` using a single-character string literal", + "consider using `insert` with a character literal", + sugg, + applicability, + ); + } +} + /// Checks for the `USELESS_ASREF` lint. fn lint_asref(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) { // when we get here, we've already checked that the call name is "as_ref" or "as_mut" diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs index 65320d6a0e0b..1983c7e5b8ca 100644 --- a/clippy_lints/src/utils/paths.rs +++ b/clippy_lints/src/utils/paths.rs @@ -51,6 +51,7 @@ pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entr pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"]; pub const INDEX: [&str; 3] = ["core", "ops", "Index"]; pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"]; +pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"]; pub const INTO: [&str; 3] = ["core", "convert", "Into"]; pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"]; pub const IO_READ: [&str; 3] = ["std", "io", "Read"]; diff --git a/tests/ui/single_char_insert_str.fixed b/tests/ui/single_char_insert_str.fixed new file mode 100644 index 000000000000..c3416720ec30 --- /dev/null +++ b/tests/ui/single_char_insert_str.fixed @@ -0,0 +1,18 @@ +// run-rustfix +#![warn(clippy::single_char_push_str)] + +fn main() { + let mut string = String::new(); + string.insert(0, 'R'); + string.insert(1, '\''); + + string.insert(0, 'u'); + string.insert_str(2, "st"); + string.insert_str(0, ""); + string.insert(0, '\x52'); + string.insert(0, '\u{0052}'); + let x: usize = 2; + string.insert(x, 'a'); + const Y: usize = 1; + string.insert(Y, 'a'); +} diff --git a/tests/ui/single_char_insert_str.rs b/tests/ui/single_char_insert_str.rs new file mode 100644 index 000000000000..2295669e81df --- /dev/null +++ b/tests/ui/single_char_insert_str.rs @@ -0,0 +1,18 @@ +// run-rustfix +#![warn(clippy::single_char_push_str)] + +fn main() { + let mut string = String::new(); + string.insert_str(0, "R"); + string.insert_str(1, "'"); + + string.insert(0, 'u'); + string.insert_str(2, "st"); + string.insert_str(0, ""); + string.insert_str(0, "\x52"); + string.insert_str(0, "\u{0052}"); + let x: usize = 2; + string.insert_str(x, r##"a"##); + const Y: usize = 1; + string.insert_str(Y, r##"a"##); +} diff --git a/tests/ui/single_char_insert_str.stderr b/tests/ui/single_char_insert_str.stderr new file mode 100644 index 000000000000..14dd1d334ea4 --- /dev/null +++ b/tests/ui/single_char_insert_str.stderr @@ -0,0 +1,40 @@ +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_insert_str.rs:6:5 + | +LL | string.insert_str(0, "R"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, 'R')` + | + = note: `-D clippy::single-char-insert-str` implied by `-D warnings` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_insert_str.rs:7:5 + | +LL | string.insert_str(1, "'"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(1, '/'')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_insert_str.rs:12:5 + | +LL | string.insert_str(0, "/x52"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, '/x52')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_insert_str.rs:13:5 + | +LL | string.insert_str(0, "/u{0052}"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, '/u{0052}')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_insert_str.rs:15:5 + | +LL | string.insert_str(x, r##"a"##); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(x, 'a')` + +error: calling `insert_str()` using a single-character string literal + --> $DIR/single_char_insert_str.rs:17:5 + | +LL | string.insert_str(Y, r##"a"##); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, 'a')` + +error: aborting due to 6 previous errors +