]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/formatting.rs
Remove crate:: prefixes from crate paths
[rust.git] / clippy_lints / src / formatting.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 use crate::utils::{differing_macro_contexts, in_macro, snippet_opt, span_note_and_lint};
11 use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
12 use rustc::{declare_tool_lint, lint_array};
13 use syntax::ast;
14 use syntax::ptr::P;
15
16 /// **What it does:** Checks for use of the non-existent `=*`, `=!` and `=-`
17 /// operators.
18 ///
19 /// **Why is this bad?** This is either a typo of `*=`, `!=` or `-=` or
20 /// confusing.
21 ///
22 /// **Known problems:** None.
23 ///
24 /// **Example:**
25 /// ```rust,ignore
26 /// a =- 42; // confusing, should it be `a -= 42` or `a = -42`?
27 /// ```
28 declare_clippy_lint! {
29     pub SUSPICIOUS_ASSIGNMENT_FORMATTING,
30     style,
31     "suspicious formatting of `*=`, `-=` or `!=`"
32 }
33
34 /// **What it does:** Checks for formatting of `else`. It lints if the `else`
35 /// is followed immediately by a newline or the `else` seems to be missing.
36 ///
37 /// **Why is this bad?** This is probably some refactoring remnant, even if the
38 /// code is correct, it might look confusing.
39 ///
40 /// **Known problems:** None.
41 ///
42 /// **Example:**
43 /// ```rust,ignore
44 /// if foo {
45 /// } { // looks like an `else` is missing here
46 /// }
47 ///
48 /// if foo {
49 /// } if bar { // looks like an `else` is missing here
50 /// }
51 ///
52 /// if foo {
53 /// } else
54 ///
55 /// { // this is the `else` block of the previous `if`, but should it be?
56 /// }
57 ///
58 /// if foo {
59 /// } else
60 ///
61 /// if bar { // this is the `else` block of the previous `if`, but should it be?
62 /// }
63 /// ```
64 declare_clippy_lint! {
65     pub SUSPICIOUS_ELSE_FORMATTING,
66     style,
67     "suspicious formatting of `else`"
68 }
69
70 /// **What it does:** Checks for possible missing comma in an array. It lints if
71 /// an array element is a binary operator expression and it lies on two lines.
72 ///
73 /// **Why is this bad?** This could lead to unexpected results.
74 ///
75 /// **Known problems:** None.
76 ///
77 /// **Example:**
78 /// ```rust,ignore
79 /// let a = &[
80 ///     -1, -2, -3 // <= no comma here
81 ///     -4, -5, -6
82 /// ];
83 /// ```
84 declare_clippy_lint! {
85     pub POSSIBLE_MISSING_COMMA,
86     correctness,
87     "possible missing comma in array"
88 }
89
90 #[derive(Copy, Clone)]
91 pub struct Formatting;
92
93 impl LintPass for Formatting {
94     fn get_lints(&self) -> LintArray {
95         lint_array!(
96             SUSPICIOUS_ASSIGNMENT_FORMATTING,
97             SUSPICIOUS_ELSE_FORMATTING,
98             POSSIBLE_MISSING_COMMA
99         )
100     }
101 }
102
103 impl EarlyLintPass for Formatting {
104     fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
105         for w in block.stmts.windows(2) {
106             match (&w[0].node, &w[1].node) {
107                 (&ast::StmtKind::Expr(ref first), &ast::StmtKind::Expr(ref second))
108                 | (&ast::StmtKind::Expr(ref first), &ast::StmtKind::Semi(ref second)) => {
109                     check_missing_else(cx, first, second);
110                 },
111                 _ => (),
112             }
113         }
114     }
115
116     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
117         check_assign(cx, expr);
118         check_else(cx, expr);
119         check_array(cx, expr);
120     }
121 }
122
123 /// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
124 fn check_assign(cx: &EarlyContext<'_>, expr: &ast::Expr) {
125     if let ast::ExprKind::Assign(ref lhs, ref rhs) = expr.node {
126         if !differing_macro_contexts(lhs.span, rhs.span) && !in_macro(lhs.span) {
127             let eq_span = lhs.span.between(rhs.span);
128             if let ast::ExprKind::Unary(op, ref sub_rhs) = rhs.node {
129                 if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
130                     let op = ast::UnOp::to_string(op);
131                     let eqop_span = lhs.span.between(sub_rhs.span);
132                     if eq_snippet.ends_with('=') {
133                         span_note_and_lint(
134                             cx,
135                             SUSPICIOUS_ASSIGNMENT_FORMATTING,
136                             eqop_span,
137                             &format!(
138                                 "this looks like you are trying to use `.. {op}= ..`, but you \
139                                  really are doing `.. = ({op} ..)`",
140                                 op = op
141                             ),
142                             eqop_span,
143                             &format!("to remove this lint, use either `{op}=` or `= {op}`", op = op),
144                         );
145                     }
146                 }
147             }
148         }
149     }
150 }
151
152 /// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`.
153 fn check_else(cx: &EarlyContext<'_>, expr: &ast::Expr) {
154     if let Some((then, &Some(ref else_))) = unsugar_if(expr) {
155         if (is_block(else_) || unsugar_if(else_).is_some())
156             && !differing_macro_contexts(then.span, else_.span)
157             && !in_macro(then.span)
158         {
159             // workaround for rust-lang/rust#43081
160             if expr.span.lo().0 == 0 && expr.span.hi().0 == 0 {
161                 return;
162             }
163
164             // this will be a span from the closing ‘}’ of the “then” block (excluding) to
165             // the
166             // “if” of the “else if” block (excluding)
167             let else_span = then.span.between(else_.span);
168
169             // the snippet should look like " else \n    " with maybe comments anywhere
170             // it’s bad when there is a ‘\n’ after the “else”
171             if let Some(else_snippet) = snippet_opt(cx, else_span) {
172                 let else_pos = else_snippet.find("else").expect("there must be a `else` here");
173
174                 if else_snippet[else_pos..].contains('\n') {
175                     let else_desc = if unsugar_if(else_).is_some() { "if" } else { "{..}" };
176
177                     span_note_and_lint(
178                         cx,
179                         SUSPICIOUS_ELSE_FORMATTING,
180                         else_span,
181                         &format!("this is an `else {}` but the formatting might hide it", else_desc),
182                         else_span,
183                         &format!(
184                             "to remove this lint, remove the `else` or remove the new line between \
185                              `else` and `{}`",
186                             else_desc,
187                         ),
188                     );
189                 }
190             }
191         }
192     }
193 }
194
195 fn has_unary_equivalent(bin_op: ast::BinOpKind) -> bool {
196     // &, *, -
197     bin_op == ast::BinOpKind::And || bin_op == ast::BinOpKind::Mul || bin_op == ast::BinOpKind::Sub
198 }
199
200 /// Implementation of the `POSSIBLE_MISSING_COMMA` lint for array
201 fn check_array(cx: &EarlyContext<'_>, expr: &ast::Expr) {
202     if let ast::ExprKind::Array(ref array) = expr.node {
203         for element in array {
204             if let ast::ExprKind::Binary(ref op, ref lhs, _) = element.node {
205                 if has_unary_equivalent(op.node) && !differing_macro_contexts(lhs.span, op.span) {
206                     let space_span = lhs.span.between(op.span);
207                     if let Some(space_snippet) = snippet_opt(cx, space_span) {
208                         let lint_span = lhs.span.with_lo(lhs.span.hi());
209                         if space_snippet.contains('\n') {
210                             span_note_and_lint(
211                                 cx,
212                                 POSSIBLE_MISSING_COMMA,
213                                 lint_span,
214                                 "possibly missing a comma here",
215                                 lint_span,
216                                 "to remove this lint, add a comma or write the expr in a single line",
217                             );
218                         }
219                     }
220                 }
221             }
222         }
223     }
224 }
225
226 fn check_missing_else(cx: &EarlyContext<'_>, first: &ast::Expr, second: &ast::Expr) {
227     if !differing_macro_contexts(first.span, second.span)
228         && !in_macro(first.span)
229         && unsugar_if(first).is_some()
230         && (is_block(second) || unsugar_if(second).is_some())
231     {
232         // where the else would be
233         let else_span = first.span.between(second.span);
234
235         if let Some(else_snippet) = snippet_opt(cx, else_span) {
236             if !else_snippet.contains('\n') {
237                 let (looks_like, next_thing) = if unsugar_if(second).is_some() {
238                     ("an `else if`", "the second `if`")
239                 } else {
240                     ("an `else {..}`", "the next block")
241                 };
242
243                 span_note_and_lint(
244                     cx,
245                     SUSPICIOUS_ELSE_FORMATTING,
246                     else_span,
247                     &format!("this looks like {} but the `else` is missing", looks_like),
248                     else_span,
249                     &format!(
250                         "to remove this lint, add the missing `else` or add a new line before {}",
251                         next_thing,
252                     ),
253                 );
254             }
255         }
256     }
257 }
258
259 fn is_block(expr: &ast::Expr) -> bool {
260     if let ast::ExprKind::Block(..) = expr.node {
261         true
262     } else {
263         false
264     }
265 }
266
267 /// Match `if` or `if let` expressions and return the `then` and `else` block.
268 fn unsugar_if(expr: &ast::Expr) -> Option<(&P<ast::Block>, &Option<P<ast::Expr>>)> {
269     match expr.node {
270         ast::ExprKind::If(_, ref then, ref else_) | ast::ExprKind::IfLet(_, _, ref then, ref else_) => {
271             Some((then, else_))
272         },
273         _ => None,
274     }
275 }