]> git.lizzy.rs Git - rust.git/blob - src/strings.rs
pulled strings passes together, added more tests
[rust.git] / src / strings.rs
1 //! This LintPass catches both string addition and string addition + assignment
2 //!
3 //! Note that since we have two lints where one subsumes the other, we try to
4 //! disable the subsumed lint unless it has a higher level
5
6 use rustc::lint::*;
7 use rustc::middle::ty::TypeVariants::TyStruct;
8 use syntax::ast::*;
9 use syntax::codemap::{Span, Spanned};
10 use eq_op::is_exp_equal;
11 use types::match_ty_unwrap;
12 use utils::{match_def_path, span_lint, walk_ptrs_ty, get_parent_expr};
13
14 declare_lint! {
15     pub STRING_ADD_ASSIGN,
16     Warn,
17     "Warn on `x = x + ..` where x is a `String`"
18 }
19
20 declare_lint! {
21     pub STRING_ADD,
22     Allow,
23     "Warn on `x + ..` where x is a `String`"
24 }
25
26 #[derive(Copy, Clone)]
27 pub struct StringAdd;
28
29 impl LintPass for StringAdd {
30     fn get_lints(&self) -> LintArray {
31         lint_array!(STRING_ADD, STRING_ADD_ASSIGN)
32     }
33
34     fn check_expr(&mut self, cx: &Context, e: &Expr) {
35         if let &ExprBinary(Spanned{ node: BiAdd, .. }, ref left, _) = &e.node {
36             if is_string(cx, left) {
37                 if let Allow = cx.current_level(STRING_ADD_ASSIGN) {
38                     // the string_add_assign is allow, so no duplicates
39                 } else {
40                     let parent = get_parent_expr(cx, e);
41                     if let Some(ref p) = parent {
42                         if let &ExprAssign(ref target, _) = &p.node {
43                             // avoid duplicate matches
44                             if is_exp_equal(target, left) { return; }
45                         }
46                     }
47                 }
48                 //TODO check for duplicates
49                  span_lint(cx, STRING_ADD, e.span,
50                         "you add something to a string. \
51                         Consider using `String::push_str()` instead.")
52             }
53         } else if let &ExprAssign(ref target, ref  src) = &e.node {
54             if is_string(cx, target) && is_add(src, target) {
55                 span_lint(cx, STRING_ADD_ASSIGN, e.span,
56                     "you assign the result of adding something to this string. \
57                     Consider using `String::push_str()` instead")
58             }
59         }
60     }
61 }
62
63 fn is_string(cx: &Context, e: &Expr) -> bool {
64     let ty = walk_ptrs_ty(cx.tcx.expr_ty(e));
65     if let TyStruct(did, _) = ty.sty {
66         match_def_path(cx, did.did, &["collections", "string", "String"])
67     } else { false }
68 }
69
70 fn is_add(src: &Expr, target: &Expr) -> bool {
71     match &src.node {
72         &ExprBinary(Spanned{ node: BiAdd, .. }, ref left, _) =>
73             is_exp_equal(target, left),
74         &ExprBlock(ref block) => block.stmts.is_empty() &&
75             block.expr.as_ref().map_or(false, |expr| is_add(&*expr, target)),
76         &ExprParen(ref expr) => is_add(&*expr, target),
77         _ => false
78     }
79 }