cargo_fl/rules/
unwrap_usage.rs1use 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, 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 syn::visit::visit_expr_method_call(self, method_call);
82 }
83
84 fn visit_expr_call(&mut self, call: &'a ExprCall) {
85 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 syn::visit::visit_expr_call(self, call);
98 }
99}