]>
Commit | Line | Data |
---|---|---|
ea8adc8c XL |
1 | //! A group of attributes that can be attached to Rust code in order |
2 | //! to generate a clippy lint detecting said code automatically. | |
3 | ||
4 | #![allow(print_stdout, use_debug)] | |
5 | ||
6 | use rustc::lint::*; | |
7 | use rustc::hir; | |
abe05a73 XL |
8 | use rustc::hir::{Expr, Expr_, QPath}; |
9 | use rustc::hir::intravisit::{NestedVisitorMap, Visitor}; | |
10 | use syntax::ast::{self, Attribute, LitKind, NodeId, DUMMY_NODE_ID}; | |
ea8adc8c XL |
11 | use syntax::codemap::Span; |
12 | use std::collections::HashMap; | |
13 | ||
14 | /// **What it does:** Generates clippy code that detects the offending pattern | |
15 | /// | |
16 | /// **Example:** | |
17 | /// ```rust | |
18 | /// fn foo() { | |
19 | /// // detect the following pattern | |
20 | /// #[clippy(author)] | |
21 | /// if x == 42 { | |
22 | /// // but ignore everything from here on | |
23 | /// #![clippy(author = "ignore")] | |
24 | /// } | |
25 | /// } | |
26 | /// ``` | |
27 | /// | |
28 | /// prints | |
29 | /// | |
30 | /// ``` | |
abe05a73 XL |
31 | /// if_chain!{ |
32 | /// if let Expr_::ExprIf(ref cond, ref then, None) = item.node, | |
33 | /// if let Expr_::ExprBinary(BinOp::Eq, ref left, ref right) = cond.node, | |
34 | /// if let Expr_::ExprPath(ref path) = left.node, | |
35 | /// if let Expr_::ExprLit(ref lit) = right.node, | |
36 | /// if let LitKind::Int(42, _) = lit.node, | |
37 | /// then { | |
38 | /// // report your lint here | |
39 | /// } | |
40 | /// } | |
ea8adc8c XL |
41 | /// ``` |
42 | declare_lint! { | |
43 | pub LINT_AUTHOR, | |
44 | Warn, | |
45 | "helper for writing lints" | |
46 | } | |
47 | ||
48 | pub struct Pass; | |
49 | ||
50 | impl LintPass for Pass { | |
51 | fn get_lints(&self) -> LintArray { | |
52 | lint_array!(LINT_AUTHOR) | |
53 | } | |
54 | } | |
55 | ||
56 | fn prelude() { | |
abe05a73 | 57 | println!("if_chain! {{"); |
ea8adc8c XL |
58 | } |
59 | ||
60 | fn done() { | |
abe05a73 XL |
61 | println!(" then {{"); |
62 | println!(" // report your lint here"); | |
63 | println!(" }}"); | |
64 | println!("}}"); | |
ea8adc8c XL |
65 | } |
66 | ||
67 | impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass { | |
68 | fn check_item(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) { | |
69 | if !has_attr(&item.attrs) { | |
70 | return; | |
71 | } | |
72 | prelude(); | |
73 | PrintVisitor::new("item").visit_item(item); | |
74 | done(); | |
75 | } | |
76 | ||
77 | fn check_impl_item(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem) { | |
78 | if !has_attr(&item.attrs) { | |
79 | return; | |
80 | } | |
81 | prelude(); | |
82 | PrintVisitor::new("item").visit_impl_item(item); | |
83 | done(); | |
84 | } | |
85 | ||
86 | fn check_trait_item(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem) { | |
87 | if !has_attr(&item.attrs) { | |
88 | return; | |
89 | } | |
90 | prelude(); | |
91 | PrintVisitor::new("item").visit_trait_item(item); | |
92 | done(); | |
93 | } | |
94 | ||
95 | fn check_variant(&mut self, _cx: &LateContext<'a, 'tcx>, var: &'tcx hir::Variant, generics: &hir::Generics) { | |
96 | if !has_attr(&var.node.attrs) { | |
97 | return; | |
98 | } | |
99 | prelude(); | |
100 | PrintVisitor::new("var").visit_variant(var, generics, DUMMY_NODE_ID); | |
101 | done(); | |
102 | } | |
103 | ||
104 | fn check_struct_field(&mut self, _cx: &LateContext<'a, 'tcx>, field: &'tcx hir::StructField) { | |
105 | if !has_attr(&field.attrs) { | |
106 | return; | |
107 | } | |
108 | prelude(); | |
109 | PrintVisitor::new("field").visit_struct_field(field); | |
110 | done(); | |
111 | } | |
112 | ||
113 | fn check_expr(&mut self, _cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) { | |
114 | if !has_attr(&expr.attrs) { | |
115 | return; | |
116 | } | |
117 | prelude(); | |
118 | PrintVisitor::new("expr").visit_expr(expr); | |
119 | done(); | |
120 | } | |
121 | ||
122 | fn check_arm(&mut self, _cx: &LateContext<'a, 'tcx>, arm: &'tcx hir::Arm) { | |
123 | if !has_attr(&arm.attrs) { | |
124 | return; | |
125 | } | |
126 | prelude(); | |
127 | PrintVisitor::new("arm").visit_arm(arm); | |
128 | done(); | |
129 | } | |
130 | ||
131 | fn check_stmt(&mut self, _cx: &LateContext<'a, 'tcx>, stmt: &'tcx hir::Stmt) { | |
132 | if !has_attr(stmt.node.attrs()) { | |
133 | return; | |
134 | } | |
135 | prelude(); | |
136 | PrintVisitor::new("stmt").visit_stmt(stmt); | |
137 | done(); | |
138 | } | |
139 | ||
140 | fn check_foreign_item(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ForeignItem) { | |
141 | if !has_attr(&item.attrs) { | |
142 | return; | |
143 | } | |
144 | prelude(); | |
145 | PrintVisitor::new("item").visit_foreign_item(item); | |
146 | done(); | |
147 | } | |
148 | } | |
149 | ||
150 | impl PrintVisitor { | |
151 | fn new(s: &'static str) -> Self { | |
152 | Self { | |
153 | ids: HashMap::new(), | |
154 | current: s.to_owned(), | |
155 | } | |
156 | } | |
157 | ||
158 | fn next(&mut self, s: &'static str) -> String { | |
159 | use std::collections::hash_map::Entry::*; | |
160 | match self.ids.entry(s) { | |
161 | // already there: start numbering from `1` | |
162 | Occupied(mut occ) => { | |
163 | let val = occ.get_mut(); | |
164 | *val += 1; | |
165 | format!("{}{}", s, *val) | |
166 | }, | |
167 | // not there: insert and return name as given | |
168 | Vacant(vac) => { | |
169 | vac.insert(0); | |
170 | s.to_owned() | |
171 | }, | |
172 | } | |
173 | } | |
174 | } | |
175 | ||
176 | struct PrintVisitor { | |
177 | /// Fields are the current index that needs to be appended to pattern | |
178 | /// binding names | |
179 | ids: HashMap<&'static str, usize>, | |
180 | /// the name that needs to be destructured | |
181 | current: String, | |
182 | } | |
183 | ||
184 | impl<'tcx> Visitor<'tcx> for PrintVisitor { | |
185 | fn visit_expr(&mut self, expr: &Expr) { | |
abe05a73 | 186 | print!(" if let Expr_::Expr"); |
ea8adc8c XL |
187 | let current = format!("{}.node", self.current); |
188 | match expr.node { | |
189 | Expr_::ExprBox(ref inner) => { | |
190 | let inner_pat = self.next("inner"); | |
abe05a73 | 191 | println!("Box(ref {}) = {};", inner_pat, current); |
ea8adc8c XL |
192 | self.current = inner_pat; |
193 | self.visit_expr(inner); | |
194 | }, | |
195 | Expr_::ExprArray(ref elements) => { | |
196 | let elements_pat = self.next("elements"); | |
abe05a73 XL |
197 | println!("Array(ref {}) = {};", elements_pat, current); |
198 | println!(" if {}.len() == {};", elements_pat, elements.len()); | |
ea8adc8c XL |
199 | for (i, element) in elements.iter().enumerate() { |
200 | self.current = format!("{}[{}]", elements_pat, i); | |
201 | self.visit_expr(element); | |
202 | } | |
203 | }, | |
204 | Expr_::ExprCall(ref _func, ref _args) => { | |
abe05a73 | 205 | println!("Call(ref func, ref args) = {};", current); |
ea8adc8c XL |
206 | println!(" // unimplemented: `ExprCall` is not further destructured at the moment"); |
207 | }, | |
208 | Expr_::ExprMethodCall(ref _method_name, ref _generics, ref _args) => { | |
abe05a73 | 209 | println!("MethodCall(ref method_name, ref generics, ref args) = {};", current); |
ea8adc8c XL |
210 | println!(" // unimplemented: `ExprMethodCall` is not further destructured at the moment"); |
211 | }, | |
212 | Expr_::ExprTup(ref elements) => { | |
213 | let elements_pat = self.next("elements"); | |
abe05a73 XL |
214 | println!("Tup(ref {}) = {};", elements_pat, current); |
215 | println!(" if {}.len() == {};", elements_pat, elements.len()); | |
ea8adc8c XL |
216 | for (i, element) in elements.iter().enumerate() { |
217 | self.current = format!("{}[{}]", elements_pat, i); | |
218 | self.visit_expr(element); | |
219 | } | |
220 | }, | |
221 | Expr_::ExprBinary(ref op, ref left, ref right) => { | |
222 | let op_pat = self.next("op"); | |
223 | let left_pat = self.next("left"); | |
224 | let right_pat = self.next("right"); | |
abe05a73 XL |
225 | println!("Binary(ref {}, ref {}, ref {}) = {};", op_pat, left_pat, right_pat, current); |
226 | println!(" if BinOp_::{:?} == {}.node;", op.node, op_pat); | |
ea8adc8c XL |
227 | self.current = left_pat; |
228 | self.visit_expr(left); | |
229 | self.current = right_pat; | |
230 | self.visit_expr(right); | |
231 | }, | |
232 | Expr_::ExprUnary(ref op, ref inner) => { | |
233 | let inner_pat = self.next("inner"); | |
abe05a73 | 234 | println!("Unary(UnOp::{:?}, ref {}) = {};", op, inner_pat, current); |
ea8adc8c XL |
235 | self.current = inner_pat; |
236 | self.visit_expr(inner); | |
237 | }, | |
238 | Expr_::ExprLit(ref lit) => { | |
239 | let lit_pat = self.next("lit"); | |
abe05a73 | 240 | println!("Lit(ref {}) = {};", lit_pat, current); |
ea8adc8c | 241 | match lit.node { |
abe05a73 XL |
242 | LitKind::Bool(val) => println!(" if let LitKind::Bool({:?}) = {}.node;", val, lit_pat), |
243 | LitKind::Char(c) => println!(" if let LitKind::Char({:?}) = {}.node;", c, lit_pat), | |
244 | LitKind::Byte(b) => println!(" if let LitKind::Byte({}) = {}.node;", b, lit_pat), | |
ea8adc8c | 245 | // FIXME: also check int type |
abe05a73 XL |
246 | LitKind::Int(i, _) => println!(" if let LitKind::Int({}, _) = {}.node;", i, lit_pat), |
247 | LitKind::Float(..) => println!(" if let LitKind::Float(..) = {}.node;", lit_pat), | |
248 | LitKind::FloatUnsuffixed(_) => { | |
249 | println!(" if let LitKind::FloatUnsuffixed(_) = {}.node;", lit_pat) | |
250 | }, | |
ea8adc8c XL |
251 | LitKind::ByteStr(ref vec) => { |
252 | let vec_pat = self.next("vec"); | |
abe05a73 XL |
253 | println!(" if let LitKind::ByteStr(ref {}) = {}.node;", vec_pat, lit_pat); |
254 | println!(" if let [{:?}] = **{};", vec, vec_pat); | |
ea8adc8c XL |
255 | }, |
256 | LitKind::Str(ref text, _) => { | |
257 | let str_pat = self.next("s"); | |
abe05a73 XL |
258 | println!(" if let LitKind::Str(ref {}) = {}.node;", str_pat, lit_pat); |
259 | println!(" if {}.as_str() == {:?}", str_pat, &*text.as_str()) | |
ea8adc8c XL |
260 | }, |
261 | } | |
262 | }, | |
263 | Expr_::ExprCast(ref expr, ref _ty) => { | |
264 | let cast_pat = self.next("expr"); | |
abe05a73 | 265 | println!("Cast(ref {}, _) = {};", cast_pat, current); |
ea8adc8c XL |
266 | self.current = cast_pat; |
267 | self.visit_expr(expr); | |
268 | }, | |
269 | Expr_::ExprType(ref expr, ref _ty) => { | |
270 | let cast_pat = self.next("expr"); | |
abe05a73 | 271 | println!("Type(ref {}, _) = {};", cast_pat, current); |
ea8adc8c XL |
272 | self.current = cast_pat; |
273 | self.visit_expr(expr); | |
274 | }, | |
275 | Expr_::ExprIf(ref cond, ref then, ref opt_else) => { | |
276 | let cond_pat = self.next("cond"); | |
277 | let then_pat = self.next("then"); | |
278 | if let Some(ref else_) = *opt_else { | |
279 | let else_pat = self.next("else_"); | |
abe05a73 | 280 | println!("If(ref {}, ref {}, Some(ref {})) = {};", cond_pat, then_pat, else_pat, current); |
ea8adc8c XL |
281 | self.current = else_pat; |
282 | self.visit_expr(else_); | |
283 | } else { | |
abe05a73 | 284 | println!("If(ref {}, ref {}, None) = {};", cond_pat, then_pat, current); |
ea8adc8c XL |
285 | } |
286 | self.current = cond_pat; | |
287 | self.visit_expr(cond); | |
288 | self.current = then_pat; | |
289 | self.visit_expr(then); | |
290 | }, | |
291 | Expr_::ExprWhile(ref _cond, ref _body, ref _opt_label) => { | |
abe05a73 | 292 | println!("While(ref cond, ref body, ref opt_label) = {};", current); |
ea8adc8c XL |
293 | println!(" // unimplemented: `ExprWhile` is not further destructured at the moment"); |
294 | }, | |
295 | Expr_::ExprLoop(ref _body, ref _opt_label, ref _desuraging) => { | |
abe05a73 | 296 | println!("Loop(ref body, ref opt_label, ref desugaring) = {};", current); |
ea8adc8c XL |
297 | println!(" // unimplemented: `ExprLoop` is not further destructured at the moment"); |
298 | }, | |
299 | Expr_::ExprMatch(ref _expr, ref _arms, ref _desugaring) => { | |
abe05a73 | 300 | println!("Match(ref expr, ref arms, ref desugaring) = {};", current); |
ea8adc8c XL |
301 | println!(" // unimplemented: `ExprMatch` is not further destructured at the moment"); |
302 | }, | |
303 | Expr_::ExprClosure(ref _capture_clause, ref _func, _, _, _) => { | |
abe05a73 | 304 | println!("Closure(ref capture_clause, ref func, _, _, _) = {};", current); |
ea8adc8c XL |
305 | println!(" // unimplemented: `ExprClosure` is not further destructured at the moment"); |
306 | }, | |
307 | Expr_::ExprYield(ref sub) => { | |
308 | let sub_pat = self.next("sub"); | |
abe05a73 | 309 | println!("Yield(ref sub) = {};", current); |
ea8adc8c XL |
310 | self.current = sub_pat; |
311 | self.visit_expr(sub); | |
312 | }, | |
313 | Expr_::ExprBlock(ref block) => { | |
314 | let block_pat = self.next("block"); | |
abe05a73 | 315 | println!("Block(ref {}) = {};", block_pat, current); |
ea8adc8c XL |
316 | self.current = block_pat; |
317 | self.visit_block(block); | |
318 | }, | |
319 | Expr_::ExprAssign(ref target, ref value) => { | |
320 | let target_pat = self.next("target"); | |
321 | let value_pat = self.next("value"); | |
abe05a73 | 322 | println!("Assign(ref {}, ref {}) = {};", target_pat, value_pat, current); |
ea8adc8c XL |
323 | self.current = target_pat; |
324 | self.visit_expr(target); | |
325 | self.current = value_pat; | |
326 | self.visit_expr(value); | |
327 | }, | |
328 | Expr_::ExprAssignOp(ref op, ref target, ref value) => { | |
329 | let op_pat = self.next("op"); | |
330 | let target_pat = self.next("target"); | |
331 | let value_pat = self.next("value"); | |
abe05a73 XL |
332 | println!("AssignOp(ref {}, ref {}, ref {}) = {};", op_pat, target_pat, value_pat, current); |
333 | println!(" if BinOp_::{:?} == {}.node;", op.node, op_pat); | |
ea8adc8c XL |
334 | self.current = target_pat; |
335 | self.visit_expr(target); | |
336 | self.current = value_pat; | |
337 | self.visit_expr(value); | |
338 | }, | |
339 | Expr_::ExprField(ref object, ref field_name) => { | |
340 | let obj_pat = self.next("object"); | |
341 | let field_name_pat = self.next("field_name"); | |
abe05a73 XL |
342 | println!("Field(ref {}, ref {}) = {};", obj_pat, field_name_pat, current); |
343 | println!(" if {}.node.as_str() == {:?}", field_name_pat, field_name.node.as_str()); | |
ea8adc8c XL |
344 | self.current = obj_pat; |
345 | self.visit_expr(object); | |
346 | }, | |
347 | Expr_::ExprTupField(ref object, ref field_id) => { | |
348 | let obj_pat = self.next("object"); | |
349 | let field_id_pat = self.next("field_id"); | |
abe05a73 XL |
350 | println!("TupField(ref {}, ref {}) = {};", obj_pat, field_id_pat, current); |
351 | println!(" if {}.node == {}", field_id_pat, field_id.node); | |
ea8adc8c XL |
352 | self.current = obj_pat; |
353 | self.visit_expr(object); | |
354 | }, | |
355 | Expr_::ExprIndex(ref object, ref index) => { | |
356 | let object_pat = self.next("object"); | |
357 | let index_pat = self.next("index"); | |
abe05a73 | 358 | println!("Index(ref {}, ref {}) = {};", object_pat, index_pat, current); |
ea8adc8c XL |
359 | self.current = object_pat; |
360 | self.visit_expr(object); | |
361 | self.current = index_pat; | |
362 | self.visit_expr(index); | |
363 | }, | |
364 | Expr_::ExprPath(ref path) => { | |
365 | let path_pat = self.next("path"); | |
abe05a73 | 366 | println!("Path(ref {}) = {};", path_pat, current); |
ea8adc8c XL |
367 | self.current = path_pat; |
368 | self.visit_qpath(path, expr.id, expr.span); | |
369 | }, | |
370 | Expr_::ExprAddrOf(mutability, ref inner) => { | |
371 | let inner_pat = self.next("inner"); | |
abe05a73 | 372 | println!("AddrOf({:?}, ref {}) = {};", mutability, inner_pat, current); |
ea8adc8c XL |
373 | self.current = inner_pat; |
374 | self.visit_expr(inner); | |
375 | }, | |
376 | Expr_::ExprBreak(ref _destination, ref opt_value) => { | |
377 | let destination_pat = self.next("destination"); | |
378 | if let Some(ref value) = *opt_value { | |
379 | let value_pat = self.next("value"); | |
abe05a73 | 380 | println!("Break(ref {}, Some(ref {})) = {};", destination_pat, value_pat, current); |
ea8adc8c XL |
381 | self.current = value_pat; |
382 | self.visit_expr(value); | |
383 | } else { | |
abe05a73 | 384 | println!("Break(ref {}, None) = {};", destination_pat, current); |
ea8adc8c XL |
385 | } |
386 | // FIXME: implement label printing | |
387 | }, | |
388 | Expr_::ExprAgain(ref _destination) => { | |
389 | let destination_pat = self.next("destination"); | |
abe05a73 | 390 | println!("Again(ref {}) = {};", destination_pat, current); |
ea8adc8c XL |
391 | // FIXME: implement label printing |
392 | }, | |
abe05a73 XL |
393 | Expr_::ExprRet(ref opt_value) => if let Some(ref value) = *opt_value { |
394 | let value_pat = self.next("value"); | |
395 | println!("Ret(Some(ref {})) = {};", value_pat, current); | |
396 | self.current = value_pat; | |
397 | self.visit_expr(value); | |
398 | } else { | |
399 | println!("Ret(None) = {};", current); | |
ea8adc8c XL |
400 | }, |
401 | Expr_::ExprInlineAsm(_, ref _input, ref _output) => { | |
abe05a73 | 402 | println!("InlineAsm(_, ref input, ref output) = {};", current); |
ea8adc8c XL |
403 | println!(" // unimplemented: `ExprInlineAsm` is not further destructured at the moment"); |
404 | }, | |
405 | Expr_::ExprStruct(ref path, ref fields, ref opt_base) => { | |
406 | let path_pat = self.next("path"); | |
407 | let fields_pat = self.next("fields"); | |
408 | if let Some(ref base) = *opt_base { | |
409 | let base_pat = self.next("base"); | |
410 | println!( | |
abe05a73 | 411 | "Struct(ref {}, ref {}, Some(ref {})) = {};", |
ea8adc8c XL |
412 | path_pat, |
413 | fields_pat, | |
414 | base_pat, | |
415 | current | |
416 | ); | |
417 | self.current = base_pat; | |
418 | self.visit_expr(base); | |
419 | } else { | |
abe05a73 | 420 | println!("Struct(ref {}, ref {}, None) = {};", path_pat, fields_pat, current); |
ea8adc8c XL |
421 | } |
422 | self.current = path_pat; | |
423 | self.visit_qpath(path, expr.id, expr.span); | |
abe05a73 | 424 | println!(" if {}.len() == {};", fields_pat, fields.len()); |
ea8adc8c XL |
425 | println!(" // unimplemented: field checks"); |
426 | }, | |
427 | // FIXME: compute length (needs type info) | |
428 | Expr_::ExprRepeat(ref value, _) => { | |
429 | let value_pat = self.next("value"); | |
abe05a73 | 430 | println!("Repeat(ref {}, _) = {};", value_pat, current); |
ea8adc8c XL |
431 | println!("// unimplemented: repeat count check"); |
432 | self.current = value_pat; | |
433 | self.visit_expr(value); | |
434 | }, | |
435 | } | |
436 | } | |
437 | ||
438 | fn visit_qpath(&mut self, path: &QPath, _: NodeId, _: Span) { | |
abe05a73 | 439 | print!(" if match_qpath({}, &[", self.current); |
ea8adc8c | 440 | print_path(path, &mut true); |
abe05a73 | 441 | println!("]);"); |
ea8adc8c XL |
442 | } |
443 | fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { | |
444 | NestedVisitorMap::None | |
445 | } | |
446 | } | |
447 | ||
448 | fn has_attr(attrs: &[Attribute]) -> bool { | |
449 | attrs.iter().any(|attr| { | |
abe05a73 XL |
450 | attr.check_name("clippy") && attr.meta_item_list().map_or(false, |list| { |
451 | list.len() == 1 && match list[0].node { | |
452 | ast::NestedMetaItemKind::MetaItem(ref it) => it.name == "author", | |
453 | ast::NestedMetaItemKind::Literal(_) => false, | |
454 | } | |
455 | }) | |
ea8adc8c XL |
456 | }) |
457 | } | |
458 | ||
459 | fn print_path(path: &QPath, first: &mut bool) { | |
460 | match *path { | |
abe05a73 XL |
461 | QPath::Resolved(_, ref path) => for segment in &path.segments { |
462 | if *first { | |
463 | *first = false; | |
464 | } else { | |
465 | print!(", "); | |
466 | } | |
467 | print!("{:?}", segment.name.as_str()); | |
468 | }, | |
469 | QPath::TypeRelative(ref ty, ref segment) => match ty.node { | |
470 | hir::Ty_::TyPath(ref inner_path) => { | |
471 | print_path(inner_path, first); | |
ea8adc8c XL |
472 | if *first { |
473 | *first = false; | |
474 | } else { | |
475 | print!(", "); | |
476 | } | |
477 | print!("{:?}", segment.name.as_str()); | |
abe05a73 XL |
478 | }, |
479 | ref other => print!("/* unimplemented: {:?}*/", other), | |
ea8adc8c XL |
480 | }, |
481 | } | |
482 | } |