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