]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/open_options.rs
Merge pull request #1355 from philipturnbull/deref-addrof
[rust.git] / clippy_lints / src / open_options.rs
1 use rustc::hir::{Expr, ExprMethodCall, ExprLit};
2 use rustc::lint::*;
3 use syntax::ast::LitKind;
4 use syntax::codemap::{Span, Spanned};
5 use utils::{match_type, paths, span_lint, walk_ptrs_ty_depth};
6
7 /// **What it does:** Checks for duplicate open options as well as combinations
8 /// that make no sense.
9 ///
10 /// **Why is this bad?** In the best case, the code will be harder to read than
11 /// necessary. I don't know the worst case.
12 ///
13 /// **Known problems:** None.
14 ///
15 /// **Example:**
16 /// ```rust
17 /// OpenOptions::new().read(true).truncate(true)
18 /// ```
19 declare_lint! {
20     pub NONSENSICAL_OPEN_OPTIONS,
21     Warn,
22     "nonsensical combination of options for opening a file"
23 }
24
25
26 #[derive(Copy,Clone)]
27 pub struct NonSensical;
28
29 impl LintPass for NonSensical {
30     fn get_lints(&self) -> LintArray {
31         lint_array!(NONSENSICAL_OPEN_OPTIONS)
32     }
33 }
34
35 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonSensical {
36     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
37         if let ExprMethodCall(ref name, _, ref arguments) = e.node {
38             let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.tables().expr_ty(&arguments[0]));
39             if &*name.node.as_str() == "open" && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) {
40                 let mut options = Vec::new();
41                 get_open_options(cx, &arguments[0], &mut options);
42                 check_open_options(cx, &options, e.span);
43             }
44         }
45     }
46 }
47
48 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
49 enum Argument {
50     True,
51     False,
52     Unknown,
53 }
54
55 #[derive(Debug)]
56 enum OpenOption {
57     Write,
58     Read,
59     Truncate,
60     Create,
61     Append,
62 }
63
64 fn get_open_options(cx: &LateContext, argument: &Expr, options: &mut Vec<(OpenOption, Argument)>) {
65     if let ExprMethodCall(ref name, _, ref arguments) = argument.node {
66         let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.tables().expr_ty(&arguments[0]));
67
68         // Only proceed if this is a call on some object of type std::fs::OpenOptions
69         if match_type(cx, obj_ty, &paths::OPEN_OPTIONS) && arguments.len() >= 2 {
70
71             let argument_option = match arguments[1].node {
72                 ExprLit(ref span) => {
73                     if let Spanned { node: LitKind::Bool(lit), .. } = **span {
74                         if lit {
75                             Argument::True
76                         } else {
77                             Argument::False
78                         }
79                     } else {
80                         return; // The function is called with a literal
81                                 // which is not a boolean literal. This is theoretically
82                                 // possible, but not very likely.
83                     }
84                 }
85                 _ => Argument::Unknown,
86             };
87
88             match &*name.node.as_str() {
89                 "create" => {
90                     options.push((OpenOption::Create, argument_option));
91                 }
92                 "append" => {
93                     options.push((OpenOption::Append, argument_option));
94                 }
95                 "truncate" => {
96                     options.push((OpenOption::Truncate, argument_option));
97                 }
98                 "read" => {
99                     options.push((OpenOption::Read, argument_option));
100                 }
101                 "write" => {
102                     options.push((OpenOption::Write, argument_option));
103                 }
104                 _ => (),
105             }
106
107             get_open_options(cx, &arguments[0], options);
108         }
109     }
110 }
111
112 fn check_open_options(cx: &LateContext, options: &[(OpenOption, Argument)], span: Span) {
113     let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false);
114     let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) = (false,
115                                                                                            false,
116                                                                                            false,
117                                                                                            false,
118                                                                                            false);
119     // This code is almost duplicated (oh, the irony), but I haven't found a way to unify it.
120
121     for option in options {
122         match *option {
123             (OpenOption::Create, arg) => {
124                 if create {
125                     span_lint(cx,
126                               NONSENSICAL_OPEN_OPTIONS,
127                               span,
128                               "the method \"create\" is called more than once");
129                 } else {
130                     create = true
131                 }
132                 create_arg = create_arg || (arg == Argument::True);;
133             }
134             (OpenOption::Append, arg) => {
135                 if append {
136                     span_lint(cx,
137                               NONSENSICAL_OPEN_OPTIONS,
138                               span,
139                               "the method \"append\" is called more than once");
140                 } else {
141                     append = true
142                 }
143                 append_arg = append_arg || (arg == Argument::True);;
144             }
145             (OpenOption::Truncate, arg) => {
146                 if truncate {
147                     span_lint(cx,
148                               NONSENSICAL_OPEN_OPTIONS,
149                               span,
150                               "the method \"truncate\" is called more than once");
151                 } else {
152                     truncate = true
153                 }
154                 truncate_arg = truncate_arg || (arg == Argument::True);
155             }
156             (OpenOption::Read, arg) => {
157                 if read {
158                     span_lint(cx,
159                               NONSENSICAL_OPEN_OPTIONS,
160                               span,
161                               "the method \"read\" is called more than once");
162                 } else {
163                     read = true
164                 }
165                 read_arg = read_arg || (arg == Argument::True);;
166             }
167             (OpenOption::Write, arg) => {
168                 if write {
169                     span_lint(cx,
170                               NONSENSICAL_OPEN_OPTIONS,
171                               span,
172                               "the method \"write\" is called more than once");
173                 } else {
174                     write = true
175                 }
176                 write_arg = write_arg || (arg == Argument::True);;
177             }
178         }
179     }
180
181     if read && truncate && read_arg && truncate_arg && !(write && write_arg) {
182         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "file opened with \"truncate\" and \"read\"");
183     }
184     if append && truncate && append_arg && truncate_arg {
185         span_lint(cx,
186                   NONSENSICAL_OPEN_OPTIONS,
187                   span,
188                   "file opened with \"append\" and \"truncate\"");
189     }
190 }