]>
Commit | Line | Data |
---|---|---|
ea8adc8c XL |
1 | use rustc::hir::*; |
2 | use rustc::lint::*; | |
3 | use syntax::codemap::Spanned; | |
4 | use utils::SpanlessEq; | |
abe05a73 | 5 | use utils::{get_parent_expr, is_allowed, match_type, paths, span_lint, span_lint_and_sugg, walk_ptrs_ty}; |
ea8adc8c XL |
6 | |
7 | /// **What it does:** Checks for string appends of the form `x = x + y` (without | |
8 | /// `let`!). | |
9 | /// | |
10 | /// **Why is this bad?** It's not really bad, but some people think that the | |
0531ce1d XL |
11 | /// `.push_str(_)` method is more readable. Also creates a new heap allocation and throws |
12 | /// away the old one. | |
ea8adc8c XL |
13 | /// |
14 | /// **Known problems:** None. | |
15 | /// | |
16 | /// **Example:** | |
17 | /// | |
18 | /// ```rust | |
19 | /// let mut x = "Hello".to_owned(); | |
20 | /// x = x + ", World"; | |
21 | /// ``` | |
0531ce1d | 22 | declare_clippy_lint! { |
ea8adc8c | 23 | pub STRING_ADD_ASSIGN, |
0531ce1d | 24 | pedantic, |
ea8adc8c XL |
25 | "using `x = x + ..` where x is a `String` instead of `push_str()`" |
26 | } | |
27 | ||
28 | /// **What it does:** Checks for all instances of `x + _` where `x` is of type | |
29 | /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not* | |
30 | /// match. | |
31 | /// | |
32 | /// **Why is this bad?** It's not bad in and of itself. However, this particular | |
33 | /// `Add` implementation is asymmetric (the other operand need not be `String`, | |
34 | /// but `x` does), while addition as mathematically defined is symmetric, also | |
35 | /// the `String::push_str(_)` function is a perfectly good replacement. | |
36 | /// Therefore some dislike it and wish not to have it in their code. | |
37 | /// | |
38 | /// That said, other people think that string addition, having a long tradition | |
39 | /// in other languages is actually fine, which is why we decided to make this | |
40 | /// particular lint `allow` by default. | |
41 | /// | |
42 | /// **Known problems:** None. | |
43 | /// | |
44 | /// **Example:** | |
45 | /// | |
46 | /// ```rust | |
47 | /// let x = "Hello".to_owned(); | |
48 | /// x + ", World" | |
49 | /// ``` | |
0531ce1d | 50 | declare_clippy_lint! { |
ea8adc8c | 51 | pub STRING_ADD, |
0531ce1d | 52 | restriction, |
ea8adc8c XL |
53 | "using `x + ..` where x is a `String` instead of `push_str()`" |
54 | } | |
55 | ||
56 | /// **What it does:** Checks for the `as_bytes` method called on string literals | |
57 | /// that contain only ASCII characters. | |
58 | /// | |
59 | /// **Why is this bad?** Byte string literals (e.g. `b"foo"`) can be used | |
60 | /// instead. They are shorter but less discoverable than `as_bytes()`. | |
61 | /// | |
62 | /// **Known Problems:** None. | |
63 | /// | |
64 | /// **Example:** | |
65 | /// ```rust | |
66 | /// let bs = "a byte string".as_bytes(); | |
67 | /// ``` | |
0531ce1d | 68 | declare_clippy_lint! { |
ea8adc8c | 69 | pub STRING_LIT_AS_BYTES, |
0531ce1d | 70 | style, |
ea8adc8c XL |
71 | "calling `as_bytes` on a string literal instead of using a byte string literal" |
72 | } | |
73 | ||
74 | #[derive(Copy, Clone)] | |
75 | pub struct StringAdd; | |
76 | ||
77 | impl LintPass for StringAdd { | |
78 | fn get_lints(&self) -> LintArray { | |
79 | lint_array!(STRING_ADD, STRING_ADD_ASSIGN) | |
80 | } | |
81 | } | |
82 | ||
83 | impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StringAdd { | |
84 | fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) { | |
85 | if let ExprBinary(Spanned { node: BiAdd, .. }, ref left, _) = e.node { | |
86 | if is_string(cx, left) { | |
87 | if !is_allowed(cx, STRING_ADD_ASSIGN, e.id) { | |
88 | let parent = get_parent_expr(cx, e); | |
89 | if let Some(p) = parent { | |
90 | if let ExprAssign(ref target, _) = p.node { | |
91 | // avoid duplicate matches | |
92 | if SpanlessEq::new(cx).eq_expr(target, left) { | |
93 | return; | |
94 | } | |
95 | } | |
96 | } | |
97 | } | |
98 | span_lint( | |
99 | cx, | |
100 | STRING_ADD, | |
101 | e.span, | |
102 | "you added something to a string. Consider using `String::push_str()` instead", | |
103 | ); | |
104 | } | |
105 | } else if let ExprAssign(ref target, ref src) = e.node { | |
106 | if is_string(cx, target) && is_add(cx, src, target) { | |
107 | span_lint( | |
108 | cx, | |
109 | STRING_ADD_ASSIGN, | |
110 | e.span, | |
111 | "you assigned the result of adding something to this string. Consider using \ | |
abe05a73 | 112 | `String::push_str()` instead", |
ea8adc8c XL |
113 | ); |
114 | } | |
115 | } | |
116 | } | |
117 | } | |
118 | ||
119 | fn is_string(cx: &LateContext, e: &Expr) -> bool { | |
120 | match_type(cx, walk_ptrs_ty(cx.tables.expr_ty(e)), &paths::STRING) | |
121 | } | |
122 | ||
123 | fn is_add(cx: &LateContext, src: &Expr, target: &Expr) -> bool { | |
124 | match src.node { | |
125 | ExprBinary(Spanned { node: BiAdd, .. }, ref left, _) => SpanlessEq::new(cx).eq_expr(target, left), | |
94b46f34 | 126 | ExprBlock(ref block, _) => { |
abe05a73 XL |
127 | block.stmts.is_empty() |
128 | && block | |
129 | .expr | |
130 | .as_ref() | |
131 | .map_or(false, |expr| is_add(cx, expr, target)) | |
ea8adc8c XL |
132 | }, |
133 | _ => false, | |
134 | } | |
135 | } | |
136 | ||
137 | #[derive(Copy, Clone)] | |
138 | pub struct StringLitAsBytes; | |
139 | ||
140 | impl LintPass for StringLitAsBytes { | |
141 | fn get_lints(&self) -> LintArray { | |
142 | lint_array!(STRING_LIT_AS_BYTES) | |
143 | } | |
144 | } | |
145 | ||
146 | impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StringLitAsBytes { | |
147 | fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) { | |
ea8adc8c | 148 | use syntax::ast::LitKind; |
abe05a73 | 149 | use utils::{in_macro, snippet}; |
ea8adc8c XL |
150 | |
151 | if let ExprMethodCall(ref path, _, ref args) = e.node { | |
152 | if path.name == "as_bytes" { | |
153 | if let ExprLit(ref lit) = args[0].node { | |
154 | if let LitKind::Str(ref lit_content, _) = lit.node { | |
155 | if lit_content.as_str().chars().all(|c| c.is_ascii()) && !in_macro(args[0].span) { | |
156 | span_lint_and_sugg( | |
157 | cx, | |
158 | STRING_LIT_AS_BYTES, | |
159 | e.span, | |
160 | "calling `as_bytes()` on a string literal", | |
161 | "consider using a byte string literal instead", | |
162 | format!("b{}", snippet(cx, args[0].span, r#""foo""#)), | |
163 | ); | |
164 | } | |
165 | } | |
166 | } | |
167 | } | |
168 | } | |
169 | } | |
170 | } |