Skip to content

Commit 3ee9c2e

Browse files
committed
Auto merge of #6037 - matthiaskrgr:single_char_insert, r=flip1995
single_char_insert_str: lint using insert_str() on single-char literals and suggest insert() Fixes #6026 changelog: add single_char_insert_str lint which lints using string.insert_str() with single char literals and suggests string.insert() with a char
2 parents c45255b + f8ac1f9 commit 3ee9c2e

14 files changed

+278
-125
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Current beta, release 2020-11-19
2222
* [`map_err_ignore`] [#5998](https://github.com/rust-lang/rust-clippy/pull/5998)
2323
* [`rc_buffer`] [#6044](https://github.com/rust-lang/rust-clippy/pull/6044)
2424
* [`to_string_in_display`] [#5831](https://github.com/rust-lang/rust-clippy/pull/5831)
25-
* [`single_char_push_str`] [#5881](https://github.com/rust-lang/rust-clippy/pull/5881)
25+
* `single_char_push_str` [#5881](https://github.com/rust-lang/rust-clippy/pull/5881)
2626

2727
### Moves and Deprecations
2828

@@ -1939,8 +1939,8 @@ Released 2018-09-13
19391939
[`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq
19401940
[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
19411941
[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
1942+
[`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str
19421943
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
1943-
[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
19441944
[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
19451945
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
19461946
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match

clippy_lints/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
714714
&methods::RESULT_MAP_OR_INTO_OPTION,
715715
&methods::SEARCH_IS_SOME,
716716
&methods::SHOULD_IMPLEMENT_TRAIT,
717+
&methods::SINGLE_CHAR_ADD_STR,
717718
&methods::SINGLE_CHAR_PATTERN,
718-
&methods::SINGLE_CHAR_PUSH_STR,
719719
&methods::SKIP_WHILE_NEXT,
720720
&methods::STRING_EXTEND_CHARS,
721721
&methods::SUSPICIOUS_MAP,
@@ -1439,8 +1439,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14391439
LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
14401440
LintId::of(&methods::SEARCH_IS_SOME),
14411441
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
1442+
LintId::of(&methods::SINGLE_CHAR_ADD_STR),
14421443
LintId::of(&methods::SINGLE_CHAR_PATTERN),
1443-
LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
14441444
LintId::of(&methods::SKIP_WHILE_NEXT),
14451445
LintId::of(&methods::STRING_EXTEND_CHARS),
14461446
LintId::of(&methods::SUSPICIOUS_MAP),
@@ -1632,7 +1632,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16321632
LintId::of(&methods::OPTION_MAP_OR_NONE),
16331633
LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
16341634
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
1635-
LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
1635+
LintId::of(&methods::SINGLE_CHAR_ADD_STR),
16361636
LintId::of(&methods::STRING_EXTEND_CHARS),
16371637
LintId::of(&methods::UNNECESSARY_FOLD),
16381638
LintId::of(&methods::UNNECESSARY_LAZY_EVALUATIONS),
@@ -1959,6 +1959,7 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
19591959
ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles");
19601960
ls.register_renamed("clippy::identity_conversion", "clippy::useless_conversion");
19611961
ls.register_renamed("clippy::zero_width_space", "clippy::invisible_characters");
1962+
ls.register_renamed("clippy::single_char_push_str", "clippy::single_char_add_str");
19621963
}
19631964

19641965
// only exists to let the dogfood integration test works.

clippy_lints/src/methods/mod.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,8 +1290,8 @@ declare_clippy_lint! {
12901290
}
12911291

12921292
declare_clippy_lint! {
1293-
/// **What it does:** Warns when using `push_str` with a single-character string literal,
1294-
/// and `push` with a `char` would work fine.
1293+
/// **What it does:** Warns when using `push_str`/`insert_str` with a single-character string literal
1294+
/// where `push`/`insert` with a `char` would work fine.
12951295
///
12961296
/// **Why is this bad?** It's less clear that we are pushing a single character.
12971297
///
@@ -1300,16 +1300,18 @@ declare_clippy_lint! {
13001300
/// **Example:**
13011301
/// ```rust
13021302
/// let mut string = String::new();
1303+
/// string.insert_str(0, "R");
13031304
/// string.push_str("R");
13041305
/// ```
13051306
/// Could be written as
13061307
/// ```rust
13071308
/// let mut string = String::new();
1309+
/// string.insert(0, 'R');
13081310
/// string.push('R');
13091311
/// ```
1310-
pub SINGLE_CHAR_PUSH_STR,
1312+
pub SINGLE_CHAR_ADD_STR,
13111313
style,
1312-
"`push_str()` used with a single-character string literal as parameter"
1314+
"`push_str()` or `insert_str()` used with a single-character string literal as parameter"
13131315
}
13141316

13151317
declare_clippy_lint! {
@@ -1390,7 +1392,7 @@ declare_lint_pass!(Methods => [
13901392
INEFFICIENT_TO_STRING,
13911393
NEW_RET_NO_SELF,
13921394
SINGLE_CHAR_PATTERN,
1393-
SINGLE_CHAR_PUSH_STR,
1395+
SINGLE_CHAR_ADD_STR,
13941396
SEARCH_IS_SOME,
13951397
FILTER_NEXT,
13961398
SKIP_WHILE_NEXT,
@@ -1521,6 +1523,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
15211523
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
15221524
if match_def_path(cx, fn_def_id, &paths::PUSH_STR) {
15231525
lint_single_char_push_string(cx, expr, args);
1526+
} else if match_def_path(cx, fn_def_id, &paths::INSERT_STR) {
1527+
lint_single_char_insert_string(cx, expr, args);
15241528
}
15251529
}
15261530

@@ -3202,7 +3206,7 @@ fn get_hint_if_single_char_arg(
32023206
if let hir::ExprKind::Lit(lit) = &arg.kind;
32033207
if let ast::LitKind::Str(r, style) = lit.node;
32043208
let string = r.as_str();
3205-
if string.len() == 1;
3209+
if string.chars().count() == 1;
32063210
then {
32073211
let snip = snippet_with_applicability(cx, arg.span, &string, applicability);
32083212
let ch = if let ast::StrStyle::Raw(nhash) = style {
@@ -3241,11 +3245,12 @@ fn lint_single_char_pattern(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, arg: &h
32413245
fn lint_single_char_push_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
32423246
let mut applicability = Applicability::MachineApplicable;
32433247
if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability) {
3244-
let base_string_snippet = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
3248+
let base_string_snippet =
3249+
snippet_with_applicability(cx, args[0].span.source_callsite(), "..", &mut applicability);
32453250
let sugg = format!("{}.push({})", base_string_snippet, extension_string);
32463251
span_lint_and_sugg(
32473252
cx,
3248-
SINGLE_CHAR_PUSH_STR,
3253+
SINGLE_CHAR_ADD_STR,
32493254
expr.span,
32503255
"calling `push_str()` using a single-character string literal",
32513256
"consider using `push` with a character literal",
@@ -3255,6 +3260,26 @@ fn lint_single_char_push_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args
32553260
}
32563261
}
32573262

3263+
/// lint for length-1 `str`s as argument for `insert_str`
3264+
fn lint_single_char_insert_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
3265+
let mut applicability = Applicability::MachineApplicable;
3266+
if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[2], &mut applicability) {
3267+
let base_string_snippet =
3268+
snippet_with_applicability(cx, args[0].span.source_callsite(), "_", &mut applicability);
3269+
let pos_arg = snippet_with_applicability(cx, args[1].span, "..", &mut applicability);
3270+
let sugg = format!("{}.insert({}, {})", base_string_snippet, pos_arg, extension_string);
3271+
span_lint_and_sugg(
3272+
cx,
3273+
SINGLE_CHAR_ADD_STR,
3274+
expr.span,
3275+
"calling `insert_str()` using a single-character string literal",
3276+
"consider using `insert` with a character literal",
3277+
sugg,
3278+
applicability,
3279+
);
3280+
}
3281+
}
3282+
32583283
/// Checks for the `USELESS_ASREF` lint.
32593284
fn lint_asref(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) {
32603285
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"

clippy_lints/src/utils/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entr
5252
pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
5353
pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
5454
pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
55+
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
5556
pub const INTO: [&str; 3] = ["core", "convert", "Into"];
5657
pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
5758
pub const IO_READ: [&str; 3] = ["std", "io", "Read"];

src/lintlist/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,16 +2147,16 @@ vec![
21472147
module: "non_expressive_names",
21482148
},
21492149
Lint {
2150-
name: "single_char_pattern",
2151-
group: "perf",
2152-
desc: "using a single-character str where a char could be used, e.g., `_.split(\"x\")`",
2150+
name: "single_char_add_str",
2151+
group: "style",
2152+
desc: "`push_str()` or `insert_str()` used with a single-character string literal as parameter",
21532153
deprecation: None,
21542154
module: "methods",
21552155
},
21562156
Lint {
2157-
name: "single_char_push_str",
2158-
group: "style",
2159-
desc: "`push_str()` used with a single-character string literal as parameter",
2157+
name: "single_char_pattern",
2158+
group: "perf",
2159+
desc: "using a single-character str where a char could be used, e.g., `_.split(\"x\")`",
21602160
deprecation: None,
21612161
module: "methods",
21622162
},

tests/ui/single_char_add_str.fixed

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// run-rustfix
2+
#![warn(clippy::single_char_add_str)]
3+
4+
macro_rules! get_string {
5+
() => {
6+
String::from("Hello world!")
7+
};
8+
}
9+
10+
fn main() {
11+
// `push_str` tests
12+
13+
let mut string = String::new();
14+
string.push('R');
15+
string.push('\'');
16+
17+
string.push('u');
18+
string.push_str("st");
19+
string.push_str("");
20+
string.push('\x52');
21+
string.push('\u{0052}');
22+
string.push('a');
23+
24+
get_string!().push('ö');
25+
26+
// `insert_str` tests
27+
28+
let mut string = String::new();
29+
string.insert(0, 'R');
30+
string.insert(1, '\'');
31+
32+
string.insert(0, 'u');
33+
string.insert_str(2, "st");
34+
string.insert_str(0, "");
35+
string.insert(0, '\x52');
36+
string.insert(0, '\u{0052}');
37+
let x: usize = 2;
38+
string.insert(x, 'a');
39+
const Y: usize = 1;
40+
string.insert(Y, 'a');
41+
string.insert(Y, '"');
42+
string.insert(Y, '\'');
43+
44+
get_string!().insert(1, '?');
45+
}

tests/ui/single_char_add_str.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// run-rustfix
2+
#![warn(clippy::single_char_add_str)]
3+
4+
macro_rules! get_string {
5+
() => {
6+
String::from("Hello world!")
7+
};
8+
}
9+
10+
fn main() {
11+
// `push_str` tests
12+
13+
let mut string = String::new();
14+
string.push_str("R");
15+
string.push_str("'");
16+
17+
string.push('u');
18+
string.push_str("st");
19+
string.push_str("");
20+
string.push_str("\x52");
21+
string.push_str("\u{0052}");
22+
string.push_str(r##"a"##);
23+
24+
get_string!().push_str("ö");
25+
26+
// `insert_str` tests
27+
28+
let mut string = String::new();
29+
string.insert_str(0, "R");
30+
string.insert_str(1, "'");
31+
32+
string.insert(0, 'u');
33+
string.insert_str(2, "st");
34+
string.insert_str(0, "");
35+
string.insert_str(0, "\x52");
36+
string.insert_str(0, "\u{0052}");
37+
let x: usize = 2;
38+
string.insert_str(x, r##"a"##);
39+
const Y: usize = 1;
40+
string.insert_str(Y, r##"a"##);
41+
string.insert_str(Y, r##"""##);
42+
string.insert_str(Y, r##"'"##);
43+
44+
get_string!().insert_str(1, "?");
45+
}

tests/ui/single_char_add_str.stderr

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
error: calling `push_str()` using a single-character string literal
2+
--> $DIR/single_char_add_str.rs:14:5
3+
|
4+
LL | string.push_str("R");
5+
| ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('R')`
6+
|
7+
= note: `-D clippy::single-char-add-str` implied by `-D warnings`
8+
9+
error: calling `push_str()` using a single-character string literal
10+
--> $DIR/single_char_add_str.rs:15:5
11+
|
12+
LL | string.push_str("'");
13+
| ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/'')`
14+
15+
error: calling `push_str()` using a single-character string literal
16+
--> $DIR/single_char_add_str.rs:20:5
17+
|
18+
LL | string.push_str("/x52");
19+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/x52')`
20+
21+
error: calling `push_str()` using a single-character string literal
22+
--> $DIR/single_char_add_str.rs:21:5
23+
|
24+
LL | string.push_str("/u{0052}");
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/u{0052}')`
26+
27+
error: calling `push_str()` using a single-character string literal
28+
--> $DIR/single_char_add_str.rs:22:5
29+
|
30+
LL | string.push_str(r##"a"##);
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('a')`
32+
33+
error: calling `push_str()` using a single-character string literal
34+
--> $DIR/single_char_add_str.rs:24:5
35+
|
36+
LL | get_string!().push_str("ö");
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `get_string!().push('ö')`
38+
39+
error: calling `insert_str()` using a single-character string literal
40+
--> $DIR/single_char_add_str.rs:29:5
41+
|
42+
LL | string.insert_str(0, "R");
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, 'R')`
44+
45+
error: calling `insert_str()` using a single-character string literal
46+
--> $DIR/single_char_add_str.rs:30:5
47+
|
48+
LL | string.insert_str(1, "'");
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(1, '/'')`
50+
51+
error: calling `insert_str()` using a single-character string literal
52+
--> $DIR/single_char_add_str.rs:35:5
53+
|
54+
LL | string.insert_str(0, "/x52");
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, '/x52')`
56+
57+
error: calling `insert_str()` using a single-character string literal
58+
--> $DIR/single_char_add_str.rs:36:5
59+
|
60+
LL | string.insert_str(0, "/u{0052}");
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, '/u{0052}')`
62+
63+
error: calling `insert_str()` using a single-character string literal
64+
--> $DIR/single_char_add_str.rs:38:5
65+
|
66+
LL | string.insert_str(x, r##"a"##);
67+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(x, 'a')`
68+
69+
error: calling `insert_str()` using a single-character string literal
70+
--> $DIR/single_char_add_str.rs:40:5
71+
|
72+
LL | string.insert_str(Y, r##"a"##);
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, 'a')`
74+
75+
error: calling `insert_str()` using a single-character string literal
76+
--> $DIR/single_char_add_str.rs:41:5
77+
|
78+
LL | string.insert_str(Y, r##"""##);
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, '"')`
80+
81+
error: calling `insert_str()` using a single-character string literal
82+
--> $DIR/single_char_add_str.rs:42:5
83+
|
84+
LL | string.insert_str(Y, r##"'"##);
85+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, '/'')`
86+
87+
error: calling `insert_str()` using a single-character string literal
88+
--> $DIR/single_char_add_str.rs:44:5
89+
|
90+
LL | get_string!().insert_str(1, "?");
91+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `get_string!().insert(1, '?')`
92+
93+
error: aborting due to 15 previous errors
94+

tests/ui/single_char_pattern.fixed

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,9 @@ fn main() {
1212

1313
let y = "x";
1414
x.split(y);
15-
// Not yet testing for multi-byte characters
16-
// Changing `r.len() == 1` to `r.chars().count() == 1` in `lint_clippy::single_char_pattern`
17-
// should have done this but produced an ICE
18-
//
19-
// We may not want to suggest changing these anyway
20-
// See: https://github.com/rust-lang/rust-clippy/issues/650#issuecomment-184328984
21-
x.split("ß");
22-
x.split("ℝ");
23-
x.split("💣");
15+
x.split('ß');
16+
x.split('ℝ');
17+
x.split('💣');
2418
// Can't use this lint for unicode code points which don't fit in a char
2519
x.split("❤️");
2620
x.contains('x');

0 commit comments

Comments
 (0)