Skip to content

Commit a4413f7

Browse files
committed
Register new lint collapsible_str_replace to methods
1 parent 3a54117 commit a4413f7

File tree

7 files changed

+94
-0
lines changed

7 files changed

+94
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3642,6 +3642,7 @@ Released 2018-09-13
36423642
[`collapsible_else_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if
36433643
[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
36443644
[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match
3645+
[`collapsible_str_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace
36453646
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
36463647
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
36473648
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
154154
LintId::of(methods::CHARS_NEXT_CMP),
155155
LintId::of(methods::CLONE_DOUBLE_REF),
156156
LintId::of(methods::CLONE_ON_COPY),
157+
LintId::of(methods::COLLAPSIBLE_STR_REPLACE),
157158
LintId::of(methods::ERR_EXPECT),
158159
LintId::of(methods::EXPECT_FUN_CALL),
159160
LintId::of(methods::EXTEND_WITH_DRAIN),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ store.register_lints(&[
287287
methods::CLONE_DOUBLE_REF,
288288
methods::CLONE_ON_COPY,
289289
methods::CLONE_ON_REF_PTR,
290+
methods::COLLAPSIBLE_STR_REPLACE,
290291
methods::ERR_EXPECT,
291292
methods::EXPECT_FUN_CALL,
292293
methods::EXPECT_USED,

clippy_lints/src/lib.register_perf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
1313
LintId::of(loops::MISSING_SPIN_LOOP),
1414
LintId::of(loops::NEEDLESS_COLLECT),
1515
LintId::of(manual_retain::MANUAL_RETAIN),
16+
LintId::of(methods::COLLAPSIBLE_STR_REPLACE),
1617
LintId::of(methods::EXPECT_FUN_CALL),
1718
LintId::of(methods::EXTEND_WITH_DRAIN),
1819
LintId::of(methods::ITER_NTH),
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use rustc_hir as hir;
2+
use rustc_hir::*;
3+
use rustc_lint::{LateContext, LateLintPass};
4+
use rustc_session::{declare_lint_pass, declare_tool_lint};
5+
6+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {}

clippy_lints/src/methods/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod chars_next_cmp_with_unwrap;
1212
mod clone_on_copy;
1313
mod clone_on_ref_ptr;
1414
mod cloned_instead_of_copied;
15+
mod collapsible_str_replace;
1516
mod err_expect;
1617
mod expect_fun_call;
1718
mod expect_used;
@@ -137,6 +138,32 @@ declare_clippy_lint! {
137138
"used `cloned` where `copied` could be used instead"
138139
}
139140

141+
declare_clippy_lint! {
142+
/// ### What it does
143+
/// Checks for consecutive calls to `str::replace` (2 or more)
144+
/// that can be collapsed into a single call.
145+
///
146+
/// ### Why is this bad?
147+
/// Consecutive `str::replace` calls scan the string multiple times
148+
/// with repetitive code.
149+
///
150+
/// ### Example
151+
/// ```rust
152+
/// let hello = "hesuo worpd"
153+
/// .replace('s', "l")
154+
/// .replace("u", "l")
155+
/// .replace('p', "l")
156+
/// ```
157+
/// Use instead:
158+
/// ```rust
159+
/// let hello = "hesuo worpd".replace(|c| matches!(c, 's' | 'u' | 'p'), "l")
160+
/// ```
161+
#[clippy::version = "1.64.0"]
162+
pub COLLAPSIBLE_STR_REPLACE,
163+
perf,
164+
"collapse consecutive calls to str::replace (2 or more) into a single call"
165+
}
166+
140167
declare_clippy_lint! {
141168
/// ### What it does
142169
/// Checks for usage of `_.cloned().<func>()` where call to `.cloned()` can be postponed.
@@ -3001,6 +3028,7 @@ impl_lint_pass!(Methods => [
30013028
CLONE_ON_COPY,
30023029
CLONE_ON_REF_PTR,
30033030
CLONE_DOUBLE_REF,
3031+
COLLAPSIBLE_STR_REPLACE,
30043032
ITER_OVEREAGER_CLONED,
30053033
CLONED_INSTEAD_OF_COPIED,
30063034
FLAT_MAP_OPTION,
@@ -3491,6 +3519,7 @@ impl Methods {
34913519
("sort_unstable_by", [arg]) => {
34923520
unnecessary_sort_by::check(cx, expr, recv, arg, true);
34933521
},
3522+
("replace", [_, _]) => collapsible_str_replace::check(cx, expr, recv, args),
34943523
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
34953524
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
34963525
suspicious_splitn::check(cx, name, expr, recv, count);

tests/ui/collapsible_str_replace.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#![warn(clippy::collapsible_str_replace)]
2+
3+
fn main() {
4+
let misspelled = "hesuo worpd";
5+
6+
let p = 'p';
7+
let s = 's';
8+
let u = 'u';
9+
10+
// If the first argument to a single `str::replace` call is a slice and none of the chars
11+
// are variables, recommend `collapsible_str_replace`
12+
let replacement = misspelled.replace(&['s', 'u', 'p'], "l");
13+
println!("{replacement}");
14+
15+
// The first iteration of `collapsible_str_replace` will not create lint if the first argument to
16+
// a single `str::replace` call is a slice and one or more of its chars are variables
17+
let replacement = misspelled.replace(&['s', u, 'p'], "l");
18+
println!("{replacement}");
19+
20+
let replacement = misspelled.replace(&[s, u, 'p'], "l");
21+
println!("{replacement}");
22+
23+
let replacement = misspelled.replace(&[s, u, p], "l");
24+
println!("{replacement}");
25+
26+
// If there is a single call to `str::replace` and the first argument is a char or a variable, don't
27+
// not recommend `collapsible_str_replace`
28+
let replacement = misspelled.replace('s', "l");
29+
println!("{replacement}");
30+
31+
let replacement = misspelled.replace(s, "l");
32+
println!("{replacement}");
33+
34+
// If there are consecutive calls to `str::replace` and none of the chars are variables,
35+
// recommend `collapsible_str_replace`
36+
let replacement = misspelled.replace('s', "l").replace('u', "l");
37+
println!("{replacement}");
38+
39+
let replacement = misspelled.replace('s', "l").replace('u', "l").replace('p', "l");
40+
println!("{replacement}");
41+
42+
// If there are consecutive calls to `str::replace` and all or any chars are variables,
43+
// recommend the fallback `misspelled.replace(&[s, u, p], "l")`
44+
let replacement = misspelled.replace(s, "l").replace('u', "l");
45+
println!("{replacement}");
46+
47+
let replacement = misspelled.replace(s, "l").replace('u', "l").replace('p', "l");
48+
println!("{replacement}");
49+
50+
let replacement = misspelled.replace(s, "l").replace(u, "l").replace('p', "l");
51+
println!("{replacement}");
52+
53+
let replacement = misspelled.replace(s, "l").replace(u, "l").replace(p, "l");
54+
println!("{replacement}");
55+
}

0 commit comments

Comments
 (0)