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, syntax_factory::SyntaxFactory,
13 },
14 syntax_editor::{Position, SyntaxEditor},
15 ted,
16};
17
18use crate::{
19 RootDatabase,
20 imports::merge_imports::{
21 MergeBehavior, NormalizationStyle, common_prefix, eq_attrs, eq_visibility,
22 try_merge_imports, use_tree_cmp,
23 },
24};
25
26pub use hir::PrefixKind;
27
28#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub enum ImportGranularity {
31 Crate,
33 Module,
35 Item,
37 One,
40}
41
42impl From<ImportGranularity> for NormalizationStyle {
43 fn from(granularity: ImportGranularity) -> Self {
44 match granularity {
45 ImportGranularity::One => NormalizationStyle::One,
46 _ => NormalizationStyle::Default,
47 }
48 }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub struct InsertUseConfig {
53 pub granularity: ImportGranularity,
54 pub enforce_granularity: bool,
55 pub prefix_kind: PrefixKind,
56 pub group: bool,
57 pub skip_glob_imports: bool,
58}
59
60#[derive(Debug, Clone)]
61pub struct ImportScope {
62 pub kind: ImportScopeKind,
63 pub required_cfgs: Vec<ast::Attr>,
64}
65
66#[derive(Debug, Clone)]
67pub enum ImportScopeKind {
68 File(ast::SourceFile),
69 Module(ast::ItemList),
70 Block(ast::StmtList),
71}
72
73impl ImportScope {
74 pub fn find_insert_use_container(
77 position: &SyntaxNode,
78 sema: &Semantics<'_, RootDatabase>,
79 ) -> Option<Self> {
80 let mut block = None;
82 let mut required_cfgs = Vec::new();
83 for syntax in sema.ancestors_with_macros(position.clone()) {
87 if let Some(file) = ast::SourceFile::cast(syntax.clone()) {
88 return Some(ImportScope { kind: ImportScopeKind::File(file), required_cfgs });
89 } else if let Some(module) = ast::Module::cast(syntax.clone()) {
90 return sema
93 .original_ast_node(module)?
94 .item_list()
95 .map(ImportScopeKind::Module)
96 .map(|kind| ImportScope { kind, required_cfgs });
97 } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax.clone()) {
98 if block.is_none()
99 && let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone())
100 && let Some(b) = sema.original_ast_node(b)
101 {
102 block = b.stmt_list();
103 }
104 if has_attrs
105 .attrs()
106 .any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
107 {
108 if let Some(b) = block.clone() {
109 let current_cfgs = has_attrs.attrs().filter(|attr| {
110 attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
111 });
112
113 let total_cfgs: Vec<_> =
114 required_cfgs.iter().cloned().chain(current_cfgs).collect();
115
116 let parent = syntax.parent();
117 let mut can_merge = false;
118 if let Some(parent) = parent {
119 can_merge = parent.children().filter_map(ast::Use::cast).any(|u| {
120 let u_attrs = u.attrs().filter(|attr| {
121 attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
122 });
123 crate::imports::merge_imports::eq_attrs(
124 u_attrs,
125 total_cfgs.iter().cloned(),
126 )
127 });
128 }
129
130 if !can_merge {
131 return Some(ImportScope {
132 kind: ImportScopeKind::Block(b),
133 required_cfgs,
134 });
135 }
136 }
137 required_cfgs.extend(has_attrs.attrs().filter(|attr| {
138 attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
139 }));
140 }
141 }
142 }
143 None
144 }
145
146 pub fn as_syntax_node(&self) -> &SyntaxNode {
147 match &self.kind {
148 ImportScopeKind::File(file) => file.syntax(),
149 ImportScopeKind::Module(item_list) => item_list.syntax(),
150 ImportScopeKind::Block(block) => block.syntax(),
151 }
152 }
153
154 pub fn clone_for_update(&self) -> Self {
155 Self {
156 kind: match &self.kind {
157 ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()),
158 ImportScopeKind::Module(item_list) => {
159 ImportScopeKind::Module(item_list.clone_for_update())
160 }
161 ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()),
162 },
163 required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(),
164 }
165 }
166}
167
168pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
170 insert_use_with_alias_option(scope, path, cfg, None);
171}
172
173pub fn insert_use_with_editor(
175 scope: &ImportScope,
176 path: ast::Path,
177 cfg: &InsertUseConfig,
178 syntax_editor: &mut SyntaxEditor,
179 syntax_factory: &SyntaxFactory,
180) {
181 insert_use_with_alias_option_with_editor(scope, path, cfg, None, syntax_editor, syntax_factory);
182}
183
184pub fn insert_use_as_alias(
185 scope: &ImportScope,
186 path: ast::Path,
187 cfg: &InsertUseConfig,
188 edition: span::Edition,
189) {
190 let text: &str = "use foo as _";
191 let parse = syntax::SourceFile::parse(text, edition);
192 let node = parse
193 .tree()
194 .syntax()
195 .descendants()
196 .find_map(ast::UseTree::cast)
197 .expect("Failed to make ast node `Rename`");
198 let alias = node.rename();
199
200 insert_use_with_alias_option(scope, path, cfg, alias);
201}
202
203fn insert_use_with_alias_option(
204 scope: &ImportScope,
205 path: ast::Path,
206 cfg: &InsertUseConfig,
207 alias: Option<ast::Rename>,
208) {
209 let _p = tracing::info_span!("insert_use_with_alias_option").entered();
210 let mut mb = match cfg.granularity {
211 ImportGranularity::Crate => Some(MergeBehavior::Crate),
212 ImportGranularity::Module => Some(MergeBehavior::Module),
213 ImportGranularity::One => Some(MergeBehavior::One),
214 ImportGranularity::Item => None,
215 };
216 if !cfg.enforce_granularity {
217 let file_granularity = guess_granularity_from_scope(scope);
218 mb = match file_granularity {
219 ImportGranularityGuess::Unknown => mb,
220 ImportGranularityGuess::Item => None,
221 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
222 ImportGranularityGuess::ModuleOrItem => match mb {
224 Some(MergeBehavior::Module) | None => mb,
225 Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
228 },
229 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
230 ImportGranularityGuess::CrateOrModule => match mb {
231 Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
232 Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
233 },
234 ImportGranularityGuess::One => Some(MergeBehavior::One),
235 };
236 }
237
238 let mut use_tree = make::use_tree(path, None, alias, false);
239 if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
240 use_tree = use_tree.clone_for_update();
241 use_tree.wrap_in_tree_list();
242 }
243 let use_item = make::use_(None, None, use_tree).clone_for_update();
244 for attr in
245 scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
246 {
247 ted::insert(ted::Position::first_child_of(use_item.syntax()), attr);
248 }
249
250 if let Some(mb) = mb {
252 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
253 for existing_use in
254 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
255 {
256 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
257 ted::replace(existing_use.syntax(), merged.syntax());
258 return;
259 }
260 }
261 }
262 insert_use_(scope, use_item, cfg.group);
265}
266
267fn insert_use_with_alias_option_with_editor(
268 scope: &ImportScope,
269 path: ast::Path,
270 cfg: &InsertUseConfig,
271 alias: Option<ast::Rename>,
272 syntax_editor: &mut SyntaxEditor,
273 syntax_factory: &SyntaxFactory,
274) {
275 let _p = tracing::info_span!("insert_use_with_alias_option").entered();
276 let mut mb = match cfg.granularity {
277 ImportGranularity::Crate => Some(MergeBehavior::Crate),
278 ImportGranularity::Module => Some(MergeBehavior::Module),
279 ImportGranularity::One => Some(MergeBehavior::One),
280 ImportGranularity::Item => None,
281 };
282 if !cfg.enforce_granularity {
283 let file_granularity = guess_granularity_from_scope(scope);
284 mb = match file_granularity {
285 ImportGranularityGuess::Unknown => mb,
286 ImportGranularityGuess::Item => None,
287 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
288 ImportGranularityGuess::ModuleOrItem => match mb {
290 Some(MergeBehavior::Module) | None => mb,
291 Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
294 },
295 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
296 ImportGranularityGuess::CrateOrModule => match mb {
297 Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
298 Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
299 },
300 ImportGranularityGuess::One => Some(MergeBehavior::One),
301 };
302 }
303
304 let use_tree = syntax_factory.use_tree(path, None, alias, false);
305 if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
306 use_tree.wrap_in_tree_list();
307 }
308 let use_item = make::use_(None, None, use_tree).clone_for_update();
309 for attr in
310 scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
311 {
312 syntax_editor.insert(Position::first_child_of(use_item.syntax()), attr);
313 }
314
315 if let Some(mb) = mb {
317 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
318 for existing_use in
319 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
320 {
321 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
322 syntax_editor.replace(existing_use.syntax(), merged.syntax());
323 return;
324 }
325 }
326 }
327 insert_use_with_editor_(scope, use_item, cfg.group, syntax_editor, syntax_factory);
330}
331
332pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
333 if path.parent_path().is_some() {
335 return None;
336 }
337 let use_tree = path.syntax().parent().and_then(ast::UseTree::cast)?;
338 if use_tree.use_tree_list().is_some() || use_tree.star_token().is_some() {
339 return None;
340 }
341 if let Some(use_) = use_tree.syntax().parent().and_then(ast::Use::cast) {
342 return Some(Box::new(use_));
343 }
344 Some(Box::new(use_tree))
345}
346
347pub fn remove_path_if_in_use_stmt(path: &ast::Path) {
348 if let Some(node) = ast_to_remove_for_path_in_use_stmt(path) {
349 node.remove();
350 }
351}
352
353#[derive(Eq, PartialEq, PartialOrd, Ord)]
354enum ImportGroup {
355 Std,
357 ExternCrate,
358 ThisCrate,
359 ThisModule,
360 SuperModule,
361 One,
362}
363
364impl ImportGroup {
365 fn new(use_tree: &ast::UseTree) -> ImportGroup {
366 if use_tree.path().is_none() && use_tree.use_tree_list().is_some() {
367 return ImportGroup::One;
368 }
369
370 let Some(first_segment) = use_tree.path().as_ref().and_then(ast::Path::first_segment)
371 else {
372 return ImportGroup::ExternCrate;
373 };
374
375 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
376 match kind {
377 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
378 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
379 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
380 PathSegmentKind::Name(name) => match name.text().as_str() {
381 "std" => ImportGroup::Std,
382 "core" => ImportGroup::Std,
383 _ => ImportGroup::ExternCrate,
384 },
385 PathSegmentKind::SelfTypeKw => ImportGroup::ExternCrate,
387 PathSegmentKind::Type { .. } => ImportGroup::ExternCrate,
388 }
389 }
390}
391
392#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
393enum ImportGranularityGuess {
394 Unknown,
395 Item,
396 Module,
397 ModuleOrItem,
398 Crate,
399 CrateOrModule,
400 One,
401}
402
403fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
404 let use_stmt = |item| match item {
407 ast::Item::Use(use_) => {
408 let use_tree = use_.use_tree()?;
409 Some((use_tree, use_.visibility(), use_.attrs()))
410 }
411 _ => None,
412 };
413 let mut use_stmts = match &scope.kind {
414 ImportScopeKind::File(f) => f.items(),
415 ImportScopeKind::Module(m) => m.items(),
416 ImportScopeKind::Block(b) => b.items(),
417 }
418 .filter_map(use_stmt);
419 let mut res = ImportGranularityGuess::Unknown;
420 let Some((mut prev, mut prev_vis, mut prev_attrs)) = use_stmts.next() else { return res };
421
422 let is_tree_one_style =
423 |use_tree: &ast::UseTree| use_tree.path().is_none() && use_tree.use_tree_list().is_some();
424 let mut seen_one_style_groups = Vec::new();
425
426 loop {
427 if is_tree_one_style(&prev) {
428 if res != ImportGranularityGuess::One {
429 if res != ImportGranularityGuess::Unknown {
430 break ImportGranularityGuess::Unknown;
432 }
433
434 res = ImportGranularityGuess::One;
435 seen_one_style_groups.push((prev_vis.clone(), prev_attrs.clone()));
436 }
437 } else if let Some(use_tree_list) = prev.use_tree_list() {
438 if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
439 break ImportGranularityGuess::Crate;
441 } else {
442 res = ImportGranularityGuess::CrateOrModule;
444 }
445 }
446
447 let Some((curr, curr_vis, curr_attrs)) = use_stmts.next() else { break res };
448 if is_tree_one_style(&curr) {
449 if res != ImportGranularityGuess::One
450 || seen_one_style_groups.iter().any(|(prev_vis, prev_attrs)| {
451 eq_visibility(prev_vis.clone(), curr_vis.clone())
452 && eq_attrs(prev_attrs.clone(), curr_attrs.clone())
453 })
454 {
455 break ImportGranularityGuess::Unknown;
458 }
459 seen_one_style_groups.push((curr_vis.clone(), curr_attrs.clone()));
460 } else if eq_visibility(prev_vis, curr_vis.clone())
461 && eq_attrs(prev_attrs, curr_attrs.clone())
462 && let Some((prev_path, curr_path)) = prev.path().zip(curr.path())
463 && let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path)
464 {
465 if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
466 let prefix_c = prev_prefix.qualifiers().count();
467 let curr_c = curr_path.qualifiers().count() - prefix_c;
468 let prev_c = prev_path.qualifiers().count() - prefix_c;
469 if curr_c == 1 && prev_c == 1 {
470 break ImportGranularityGuess::Item;
472 } else {
473 res = ImportGranularityGuess::ModuleOrItem;
475 }
476 } else {
477 break ImportGranularityGuess::Module;
480 }
481 }
482 prev = curr;
483 prev_vis = curr_vis;
484 prev_attrs = curr_attrs;
485 }
486}
487
488fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
489 let scope_syntax = scope.as_syntax_node();
490 let insert_use_tree =
491 use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
492 let group = ImportGroup::new(&insert_use_tree);
493 let path_node_iter = scope_syntax
494 .children()
495 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
496 .flat_map(|(use_, node)| {
497 let tree = use_.use_tree()?;
498 Some((tree, node))
499 });
500
501 if group_imports {
502 let group_iter = path_node_iter
505 .clone()
506 .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
507 .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
508
509 let mut last = None;
511 let post_insert: Option<(_, SyntaxNode)> = group_iter
513 .inspect(|(.., node)| last = Some(node.clone()))
514 .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
515
516 if let Some((.., node)) = post_insert {
517 cov_mark::hit!(insert_group);
518 return ted::insert(ted::Position::before(node), use_item.syntax());
520 }
521 if let Some(node) = last {
522 cov_mark::hit!(insert_group_last);
523 return ted::insert(ted::Position::after(node), use_item.syntax());
525 }
526
527 let mut last = None;
530 let post_group = path_node_iter
532 .inspect(|(.., node)| last = Some(node.clone()))
533 .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
534 if let Some((.., node)) = post_group {
535 cov_mark::hit!(insert_group_new_group);
536 ted::insert(ted::Position::before(&node), use_item.syntax());
537 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
538 ted::insert(ted::Position::after(node), make::tokens::single_newline());
539 }
540 return;
541 }
542 if let Some(node) = last {
544 cov_mark::hit!(insert_group_no_group);
545 ted::insert(ted::Position::after(&node), use_item.syntax());
546 ted::insert(ted::Position::after(node), make::tokens::single_newline());
547 return;
548 }
549 } else {
550 if let Some((_, node)) = path_node_iter.last() {
552 cov_mark::hit!(insert_no_grouping_last);
553 ted::insert(ted::Position::after(node), use_item.syntax());
554 return;
555 }
556 }
557
558 let l_curly = match &scope.kind {
559 ImportScopeKind::File(_) => None,
560 ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
562 ImportScopeKind::Block(block) => block.l_curly_token(),
564 };
565 if let Some(last_inner_element) = scope_syntax
568 .children_with_tokens()
569 .skip(l_curly.is_some() as usize)
571 .take_while(|child| match child {
572 NodeOrToken::Node(node) => {
573 is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
574 }
575 NodeOrToken::Token(token) => {
576 [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
577 .contains(&token.kind())
578 }
579 })
580 .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
581 .last()
582 {
583 cov_mark::hit!(insert_empty_inner_attr);
584 ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
585 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
586 } else {
587 match l_curly {
588 Some(b) => {
589 cov_mark::hit!(insert_empty_module);
590 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
591 ted::insert(ted::Position::after(&b), use_item.syntax());
592 }
593 None => {
594 cov_mark::hit!(insert_empty_file);
595 ted::insert(
596 ted::Position::first_child_of(scope_syntax),
597 make::tokens::blank_line(),
598 );
599 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
600 }
601 }
602 }
603}
604
605fn insert_use_with_editor_(
606 scope: &ImportScope,
607 use_item: ast::Use,
608 group_imports: bool,
609 syntax_editor: &mut SyntaxEditor,
610 syntax_factory: &SyntaxFactory,
611) {
612 let scope_syntax = scope.as_syntax_node();
613 let insert_use_tree =
614 use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
615 let group = ImportGroup::new(&insert_use_tree);
616 let path_node_iter = scope_syntax
617 .children()
618 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
619 .flat_map(|(use_, node)| {
620 let tree = use_.use_tree()?;
621 Some((tree, node))
622 });
623
624 if group_imports {
625 let group_iter = path_node_iter
628 .clone()
629 .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
630 .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
631
632 let mut last = None;
634 let post_insert: Option<(_, SyntaxNode)> = group_iter
636 .inspect(|(.., node)| last = Some(node.clone()))
637 .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
638
639 if let Some((.., node)) = post_insert {
640 cov_mark::hit!(insert_group);
641 return syntax_editor.insert(Position::before(node), use_item.syntax());
643 }
644 if let Some(node) = last {
645 cov_mark::hit!(insert_group_last);
646 return syntax_editor.insert(Position::after(node), use_item.syntax());
648 }
649
650 let mut last = None;
653 let post_group = path_node_iter
655 .inspect(|(.., node)| last = Some(node.clone()))
656 .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
657 if let Some((.., node)) = post_group {
658 cov_mark::hit!(insert_group_new_group);
659 syntax_editor.insert(Position::before(&node), use_item.syntax());
660 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
661 syntax_editor.insert(Position::after(node), syntax_factory.whitespace("\n"));
662 }
663 return;
664 }
665 if let Some(node) = last {
667 cov_mark::hit!(insert_group_no_group);
668 syntax_editor.insert(Position::after(&node), use_item.syntax());
669 syntax_editor.insert(Position::after(node), syntax_factory.whitespace("\n"));
670 return;
671 }
672 } else {
673 if let Some((_, node)) = path_node_iter.last() {
675 cov_mark::hit!(insert_no_grouping_last);
676 syntax_editor.insert(Position::after(node), use_item.syntax());
677 return;
678 }
679 }
680
681 let l_curly = match &scope.kind {
682 ImportScopeKind::File(_) => None,
683 ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
685 ImportScopeKind::Block(block) => block.l_curly_token(),
687 };
688 if let Some(last_inner_element) = scope_syntax
691 .children_with_tokens()
692 .skip(l_curly.is_some() as usize)
694 .take_while(|child| match child {
695 NodeOrToken::Node(node) => {
696 is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
697 }
698 NodeOrToken::Token(token) => {
699 [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
700 .contains(&token.kind())
701 }
702 })
703 .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
704 .last()
705 {
706 cov_mark::hit!(insert_empty_inner_attr);
707 syntax_editor.insert(Position::after(&last_inner_element), use_item.syntax());
708 syntax_editor.insert(Position::after(last_inner_element), syntax_factory.whitespace("\n"));
709 } else {
710 match l_curly {
711 Some(b) => {
712 cov_mark::hit!(insert_empty_module);
713 syntax_editor.insert(Position::after(&b), syntax_factory.whitespace("\n"));
714 syntax_editor.insert(Position::after(&b), use_item.syntax());
715 }
716 None => {
717 cov_mark::hit!(insert_empty_file);
718 syntax_editor.insert(
719 Position::first_child_of(scope_syntax),
720 syntax_factory.whitespace("\n\n"),
721 );
722 syntax_editor.insert(Position::first_child_of(scope_syntax), use_item.syntax());
723 }
724 }
725 }
726}
727
728fn is_inner_attribute(node: SyntaxNode) -> bool {
729 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
730}