]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/formatting.rs
Rustup to rust-lang/rust#67806
[rust.git] / clippy_lints / src / formatting.rs
1 use crate::utils::{differing_macro_contexts, snippet_opt, span_help_and_lint, span_note_and_lint};
2 use if_chain::if_chain;
3 use rustc::lint::{in_external_macro, EarlyContext, EarlyLintPass};
4 use rustc_session::{declare_lint_pass, declare_tool_lint};
5 use syntax::ast::*;
6
7 declare_clippy_lint! {
8     /// **What it does:** Checks for use of the non-existent `=*`, `=!` and `=-`
9     /// operators.
10     ///
11     /// **Why is this bad?** This is either a typo of `*=`, `!=` or `-=` or
12     /// confusing.
13     ///
14     /// **Known problems:** None.
15     ///
16     /// **Example:**
17     /// ```rust,ignore
18     /// a =- 42; // confusing, should it be `a -= 42` or `a = -42`?
19     /// ```
20     pub SUSPICIOUS_ASSIGNMENT_FORMATTING,
21     style,
22     "suspicious formatting of `*=`, `-=` or `!=`"
23 }
24
25 declare_clippy_lint! {
26     /// **What it does:** Checks the formatting of a unary operator on the right hand side
27     /// of a binary operator. It lints if there is no space between the binary and unary operators,
28     /// but there is a space between the unary and its operand.
29     ///
30     /// **Why is this bad?** This is either a typo in the binary operator or confusing.
31     ///
32     /// **Known problems:** None.
33     ///
34     /// **Example:**
35     /// ```rust,ignore
36     /// if foo <- 30 { // this should be `foo < -30` but looks like a different operator
37     /// }
38     ///
39     /// if foo &&! bar { // this should be `foo && !bar` but looks like a different operator
40     /// }
41     /// ```
42     pub SUSPICIOUS_UNARY_OP_FORMATTING,
43     style,
44     "suspicious formatting of unary `-` or `!` on the RHS of a BinOp"
45 }
46
47 declare_clippy_lint! {
48     /// **What it does:** Checks for formatting of `else`. It lints if the `else`
49     /// is followed immediately by a newline or the `else` seems to be missing.
50     ///
51     /// **Why is this bad?** This is probably some refactoring remnant, even if the
52     /// code is correct, it might look confusing.
53     ///
54     /// **Known problems:** None.
55     ///
56     /// **Example:**
57     /// ```rust,ignore
58     /// if foo {
59     /// } { // looks like an `else` is missing here
60     /// }
61     ///
62     /// if foo {
63     /// } if bar { // looks like an `else` is missing here
64     /// }
65     ///
66     /// if foo {
67     /// } else
68     ///
69     /// { // this is the `else` block of the previous `if`, but should it be?
70     /// }
71     ///
72     /// if foo {
73     /// } else
74     ///
75     /// if bar { // this is the `else` block of the previous `if`, but should it be?
76     /// }
77     /// ```
78     pub SUSPICIOUS_ELSE_FORMATTING,
79     style,
80     "suspicious formatting of `else`"
81 }
82
83 declare_clippy_lint! {
84     /// **What it does:** Checks for possible missing comma in an array. It lints if
85     /// an array element is a binary operator expression and it lies on two lines.
86     ///
87     /// **Why is this bad?** This could lead to unexpected results.
88     ///
89     /// **Known problems:** None.
90     ///
91     /// **Example:**
92     /// ```rust,ignore
93     /// let a = &[
94     ///     -1, -2, -3 // <= no comma here
95     ///     -4, -5, -6
96     /// ];
97     /// ```
98     pub POSSIBLE_MISSING_COMMA,
99     correctness,
100     "possible missing comma in array"
101 }
102
103 declare_lint_pass!(Formatting => [
104     SUSPICIOUS_ASSIGNMENT_FORMATTING,
105     SUSPICIOUS_UNARY_OP_FORMATTING,
106     SUSPICIOUS_ELSE_FORMATTING,
107     POSSIBLE_MISSING_COMMA
108 ]);
109
110 impl EarlyLintPass for Formatting {
111     fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
112         for w in block.stmts.windows(2) {
113             match (&w[0].kind, &w[1].kind) {
114                 (&StmtKind::Expr(ref first), &StmtKind::Expr(ref second))
115                 | (&StmtKind::Expr(ref first), &StmtKind::Semi(ref second)) => {
116                     check_missing_else(cx, first, second);
117                 },
118                 _ => (),
119             }
120         }
121     }
122
123     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
124         check_assign(cx, expr);
125         check_unop(cx, expr);
126         check_else(cx, expr);
127         check_array(cx, expr);
128     }
129 }
130
131 /// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
132 fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
133     if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind {
134         if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion() {
135             let eq_span = lhs.span.between(rhs.span);
136             if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind {
137                 if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
138                     let op = UnOp::to_string(op);
139                     let eqop_span = lhs.span.between(sub_rhs.span);
140                     if eq_snippet.ends_with('=') {
141                         span_note_and_lint(
142                             cx,
143                             SUSPICIOUS_ASSIGNMENT_FORMATTING,
144                             eqop_span,
145                             &format!(
146                                 "this looks like you are trying to use `.. {op}= ..`, but you \
147                                  really are doing `.. = ({op} ..)`",
148                                 op = op
149                             ),
150                             eqop_span,
151                             &format!("to remove this lint, use either `{op}=` or `= {op}`", op = op),
152                         );
153                     }
154                 }
155             }
156         }
157     }
158 }
159
160 /// Implementation of the `SUSPICIOUS_UNARY_OP_FORMATTING` lint.
161 fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
162     if_chain! {
163         if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind;
164         if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion();
165         // span between BinOp LHS and RHS
166         let binop_span = lhs.span.between(rhs.span);
167         // if RHS is a UnOp
168         if let ExprKind::Unary(op, ref un_rhs) = rhs.kind;
169         // from UnOp operator to UnOp operand
170         let unop_operand_span = rhs.span.until(un_rhs.span);
171         if let Some(binop_snippet) = snippet_opt(cx, binop_span);
172         if let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span);
173         let binop_str = BinOpKind::to_string(&binop.node);
174         // no space after BinOp operator and space after UnOp operator
175         if binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ');
176         then {
177             let unop_str = UnOp::to_string(op);
178             let eqop_span = lhs.span.between(un_rhs.span);
179             span_help_and_lint(
180                 cx,
181                 SUSPICIOUS_UNARY_OP_FORMATTING,
182                 eqop_span,
183                 &format!(
184                     "by not having a space between `{binop}` and `{unop}` it looks like \
185                      `{binop}{unop}` is a single operator",
186                     binop = binop_str,
187                     unop = unop_str
188                 ),
189                 &format!(
190                     "put a space between `{binop}` and `{unop}` and remove the space after `{unop}`",
191                     binop = binop_str,
192                     unop = unop_str
193                 ),
194             );
195         }
196     }
197 }
198
199 /// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`.
200 fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
201     if_chain! {
202         if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
203         if is_block(else_) || is_if(else_);
204         if !differing_macro_contexts(then.span, else_.span);
205         if !then.span.from_expansion() && !in_external_macro(cx.sess, expr.span);
206
207         // workaround for rust-lang/rust#43081
208         if expr.span.lo().0 != 0 && expr.span.hi().0 != 0;
209
210         // this will be a span from the closing ‘}’ of the “then” block (excluding) to
211         // the “if” of the “else if” block (excluding)
212         let else_span = then.span.between(else_.span);
213
214         // the snippet should look like " else \n    " with maybe comments anywhere
215         // it’s bad when there is a ‘\n’ after the “else”
216         if let Some(else_snippet) = snippet_opt(cx, else_span);
217         if let Some(else_pos) = else_snippet.find("else");
218         if else_snippet[else_pos..].contains('\n');
219         let else_desc = if is_if(else_) { "if" } else { "{..}" };
220
221         then {
222             span_note_and_lint(
223                 cx,
224                 SUSPICIOUS_ELSE_FORMATTING,
225                 else_span,
226                 &format!("this is an `else {}` but the formatting might hide it", else_desc),
227                 else_span,
228                 &format!(
229                     "to remove this lint, remove the `else` or remove the new line between \
230                      `else` and `{}`",
231                     else_desc,
232                 ),
233             );
234         }
235     }
236 }
237
238 #[must_use]
239 fn has_unary_equivalent(bin_op: BinOpKind) -> bool {
240     // &, *, -
241     bin_op == BinOpKind::And || bin_op == BinOpKind::Mul || bin_op == BinOpKind::Sub
242 }
243
244 /// Implementation of the `POSSIBLE_MISSING_COMMA` lint for array
245 fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
246     if let ExprKind::Array(ref array) = expr.kind {
247         for element in array {
248             if let ExprKind::Binary(ref op, ref lhs, _) = element.kind {
249                 if has_unary_equivalent(op.node) && !differing_macro_contexts(lhs.span, op.span) {
250                     let space_span = lhs.span.between(op.span);
251                     if let Some(space_snippet) = snippet_opt(cx, space_span) {
252                         let lint_span = lhs.span.with_lo(lhs.span.hi());
253                         if space_snippet.contains('\n') {
254                             span_note_and_lint(
255                                 cx,
256                                 POSSIBLE_MISSING_COMMA,
257                                 lint_span,
258                                 "possibly missing a comma here",
259                                 lint_span,
260                                 "to remove this lint, add a comma or write the expr in a single line",
261                             );
262                         }
263                     }
264                 }
265             }
266         }
267     }
268 }
269
270 fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
271     if !differing_macro_contexts(first.span, second.span)
272         && !first.span.from_expansion()
273         && is_if(first)
274         && (is_block(second) || is_if(second))
275     {
276         // where the else would be
277         let else_span = first.span.between(second.span);
278
279         if let Some(else_snippet) = snippet_opt(cx, else_span) {
280             if !else_snippet.contains('\n') {
281                 let (looks_like, next_thing) = if is_if(second) {
282                     ("an `else if`", "the second `if`")
283                 } else {
284                     ("an `else {..}`", "the next block")
285                 };
286
287                 span_note_and_lint(
288                     cx,
289                     SUSPICIOUS_ELSE_FORMATTING,
290                     else_span,
291                     &format!("this looks like {} but the `else` is missing", looks_like),
292                     else_span,
293                     &format!(
294                         "to remove this lint, add the missing `else` or add a new line before {}",
295                         next_thing,
296                     ),
297                 );
298             }
299         }
300     }
301 }
302
303 fn is_block(expr: &Expr) -> bool {
304     if let ExprKind::Block(..) = expr.kind {
305         true
306     } else {
307         false
308     }
309 }
310
311 /// Check if the expression is an `if` or `if let`
312 fn is_if(expr: &Expr) -> bool {
313     if let ExprKind::If(..) = expr.kind {
314         true
315     } else {
316         false
317     }
318 }