cargo_fl/rules/
imports.rs1use 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 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 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 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 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 if !matches!(item, syn::Item::Use(_)) {
117 collector.visit_item(item);
118 }
119 }
120
121 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![], }),
138 });
139 }
140 }
141
142 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}