1#[cfg(test)]
3mod tests;
4
5use std::cmp::Ordering;
6
7use hir::Semantics;
8use syntax::{
9 NodeOrToken, SyntaxKind, SyntaxNode,
10 ast::{
11 self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind, edit::IndentLevel,
12 },
13 syntax_editor::{Position, SyntaxEditor},
14};
15
16use crate::{
17 RootDatabase,
18 imports::merge_imports::{
19 MergeBehavior, NormalizationStyle, common_prefix, eq_attrs, eq_visibility,
20 try_merge_imports, use_tree_cmp, wrap_in_tree_list,
21 },
22};
23
24pub use hir::PrefixKind;
25
26#[derive(Copy, Clone, Debug, PartialEq, Eq)]
28pub enum ImportGranularity {
29 Crate,
31 Module,
33 Item,
35 One,
38}
39
40impl From<ImportGranularity> for NormalizationStyle {
41 fn from(granularity: ImportGranularity) -> Self {
42 match granularity {
43 ImportGranularity::One => NormalizationStyle::One,
44 _ => NormalizationStyle::Default,
45 }
46 }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub struct InsertUseConfig {
51 pub granularity: ImportGranularity,
52 pub enforce_granularity: bool,
53 pub prefix_kind: PrefixKind,
54 pub group: bool,
55 pub skip_glob_imports: bool,
56}
57
58#[derive(Debug, Clone)]
59pub struct ImportScope {
60 pub kind: ImportScopeKind,
61 pub required_cfgs: Vec<ast::Attr>,
62}
63
64#[derive(Debug, Clone)]
65pub enum ImportScopeKind {
66 File(ast::SourceFile),
67 Module(ast::ItemList),
68 Block(ast::StmtList),
69}
70
71impl ImportScope {
72 pub fn find_insert_use_container(
75 position: &SyntaxNode,
76 sema: &Semantics<'_, RootDatabase>,
77 ) -> Option<Self> {
78 let mut block = None;
80 let mut required_cfgs = Vec::new();
81 for syntax in sema.ancestors_with_macros(position.clone()) {
85 if let Some(file) = ast::SourceFile::cast(syntax.clone()) {
86 return Some(ImportScope { kind: ImportScopeKind::File(file), required_cfgs });
87 } else if let Some(module) = ast::Module::cast(syntax.clone()) {
88 return sema
91 .original_ast_node(module)?
92 .item_list()
93 .map(ImportScopeKind::Module)
94 .map(|kind| ImportScope { kind, required_cfgs });
95 } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax.clone()) {
96 if block.is_none()
97 && let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone())
98 && let Some(b) = sema.original_ast_node(b)
99 {
100 block = b.stmt_list();
101 }
102 if has_attrs.attrs().any(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_))))
103 {
104 if let Some(b) = block.clone() {
105 let current_cfgs = has_attrs
106 .attrs()
107 .filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_))));
108
109 let total_cfgs: Vec<_> =
110 required_cfgs.iter().cloned().chain(current_cfgs).collect();
111
112 let parent = syntax.parent();
113 let mut can_merge = false;
114 if let Some(parent) = parent {
115 can_merge = parent.children().filter_map(ast::Use::cast).any(|u| {
116 let u_attrs = u.attrs().filter(|attr| {
117 matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))
118 });
119 crate::imports::merge_imports::eq_attrs(
120 u_attrs,
121 total_cfgs.iter().cloned(),
122 )
123 });
124 }
125
126 if !can_merge {
127 return Some(ImportScope {
128 kind: ImportScopeKind::Block(b),
129 required_cfgs,
130 });
131 }
132 }
133 required_cfgs.extend(
134 has_attrs
135 .attrs()
136 .filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))),
137 );
138 }
139 }
140 }
141 None
142 }
143
144 pub fn as_syntax_node(&self) -> &SyntaxNode {
145 match &self.kind {
146 ImportScopeKind::File(file) => file.syntax(),
147 ImportScopeKind::Module(item_list) => item_list.syntax(),
148 ImportScopeKind::Block(block) => block.syntax(),
149 }
150 }
151}
152
153pub fn insert_use_with_editor(
155 scope: &ImportScope,
156 path: ast::Path,
157 cfg: &InsertUseConfig,
158 syntax_editor: &SyntaxEditor,
159) {
160 insert_use_with_alias_option_with_editor(scope, path, cfg, None, syntax_editor);
161}
162
163pub fn insert_uses_with_editor(
164 scope: &ImportScope,
165 paths: impl IntoIterator<Item = ast::Path>,
166 cfg: &InsertUseConfig,
167 syntax_editor: &SyntaxEditor,
168) {
169 let paths = paths.into_iter().collect::<Vec<_>>();
170 if paths.len() > 1
171 && scope.as_syntax_node().parent().is_none()
172 && scope.required_cfgs.is_empty()
173 && !scope.as_syntax_node().children().any(|node| ast::Use::cast(node).is_some())
174 {
175 let make = syntax_editor.make();
176 let elements = paths
177 .into_iter()
178 .flat_map(|path| {
179 let use_tree = make.use_tree(path, None, None, false);
180 let use_item = make.use_(None, None, use_tree);
181 [use_item.syntax().clone().into(), make.whitespace("\n").into()]
182 })
183 .chain([make.whitespace("\n").into()])
184 .collect();
185 syntax_editor.insert_all(Position::first_child_of(scope.as_syntax_node()), elements);
186 return;
187 }
188
189 for path in paths {
190 insert_use_with_editor(scope, path, cfg, syntax_editor);
191 }
192}
193
194pub fn insert_use_as_alias_with_editor(
195 scope: &ImportScope,
196 path: ast::Path,
197 cfg: &InsertUseConfig,
198 edition: span::Edition,
199 editor: &SyntaxEditor,
200) {
201 let text: &str = "use foo as _";
202 let parse = syntax::SourceFile::parse(text, edition);
203 let node = parse
204 .tree()
205 .syntax()
206 .descendants()
207 .find_map(ast::UseTree::cast)
208 .expect("Failed to make ast node `Rename`");
209 let alias = node.rename();
210
211 insert_use_with_alias_option_with_editor(scope, path, cfg, alias, editor);
212}
213
214fn insert_use_with_alias_option_with_editor(
215 scope: &ImportScope,
216 path: ast::Path,
217 cfg: &InsertUseConfig,
218 alias: Option<ast::Rename>,
219 syntax_editor: &SyntaxEditor,
220) {
221 let make = syntax_editor.make();
222 let _p = tracing::info_span!("insert_use_with_alias_option").entered();
223 let mut mb = match cfg.granularity {
224 ImportGranularity::Crate => Some(MergeBehavior::Crate),
225 ImportGranularity::Module => Some(MergeBehavior::Module),
226 ImportGranularity::One => Some(MergeBehavior::One),
227 ImportGranularity::Item => None,
228 };
229 if !cfg.enforce_granularity {
230 let file_granularity = guess_granularity_from_scope(scope);
231 mb = match file_granularity {
232 ImportGranularityGuess::Unknown => mb,
233 ImportGranularityGuess::Item => None,
234 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
235 ImportGranularityGuess::ModuleOrItem => match mb {
237 Some(MergeBehavior::Module) | None => mb,
238 Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
241 },
242 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
243 ImportGranularityGuess::CrateOrModule => match mb {
244 Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
245 Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
246 },
247 ImportGranularityGuess::One => Some(MergeBehavior::One),
248 };
249 }
250
251 let mut use_tree = make.use_tree(path, None, alias, false);
252 if mb == Some(MergeBehavior::One)
253 && use_tree.path().is_some()
254 && let Some(wrapped) = wrap_in_tree_list(&use_tree, make)
255 {
256 use_tree = wrapped;
257 }
258 let use_item = make.use_(scope.required_cfgs.iter().cloned().rev(), None, use_tree);
259
260 if let Some(mb) = mb {
262 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
263 for existing_use in
264 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
265 {
266 if let Some(merged) =
267 try_merge_imports(syntax_editor.make(), &existing_use, &use_item, mb)
268 {
269 syntax_editor.replace(existing_use.syntax(), merged.syntax());
270 return;
271 }
272 }
273 }
274 insert_use_with_editor_(scope, use_item, cfg.group, syntax_editor);
277}
278
279pub fn remove_use_tree_if_simple(use_tree: &ast::UseTree, editor: &SyntaxEditor) {
280 if use_tree.use_tree_list().is_some() || use_tree.star_token().is_some() {
281 return;
282 }
283 if let Some(use_) = use_tree.syntax().parent().and_then(ast::Use::cast) {
284 syntax::syntax_editor::Removable::remove(&use_, editor);
285 } else {
286 syntax::syntax_editor::Removable::remove(use_tree, editor);
287 }
288}
289
290#[derive(Eq, PartialEq, PartialOrd, Ord)]
291enum ImportGroup {
292 Std,
294 ExternCrate,
295 ThisCrate,
296 ThisModule,
297 SuperModule,
298 One,
299}
300
301impl ImportGroup {
302 fn new(use_tree: &ast::UseTree) -> ImportGroup {
303 if use_tree.path().is_none() && use_tree.use_tree_list().is_some() {
304 return ImportGroup::One;
305 }
306
307 let Some(first_segment) = use_tree.path().as_ref().and_then(ast::Path::first_segment)
308 else {
309 return ImportGroup::ExternCrate;
310 };
311
312 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
313 match kind {
314 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
315 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
316 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
317 PathSegmentKind::Name(name) => match name.text().as_str() {
318 "std" => ImportGroup::Std,
319 "core" => ImportGroup::Std,
320 _ => ImportGroup::ExternCrate,
321 },
322 PathSegmentKind::SelfTypeKw => ImportGroup::ExternCrate,
324 PathSegmentKind::Type { .. } => ImportGroup::ExternCrate,
325 }
326 }
327}
328
329#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
330enum ImportGranularityGuess {
331 Unknown,
332 Item,
333 Module,
334 ModuleOrItem,
335 Crate,
336 CrateOrModule,
337 One,
338}
339
340fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
341 let use_stmt = |item| match item {
344 ast::Item::Use(use_) => {
345 let use_tree = use_.use_tree()?;
346 Some((use_tree, use_.visibility(), use_.attrs()))
347 }
348 _ => None,
349 };
350 let mut use_stmts = match &scope.kind {
351 ImportScopeKind::File(f) => f.items(),
352 ImportScopeKind::Module(m) => m.items(),
353 ImportScopeKind::Block(b) => b.items(),
354 }
355 .filter_map(use_stmt);
356 let mut res = ImportGranularityGuess::Unknown;
357 let Some((mut prev, mut prev_vis, mut prev_attrs)) = use_stmts.next() else { return res };
358
359 let is_tree_one_style =
360 |use_tree: &ast::UseTree| use_tree.path().is_none() && use_tree.use_tree_list().is_some();
361 let mut seen_one_style_groups = Vec::new();
362
363 loop {
364 if is_tree_one_style(&prev) {
365 if res != ImportGranularityGuess::One {
366 if res != ImportGranularityGuess::Unknown {
367 break ImportGranularityGuess::Unknown;
369 }
370
371 res = ImportGranularityGuess::One;
372 seen_one_style_groups.push((prev_vis.clone(), prev_attrs.clone()));
373 }
374 } else if let Some(use_tree_list) = prev.use_tree_list() {
375 if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
376 break ImportGranularityGuess::Crate;
378 } else {
379 res = ImportGranularityGuess::CrateOrModule;
381 }
382 }
383
384 let Some((curr, curr_vis, curr_attrs)) = use_stmts.next() else { break res };
385 if is_tree_one_style(&curr) {
386 if res != ImportGranularityGuess::One
387 || seen_one_style_groups.iter().any(|(prev_vis, prev_attrs)| {
388 eq_visibility(prev_vis.clone(), curr_vis.clone())
389 && eq_attrs(prev_attrs.clone(), curr_attrs.clone())
390 })
391 {
392 break ImportGranularityGuess::Unknown;
395 }
396 seen_one_style_groups.push((curr_vis.clone(), curr_attrs.clone()));
397 } else if eq_visibility(prev_vis, curr_vis.clone())
398 && eq_attrs(prev_attrs, curr_attrs.clone())
399 && let Some((prev_path, curr_path)) = prev.path().zip(curr.path())
400 && let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path)
401 {
402 if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
403 let prefix_c = prev_prefix.qualifiers().count();
404 let curr_c = curr_path.qualifiers().count() - prefix_c;
405 let prev_c = prev_path.qualifiers().count() - prefix_c;
406 if curr_c == 1 && prev_c == 1 {
407 break ImportGranularityGuess::Item;
409 } else {
410 res = ImportGranularityGuess::ModuleOrItem;
412 }
413 } else {
414 break ImportGranularityGuess::Module;
417 }
418 }
419 prev = curr;
420 prev_vis = curr_vis;
421 prev_attrs = curr_attrs;
422 }
423}
424
425fn insert_use_with_editor_(
426 scope: &ImportScope,
427 use_item: ast::Use,
428 group_imports: bool,
429 syntax_editor: &SyntaxEditor,
430) {
431 let make = syntax_editor.make();
432 let scope_syntax = scope.as_syntax_node();
433 let insert_use_tree =
434 use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
435 let group = ImportGroup::new(&insert_use_tree);
436 let path_node_iter = scope_syntax
437 .children()
438 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
439 .flat_map(|(use_, node)| {
440 let tree = use_.use_tree()?;
441 Some((tree, node))
442 });
443
444 if group_imports {
445 let group_iter = path_node_iter
448 .clone()
449 .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
450 .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
451
452 let mut last = None;
454 let post_insert: Option<(_, SyntaxNode)> = group_iter
456 .inspect(|(.., node)| last = Some(node.clone()))
457 .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
458
459 if let Some((.., node)) = post_insert {
460 cov_mark::hit!(insert_group);
461 return syntax_editor.insert_all(
463 Position::before(node),
464 vec![use_item.syntax().clone().into(), make.whitespace("\n").into()],
465 );
466 }
467 if let Some(node) = last {
468 cov_mark::hit!(insert_group_last);
469 return syntax_editor.insert_all(
471 Position::after(node),
472 vec![make.whitespace("\n").into(), use_item.syntax().clone().into()],
473 );
474 }
475
476 let mut last = None;
479 let post_group = path_node_iter
481 .inspect(|(.., node)| last = Some(node.clone()))
482 .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
483 if let Some((.., node)) = post_group {
484 cov_mark::hit!(insert_group_new_group);
485 syntax_editor.insert_all(
486 Position::before(&node),
487 vec![use_item.syntax().clone().into(), make.whitespace("\n\n").into()],
488 );
489 return;
490 }
491 if let Some(node) = last {
493 cov_mark::hit!(insert_group_no_group);
494 syntax_editor.insert_all(
495 Position::after(&node),
496 vec![make.whitespace("\n\n").into(), use_item.syntax().clone().into()],
497 );
498 return;
499 }
500 } else {
501 if let Some((_, node)) = path_node_iter.last() {
503 cov_mark::hit!(insert_no_grouping_last);
504 syntax_editor.insert_all(
505 Position::after(node),
506 vec![make.whitespace("\n").into(), use_item.syntax().clone().into()],
507 );
508 return;
509 }
510 }
511
512 let l_curly = match &scope.kind {
513 ImportScopeKind::File(_) => None,
514 ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
516 ImportScopeKind::Block(block) => block.l_curly_token(),
518 };
519 if let Some(last_inner_element) = scope_syntax
522 .children_with_tokens()
523 .skip(l_curly.is_some() as usize)
525 .take_while(|child| match child {
526 NodeOrToken::Node(node) => {
527 is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
528 }
529 NodeOrToken::Token(token) => {
530 [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
531 .contains(&token.kind())
532 }
533 })
534 .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
535 .last()
536 {
537 cov_mark::hit!(insert_empty_inner_attr);
538 let indent = if l_curly.is_some() {
539 IndentLevel::from_node(scope_syntax) + 1
540 } else {
541 IndentLevel::zero()
542 };
543 syntax_editor.insert_all(
544 Position::after(&last_inner_element),
545 vec![
546 make.whitespace(&format!("\n\n{indent}")).into(),
547 use_item.syntax().clone().into(),
548 ],
549 );
550 } else {
551 match l_curly {
552 Some(b) => {
553 cov_mark::hit!(insert_empty_module);
554 let indent = IndentLevel::from_node(scope_syntax) + 1;
555 syntax_editor.insert_all(
556 Position::after(&b),
557 vec![
558 make.whitespace(&format!("\n{indent}")).into(),
559 use_item.syntax().clone().into(),
560 make.whitespace("\n").into(),
561 ],
562 );
563 }
564 None => {
565 cov_mark::hit!(insert_empty_file);
566 syntax_editor.insert_all(
567 Position::first_child_of(scope_syntax),
568 vec![use_item.syntax().clone().into(), make.whitespace("\n\n").into()],
569 );
570 }
571 }
572 }
573}
574
575fn is_inner_attribute(node: SyntaxNode) -> bool {
576 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
577}