]> git.lizzy.rs Git - rust.git/blob - src/open_options.rs
added wiki comments + wiki-generating python script
[rust.git] / src / open_options.rs
1 use rustc::lint::*;
2 use rustc_front::hir::{Expr, ExprMethodCall, ExprLit};
3 use utils::{walk_ptrs_ty_depth, match_type, span_lint, OPEN_OPTIONS_PATH};
4 use syntax::codemap::{Span, Spanned};
5 use syntax::ast::Lit_::LitBool;
6
7 /// **What it does:** This lint checks for duplicate open options as well as combinations that make no sense. It is `Warn` by default.
8 ///
9 /// **Why is this bad?** In the best case, the code will be harder to read than necessary. I don't know the worst case.
10 ///
11 /// **Known problems:** None
12 ///
13 /// **Example:** `OpenOptions::new().read(true).truncate(true)`
14 declare_lint! {
15     pub NONSENSICAL_OPEN_OPTIONS,
16     Warn,
17     "nonsensical combination of options for opening a file"
18 }
19
20
21 #[derive(Copy,Clone)]
22 pub struct NonSensicalOpenOptions;
23
24 impl LintPass for NonSensicalOpenOptions {
25     fn get_lints(&self) -> LintArray {
26         lint_array!(NONSENSICAL_OPEN_OPTIONS)
27     }
28 }
29
30 impl LateLintPass for NonSensicalOpenOptions {
31     fn check_expr(&mut self, cx: &LateContext, e: &Expr) {
32         if let ExprMethodCall(ref name, _, ref arguments) = e.node {
33             let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.expr_ty(&arguments[0]));
34             if name.node.as_str() == "open" && match_type(cx, obj_ty, &OPEN_OPTIONS_PATH){
35                 let mut options = Vec::new();
36                 get_open_options(cx, &arguments[0], &mut options);
37                 check_open_options(cx, &options, e.span);
38             }
39         }
40     }
41 }
42
43 #[derive(Debug)]
44 enum Argument {
45     True,
46     False,
47     Unknown
48 }
49
50 #[derive(Debug)]
51 enum OpenOption {
52     Write,
53     Read,
54     Truncate,
55     Create,
56     Append
57 }
58
59 fn get_open_options(cx: &LateContext, argument: &Expr, options: &mut Vec<(OpenOption, Argument)>) {
60     if let ExprMethodCall(ref name, _, ref arguments) = argument.node {
61         let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.expr_ty(&arguments[0]));
62         
63         // Only proceed if this is a call on some object of type std::fs::OpenOptions
64         if match_type(cx, obj_ty, &OPEN_OPTIONS_PATH) && arguments.len() >= 2 {
65             
66             let argument_option = match arguments[1].node {
67                 ExprLit(ref span) => {
68                     if let Spanned {node: LitBool(lit), ..} = **span {
69                         if lit {Argument::True} else {Argument::False}
70                     } else {
71                         return; // The function is called with a literal
72                                 // which is not a boolean literal. This is theoretically
73                                 // possible, but not very likely.
74                     }
75                 }
76                 _ => {
77                     Argument::Unknown
78                 }
79             };
80             
81             match &*name.node.as_str() {
82                 "create" => {
83                     options.push((OpenOption::Create, argument_option));
84                 }
85                 "append" => {
86                     options.push((OpenOption::Append, argument_option));
87                 }
88                 "truncate" => {
89                     options.push((OpenOption::Truncate, argument_option));
90                 }
91                 "read" => {
92                     options.push((OpenOption::Read, argument_option));
93                 }
94                 "write" => {
95                     options.push((OpenOption::Write, argument_option));
96                 }
97                 _ => {}
98             }
99             
100             get_open_options(cx, &arguments[0], options);
101         }
102     }
103 }
104
105 fn check_for_duplicates(cx: &LateContext, options: &[(OpenOption, Argument)], span: Span) {
106     // This code is almost duplicated (oh, the irony), but I haven't found a way to unify it.
107     if options.iter().filter(|o| if let (OpenOption::Create, _) = **o {true} else {false}).count() > 1 {
108         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"create\" \
109                                                        is called more than once");
110     }
111     if options.iter().filter(|o| if let (OpenOption::Append, _) = **o {true} else {false}).count() > 1 {
112         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"append\" \
113                                                        is called more than once");
114     }
115     if options.iter().filter(|o| if let (OpenOption::Truncate, _) = **o {true} else {false}).count() > 1 {
116         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"truncate\" \
117                                                        is called more than once");
118     }
119     if options.iter().filter(|o| if let (OpenOption::Read, _) = **o {true} else {false}).count() > 1 {
120         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"read\" \
121                                                        is called more than once");
122     }
123     if options.iter().filter(|o| if let (OpenOption::Write, _) = **o {true} else {false}).count() > 1 {
124         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"write\" \
125                                                        is called more than once");
126     }
127 }
128
129 fn check_for_inconsistencies(cx: &LateContext, options: &[(OpenOption, Argument)], span: Span) {
130     // Truncate + read makes no sense.
131     if options.iter().filter(|o| if let (OpenOption::Read, Argument::True) = **o {true} else {false}).count() > 0 &&
132        options.iter().filter(|o| if let (OpenOption::Truncate, Argument::True) = **o {true} else {false}).count() > 0 {
133         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "File opened with \"truncate\" and \"read\"");
134     }
135     
136     // Append + truncate makes no sense.
137     if options.iter().filter(|o| if let (OpenOption::Append, Argument::True) = **o {true} else {false}).count() > 0 &&
138        options.iter().filter(|o| if let (OpenOption::Truncate, Argument::True) = **o {true} else {false}).count() > 0 {
139         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "File opened with \"append\" and \"truncate\"");
140     }
141 }
142
143 fn check_open_options(cx: &LateContext, options: &[(OpenOption, Argument)], span: Span) {
144     check_for_duplicates(cx, options, span);
145     check_for_inconsistencies(cx, options, span);
146 }