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