Skip to main content

wowlab_tidy/languages/rust/rules/correctness/
deep_exit.rs

1use ra_ap_syntax::{AstNode, ast};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "process::exit in library",
9        code: "fn f() { std::process::exit(1); }",
10        pass: false,
11    },
12    Example {
13        label: "no exit call",
14        code: "fn f() { let exit = 0; }",
15        pass: true,
16    },
17    Example {
18        label: "exit in test module",
19        code: "#[cfg(test)]\nmod tests {\n    fn t() { std::process::exit(1); }\n}",
20        pass: true,
21    },
22    Example {
23        label: "custom exit function",
24        code: "fn f() { exit(0); }",
25        pass: true,
26    },
27];
28
29crate::ast_rule!(
30    deep_exit,
31    "Ban `std::process::exit()` in library code.",
32    "process::exit() skips destructors and cleanup. Return Result from main instead so resources are released properly.",
33    High,
34);
35
36fn check_deep_exit(ctx: &AstCtx<'_>) -> Vec<Violation> {
37    ctx.nodes::<ast::CallExpr>()
38        .filter(|call| !ctx.is_in_test(call))
39        .filter_map(|call| {
40            let ast::Expr::PathExpr(path_expr) = call.expr()? else {
41                return None;
42            };
43            let path = path_expr.path()?;
44            let source = path.syntax().text().to_string();
45
46            (source.split("::").any(|segment| segment == "process")
47                && path
48                    .segment()
49                    .and_then(|segment| segment.name_ref())
50                    .is_some_and(|name| name.text() == "exit"))
51            .then(|| {
52                ctx.violation(
53                    &path,
54                    "process::exit() in library code (return Result instead)",
55                )
56            })
57        })
58        .collect()
59}
60
61crate::tidy_ast_test!(check_deep_exit, {
62    crate::example_tests!(EXAMPLES, check_deep_exit);
63});