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