Skip to main content

cargo_fl/rules/
imports.rs

1use super::*;
2use std::collections::{HashSet, HashMap};
3use syn::spanned::Spanned;
4
5pub struct ImportOrderRule;
6
7impl Rule for ImportOrderRule {
8    fn name(&self) -> &'static str {
9        "import-order"
10    }
11    
12    fn check(&self, ctx: &mut RuleContext) {
13        let mut std_imports = vec![];
14        let mut external_imports = vec![];
15        let mut local_imports = vec![];
16        let mut issues_to_report = Vec::new();
17        
18        for item in &ctx.syntax_tree.items {
19            if let syn::Item::Use(use_item) = item {
20                let path = use_path_to_string(&use_item.tree);
21                let (line, col) = ctx.line_col(use_item.span());
22                
23                if path.starts_with("std::") || path.starts_with("core::") || path.starts_with("alloc::") {
24                    std_imports.push((path, line, col));
25                } else if path.starts_with("crate::") || path.starts_with("super::") || path.starts_with("self::") {
26                    local_imports.push((path, line, col));
27                } else {
28                    external_imports.push((path, line, col));
29                }
30            }
31        }
32        
33        // Check if imports are grouped correctly
34        let mut last_std_line = 0;
35        let mut last_external_line = 0;
36        
37        for (_, line, _) in &std_imports {
38            last_std_line = last_std_line.max(*line);
39        }
40        
41        for (_, line, col) in &external_imports {
42            if *line < last_std_line {
43                issues_to_report.push(Issue {
44                    rule: self.name().to_string(),
45                    severity: Severity::Info,
46                    message: "External imports should come after standard library imports".to_string(),
47                    location: Location {
48                        line: *line,
49                        column: *col,
50                        end_line: None,
51                        end_column: None,
52                    },
53                    fix: None,
54                });
55            }
56            last_external_line = last_external_line.max(*line);
57        }
58        
59        for (_, line, col) in &local_imports {
60            if *line < last_external_line {
61                issues_to_report.push(Issue {
62                    rule: self.name().to_string(),
63                    severity: Severity::Info,
64                    message: "Local imports should come after external crate imports".to_string(),
65                    location: Location {
66                        line: *line,
67                        column: *col,
68                        end_line: None,
69                        end_column: None,
70                    },
71                    fix: None,
72                });
73            }
74        }
75        
76        // Report all issues
77        for issue in issues_to_report {
78            ctx.report(issue);
79        }
80    }
81}
82
83pub struct UnusedImportRule;
84
85impl Rule for UnusedImportRule {
86    fn name(&self) -> &'static str {
87        "unused-import"
88    }
89    
90    fn check(&self, ctx: &mut RuleContext) {
91        let mut imports = HashMap::new();
92        let mut used_idents = HashSet::new();
93        let mut issues_to_report = Vec::new();
94        
95        // Collect all imports
96        for item in &ctx.syntax_tree.items {
97            if let syn::Item::Use(use_item) = item {
98                collect_use_tree_idents(&use_item.tree, &mut imports, ctx);
99            }
100        }
101        
102        // Collect all used identifiers
103        struct IdentCollector<'a> {
104            used: &'a mut HashSet<String>,
105        }
106        
107        impl<'ast> Visit<'ast> for IdentCollector<'_> {
108            fn visit_ident(&mut self, ident: &'ast syn::Ident) {
109                self.used.insert(ident.to_string());
110            }
111        }
112        
113        let mut collector = IdentCollector { used: &mut used_idents };
114        for item in &ctx.syntax_tree.items {
115            // Skip the use statements themselves
116            if !matches!(item, syn::Item::Use(_)) {
117                collector.visit_item(item);
118            }
119        }
120        
121        // Report unused imports
122        for (name, (line, col)) in imports {
123            if !used_idents.contains(&name) {
124                issues_to_report.push(Issue {
125                    rule: self.name().to_string(),
126                    severity: Severity::Warning,
127                    message: format!("Unused import: {}", name),
128                    location: Location {
129                        line,
130                        column: col,
131                        end_line: None,
132                        end_column: None,
133                    },
134                    fix: Some(Fix {
135                        description: "Remove unused import".to_string(),
136                        replacements: vec![], // Would calculate actual removal
137                    }),
138                });
139            }
140        }
141        
142        // Report all issues
143        for issue in issues_to_report {
144            ctx.report(issue);
145        }
146    }
147}
148
149fn use_path_to_string(tree: &syn::UseTree) -> String {
150    match tree {
151        syn::UseTree::Path(p) => format!("{}::{}", p.ident, use_path_to_string(&p.tree)),
152        syn::UseTree::Name(n) => n.ident.to_string(),
153        syn::UseTree::Glob(_) => "*".to_string(),
154        syn::UseTree::Group(g) => {
155            let items: Vec<_> = g.items.iter().map(use_path_to_string).collect();
156            format!("{{{}}}", items.join(", "))
157        }
158        syn::UseTree::Rename(r) => format!("{} as {}", r.ident, r.rename),
159    }
160}
161
162fn collect_use_tree_idents(
163    tree: &syn::UseTree,
164    imports: &mut HashMap<String, (usize, usize)>,
165    ctx: &RuleContext,
166) {
167    match tree {
168        syn::UseTree::Name(n) => {
169            let (line, col) = ctx.line_col(n.ident.span());
170            imports.insert(n.ident.to_string(), (line, col));
171        }
172        syn::UseTree::Rename(r) => {
173            let (line, col) = ctx.line_col(r.rename.span());
174            imports.insert(r.rename.to_string(), (line, col));
175        }
176        syn::UseTree::Path(p) => {
177            collect_use_tree_idents(&p.tree, imports, ctx);
178        }
179        syn::UseTree::Group(g) => {
180            for item in &g.items {
181                collect_use_tree_idents(item, imports, ctx);
182            }
183        }
184        _ => {}
185    }
186}