Skip to main content

wowlab_tidy/languages/rust/rules/api/
missing_error_context.rs

1use 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});