]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/strings.rs
Merge commit '2f6439ae6a6803d030cceb3ee14c9150e91b328b' into clippyup
[rust.git] / clippy_lints / src / strings.rs
1 use rustc_errors::Applicability;
2 use rustc_hir::{BinOpKind, Expr, ExprKind};
3 use rustc_lint::{LateContext, LateLintPass, LintContext};
4 use rustc_middle::lint::in_external_macro;
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
6 use rustc_span::source_map::Spanned;
7
8 use if_chain::if_chain;
9
10 use crate::utils::SpanlessEq;
11 use crate::utils::{get_parent_expr, is_allowed, is_type_diagnostic_item, span_lint, span_lint_and_sugg};
12
13 declare_clippy_lint! {
14     /// **What it does:** Checks for string appends of the form `x = x + y` (without
15     /// `let`!).
16     ///
17     /// **Why is this bad?** It's not really bad, but some people think that the
18     /// `.push_str(_)` method is more readable.
19     ///
20     /// **Known problems:** None.
21     ///
22     /// **Example:**
23     ///
24     /// ```rust
25     /// let mut x = "Hello".to_owned();
26     /// x = x + ", World";
27     ///
28     /// // More readable
29     /// x += ", World";
30     /// x.push_str(", World");
31     /// ```
32     pub STRING_ADD_ASSIGN,
33     pedantic,
34     "using `x = x + ..` where x is a `String` instead of `push_str()`"
35 }
36
37 declare_clippy_lint! {
38     /// **What it does:** Checks for all instances of `x + _` where `x` is of type
39     /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not*
40     /// match.
41     ///
42     /// **Why is this bad?** It's not bad in and of itself. However, this particular
43     /// `Add` implementation is asymmetric (the other operand need not be `String`,
44     /// but `x` does), while addition as mathematically defined is symmetric, also
45     /// the `String::push_str(_)` function is a perfectly good replacement.
46     /// Therefore, some dislike it and wish not to have it in their code.
47     ///
48     /// That said, other people think that string addition, having a long tradition
49     /// in other languages is actually fine, which is why we decided to make this
50     /// particular lint `allow` by default.
51     ///
52     /// **Known problems:** None.
53     ///
54     /// **Example:**
55     ///
56     /// ```rust
57     /// let x = "Hello".to_owned();
58     /// x + ", World";
59     /// ```
60     pub STRING_ADD,
61     restriction,
62     "using `x + ..` where x is a `String` instead of `push_str()`"
63 }
64
65 declare_clippy_lint! {
66     /// **What it does:** Checks for the `as_bytes` method called on string literals
67     /// that contain only ASCII characters.
68     ///
69     /// **Why is this bad?** Byte string literals (e.g., `b"foo"`) can be used
70     /// instead. They are shorter but less discoverable than `as_bytes()`.
71     ///
72     /// **Known Problems:**
73     /// `"str".as_bytes()` and the suggested replacement of `b"str"` are not
74     /// equivalent because they have different types. The former is `&[u8]`
75     /// while the latter is `&[u8; 3]`. That means in general they will have a
76     /// different set of methods and different trait implementations.
77     ///
78     /// ```compile_fail
79     /// fn f(v: Vec<u8>) {}
80     ///
81     /// f("...".as_bytes().to_owned()); // works
82     /// f(b"...".to_owned()); // does not work, because arg is [u8; 3] not Vec<u8>
83     ///
84     /// fn g(r: impl std::io::Read) {}
85     ///
86     /// g("...".as_bytes()); // works
87     /// g(b"..."); // does not work
88     /// ```
89     ///
90     /// The actual equivalent of `"str".as_bytes()` with the same type is not
91     /// `b"str"` but `&b"str"[..]`, which is a great deal of punctuation and not
92     /// more readable than a function call.
93     ///
94     /// **Example:**
95     /// ```rust
96     /// // Bad
97     /// let bs = "a byte string".as_bytes();
98     ///
99     /// // Good
100     /// let bs = b"a byte string";
101     /// ```
102     pub STRING_LIT_AS_BYTES,
103     nursery,
104     "calling `as_bytes` on a string literal instead of using a byte string literal"
105 }
106
107 declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN]);
108
109 impl<'tcx> LateLintPass<'tcx> for StringAdd {
110     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
111         if in_external_macro(cx.sess(), e.span) {
112             return;
113         }
114
115         if let ExprKind::Binary(
116             Spanned {
117                 node: BinOpKind::Add, ..
118             },
119             ref left,
120             _,
121         ) = e.kind
122         {
123             if is_string(cx, left) {
124                 if !is_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
125                     let parent = get_parent_expr(cx, e);
126                     if let Some(p) = parent {
127                         if let ExprKind::Assign(ref target, _, _) = p.kind {
128                             // avoid duplicate matches
129                             if SpanlessEq::new(cx).eq_expr(target, left) {
130                                 return;
131                             }
132                         }
133                     }
134                 }
135                 span_lint(
136                     cx,
137                     STRING_ADD,
138                     e.span,
139                     "you added something to a string. Consider using `String::push_str()` instead",
140                 );
141             }
142         } else if let ExprKind::Assign(ref target, ref src, _) = e.kind {
143             if is_string(cx, target) && is_add(cx, src, target) {
144                 span_lint(
145                     cx,
146                     STRING_ADD_ASSIGN,
147                     e.span,
148                     "you assigned the result of adding something to this string. Consider using \
149                      `String::push_str()` instead",
150                 );
151             }
152         }
153     }
154 }
155
156 fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
157     is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym!(string_type))
158 }
159
160 fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
161     match src.kind {
162         ExprKind::Binary(
163             Spanned {
164                 node: BinOpKind::Add, ..
165             },
166             ref left,
167             _,
168         ) => SpanlessEq::new(cx).eq_expr(target, left),
169         ExprKind::Block(ref block, _) => {
170             block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target))
171         },
172         _ => false,
173     }
174 }
175
176 // Max length a b"foo" string can take
177 const MAX_LENGTH_BYTE_STRING_LIT: usize = 32;
178
179 declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES]);
180
181 impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
182     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
183         use crate::utils::{snippet, snippet_with_applicability};
184         use rustc_ast::LitKind;
185
186         if_chain! {
187             if let ExprKind::MethodCall(path, _, args, _) = &e.kind;
188             if path.ident.name == sym!(as_bytes);
189             if let ExprKind::Lit(lit) = &args[0].kind;
190             if let LitKind::Str(lit_content, _) = &lit.node;
191             then {
192                 let callsite = snippet(cx, args[0].span.source_callsite(), r#""foo""#);
193                 let mut applicability = Applicability::MachineApplicable;
194                 if callsite.starts_with("include_str!") {
195                     span_lint_and_sugg(
196                         cx,
197                         STRING_LIT_AS_BYTES,
198                         e.span,
199                         "calling `as_bytes()` on `include_str!(..)`",
200                         "consider using `include_bytes!(..)` instead",
201                         snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability).replacen(
202                             "include_str",
203                             "include_bytes",
204                             1,
205                         ),
206                         applicability,
207                     );
208                 } else if lit_content.as_str().is_ascii()
209                     && lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT
210                     && !args[0].span.from_expansion()
211                 {
212                     span_lint_and_sugg(
213                         cx,
214                         STRING_LIT_AS_BYTES,
215                         e.span,
216                         "calling `as_bytes()` on a string literal",
217                         "consider using a byte string literal instead",
218                         format!(
219                             "b{}",
220                             snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability)
221                         ),
222                         applicability,
223                     );
224                 }
225             }
226         }
227     }
228 }