wowlab_tidy/languages/rust/rules/api/
missing_error_context.rs1use ra_ap_syntax::{AstNode, ast, ast::HasArgList};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "wildcard discard",
9 code: r#"fn f() { let _ = Ok::<i32, i32>(1).map_err(|_| "bad"); }"#,
10 pass: false,
11 },
12 Example {
13 label: "named param",
14 code: r#"fn f() { let _ = Ok::<i32, i32>(1).map_err(|e| format!("{e}")); }"#,
15 pass: true,
16 },
17 Example {
18 label: "underscore-prefixed param",
19 code: r#"fn f() { let _ = Ok::<i32, i32>(1).map_err(|_e| "bad"); }"#,
20 pass: false,
21 },
22 Example {
23 label: "function ref",
24 code: "fn f() { let _ = Ok::<i32, String>(1).map_err(String::from); }",
25 pass: true,
26 },
27 Example {
28 label: "discard in test module",
29 code: "#[cfg(test)]\nmod tests {\n fn t() { let _ = Ok::<i32, i32>(1).map_err(|_| \"bad\"); }\n}",
30 pass: true,
31 },
32];
33
34crate::ast_rule!(
35 missing_error_context,
36 "Flag `.map_err(|_| ...)` that discards the original error.",
37 "Discarding the original error in map_err(|_| ...) destroys the root cause, making failures hard to diagnose.",
38 Medium,
39);
40
41fn check_missing_error_context(ctx: &AstCtx<'_>) -> Vec<Violation> {
42 ctx.nodes::<ast::MethodCallExpr>()
43 .filter(|call| !ctx.is_in_test(call))
44 .filter_map(|call| {
45 let method = call.name_ref()?;
46
47 if method.text() != "map_err" {
48 return None;
49 }
50
51 let closure = call.arg_list()?.args().next().and_then(|arg| match arg {
52 ast::Expr::ClosureExpr(closure) => Some(closure),
53 _ => None,
54 })?;
55 let param = closure.param_list()?.syntax().text().to_string();
56 let binding = param.trim().trim_matches('|').trim();
57
58 (binding == "_" || binding.starts_with('_')).then(|| {
59 ctx.violation(
60 &method,
61 ".map_err(|_| ...) discards the original error — use the error variable",
62 )
63 })
64 })
65 .collect()
66}
67
68crate::tidy_ast_test!(check_missing_error_context, {
69 crate::example_tests!(EXAMPLES, check_missing_error_context);
70});