@@ -7,6 +7,7 @@ use rustc::ty::subst::Substs;
7
7
use rustc_const_eval:: ConstContext ;
8
8
use std:: borrow:: Cow ;
9
9
use std:: fmt;
10
+ use syntax:: ast;
10
11
use syntax:: codemap:: Span ;
11
12
use utils:: { get_trait_def_id, implements_trait, in_external_macro, in_macro, is_copy, is_self, is_self_ty,
12
13
iter_input_pats, last_path_segment, match_def_path, match_path, match_qpath, match_trait_method,
@@ -544,6 +545,24 @@ declare_lint! {
544
545
"using `.cloned().collect()` on slice to create a `Vec`"
545
546
}
546
547
548
+ /// **What it does:** Checks for usage of `.chars().last()` or
549
+ /// `.chars().next_back()` on a `str` to check if it ends with a given char.
550
+ ///
551
+ /// **Why is this bad?** Readability, this can be written more concisely as
552
+ /// `_.ends_with(_)`.
553
+ ///
554
+ /// **Known problems:** None.
555
+ ///
556
+ /// **Example:**
557
+ /// ```rust
558
+ /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-')
559
+ /// ```
560
+ declare_lint ! {
561
+ pub CHARS_LAST_CMP ,
562
+ Warn ,
563
+ "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char"
564
+ }
565
+
547
566
impl LintPass for Pass {
548
567
fn get_lints ( & self ) -> LintArray {
549
568
lint_array ! (
@@ -557,6 +576,7 @@ impl LintPass for Pass {
557
576
OPTION_MAP_UNWRAP_OR_ELSE ,
558
577
OR_FUN_CALL ,
559
578
CHARS_NEXT_CMP ,
579
+ CHARS_LAST_CMP ,
560
580
CLONE_ON_COPY ,
561
581
CLONE_ON_REF_PTR ,
562
582
CLONE_DOUBLE_REF ,
@@ -648,9 +668,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
648
668
}
649
669
} ,
650
670
hir:: ExprBinary ( op, ref lhs, ref rhs) if op. node == hir:: BiEq || op. node == hir:: BiNe => {
651
- if !lint_chars_next ( cx, expr, lhs, rhs, op. node == hir:: BiEq ) {
652
- lint_chars_next ( cx, expr, rhs, lhs, op. node == hir:: BiEq ) ;
653
- }
671
+ let mut info = BinaryExprInfo {
672
+ expr : expr,
673
+ chain : lhs,
674
+ other : rhs,
675
+ eq : op. node == hir:: BiEq ,
676
+ } ;
677
+ lint_binary_expr_with_method_call ( cx, & mut info) ;
654
678
} ,
655
679
_ => ( ) ,
656
680
}
@@ -1285,11 +1309,39 @@ fn lint_search_is_some<'a, 'tcx>(
1285
1309
}
1286
1310
}
1287
1311
1288
- /// Checks for the `CHARS_NEXT_CMP` lint.
1289
- fn lint_chars_next < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & ' tcx hir:: Expr , chain : & ' tcx hir:: Expr , other : & ' tcx hir:: Expr , eq : bool ) -> bool {
1312
+ /// Used for `lint_binary_expr_with_method_call`.
1313
+ #[ derive( Copy , Clone ) ]
1314
+ struct BinaryExprInfo < ' a > {
1315
+ expr : & ' a hir:: Expr ,
1316
+ chain : & ' a hir:: Expr ,
1317
+ other : & ' a hir:: Expr ,
1318
+ eq : bool ,
1319
+ }
1320
+
1321
+ /// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
1322
+ fn lint_binary_expr_with_method_call < ' a , ' tcx : ' a > ( cx : & LateContext < ' a , ' tcx > , info : & mut BinaryExprInfo ) {
1323
+ macro_rules! lint_with_both_lhs_and_rhs {
1324
+ ( $func: ident, $cx: expr, $info: ident) => {
1325
+ if !$func( $cx, $info) {
1326
+ :: std:: mem:: swap( & mut $info. chain, & mut $info. other) ;
1327
+ if $func( $cx, $info) {
1328
+ return ;
1329
+ }
1330
+ }
1331
+ }
1332
+ }
1333
+
1334
+ lint_with_both_lhs_and_rhs ! ( lint_chars_next_cmp, cx, info) ;
1335
+ lint_with_both_lhs_and_rhs ! ( lint_chars_last_cmp, cx, info) ;
1336
+ lint_with_both_lhs_and_rhs ! ( lint_chars_next_cmp_with_unwrap, cx, info) ;
1337
+ lint_with_both_lhs_and_rhs ! ( lint_chars_last_cmp_with_unwrap, cx, info) ;
1338
+ }
1339
+
1340
+ /// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_NEXT_CMP` lints.
1341
+ fn lint_chars_cmp < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , info : & BinaryExprInfo , chain_methods : & [ & str ] , lint : & ' static Lint , suggest : & str ) -> bool {
1290
1342
if_let_chain ! { [
1291
- let Some ( args) = method_chain_args( chain, & [ "chars" , "next" ] ) ,
1292
- let hir:: ExprCall ( ref fun, ref arg_char) = other. node,
1343
+ let Some ( args) = method_chain_args( info . chain, chain_methods ) ,
1344
+ let hir:: ExprCall ( ref fun, ref arg_char) = info . other. node,
1293
1345
arg_char. len( ) == 1 ,
1294
1346
let hir:: ExprPath ( ref qpath) = fun. node,
1295
1347
let Some ( segment) = single_segment_path( qpath) ,
@@ -1302,13 +1354,14 @@ fn lint_chars_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr,
1302
1354
}
1303
1355
1304
1356
span_lint_and_sugg( cx,
1305
- CHARS_NEXT_CMP ,
1306
- expr. span,
1307
- "you should use the `starts_with ` method" ,
1357
+ lint ,
1358
+ info . expr. span,
1359
+ & format! ( "you should use the `{} ` method" , suggest ) ,
1308
1360
"like this" ,
1309
- format!( "{}{}.starts_with ({})" ,
1310
- if eq { "" } else { "!" } ,
1361
+ format!( "{}{}.{} ({})" ,
1362
+ if info . eq { "" } else { "!" } ,
1311
1363
snippet( cx, args[ 0 ] [ 0 ] . span, "_" ) ,
1364
+ suggest,
1312
1365
snippet( cx, arg_char[ 0 ] . span, "_" ) ) ) ;
1313
1366
1314
1367
return true ;
@@ -1317,6 +1370,60 @@ fn lint_chars_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr,
1317
1370
false
1318
1371
}
1319
1372
1373
+ /// Checks for the `CHARS_NEXT_CMP` lint.
1374
+ fn lint_chars_next_cmp < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , info : & BinaryExprInfo ) -> bool {
1375
+ lint_chars_cmp ( cx, info, & [ "chars" , "next" ] , CHARS_NEXT_CMP , "starts_with" )
1376
+ }
1377
+
1378
+ /// Checks for the `CHARS_LAST_CMP` lint.
1379
+ fn lint_chars_last_cmp < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , info : & BinaryExprInfo ) -> bool {
1380
+ if lint_chars_cmp ( cx, info, & [ "chars" , "last" ] , CHARS_NEXT_CMP , "ends_with" ) {
1381
+ true
1382
+ } else {
1383
+ lint_chars_cmp ( cx, info, & [ "chars" , "next_back" ] , CHARS_NEXT_CMP , "ends_with" )
1384
+ }
1385
+ }
1386
+
1387
+ /// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`.
1388
+ fn lint_chars_cmp_with_unwrap < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , info : & BinaryExprInfo , chain_methods : & [ & str ] , lint : & ' static Lint , suggest : & str ) -> bool {
1389
+ if_let_chain ! { [
1390
+ let Some ( args) = method_chain_args( info. chain, chain_methods) ,
1391
+ let hir:: ExprLit ( ref lit) = info. other. node,
1392
+ let ast:: LitKind :: Char ( c) = lit. node,
1393
+ ] , {
1394
+ span_lint_and_sugg(
1395
+ cx,
1396
+ lint,
1397
+ info. expr. span,
1398
+ & format!( "you should use the `{}` method" , suggest) ,
1399
+ "like this" ,
1400
+ format!( "{}{}.{}('{}')" ,
1401
+ if info. eq { "" } else { "!" } ,
1402
+ snippet( cx, args[ 0 ] [ 0 ] . span, "_" ) ,
1403
+ suggest,
1404
+ c)
1405
+ ) ;
1406
+
1407
+ return true ;
1408
+ } }
1409
+
1410
+ false
1411
+ }
1412
+
1413
+ /// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`.
1414
+ fn lint_chars_next_cmp_with_unwrap < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , info : & BinaryExprInfo ) -> bool {
1415
+ lint_chars_cmp_with_unwrap ( cx, info, & [ "chars" , "next" , "unwrap" ] , CHARS_NEXT_CMP , "starts_with" )
1416
+ }
1417
+
1418
+ /// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`.
1419
+ fn lint_chars_last_cmp_with_unwrap < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , info : & BinaryExprInfo ) -> bool {
1420
+ if lint_chars_cmp_with_unwrap ( cx, info, & [ "chars" , "last" , "unwrap" ] , CHARS_LAST_CMP , "ends_with" ) {
1421
+ true
1422
+ } else {
1423
+ lint_chars_cmp_with_unwrap ( cx, info, & [ "chars" , "next_back" , "unwrap" ] , CHARS_LAST_CMP , "ends_with" )
1424
+ }
1425
+ }
1426
+
1320
1427
/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
1321
1428
fn lint_single_char_pattern < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & ' tcx hir:: Expr , arg : & ' tcx hir:: Expr ) {
1322
1429
let parent_item = cx. tcx . hir . get_parent ( arg. id ) ;
0 commit comments