ryo_executor/engine/impls/
organize_imports.rs1use ryo_analysis::SymbolKind;
15use ryo_mutations::idiom::OrganizeImportsMutation;
16use ryo_mutations::{Mutation, MutationResult};
17use ryo_source::pure::{PureItem, PureUse, PureUseTree, PureVis};
18use std::collections::BTreeMap;
19
20use crate::engine::{ASTMutationContext, ASTRegApply};
21
22impl ASTRegApply for OrganizeImportsMutation {
23 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
24 let mut total_changes = 0;
25
26 let module_ids: Vec<_> = ctx
28 .symbol_registry
29 .iter()
30 .filter(|(id, _)| {
31 matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Mod))
32 && ctx.ast_registry.has_module_items(*id)
33 })
34 .map(|(id, _)| id)
35 .collect();
36
37 let module_count = module_ids.len();
38
39 for module_id in module_ids {
41 if let Some(module_items) = ctx.ast_registry.get_module_items_mut(module_id) {
42 let changes =
43 organize_module_items(module_items, self.deduplicate, self.merge_groups);
44 if changes > 0 {
45 ctx.emit_modified(
47 module_id,
48 crate::engine::events::ModificationType::Other(
49 "imports organized".to_string(),
50 ),
51 );
52 }
53 total_changes += changes;
54 }
55 }
56
57 MutationResult {
58 mutation_type: self.mutation_type().to_string(),
59 changes: total_changes,
60 description: if total_changes > 0 {
61 format!("Organized imports in {} module(s)", module_count)
62 } else {
63 "No imports to organize".to_string()
64 },
65 }
66 }
67}
68
69fn organize_module_items(
73 items: &mut Vec<PureItem>,
74 deduplicate: bool,
75 merge_groups: bool,
76) -> usize {
77 let mut uses: Vec<PureUse> = Vec::new();
79 let mut other_items: Vec<PureItem> = Vec::new();
80
81 for item in items.drain(..) {
82 if let PureItem::Use(u) = item {
83 uses.push(u);
84 } else {
85 other_items.push(item);
86 }
87 }
88
89 let original_count = uses.len();
90
91 if uses.is_empty() {
92 *items = other_items;
93 return 0;
94 }
95
96 if deduplicate {
98 let mut seen = Vec::new();
99 uses.retain(|u| {
100 let dominated = seen
101 .iter()
102 .any(|existing: &PureUseTree| trees_equal(existing, &u.tree));
103 if !dominated {
104 seen.push(u.tree.clone());
105 true
106 } else {
107 false
108 }
109 });
110 }
111
112 if merge_groups {
114 let mut pub_uses: Vec<PureUseTree> = Vec::new();
116 let mut priv_uses: Vec<PureUseTree> = Vec::new();
117
118 for u in uses {
119 match u.vis {
120 PureVis::Private => priv_uses.push(u.tree),
121 _ => pub_uses.push(u.tree),
122 }
123 }
124
125 let merged_priv = merge_trees(priv_uses);
127 let merged_pub = merge_trees(pub_uses);
128
129 uses = merged_priv
130 .into_iter()
131 .map(|tree| PureUse {
132 vis: PureVis::Private,
133 tree,
134 })
135 .chain(merged_pub.into_iter().map(|tree| PureUse {
136 vis: PureVis::Public,
137 tree,
138 }))
139 .collect();
140 }
141
142 uses.sort_by(use_cmp);
144
145 let changes = if uses.len() != original_count {
147 original_count.saturating_sub(uses.len())
148 } else {
149 0
150 };
151
152 items.clear();
154 items.extend(uses.into_iter().map(PureItem::Use));
155 items.extend(other_items);
156
157 changes
158}
159
160fn trees_equal(a: &PureUseTree, b: &PureUseTree) -> bool {
162 match (a, b) {
163 (PureUseTree::Path { path: pa, tree: ta }, PureUseTree::Path { path: pb, tree: tb }) => {
164 pa == pb && trees_equal(ta, tb)
165 }
166 (PureUseTree::Name(na), PureUseTree::Name(nb)) => na == nb,
167 (
168 PureUseTree::Rename {
169 name: na,
170 rename: ra,
171 },
172 PureUseTree::Rename {
173 name: nb,
174 rename: rb,
175 },
176 ) => na == nb && ra == rb,
177 (PureUseTree::Glob, PureUseTree::Glob) => true,
178 (PureUseTree::Group(ga), PureUseTree::Group(gb)) => {
179 ga.len() == gb.len() && ga.iter().zip(gb.iter()).all(|(a, b)| trees_equal(a, b))
180 }
181 _ => false,
182 }
183}
184
185fn merge_trees(trees: Vec<PureUseTree>) -> Vec<PureUseTree> {
187 if trees.is_empty() {
188 return vec![];
189 }
190
191 let mut groups: BTreeMap<String, Vec<PureUseTree>> = BTreeMap::new();
193 let mut ungroupable: Vec<PureUseTree> = Vec::new();
194
195 for tree in trees {
196 match &tree {
197 PureUseTree::Path {
198 path,
199 tree: subtree,
200 } => {
201 groups
202 .entry(path.clone())
203 .or_default()
204 .push((**subtree).clone());
205 }
206 _ => ungroupable.push(tree),
207 }
208 }
209
210 let mut result = ungroupable;
212 for (path, subtrees) in groups {
213 if subtrees.len() == 1 {
214 result.push(PureUseTree::Path {
215 path,
216 tree: Box::new(
217 subtrees
218 .into_iter()
219 .next()
220 .expect("len() == 1 guard above guarantees at least one element"),
221 ),
222 });
223 } else {
224 let merged = merge_trees(subtrees);
226 result.push(PureUseTree::Path {
227 path,
228 tree: Box::new(PureUseTree::Group(merged)),
229 });
230 }
231 }
232
233 result
234}
235
236fn use_cmp(a: &PureUse, b: &PureUse) -> std::cmp::Ordering {
238 let vis_cmp = match (&a.vis, &b.vis) {
240 (PureVis::Private, PureVis::Private) => std::cmp::Ordering::Equal,
241 (PureVis::Private, _) => std::cmp::Ordering::Less,
242 (_, PureVis::Private) => std::cmp::Ordering::Greater,
243 _ => std::cmp::Ordering::Equal,
244 };
245
246 if vis_cmp != std::cmp::Ordering::Equal {
247 return vis_cmp;
248 }
249
250 tree_cmp(&a.tree, &b.tree)
252}
253
254fn tree_cmp(a: &PureUseTree, b: &PureUseTree) -> std::cmp::Ordering {
255 let a_str = tree_to_string(a);
256 let b_str = tree_to_string(b);
257 a_str.cmp(&b_str)
258}
259
260fn tree_to_string(tree: &PureUseTree) -> String {
261 match tree {
262 PureUseTree::Path { path, tree } => format!("{}::{}", path, tree_to_string(tree)),
263 PureUseTree::Name(name) => name.clone(),
264 PureUseTree::Rename { name, rename } => format!("{} as {}", name, rename),
265 PureUseTree::Glob => "*".to_string(),
266 PureUseTree::Group(trees) => {
267 let parts: Vec<_> = trees.iter().map(tree_to_string).collect();
268 format!("{{{}}}", parts.join(", "))
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 fn make_use(path: &str) -> PureUse {
278 let parts: Vec<&str> = path.split("::").collect();
279 let tree = parts
280 .iter()
281 .rev()
282 .fold(None, |acc, part| {
283 Some(match acc {
284 None => PureUseTree::Name(part.to_string()),
285 Some(subtree) => PureUseTree::Path {
286 path: part.to_string(),
287 tree: Box::new(subtree),
288 },
289 })
290 })
291 .unwrap();
292
293 PureUse {
294 vis: PureVis::Private,
295 tree,
296 }
297 }
298
299 #[test]
300 fn test_organize_deduplicates() {
301 let mut items = vec![
302 PureItem::Use(make_use("std::io")),
303 PureItem::Use(make_use("std::io")),
304 PureItem::Use(make_use("std::fs")),
305 ];
306
307 let changes = organize_module_items(&mut items, true, false);
308
309 assert_eq!(changes, 1);
310 assert_eq!(
311 items
312 .iter()
313 .filter(|i| matches!(i, PureItem::Use(_)))
314 .count(),
315 2
316 );
317 }
318
319 #[test]
320 fn test_organize_no_changes_when_unique() {
321 let mut items = vec![
322 PureItem::Use(make_use("std::io")),
323 PureItem::Use(make_use("std::fs")),
324 ];
325
326 let changes = organize_module_items(&mut items, true, false);
327
328 assert_eq!(changes, 0);
329 }
330
331 #[test]
332 fn test_trees_equal() {
333 let tree1 = PureUseTree::Name("foo".to_string());
334 let tree2 = PureUseTree::Name("foo".to_string());
335
336 assert!(trees_equal(&tree1, &tree2));
337 }
338
339 #[test]
340 fn test_trees_not_equal_different_names() {
341 let tree1 = PureUseTree::Name("foo".to_string());
342 let tree2 = PureUseTree::Name("bar".to_string());
343
344 assert!(!trees_equal(&tree1, &tree2));
345 }
346}