]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/open_options.rs
Do some additional cleanup around `InternedString` and `Symbol`
[rust.git] / clippy_lints / src / open_options.rs
1 use rustc::hir::{Expr, ExprMethodCall, ExprLit};
2 use rustc::lint::*;
3 use syntax::ast::LitKind;
4 use syntax::codemap::{Span, Spanned};
5 use utils::{match_type, paths, span_lint, walk_ptrs_ty_depth};
6
7 /// **What it does:** Checks for duplicate open options as well as combinations
8 /// that make no sense.
9 ///
10 /// **Why is this bad?** In the best case, the code will be harder to read than
11 /// necessary. I don't know the worst case.
12 ///
13 /// **Known problems:** None.
14 ///
15 /// **Example:**
16 /// ```rust
17 /// OpenOptions::new().read(true).truncate(true)
18 /// ```
19 declare_lint! {
20     pub NONSENSICAL_OPEN_OPTIONS,
21     Warn,
22     "nonsensical combination of options for opening a file"
23 }
24
25
26 #[derive(Copy,Clone)]
27 pub struct NonSensical;
28
29 impl LintPass for NonSensical {
30     fn get_lints(&self) -> LintArray {
31         lint_array!(NONSENSICAL_OPEN_OPTIONS)
32     }
33 }
34
35 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonSensical {
36     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
37         if let ExprMethodCall(ref name, _, ref arguments) = e.node {
38             let (obj_ty, _) = walk_ptrs_ty_depth(cx.tables.expr_ty(&arguments[0]));
39             if name.node == "open" && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) {
40                 let mut options = Vec::new();
41                 get_open_options(cx, &arguments[0], &mut options);
42                 check_open_options(cx, &options, e.span);
43             }
44         }
45     }
46 }
47
48 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
49 enum Argument {
50     True,
51     False,
52     Unknown,
53 }
54
55 #[derive(Debug)]
56 enum OpenOption {
57     Write,
58     Read,
59     Truncate,
60     Create,
61     Append,
62 }
63
64 fn get_open_options(cx: &LateContext, argument: &Expr, options: &mut Vec<(OpenOption, Argument)>) {
65     if let ExprMethodCall(ref name, _, ref arguments) = argument.node {
66         let (obj_ty, _) = walk_ptrs_ty_depth(cx.tables.expr_ty(&arguments[0]));
67
68         // Only proceed if this is a call on some object of type std::fs::OpenOptions
69         if match_type(cx, obj_ty, &paths::OPEN_OPTIONS) && arguments.len() >= 2 {
70
71             let argument_option = match arguments[1].node {
72                 ExprLit(ref span) => {
73                     if let Spanned { node: LitKind::Bool(lit), .. } = **span {
74                         if lit { Argument::True } else { Argument::False }
75                     } else {
76                         return; // The function is called with a literal
77                         // which is not a boolean literal. This is theoretically
78                         // possible, but not very likely.
79                     }
80                 },
81                 _ => Argument::Unknown,
82             };
83
84             match &*name.node.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 unify it.
113
114     for option in options {
115         match *option {
116             (OpenOption::Create, arg) => {
117                 if create {
118                     span_lint(cx,
119                               NONSENSICAL_OPEN_OPTIONS,
120                               span,
121                               "the method \"create\" is called more than once");
122                 } else {
123                     create = true
124                 }
125                 create_arg = create_arg || (arg == Argument::True);;
126             },
127             (OpenOption::Append, arg) => {
128                 if append {
129                     span_lint(cx,
130                               NONSENSICAL_OPEN_OPTIONS,
131                               span,
132                               "the method \"append\" is called more than once");
133                 } else {
134                     append = true
135                 }
136                 append_arg = append_arg || (arg == Argument::True);;
137             },
138             (OpenOption::Truncate, arg) => {
139                 if truncate {
140                     span_lint(cx,
141                               NONSENSICAL_OPEN_OPTIONS,
142                               span,
143                               "the method \"truncate\" is called more than once");
144                 } else {
145                     truncate = true
146                 }
147                 truncate_arg = truncate_arg || (arg == Argument::True);
148             },
149             (OpenOption::Read, arg) => {
150                 if read {
151                     span_lint(cx,
152                               NONSENSICAL_OPEN_OPTIONS,
153                               span,
154                               "the method \"read\" is called more than once");
155                 } else {
156                     read = true
157                 }
158                 read_arg = read_arg || (arg == Argument::True);;
159             },
160             (OpenOption::Write, arg) => {
161                 if write {
162                     span_lint(cx,
163                               NONSENSICAL_OPEN_OPTIONS,
164                               span,
165                               "the method \"write\" is called more than once");
166                 } else {
167                     write = true
168                 }
169                 write_arg = write_arg || (arg == Argument::True);;
170             },
171         }
172     }
173
174     if read && truncate && read_arg && truncate_arg && !(write && write_arg) {
175         span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "file opened with \"truncate\" and \"read\"");
176     }
177     if append && truncate && append_arg && truncate_arg {
178         span_lint(cx,
179                   NONSENSICAL_OPEN_OPTIONS,
180                   span,
181                   "file opened with \"append\" and \"truncate\"");
182     }
183 }