@@ -1306,6 +1306,29 @@ declare_clippy_lint! {
1306
1306
"using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
1307
1307
}
1308
1308
1309
+ declare_clippy_lint ! {
1310
+ /// **What it does:** Warns when using push_str with a single-character string literal,
1311
+ /// and push with a char would work fine.
1312
+ ///
1313
+ /// **Why is this bad?** It's less clear that we are pushing a single character
1314
+ ///
1315
+ /// **Known problems:** None
1316
+ ///
1317
+ /// **Example:**
1318
+ /// ```
1319
+ /// let mut string = String::new();
1320
+ /// string.push_str("R");
1321
+ /// ```
1322
+ /// Could be written as
1323
+ /// ```
1324
+ /// let mut string = String::new();
1325
+ /// string.push('R');
1326
+ /// ```
1327
+ pub SINGLE_CHAR_PUSH_STR ,
1328
+ style,
1329
+ "`push_str()` used with a single-character string literal as parameter"
1330
+ }
1331
+
1309
1332
declare_lint_pass ! ( Methods => [
1310
1333
UNWRAP_USED ,
1311
1334
EXPECT_USED ,
@@ -1327,6 +1350,7 @@ declare_lint_pass!(Methods => [
1327
1350
INEFFICIENT_TO_STRING ,
1328
1351
NEW_RET_NO_SELF ,
1329
1352
SINGLE_CHAR_PATTERN ,
1353
+ SINGLE_CHAR_PUSH_STR ,
1330
1354
SEARCH_IS_SOME ,
1331
1355
TEMPORARY_CSTRING_AS_PTR ,
1332
1356
FILTER_NEXT ,
@@ -1441,6 +1465,12 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
1441
1465
inefficient_to_string:: lint ( cx, expr, & args[ 0 ] , self_ty) ;
1442
1466
}
1443
1467
1468
+ if let Some ( fn_def_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id ) {
1469
+ if match_def_path ( cx, fn_def_id, & paths:: PUSH_STR ) {
1470
+ lint_single_char_push_string ( cx, expr, args) ;
1471
+ }
1472
+ }
1473
+
1444
1474
match self_ty. kind {
1445
1475
ty:: Ref ( _, ty, _) if ty. kind == ty:: Str => {
1446
1476
for & ( method, pos) in & PATTERN_METHODS {
@@ -3124,15 +3154,18 @@ fn lint_chars_last_cmp_with_unwrap<'tcx>(cx: &LateContext<'tcx>, info: &BinaryEx
3124
3154
}
3125
3155
}
3126
3156
3127
- /// lint for length-1 `str`s for methods in `PATTERN_METHODS`
3128
- fn lint_single_char_pattern < ' tcx > ( cx : & LateContext < ' tcx > , _expr : & ' tcx hir:: Expr < ' _ > , arg : & ' tcx hir:: Expr < ' _ > ) {
3157
+ fn get_hint_if_single_char_arg (
3158
+ cx : & LateContext < ' _ > ,
3159
+ arg : & hir:: Expr < ' _ > ,
3160
+ applicability : & mut Applicability ,
3161
+ ) -> Option < String > {
3129
3162
if_chain ! {
3130
3163
if let hir:: ExprKind :: Lit ( lit) = & arg. kind;
3131
3164
if let ast:: LitKind :: Str ( r, style) = lit. node;
3132
- if r. as_str( ) . len( ) == 1 ;
3165
+ let string = r. as_str( ) ;
3166
+ if string. len( ) == 1 ;
3133
3167
then {
3134
- let mut applicability = Applicability :: MachineApplicable ;
3135
- let snip = snippet_with_applicability( cx, arg. span, ".." , & mut applicability) ;
3168
+ let snip = snippet_with_applicability( cx, arg. span, & string, applicability) ;
3136
3169
let ch = if let ast:: StrStyle :: Raw ( nhash) = style {
3137
3170
let nhash = nhash as usize ;
3138
3171
// for raw string: r##"a"##
@@ -3142,19 +3175,47 @@ fn lint_single_char_pattern<'tcx>(cx: &LateContext<'tcx>, _expr: &'tcx hir::Expr
3142
3175
& snip[ 1 ..( snip. len( ) - 1 ) ]
3143
3176
} ;
3144
3177
let hint = format!( "'{}'" , if ch == "'" { "\\ '" } else { ch } ) ;
3145
- span_lint_and_sugg(
3146
- cx,
3147
- SINGLE_CHAR_PATTERN ,
3148
- arg. span,
3149
- "single-character string constant used as pattern" ,
3150
- "try using a `char` instead" ,
3151
- hint,
3152
- applicability,
3153
- ) ;
3178
+ Some ( hint)
3179
+ } else {
3180
+ None
3154
3181
}
3155
3182
}
3156
3183
}
3157
3184
3185
+ /// lint for length-1 `str`s for methods in `PATTERN_METHODS`
3186
+ fn lint_single_char_pattern ( cx : & LateContext < ' _ > , _expr : & hir:: Expr < ' _ > , arg : & hir:: Expr < ' _ > ) {
3187
+ let mut applicability = Applicability :: MachineApplicable ;
3188
+ if let Some ( hint) = get_hint_if_single_char_arg ( cx, arg, & mut applicability) {
3189
+ span_lint_and_sugg (
3190
+ cx,
3191
+ SINGLE_CHAR_PATTERN ,
3192
+ arg. span ,
3193
+ "single-character string constant used as pattern" ,
3194
+ "try using a `char` instead" ,
3195
+ hint,
3196
+ applicability,
3197
+ ) ;
3198
+ }
3199
+ }
3200
+
3201
+ /// lint for length-1 `str`s as argument for `push_str`
3202
+ fn lint_single_char_push_string ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > , args : & [ hir:: Expr < ' _ > ] ) {
3203
+ let mut applicability = Applicability :: MachineApplicable ;
3204
+ if let Some ( extension_string) = get_hint_if_single_char_arg ( cx, & args[ 1 ] , & mut applicability) {
3205
+ let base_string_snippet = snippet_with_applicability ( cx, args[ 0 ] . span , "_" , & mut applicability) ;
3206
+ let sugg = format ! ( "{}.push({})" , base_string_snippet, extension_string) ;
3207
+ span_lint_and_sugg (
3208
+ cx,
3209
+ SINGLE_CHAR_PUSH_STR ,
3210
+ expr. span ,
3211
+ "calling `push_str()` using a single-character string literal" ,
3212
+ "consider using `push` with a character literal" ,
3213
+ sugg,
3214
+ applicability,
3215
+ ) ;
3216
+ }
3217
+ }
3218
+
3158
3219
/// Checks for the `USELESS_ASREF` lint.
3159
3220
fn lint_asref ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > , call_name : & str , as_ref_args : & [ hir:: Expr < ' _ > ] ) {
3160
3221
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"
0 commit comments