1#[cfg(test)]
3mod tests;
4
5use std::cmp::Ordering;
6
7use hir::Semantics;
8use syntax::{
9 Direction, NodeOrToken, SyntaxKind, SyntaxNode, algo,
10 ast::{
11 self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind,
12 edit_in_place::Removable, make,
13 },
14 ted,
15};
16
17use crate::{
18 RootDatabase,
19 imports::merge_imports::{
20 MergeBehavior, NormalizationStyle, common_prefix, eq_attrs, eq_visibility,
21 try_merge_imports, use_tree_cmp,
22 },
23};
24
25pub use hir::PrefixKind;
26
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
29pub enum ImportGranularity {
30 Crate,
32 Module,
34 Item,
36 One,
39}
40
41impl From<ImportGranularity> for NormalizationStyle {
42 fn from(granularity: ImportGranularity) -> Self {
43 match granularity {
44 ImportGranularity::One => NormalizationStyle::One,
45 _ => NormalizationStyle::Default,
46 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct InsertUseConfig {
52 pub granularity: ImportGranularity,
53 pub enforce_granularity: bool,
54 pub prefix_kind: PrefixKind,
55 pub group: bool,
56 pub skip_glob_imports: bool,
57}
58
59#[derive(Debug, Clone)]
60pub struct ImportScope {
61 pub kind: ImportScopeKind,
62 pub required_cfgs: Vec<ast::Attr>,
63}
64
65#[derive(Debug, Clone)]
66pub enum ImportScopeKind {
67 File(ast::SourceFile),
68 Module(ast::ItemList),
69 Block(ast::StmtList),
70}
71
72impl ImportScope {
73 pub fn find_insert_use_container(
76 position: &SyntaxNode,
77 sema: &Semantics<'_, RootDatabase>,
78 ) -> Option<Self> {
79 let mut block = None;
81 let mut required_cfgs = Vec::new();
82 for syntax in sema.ancestors_with_macros(position.clone()) {
86 if let Some(file) = ast::SourceFile::cast(syntax.clone()) {
87 return Some(ImportScope { kind: ImportScopeKind::File(file), required_cfgs });
88 } else if let Some(module) = ast::Module::cast(syntax.clone()) {
89 return sema
92 .original_ast_node(module)?
93 .item_list()
94 .map(ImportScopeKind::Module)
95 .map(|kind| ImportScope { kind, required_cfgs });
96 } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax) {
97 if block.is_none()
98 && let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone())
99 && let Some(b) = sema.original_ast_node(b)
100 {
101 block = b.stmt_list();
102 }
103 if has_attrs
104 .attrs()
105 .any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
106 {
107 if let Some(b) = block {
108 return Some(ImportScope {
109 kind: ImportScopeKind::Block(b),
110 required_cfgs,
111 });
112 }
113 required_cfgs.extend(has_attrs.attrs().filter(|attr| {
114 attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
115 }));
116 }
117 }
118 }
119 None
120 }
121
122 pub fn as_syntax_node(&self) -> &SyntaxNode {
123 match &self.kind {
124 ImportScopeKind::File(file) => file.syntax(),
125 ImportScopeKind::Module(item_list) => item_list.syntax(),
126 ImportScopeKind::Block(block) => block.syntax(),
127 }
128 }
129
130 pub fn clone_for_update(&self) -> Self {
131 Self {
132 kind: match &self.kind {
133 ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()),
134 ImportScopeKind::Module(item_list) => {
135 ImportScopeKind::Module(item_list.clone_for_update())
136 }
137 ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()),
138 },
139 required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(),
140 }
141 }
142}
143
144pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
146 insert_use_with_alias_option(scope, path, cfg, None);
147}
148
149pub fn insert_use_as_alias(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
150 let text: &str = "use foo as _";
151 let parse = syntax::SourceFile::parse(text, span::Edition::CURRENT_FIXME);
152 let node = parse
153 .tree()
154 .syntax()
155 .descendants()
156 .find_map(ast::UseTree::cast)
157 .expect("Failed to make ast node `Rename`");
158 let alias = node.rename();
159
160 insert_use_with_alias_option(scope, path, cfg, alias);
161}
162
163fn insert_use_with_alias_option(
164 scope: &ImportScope,
165 path: ast::Path,
166 cfg: &InsertUseConfig,
167 alias: Option<ast::Rename>,
168) {
169 let _p = tracing::info_span!("insert_use_with_alias_option").entered();
170 let mut mb = match cfg.granularity {
171 ImportGranularity::Crate => Some(MergeBehavior::Crate),
172 ImportGranularity::Module => Some(MergeBehavior::Module),
173 ImportGranularity::One => Some(MergeBehavior::One),
174 ImportGranularity::Item => None,
175 };
176 if !cfg.enforce_granularity {
177 let file_granularity = guess_granularity_from_scope(scope);
178 mb = match file_granularity {
179 ImportGranularityGuess::Unknown => mb,
180 ImportGranularityGuess::Item => None,
181 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
182 ImportGranularityGuess::ModuleOrItem => match mb {
184 Some(MergeBehavior::Module) | None => mb,
185 Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
188 },
189 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
190 ImportGranularityGuess::CrateOrModule => match mb {
191 Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
192 Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
193 },
194 ImportGranularityGuess::One => Some(MergeBehavior::One),
195 };
196 }
197
198 let mut use_tree = make::use_tree(path, None, alias, false);
199 if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
200 use_tree = use_tree.clone_for_update();
201 use_tree.wrap_in_tree_list();
202 }
203 let use_item = make::use_(None, None, use_tree).clone_for_update();
204 for attr in
205 scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
206 {
207 ted::insert(ted::Position::first_child_of(use_item.syntax()), attr);
208 }
209
210 if let Some(mb) = mb {
212 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
213 for existing_use in
214 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
215 {
216 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
217 ted::replace(existing_use.syntax(), merged.syntax());
218 return;
219 }
220 }
221 }
222 insert_use_(scope, use_item, cfg.group);
225}
226
227pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
228 if path.parent_path().is_some() {
230 return None;
231 }
232 let use_tree = path.syntax().parent().and_then(ast::UseTree::cast)?;
233 if use_tree.use_tree_list().is_some() || use_tree.star_token().is_some() {
234 return None;
235 }
236 if let Some(use_) = use_tree.syntax().parent().and_then(ast::Use::cast) {
237 return Some(Box::new(use_));
238 }
239 Some(Box::new(use_tree))
240}
241
242pub fn remove_path_if_in_use_stmt(path: &ast::Path) {
243 if let Some(node) = ast_to_remove_for_path_in_use_stmt(path) {
244 node.remove();
245 }
246}
247
248#[derive(Eq, PartialEq, PartialOrd, Ord)]
249enum ImportGroup {
250 Std,
252 ExternCrate,
253 ThisCrate,
254 ThisModule,
255 SuperModule,
256 One,
257}
258
259impl ImportGroup {
260 fn new(use_tree: &ast::UseTree) -> ImportGroup {
261 if use_tree.path().is_none() && use_tree.use_tree_list().is_some() {
262 return ImportGroup::One;
263 }
264
265 let Some(first_segment) = use_tree.path().as_ref().and_then(ast::Path::first_segment)
266 else {
267 return ImportGroup::ExternCrate;
268 };
269
270 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
271 match kind {
272 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
273 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
274 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
275 PathSegmentKind::Name(name) => match name.text().as_str() {
276 "std" => ImportGroup::Std,
277 "core" => ImportGroup::Std,
278 _ => ImportGroup::ExternCrate,
279 },
280 PathSegmentKind::SelfTypeKw => ImportGroup::ExternCrate,
282 PathSegmentKind::Type { .. } => ImportGroup::ExternCrate,
283 }
284 }
285}
286
287#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
288enum ImportGranularityGuess {
289 Unknown,
290 Item,
291 Module,
292 ModuleOrItem,
293 Crate,
294 CrateOrModule,
295 One,
296}
297
298fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
299 let use_stmt = |item| match item {
302 ast::Item::Use(use_) => {
303 let use_tree = use_.use_tree()?;
304 Some((use_tree, use_.visibility(), use_.attrs()))
305 }
306 _ => None,
307 };
308 let mut use_stmts = match &scope.kind {
309 ImportScopeKind::File(f) => f.items(),
310 ImportScopeKind::Module(m) => m.items(),
311 ImportScopeKind::Block(b) => b.items(),
312 }
313 .filter_map(use_stmt);
314 let mut res = ImportGranularityGuess::Unknown;
315 let Some((mut prev, mut prev_vis, mut prev_attrs)) = use_stmts.next() else { return res };
316
317 let is_tree_one_style =
318 |use_tree: &ast::UseTree| use_tree.path().is_none() && use_tree.use_tree_list().is_some();
319 let mut seen_one_style_groups = Vec::new();
320
321 loop {
322 if is_tree_one_style(&prev) {
323 if res != ImportGranularityGuess::One {
324 if res != ImportGranularityGuess::Unknown {
325 break ImportGranularityGuess::Unknown;
327 }
328
329 res = ImportGranularityGuess::One;
330 seen_one_style_groups.push((prev_vis.clone(), prev_attrs.clone()));
331 }
332 } else if let Some(use_tree_list) = prev.use_tree_list() {
333 if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
334 break ImportGranularityGuess::Crate;
336 } else {
337 res = ImportGranularityGuess::CrateOrModule;
339 }
340 }
341
342 let Some((curr, curr_vis, curr_attrs)) = use_stmts.next() else { break res };
343 if is_tree_one_style(&curr) {
344 if res != ImportGranularityGuess::One
345 || seen_one_style_groups.iter().any(|(prev_vis, prev_attrs)| {
346 eq_visibility(prev_vis.clone(), curr_vis.clone())
347 && eq_attrs(prev_attrs.clone(), curr_attrs.clone())
348 })
349 {
350 break ImportGranularityGuess::Unknown;
353 }
354 seen_one_style_groups.push((curr_vis.clone(), curr_attrs.clone()));
355 } else if eq_visibility(prev_vis, curr_vis.clone())
356 && eq_attrs(prev_attrs, curr_attrs.clone())
357 && let Some((prev_path, curr_path)) = prev.path().zip(curr.path())
358 && let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path)
359 {
360 if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
361 let prefix_c = prev_prefix.qualifiers().count();
362 let curr_c = curr_path.qualifiers().count() - prefix_c;
363 let prev_c = prev_path.qualifiers().count() - prefix_c;
364 if curr_c == 1 && prev_c == 1 {
365 break ImportGranularityGuess::Item;
367 } else {
368 res = ImportGranularityGuess::ModuleOrItem;
370 }
371 } else {
372 break ImportGranularityGuess::Module;
375 }
376 }
377 prev = curr;
378 prev_vis = curr_vis;
379 prev_attrs = curr_attrs;
380 }
381}
382
383fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
384 let scope_syntax = scope.as_syntax_node();
385 let insert_use_tree =
386 use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
387 let group = ImportGroup::new(&insert_use_tree);
388 let path_node_iter = scope_syntax
389 .children()
390 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
391 .flat_map(|(use_, node)| {
392 let tree = use_.use_tree()?;
393 Some((tree, node))
394 });
395
396 if group_imports {
397 let group_iter = path_node_iter
400 .clone()
401 .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
402 .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
403
404 let mut last = None;
406 let post_insert: Option<(_, SyntaxNode)> = group_iter
408 .inspect(|(.., node)| last = Some(node.clone()))
409 .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
410
411 if let Some((.., node)) = post_insert {
412 cov_mark::hit!(insert_group);
413 return ted::insert(ted::Position::before(node), use_item.syntax());
415 }
416 if let Some(node) = last {
417 cov_mark::hit!(insert_group_last);
418 return ted::insert(ted::Position::after(node), use_item.syntax());
420 }
421
422 let mut last = None;
425 let post_group = path_node_iter
427 .inspect(|(.., node)| last = Some(node.clone()))
428 .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
429 if let Some((.., node)) = post_group {
430 cov_mark::hit!(insert_group_new_group);
431 ted::insert(ted::Position::before(&node), use_item.syntax());
432 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
433 ted::insert(ted::Position::after(node), make::tokens::single_newline());
434 }
435 return;
436 }
437 if let Some(node) = last {
439 cov_mark::hit!(insert_group_no_group);
440 ted::insert(ted::Position::after(&node), use_item.syntax());
441 ted::insert(ted::Position::after(node), make::tokens::single_newline());
442 return;
443 }
444 } else {
445 if let Some((_, node)) = path_node_iter.last() {
447 cov_mark::hit!(insert_no_grouping_last);
448 ted::insert(ted::Position::after(node), use_item.syntax());
449 return;
450 }
451 }
452
453 let l_curly = match &scope.kind {
454 ImportScopeKind::File(_) => None,
455 ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
457 ImportScopeKind::Block(block) => block.l_curly_token(),
459 };
460 if let Some(last_inner_element) = scope_syntax
463 .children_with_tokens()
464 .skip(l_curly.is_some() as usize)
466 .take_while(|child| match child {
467 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
468 NodeOrToken::Token(token) => {
469 [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
470 .contains(&token.kind())
471 }
472 })
473 .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
474 .last()
475 {
476 cov_mark::hit!(insert_empty_inner_attr);
477 ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
478 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
479 } else {
480 match l_curly {
481 Some(b) => {
482 cov_mark::hit!(insert_empty_module);
483 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
484 ted::insert(ted::Position::after(&b), use_item.syntax());
485 }
486 None => {
487 cov_mark::hit!(insert_empty_file);
488 ted::insert(
489 ted::Position::first_child_of(scope_syntax),
490 make::tokens::blank_line(),
491 );
492 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
493 }
494 }
495 }
496}
497
498fn is_inner_attribute(node: SyntaxNode) -> bool {
499 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
500}