]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/open_options.rs
Merge branch 'macro-use' into HEAD
[rust.git] / clippy_lints / src / open_options.rs
1 use rustc::hir::{Expr, ExprKind};
2 use rustc::lint::*;
3 use rustc::{declare_lint, lint_array};
4 use syntax::ast::LitKind;
5 use syntax::codemap::{Span, Spanned};
6 use crate::utils::{match_type, paths, span_lint, walk_ptrs_ty};
7
8 /// **What it does:** Checks for duplicate open options as well as combinations
9 /// that make no sense.
10 ///
11 /// **Why is this bad?** In the best case, the code will be harder to read than
12 /// necessary. I don't know the worst case.
13 ///
14 /// **Known problems:** None.
15 ///
16 /// **Example:**
17 /// ```rust
18 /// OpenOptions::new().read(true).truncate(true)
19 /// ```
20 declare_clippy_lint! {
21     pub NONSENSICAL_OPEN_OPTIONS,
22     correctness,
23     "nonsensical combination of options for opening a file"
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 ExprKind::MethodCall(ref path, _, ref arguments) = e.node {
38             let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(&arguments[0]));
39             if path.ident.name == "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 ExprKind::MethodCall(ref path, _, ref arguments) = argument.node {
66         let obj_ty = walk_ptrs_ty(cx.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             let argument_option = match arguments[1].node {
71                 ExprKind::Lit(ref span) => {
72                     if let Spanned {
73                         node: LitKind::Bool(lit),
74                         ..
75                     } = **span
76                     {
77                         if lit {
78                             Argument::True
79                         } else {
80                             Argument::False
81                         }
82                     } else {
83                         return; // The function is called with a literal
84                                 // which is not a boolean literal. This is theoretically
85                                 // possible, but not very likely.
86                     }
87                 },
88                 _ => Argument::Unknown,
89             };
90
91             match &*path.ident.as_str() {
92                 "create" => {
93                     options.push((OpenOption::Create, argument_option));
94                 },
95                 "append" => {
96                     options.push((OpenOption::Append, argument_option));
97                 },
98                 "truncate" => {
99                     options.push((OpenOption::Truncate, argument_option));
100                 },
101                 "read" => {
102                     options.push((OpenOption::Read, argument_option));
103                 },
104                 "write" => {
105                     options.push((OpenOption::Write, argument_option));
106                 },
107                 _ => (),
108             }
109
110             get_open_options(cx, &arguments[0], options);
111         }
112     }
113 }
114
115 fn check_open_options(cx: &LateContext, options: &[(OpenOption, Argument)], span: Span) {
116     let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false);
117     let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) =
118         (false, false, false, false, false);
119     // This code is almost duplicated (oh, the irony), but I haven't found a way to
120     // unify it.
121
122     for option in options {
123         match *option {
124             (OpenOption::Create, arg) => {
125                 if create {
126                     span_lint(
127                         cx,
128                         NONSENSICAL_OPEN_OPTIONS,
129                         span,
130                         "the method \"create\" is called more than once",
131                     );
132                 } else {
133                     create = true
134                 }
135                 create_arg = create_arg || (arg == Argument::True);;
136             },
137             (OpenOption::Append, arg) => {
138                 if append {
139                     span_lint(
140                         cx,
141                         NONSENSICAL_OPEN_OPTIONS,
142                         span,
143                         "the method \"append\" is called more than once",
144                     );
145                 } else {
146                     append = true
147                 }
148                 append_arg = append_arg || (arg == Argument::True);;
149             },
150             (OpenOption::Truncate, arg) => {
151                 if truncate {
152                     span_lint(
153                         cx,
154                         NONSENSICAL_OPEN_OPTIONS,
155                         span,
156                         "the method \"truncate\" is called more than once",
157                     );
158                 } else {
159                     truncate = true
160                 }
161                 truncate_arg = truncate_arg || (arg == Argument::True);
162             },
163             (OpenOption::Read, arg) => {
164                 if read {
165                     span_lint(
166                         cx,
167                         NONSENSICAL_OPEN_OPTIONS,
168                         span,
169                         "the method \"read\" is called more than once",
170                     );
171                 } else {
172                     read = true
173                 }
174                 read_arg = read_arg || (arg == Argument::True);;
175             },
176             (OpenOption::Write, arg) => {
177                 if write {
178                     span_lint(
179                         cx,
180                         NONSENSICAL_OPEN_OPTIONS,
181                         span,
182                         "the method \"write\" is called more than once",
183                     );
184                 } else {
185                     write = true
186                 }
187                 write_arg = write_arg || (arg == Argument::True);;
188             },
189         }
190     }
191
192     if read && truncate && read_arg && truncate_arg && !(write && write_arg) {
193         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "file opened with \"truncate\" and \"read\"");
194     }
195     if append && truncate && append_arg && truncate_arg {
196         span_lint(
197             cx,
198             NONSENSICAL_OPEN_OPTIONS,
199             span,
200             "file opened with \"append\" and \"truncate\"",
201         );
202     }
203 }