]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/strings.rs
Auto merge of #6828 - mgacek8:issue_6758_enhance_wrong_self_convention, r=flip1995
[rust.git] / clippy_lints / src / strings.rs
1 use crate::utils::SpanlessEq;
2 use crate::utils::{
3     get_parent_expr, is_allowed, match_function_call, method_calls, paths, span_lint, span_lint_and_help,
4     span_lint_and_sugg,
5 };
6 use clippy_utils::ty::is_type_diagnostic_item;
7 use if_chain::if_chain;
8 use rustc_errors::Applicability;
9 use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath};
10 use rustc_lint::{LateContext, LateLintPass, LintContext};
11 use rustc_middle::lint::in_external_macro;
12 use rustc_middle::ty;
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::source_map::Spanned;
15 use rustc_span::sym;
16
17 declare_clippy_lint! {
18     /// **What it does:** Checks for string appends of the form `x = x + y` (without
19     /// `let`!).
20     ///
21     /// **Why is this bad?** It's not really bad, but some people think that the
22     /// `.push_str(_)` method is more readable.
23     ///
24     /// **Known problems:** None.
25     ///
26     /// **Example:**
27     ///
28     /// ```rust
29     /// let mut x = "Hello".to_owned();
30     /// x = x + ", World";
31     ///
32     /// // More readable
33     /// x += ", World";
34     /// x.push_str(", World");
35     /// ```
36     pub STRING_ADD_ASSIGN,
37     pedantic,
38     "using `x = x + ..` where x is a `String` instead of `push_str()`"
39 }
40
41 declare_clippy_lint! {
42     /// **What it does:** Checks for all instances of `x + _` where `x` is of type
43     /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not*
44     /// match.
45     ///
46     /// **Why is this bad?** It's not bad in and of itself. However, this particular
47     /// `Add` implementation is asymmetric (the other operand need not be `String`,
48     /// but `x` does), while addition as mathematically defined is symmetric, also
49     /// the `String::push_str(_)` function is a perfectly good replacement.
50     /// Therefore, some dislike it and wish not to have it in their code.
51     ///
52     /// That said, other people think that string addition, having a long tradition
53     /// in other languages is actually fine, which is why we decided to make this
54     /// particular lint `allow` by default.
55     ///
56     /// **Known problems:** None.
57     ///
58     /// **Example:**
59     ///
60     /// ```rust
61     /// let x = "Hello".to_owned();
62     /// x + ", World";
63     /// ```
64     pub STRING_ADD,
65     restriction,
66     "using `x + ..` where x is a `String` instead of `push_str()`"
67 }
68
69 declare_clippy_lint! {
70     /// **What it does:** Checks for the `as_bytes` method called on string literals
71     /// that contain only ASCII characters.
72     ///
73     /// **Why is this bad?** Byte string literals (e.g., `b"foo"`) can be used
74     /// instead. They are shorter but less discoverable than `as_bytes()`.
75     ///
76     /// **Known Problems:**
77     /// `"str".as_bytes()` and the suggested replacement of `b"str"` are not
78     /// equivalent because they have different types. The former is `&[u8]`
79     /// while the latter is `&[u8; 3]`. That means in general they will have a
80     /// different set of methods and different trait implementations.
81     ///
82     /// ```compile_fail
83     /// fn f(v: Vec<u8>) {}
84     ///
85     /// f("...".as_bytes().to_owned()); // works
86     /// f(b"...".to_owned()); // does not work, because arg is [u8; 3] not Vec<u8>
87     ///
88     /// fn g(r: impl std::io::Read) {}
89     ///
90     /// g("...".as_bytes()); // works
91     /// g(b"..."); // does not work
92     /// ```
93     ///
94     /// The actual equivalent of `"str".as_bytes()` with the same type is not
95     /// `b"str"` but `&b"str"[..]`, which is a great deal of punctuation and not
96     /// more readable than a function call.
97     ///
98     /// **Example:**
99     /// ```rust
100     /// // Bad
101     /// let bs = "a byte string".as_bytes();
102     ///
103     /// // Good
104     /// let bs = b"a byte string";
105     /// ```
106     pub STRING_LIT_AS_BYTES,
107     nursery,
108     "calling `as_bytes` on a string literal instead of using a byte string literal"
109 }
110
111 declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN]);
112
113 impl<'tcx> LateLintPass<'tcx> for StringAdd {
114     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
115         if in_external_macro(cx.sess(), e.span) {
116             return;
117         }
118
119         if let ExprKind::Binary(
120             Spanned {
121                 node: BinOpKind::Add, ..
122             },
123             ref left,
124             _,
125         ) = e.kind
126         {
127             if is_string(cx, left) {
128                 if !is_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
129                     let parent = get_parent_expr(cx, e);
130                     if let Some(p) = parent {
131                         if let ExprKind::Assign(ref target, _, _) = p.kind {
132                             // avoid duplicate matches
133                             if SpanlessEq::new(cx).eq_expr(target, left) {
134                                 return;
135                             }
136                         }
137                     }
138                 }
139                 span_lint(
140                     cx,
141                     STRING_ADD,
142                     e.span,
143                     "you added something to a string. Consider using `String::push_str()` instead",
144                 );
145             }
146         } else if let ExprKind::Assign(ref target, ref src, _) = e.kind {
147             if is_string(cx, target) && is_add(cx, src, target) {
148                 span_lint(
149                     cx,
150                     STRING_ADD_ASSIGN,
151                     e.span,
152                     "you assigned the result of adding something to this string. Consider using \
153                      `String::push_str()` instead",
154                 );
155             }
156         }
157     }
158 }
159
160 fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
161     is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::string_type)
162 }
163
164 fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
165     match src.kind {
166         ExprKind::Binary(
167             Spanned {
168                 node: BinOpKind::Add, ..
169             },
170             ref left,
171             _,
172         ) => SpanlessEq::new(cx).eq_expr(target, left),
173         ExprKind::Block(ref block, _) => {
174             block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target))
175         },
176         _ => false,
177     }
178 }
179
180 declare_clippy_lint! {
181     /// **What it does:** Check if the string is transformed to byte array and casted back to string.
182     ///
183     /// **Why is this bad?** It's unnecessary, the string can be used directly.
184     ///
185     /// **Known problems:** None
186     ///
187     /// **Example:**
188     /// ```rust
189     /// let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap();
190     /// ```
191     /// could be written as
192     /// ```rust
193     /// let _ = &"Hello World!"[6..11];
194     /// ```
195     pub STRING_FROM_UTF8_AS_BYTES,
196     complexity,
197     "casting string slices to byte slices and back"
198 }
199
200 // Max length a b"foo" string can take
201 const MAX_LENGTH_BYTE_STRING_LIT: usize = 32;
202
203 declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES, STRING_FROM_UTF8_AS_BYTES]);
204
205 impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
206     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
207         use crate::utils::{snippet, snippet_with_applicability};
208         use rustc_ast::LitKind;
209
210         if_chain! {
211             // Find std::str::converts::from_utf8
212             if let Some(args) = match_function_call(cx, e, &paths::STR_FROM_UTF8);
213
214             // Find string::as_bytes
215             if let ExprKind::AddrOf(BorrowKind::Ref, _, ref args) = args[0].kind;
216             if let ExprKind::Index(ref left, ref right) = args.kind;
217             let (method_names, expressions, _) = method_calls(left, 1);
218             if method_names.len() == 1;
219             if expressions.len() == 1;
220             if expressions[0].len() == 1;
221             if method_names[0] == sym!(as_bytes);
222
223             // Check for slicer
224             if let ExprKind::Struct(QPath::LangItem(LangItem::Range, _), _, _) = right.kind;
225
226             then {
227                 let mut applicability = Applicability::MachineApplicable;
228                 let string_expression = &expressions[0][0];
229
230                 let snippet_app = snippet_with_applicability(
231                     cx,
232                     string_expression.span, "..",
233                     &mut applicability,
234                 );
235
236                 span_lint_and_sugg(
237                     cx,
238                     STRING_FROM_UTF8_AS_BYTES,
239                     e.span,
240                     "calling a slice of `as_bytes()` with `from_utf8` should be not necessary",
241                     "try",
242                     format!("Some(&{}[{}])", snippet_app, snippet(cx, right.span, "..")),
243                     applicability
244                 )
245             }
246         }
247
248         if_chain! {
249             if let ExprKind::MethodCall(path, _, args, _) = &e.kind;
250             if path.ident.name == sym!(as_bytes);
251             if let ExprKind::Lit(lit) = &args[0].kind;
252             if let LitKind::Str(lit_content, _) = &lit.node;
253             then {
254                 let callsite = snippet(cx, args[0].span.source_callsite(), r#""foo""#);
255                 let mut applicability = Applicability::MachineApplicable;
256                 if callsite.starts_with("include_str!") {
257                     span_lint_and_sugg(
258                         cx,
259                         STRING_LIT_AS_BYTES,
260                         e.span,
261                         "calling `as_bytes()` on `include_str!(..)`",
262                         "consider using `include_bytes!(..)` instead",
263                         snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability).replacen(
264                             "include_str",
265                             "include_bytes",
266                             1,
267                         ),
268                         applicability,
269                     );
270                 } else if lit_content.as_str().is_ascii()
271                     && lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT
272                     && !args[0].span.from_expansion()
273                 {
274                     span_lint_and_sugg(
275                         cx,
276                         STRING_LIT_AS_BYTES,
277                         e.span,
278                         "calling `as_bytes()` on a string literal",
279                         "consider using a byte string literal instead",
280                         format!(
281                             "b{}",
282                             snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability)
283                         ),
284                         applicability,
285                     );
286                 }
287             }
288         }
289     }
290 }
291
292 declare_clippy_lint! {
293     /// **What it does:** This lint checks for `.to_string()` method calls on values of type `&str`.
294     ///
295     /// **Why is this bad?** The `to_string` method is also used on other types to convert them to a string.
296     /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better
297     /// expressed with `.to_owned()`.
298     ///
299     /// **Known problems:** None.
300     ///
301     /// **Example:**
302     ///
303     /// ```rust
304     /// // example code where clippy issues a warning
305     /// let _ = "str".to_string();
306     /// ```
307     /// Use instead:
308     /// ```rust
309     /// // example code which does not raise clippy warning
310     /// let _ = "str".to_owned();
311     /// ```
312     pub STR_TO_STRING,
313     restriction,
314     "using `to_string()` on a `&str`, which should be `to_owned()`"
315 }
316
317 declare_lint_pass!(StrToString => [STR_TO_STRING]);
318
319 impl LateLintPass<'_> for StrToString {
320     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
321         if_chain! {
322             if let ExprKind::MethodCall(path, _, args, _) = &expr.kind;
323             if path.ident.name == sym!(to_string);
324             let ty = cx.typeck_results().expr_ty(&args[0]);
325             if let ty::Ref(_, ty, ..) = ty.kind();
326             if *ty.kind() == ty::Str;
327             then {
328                 span_lint_and_help(
329                     cx,
330                     STR_TO_STRING,
331                     expr.span,
332                     "`to_string()` called on a `&str`",
333                     None,
334                     "consider using `.to_owned()`",
335                 );
336             }
337         }
338     }
339 }
340
341 declare_clippy_lint! {
342     /// **What it does:** This lint checks for `.to_string()` method calls on values of type `String`.
343     ///
344     /// **Why is this bad?** The `to_string` method is also used on other types to convert them to a string.
345     /// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`.
346     /// **Known problems:** None.
347     ///
348     /// **Example:**
349     ///
350     /// ```rust
351     /// // example code where clippy issues a warning
352     /// let msg = String::from("Hello World");
353     /// let _ = msg.to_string();
354     /// ```
355     /// Use instead:
356     /// ```rust
357     /// // example code which does not raise clippy warning
358     /// let msg = String::from("Hello World");
359     /// let _ = msg.clone();
360     /// ```
361     pub STRING_TO_STRING,
362     restriction,
363     "using `to_string()` on a `String`, which should be `clone()`"
364 }
365
366 declare_lint_pass!(StringToString => [STRING_TO_STRING]);
367
368 impl LateLintPass<'_> for StringToString {
369     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
370         if_chain! {
371             if let ExprKind::MethodCall(path, _, args, _) = &expr.kind;
372             if path.ident.name == sym!(to_string);
373             let ty = cx.typeck_results().expr_ty(&args[0]);
374             if is_type_diagnostic_item(cx, ty, sym::string_type);
375             then {
376                 span_lint_and_help(
377                     cx,
378                     STRING_TO_STRING,
379                     expr.span,
380                     "`to_string()` called on a `String`",
381                     None,
382                     "consider using `.clone()`",
383                 );
384             }
385         }
386     }
387 }