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