Skip to main content

cargo_fl/rules/
unwrap_usage.rs

1use crate::rules::{Issue, Location, Rule, RuleContext, Severity, Fix, Replacement};
2use syn::visit::Visit;
3use syn::{Expr, ExprCall, ExprMethodCall};
4
5pub struct UnwrapUsageRule;
6
7impl Rule for UnwrapUsageRule {
8    fn name(&self) -> &'static str {
9        "unwrap_usage"
10    }
11
12    fn check(&self, ctx: &mut RuleContext) {
13        let syntax_tree = ctx.syntax_tree.clone();
14        let mut visitor = UnwrapVisitor::new(ctx);
15        visitor.visit_file(&syntax_tree);
16    }
17}
18
19struct UnwrapVisitor<'a> {
20    ctx: &'a mut RuleContext,
21}
22
23impl<'a> UnwrapVisitor<'a> {
24    fn new(ctx: &'a mut RuleContext) -> Self {
25        Self { ctx }
26    }
27
28    fn report_unwrap(&mut self, method_name: &str, line: usize, col: usize) {
29        let suggestion = match method_name {
30            "unwrap" => "Consider using `match`, `if let`, or `expect()` with a descriptive message",
31            "unwrap_or_default" => "This is generally safe, but consider explicit handling",
32            "expect" => "Good! Using expect() with descriptive messages",
33            _ => "Consider explicit error handling"
34        };
35
36        let severity = match method_name {
37            "unwrap" => Severity::Warning,
38            "unwrap_unchecked" => Severity::Error,
39            _ => Severity::Info,
40        };
41
42        self.ctx.report(Issue {
43            rule: "unwrap_usage".to_string(),
44            severity,
45            message: format!("Found `{}()` call - {}", method_name, suggestion),
46            location: Location {
47                line,
48                column: col,
49                end_line: Some(line),
50                end_column: Some(col + method_name.len()),
51            },
52            fix: if method_name == "unwrap" {
53                Some(Fix {
54                    description: format!("Replace with expect() and descriptive message"),
55                    replacements: vec![Replacement {
56                        start: 0, // This would need proper span calculation
57                        end: 0,
58                        text: "expect(\"TODO: Add descriptive error message\")".to_string(),
59                    }],
60                })
61            } else {
62                None
63            },
64        });
65    }
66}
67
68impl<'a> Visit<'a> for UnwrapVisitor<'a> {
69    fn visit_expr_method_call(&mut self, method_call: &'a ExprMethodCall) {
70        let method_name = method_call.method.to_string();
71        
72        match method_name.as_str() {
73            "unwrap" | "unwrap_or_default" | "unwrap_unchecked" | "expect" => {
74                let (line, col) = self.ctx.line_col(method_call.method.span());
75                self.report_unwrap(&method_name, line, col);
76            }
77            _ => {}
78        }
79
80        // Continue visiting
81        syn::visit::visit_expr_method_call(self, method_call);
82    }
83
84    fn visit_expr_call(&mut self, call: &'a ExprCall) {
85        // Check for unwrap-like function calls
86        if let Expr::Path(path) = call.func.as_ref() {
87            if let Some(last_segment) = path.path.segments.last() {
88                let func_name = last_segment.ident.to_string();
89                if func_name.contains("unwrap") {
90                    let (line, col) = self.ctx.line_col(last_segment.ident.span());
91                    self.report_unwrap(&func_name, line, col);
92                }
93            }
94        }
95
96        // Continue visiting
97        syn::visit::visit_expr_call(self, call);
98    }
99}