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