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