1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use rustc::lint::*;
use syntax::codemap::mk_sp;
use syntax::ast;
use utils::{differing_macro_contexts, in_macro, snippet_opt, span_note_and_lint};
use syntax::ptr::P;
declare_lint! {
pub SUSPICIOUS_ASSIGNMENT_FORMATTING,
Warn,
"suspicious formatting of `*=`, `-=` or `!=`"
}
declare_lint! {
pub SUSPICIOUS_ELSE_FORMATTING,
Warn,
"suspicious formatting of `else if`"
}
#[derive(Copy,Clone)]
pub struct Formatting;
impl LintPass for Formatting {
fn get_lints(&self) -> LintArray {
lint_array![SUSPICIOUS_ASSIGNMENT_FORMATTING, SUSPICIOUS_ELSE_FORMATTING]
}
}
impl EarlyLintPass for Formatting {
fn check_block(&mut self, cx: &EarlyContext, block: &ast::Block) {
for w in block.stmts.windows(2) {
match (&w[0].node, &w[1].node) {
(&ast::StmtKind::Expr(ref first), &ast::StmtKind::Expr(ref second)) |
(&ast::StmtKind::Expr(ref first), &ast::StmtKind::Semi(ref second)) => {
check_consecutive_ifs(cx, first, second);
},
_ => (),
}
}
}
fn check_expr(&mut self, cx: &EarlyContext, expr: &ast::Expr) {
check_assign(cx, expr);
check_else_if(cx, expr);
}
}
fn check_assign(cx: &EarlyContext, expr: &ast::Expr) {
if let ast::ExprKind::Assign(ref lhs, ref rhs) = expr.node {
if !differing_macro_contexts(lhs.span, rhs.span) && !in_macro(cx, lhs.span) {
let eq_span = mk_sp(lhs.span.hi, rhs.span.lo);
if let ast::ExprKind::Unary(op, ref sub_rhs) = rhs.node {
if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
let op = ast::UnOp::to_string(op);
let eqop_span = mk_sp(lhs.span.hi, sub_rhs.span.lo);
if eq_snippet.ends_with('=') {
span_note_and_lint(cx,
SUSPICIOUS_ASSIGNMENT_FORMATTING,
eqop_span,
&format!("this looks like you are trying to use `.. {op}= ..`, but you \
really are doing `.. = ({op} ..)`",
op = op),
eqop_span,
&format!("to remove this lint, use either `{op}=` or `= {op}`", op = op));
}
}
}
}
}
}
fn check_else_if(cx: &EarlyContext, expr: &ast::Expr) {
if let Some((then, &Some(ref else_))) = unsugar_if(expr) {
if unsugar_if(else_).is_some() && !differing_macro_contexts(then.span, else_.span) && !in_macro(cx, then.span) {
let else_span = mk_sp(then.span.hi, else_.span.lo);
if let Some(else_snippet) = snippet_opt(cx, else_span) {
let else_pos = else_snippet.find("else").expect("there must be a `else` here");
if else_snippet[else_pos..].contains('\n') {
span_note_and_lint(cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
"this is an `else if` but the formatting might hide it",
else_span,
"to remove this lint, remove the `else` or remove the new line between `else` \
and `if`");
}
}
}
}
}
fn check_consecutive_ifs(cx: &EarlyContext, first: &ast::Expr, second: &ast::Expr) {
if !differing_macro_contexts(first.span, second.span) && !in_macro(cx, first.span) &&
unsugar_if(first).is_some() && unsugar_if(second).is_some() {
let else_span = mk_sp(first.span.hi, second.span.lo);
if let Some(else_snippet) = snippet_opt(cx, else_span) {
if !else_snippet.contains('\n') {
span_note_and_lint(cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
"this looks like an `else if` but the `else` is missing",
else_span,
"to remove this lint, add the missing `else` or add a new line before the second \
`if`");
}
}
}
}
fn unsugar_if(expr: &ast::Expr) -> Option<(&P<ast::Block>, &Option<P<ast::Expr>>)> {
match expr.node {
ast::ExprKind::If(_, ref then, ref else_) |
ast::ExprKind::IfLet(_, _, ref then, ref else_) => Some((then, else_)),
_ => None,
}
}