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