1- use crate :: utils:: { differing_macro_contexts, snippet_opt, span_note_and_lint} ;
1+ use crate :: utils:: { differing_macro_contexts, snippet_opt, span_help_and_lint , span_note_and_lint} ;
22use if_chain:: if_chain;
33use rustc:: lint:: { in_external_macro, EarlyContext , EarlyLintPass , LintArray , LintPass } ;
44use rustc:: { declare_lint_pass, declare_tool_lint} ;
@@ -22,6 +22,28 @@ declare_clippy_lint! {
2222 "suspicious formatting of `*=`, `-=` or `!=`"
2323}
2424
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+
2547declare_clippy_lint ! {
2648 /// **What it does:** Checks for formatting of `else`. It lints if the `else`
2749 /// is followed immediately by a newline or the `else` seems to be missing.
@@ -80,6 +102,7 @@ declare_clippy_lint! {
80102
81103declare_lint_pass ! ( Formatting => [
82104 SUSPICIOUS_ASSIGNMENT_FORMATTING ,
105+ SUSPICIOUS_UNARY_OP_FORMATTING ,
83106 SUSPICIOUS_ELSE_FORMATTING ,
84107 POSSIBLE_MISSING_COMMA
85108] ) ;
@@ -99,6 +122,7 @@ impl EarlyLintPass for Formatting {
99122
100123 fn check_expr ( & mut self , cx : & EarlyContext < ' _ > , expr : & Expr ) {
101124 check_assign ( cx, expr) ;
125+ check_unop ( cx, expr) ;
102126 check_else ( cx, expr) ;
103127 check_array ( cx, expr) ;
104128 }
@@ -133,6 +157,45 @@ fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
133157 }
134158}
135159
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+
136199/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`.
137200fn check_else ( cx : & EarlyContext < ' _ > , expr : & Expr ) {
138201 if_chain ! {
0 commit comments