Skip to content

Commit 72d2c2e

Browse files
committed
Lint push_str with a single-character string literal
Fixes #5875
1 parent 8ecc0fc commit 72d2c2e

File tree

8 files changed

+112
-0
lines changed

8 files changed

+112
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,7 @@ Released 2018-09-13
16991699
[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
17001700
[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
17011701
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
1702+
[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
17021703
[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
17031704
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
17041705
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else

clippy_lints/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ mod repeat_once;
287287
mod returns;
288288
mod serde_api;
289289
mod shadow;
290+
mod single_char_push_str;
290291
mod single_component_path_imports;
291292
mod slow_vector_initialization;
292293
mod stable_sort_primitive;
@@ -775,6 +776,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
775776
&shadow::SHADOW_REUSE,
776777
&shadow::SHADOW_SAME,
777778
&shadow::SHADOW_UNRELATED,
779+
&single_char_push_str::SINGLE_CHAR_PUSH_STR,
778780
&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
779781
&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
780782
&stable_sort_primitive::STABLE_SORT_PRIMITIVE,
@@ -932,6 +934,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
932934
store.register_late_pass(move || box escape::BoxedLocal{too_large_for_stack});
933935
store.register_late_pass(|| box panic_unimplemented::PanicUnimplemented);
934936
store.register_late_pass(|| box strings::StringLitAsBytes);
937+
store.register_late_pass(|| box single_char_push_str::SingleCharPushStrPass);
935938
store.register_late_pass(|| box derive::Derive);
936939
store.register_late_pass(|| box types::CharLitAsU8);
937940
store.register_late_pass(|| box vec::UselessVec);
@@ -1416,6 +1419,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14161419
LintId::of(&returns::NEEDLESS_RETURN),
14171420
LintId::of(&returns::UNUSED_UNIT),
14181421
LintId::of(&serde_api::SERDE_API_MISUSE),
1422+
LintId::of(&single_char_push_str::SINGLE_CHAR_PUSH_STR),
14191423
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
14201424
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
14211425
LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE),
@@ -1556,6 +1560,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15561560
LintId::of(&regex::TRIVIAL_REGEX),
15571561
LintId::of(&returns::NEEDLESS_RETURN),
15581562
LintId::of(&returns::UNUSED_UNIT),
1563+
LintId::of(&single_char_push_str::SINGLE_CHAR_PUSH_STR),
15591564
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
15601565
LintId::of(&strings::STRING_LIT_AS_BYTES),
15611566
LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use crate::utils::{match_def_path, paths, snippet_with_applicability, span_lint_and_sugg};
2+
use if_chain::if_chain;
3+
use rustc_ast::ast::LitKind;
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{Expr, ExprKind};
6+
use rustc_lint::{LateContext, LateLintPass};
7+
use rustc_session::{declare_lint_pass, declare_tool_lint};
8+
9+
declare_clippy_lint! {
10+
/// **What it does:** Warns when using push_str with a single-character string literal,
11+
/// and push with a char would work fine.
12+
///
13+
/// **Why is this bad?** This is in all probability not the intended outcome. At
14+
/// the least it hurts readability of the code.
15+
///
16+
/// **Known problems:** None
17+
///
18+
/// **Example:**
19+
/// ```
20+
/// let mut string = String::new();
21+
/// string.push_str("R");
22+
/// ```
23+
/// Could be written as
24+
/// ```
25+
/// let mut string = String::new();
26+
/// string.push('R');
27+
/// ```
28+
pub SINGLE_CHAR_PUSH_STR,
29+
style,
30+
"`push_str()` used with a single-character string literal as parameter"
31+
}
32+
33+
declare_lint_pass!(SingleCharPushStrPass => [SINGLE_CHAR_PUSH_STR]);
34+
35+
impl<'tcx> LateLintPass<'tcx> for SingleCharPushStrPass {
36+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
37+
if_chain! {
38+
if let ExprKind::MethodCall(_, _, ref args, _) = expr.kind;
39+
if let [base_string, extension_string] = args;
40+
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
41+
if match_def_path(cx, fn_def_id, &paths::PUSH_STR);
42+
if let ExprKind::Lit(ref lit) = extension_string.kind;
43+
if let LitKind::Str(symbol,_) = lit.node;
44+
let extension_string_val = symbol.as_str().to_string();
45+
if extension_string_val.len() == 1;
46+
then {
47+
let mut applicability = Applicability::MachineApplicable;
48+
let base_string_snippet = snippet_with_applicability(cx, base_string.span, "_", &mut applicability);
49+
let sugg = format!("{}.push({:?})", base_string_snippet, extension_string_val.chars().next().unwrap());
50+
span_lint_and_sugg(
51+
cx,
52+
SINGLE_CHAR_PUSH_STR,
53+
expr.span,
54+
"calling `push_str()` using a single-character string literal",
55+
"consider using `push` with a character literal",
56+
sugg,
57+
applicability
58+
);
59+
}
60+
}
61+
}
62+
}

clippy_lints/src/utils/paths.rs

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
8484
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
8585
pub const PTR_NULL: [&str; 2] = ["ptr", "null"];
8686
pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"];
87+
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
8788
pub const RANGE: [&str; 3] = ["core", "ops", "Range"];
8889
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
8990
pub const RANGE_FROM: [&str; 3] = ["core", "ops", "RangeFrom"];

src/lintlist/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -2012,6 +2012,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
20122012
deprecation: None,
20132013
module: "methods",
20142014
},
2015+
Lint {
2016+
name: "single_char_push_str",
2017+
group: "style",
2018+
desc: "`push_str()` used with a single-character string literal as parameter",
2019+
deprecation: None,
2020+
module: "single_char_push_str",
2021+
},
20152022
Lint {
20162023
name: "single_component_path_imports",
20172024
group: "style",

tests/ui/single_char_push_str.fixed

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// run-rustfix
2+
#![warn(clippy::single_char_push_str)]
3+
4+
fn main() {
5+
let mut string = String::new();
6+
string.push('R');
7+
string.push('\'');
8+
9+
string.push('u');
10+
}

tests/ui/single_char_push_str.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// run-rustfix
2+
#![warn(clippy::single_char_push_str)]
3+
4+
fn main() {
5+
let mut string = String::new();
6+
string.push_str("R");
7+
string.push_str("'");
8+
9+
string.push('u');
10+
}

tests/ui/single_char_push_str.stderr

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: calling `push_str()` using a single-character string literal
2+
--> $DIR/single_char_push_str.rs:6: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-push-str` implied by `-D warnings`
8+
9+
error: calling `push_str()` using a single-character string literal
10+
--> $DIR/single_char_push_str.rs:7:5
11+
|
12+
LL | string.push_str("'");
13+
| ^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('/'')`
14+
15+
error: aborting due to 2 previous errors
16+

0 commit comments

Comments
 (0)