wowlab_tidy/languages/rust/rules/correctness/
deep_exit.rs1use 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});