]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/open_options.rs
Auto merge of #6828 - mgacek8:issue_6758_enhance_wrong_self_convention, r=flip1995
[rust.git] / clippy_lints / src / open_options.rs
1 use crate::utils::{paths, span_lint};
2 use clippy_utils::ty::match_type;
3 use rustc_ast::ast::LitKind;
4 use rustc_hir::{Expr, ExprKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_session::{declare_lint_pass, declare_tool_lint};
7 use rustc_span::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<'tcx> LateLintPass<'tcx> for OpenOptions {
32     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
33         if let ExprKind::MethodCall(ref path, _, ref arguments, _) = e.kind {
34             let obj_ty = cx.typeck_results().expr_ty(&arguments[0]).peel_refs();
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.kind {
62         let obj_ty = cx.typeck_results().expr_ty(&arguments[0]).peel_refs();
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].kind {
67                 ExprKind::Lit(ref span) => {
68                     if let Spanned {
69                         node: LitKind::Bool(lit),
70                         ..
71                     } = *span
72                     {
73                         if lit { Argument::True } else { Argument::False }
74                     } else {
75                         // The function is called with a literal which is not a boolean literal.
76                         // This is theoretically possible, but not very likely.
77                         return;
78                     }
79                 },
80                 _ => Argument::Unknown,
81             };
82
83             match &*path.ident.as_str() {
84                 "create" => {
85                     options.push((OpenOption::Create, argument_option));
86                 },
87                 "append" => {
88                     options.push((OpenOption::Append, argument_option));
89                 },
90                 "truncate" => {
91                     options.push((OpenOption::Truncate, argument_option));
92                 },
93                 "read" => {
94                     options.push((OpenOption::Read, argument_option));
95                 },
96                 "write" => {
97                     options.push((OpenOption::Write, argument_option));
98                 },
99                 _ => (),
100             }
101
102             get_open_options(cx, &arguments[0], options);
103         }
104     }
105 }
106
107 fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)], span: Span) {
108     let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false);
109     let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) =
110         (false, false, false, false, false);
111     // This code is almost duplicated (oh, the irony), but I haven't found a way to
112     // unify it.
113
114     for option in options {
115         match *option {
116             (OpenOption::Create, arg) => {
117                 if create {
118                     span_lint(
119                         cx,
120                         NONSENSICAL_OPEN_OPTIONS,
121                         span,
122                         "the method `create` is called more than once",
123                     );
124                 } else {
125                     create = true
126                 }
127                 create_arg = create_arg || (arg == Argument::True);
128             },
129             (OpenOption::Append, arg) => {
130                 if append {
131                     span_lint(
132                         cx,
133                         NONSENSICAL_OPEN_OPTIONS,
134                         span,
135                         "the method `append` is called more than once",
136                     );
137                 } else {
138                     append = true
139                 }
140                 append_arg = append_arg || (arg == Argument::True);
141             },
142             (OpenOption::Truncate, arg) => {
143                 if truncate {
144                     span_lint(
145                         cx,
146                         NONSENSICAL_OPEN_OPTIONS,
147                         span,
148                         "the method `truncate` is called more than once",
149                     );
150                 } else {
151                     truncate = true
152                 }
153                 truncate_arg = truncate_arg || (arg == Argument::True);
154             },
155             (OpenOption::Read, arg) => {
156                 if read {
157                     span_lint(
158                         cx,
159                         NONSENSICAL_OPEN_OPTIONS,
160                         span,
161                         "the method `read` is called more than once",
162                     );
163                 } else {
164                     read = true
165                 }
166                 read_arg = read_arg || (arg == Argument::True);
167             },
168             (OpenOption::Write, arg) => {
169                 if write {
170                     span_lint(
171                         cx,
172                         NONSENSICAL_OPEN_OPTIONS,
173                         span,
174                         "the method `write` is called more than once",
175                     );
176                 } else {
177                     write = true
178                 }
179                 write_arg = write_arg || (arg == Argument::True);
180             },
181         }
182     }
183
184     if read && truncate && read_arg && truncate_arg && !(write && write_arg) {
185         span_lint(
186             cx,
187             NONSENSICAL_OPEN_OPTIONS,
188             span,
189             "file opened with `truncate` and `read`",
190         );
191     }
192     if append && truncate && append_arg && truncate_arg {
193         span_lint(
194             cx,
195             NONSENSICAL_OPEN_OPTIONS,
196             span,
197             "file opened with `append` and `truncate`",
198         );
199     }
200 }