2 use rustc_front::hir::{Expr, ExprMethodCall, ExprLit};
3 use utils::{walk_ptrs_ty_depth, match_type, span_lint, OPEN_OPTIONS_PATH};
4 use syntax::codemap::{Span, Spanned};
5 use syntax::ast::Lit_::LitBool;
7 /// **What it does:** This lint checks for duplicate open options as well as combinations that make no sense. It is `Warn` by default.
9 /// **Why is this bad?** In the best case, the code will be harder to read than necessary. I don't know the worst case.
11 /// **Known problems:** None
13 /// **Example:** `OpenOptions::new().read(true).truncate(true)`
15 pub NONSENSICAL_OPEN_OPTIONS,
17 "nonsensical combination of options for opening a file"
22 pub struct NonSensicalOpenOptions;
24 impl LintPass for NonSensicalOpenOptions {
25 fn get_lints(&self) -> LintArray {
26 lint_array!(NONSENSICAL_OPEN_OPTIONS)
30 impl LateLintPass for NonSensicalOpenOptions {
31 fn check_expr(&mut self, cx: &LateContext, e: &Expr) {
32 if let ExprMethodCall(ref name, _, ref arguments) = e.node {
33 let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.expr_ty(&arguments[0]));
34 if name.node.as_str() == "open" && match_type(cx, obj_ty, &OPEN_OPTIONS_PATH){
35 let mut options = Vec::new();
36 get_open_options(cx, &arguments[0], &mut options);
37 check_open_options(cx, &options, e.span);
59 fn get_open_options(cx: &LateContext, argument: &Expr, options: &mut Vec<(OpenOption, Argument)>) {
60 if let ExprMethodCall(ref name, _, ref arguments) = argument.node {
61 let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.expr_ty(&arguments[0]));
63 // Only proceed if this is a call on some object of type std::fs::OpenOptions
64 if match_type(cx, obj_ty, &OPEN_OPTIONS_PATH) && arguments.len() >= 2 {
66 let argument_option = match arguments[1].node {
67 ExprLit(ref span) => {
68 if let Spanned {node: LitBool(lit), ..} = **span {
69 if lit {Argument::True} else {Argument::False}
71 return; // The function is called with a literal
72 // which is not a boolean literal. This is theoretically
73 // possible, but not very likely.
81 match &*name.node.as_str() {
83 options.push((OpenOption::Create, argument_option));
86 options.push((OpenOption::Append, argument_option));
89 options.push((OpenOption::Truncate, argument_option));
92 options.push((OpenOption::Read, argument_option));
95 options.push((OpenOption::Write, argument_option));
100 get_open_options(cx, &arguments[0], options);
105 fn check_for_duplicates(cx: &LateContext, options: &[(OpenOption, Argument)], span: Span) {
106 // This code is almost duplicated (oh, the irony), but I haven't found a way to unify it.
107 if options.iter().filter(|o| if let (OpenOption::Create, _) = **o {true} else {false}).count() > 1 {
108 span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"create\" \
109 is called more than once");
111 if options.iter().filter(|o| if let (OpenOption::Append, _) = **o {true} else {false}).count() > 1 {
112 span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"append\" \
113 is called more than once");
115 if options.iter().filter(|o| if let (OpenOption::Truncate, _) = **o {true} else {false}).count() > 1 {
116 span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"truncate\" \
117 is called more than once");
119 if options.iter().filter(|o| if let (OpenOption::Read, _) = **o {true} else {false}).count() > 1 {
120 span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"read\" \
121 is called more than once");
123 if options.iter().filter(|o| if let (OpenOption::Write, _) = **o {true} else {false}).count() > 1 {
124 span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"write\" \
125 is called more than once");
129 fn check_for_inconsistencies(cx: &LateContext, options: &[(OpenOption, Argument)], span: Span) {
130 // Truncate + read makes no sense.
131 if options.iter().filter(|o| if let (OpenOption::Read, Argument::True) = **o {true} else {false}).count() > 0 &&
132 options.iter().filter(|o| if let (OpenOption::Truncate, Argument::True) = **o {true} else {false}).count() > 0 {
133 span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "File opened with \"truncate\" and \"read\"");
136 // Append + truncate makes no sense.
137 if options.iter().filter(|o| if let (OpenOption::Append, Argument::True) = **o {true} else {false}).count() > 0 &&
138 options.iter().filter(|o| if let (OpenOption::Truncate, Argument::True) = **o {true} else {false}).count() > 0 {
139 span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "File opened with \"append\" and \"truncate\"");
143 fn check_open_options(cx: &LateContext, options: &[(OpenOption, Argument)], span: Span) {
144 check_for_duplicates(cx, options, span);
145 check_for_inconsistencies(cx, options, span);