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