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 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.attrs().any(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_))))
105 {
106 if let Some(b) = block.clone() {
107 let current_cfgs = has_attrs
108 .attrs()
109 .filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_))));
110
111 let total_cfgs: Vec<_> =
112 required_cfgs.iter().cloned().chain(current_cfgs).collect();
113
114 let parent = syntax.parent();
115 let mut can_merge = false;
116 if let Some(parent) = parent {
117 can_merge = parent.children().filter_map(ast::Use::cast).any(|u| {
118 let u_attrs = u.attrs().filter(|attr| {
119 matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))
120 });
121 crate::imports::merge_imports::eq_attrs(
122 u_attrs,
123 total_cfgs.iter().cloned(),
124 )
125 });
126 }
127
128 if !can_merge {
129 return Some(ImportScope {
130 kind: ImportScopeKind::Block(b),
131 required_cfgs,
132 });
133 }
134 }
135 required_cfgs.extend(
136 has_attrs
137 .attrs()
138 .filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))),
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: &SyntaxEditor,
179) {
180 insert_use_with_alias_option_with_editor(scope, path, cfg, None, syntax_editor);
181}
182
183pub fn insert_use_as_alias(
184 scope: &ImportScope,
185 path: ast::Path,
186 cfg: &InsertUseConfig,
187 edition: span::Edition,
188) {
189 let text: &str = "use foo as _";
190 let parse = syntax::SourceFile::parse(text, edition);
191 let node = parse
192 .tree()
193 .syntax()
194 .descendants()
195 .find_map(ast::UseTree::cast)
196 .expect("Failed to make ast node `Rename`");
197 let alias = node.rename();
198
199 insert_use_with_alias_option(scope, path, cfg, alias);
200}
201
202fn insert_use_with_alias_option(
203 scope: &ImportScope,
204 path: ast::Path,
205 cfg: &InsertUseConfig,
206 alias: Option<ast::Rename>,
207) {
208 let _p = tracing::info_span!("insert_use_with_alias_option").entered();
209 let mut mb = match cfg.granularity {
210 ImportGranularity::Crate => Some(MergeBehavior::Crate),
211 ImportGranularity::Module => Some(MergeBehavior::Module),
212 ImportGranularity::One => Some(MergeBehavior::One),
213 ImportGranularity::Item => None,
214 };
215 if !cfg.enforce_granularity {
216 let file_granularity = guess_granularity_from_scope(scope);
217 mb = match file_granularity {
218 ImportGranularityGuess::Unknown => mb,
219 ImportGranularityGuess::Item => None,
220 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
221 ImportGranularityGuess::ModuleOrItem => match mb {
223 Some(MergeBehavior::Module) | None => mb,
224 Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
227 },
228 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
229 ImportGranularityGuess::CrateOrModule => match mb {
230 Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
231 Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
232 },
233 ImportGranularityGuess::One => Some(MergeBehavior::One),
234 };
235 }
236
237 let mut use_tree = make::use_tree(path, None, alias, false);
238 if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
239 use_tree = use_tree.clone_for_update();
240 use_tree.wrap_in_tree_list();
241 }
242 let use_item = make::use_(None, None, use_tree).clone_for_update();
243 for attr in
244 scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
245 {
246 ted::insert(ted::Position::first_child_of(use_item.syntax()), attr);
247 }
248
249 if let Some(mb) = mb {
251 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
252 for existing_use in
253 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
254 {
255 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
256 ted::replace(existing_use.syntax(), merged.syntax());
257 return;
258 }
259 }
260 }
261 insert_use_(scope, use_item, cfg.group);
264}
265
266fn insert_use_with_alias_option_with_editor(
267 scope: &ImportScope,
268 path: ast::Path,
269 cfg: &InsertUseConfig,
270 alias: Option<ast::Rename>,
271 syntax_editor: &SyntaxEditor,
272) {
273 let make = syntax_editor.make();
274 let _p = tracing::info_span!("insert_use_with_alias_option").entered();
275 let mut mb = match cfg.granularity {
276 ImportGranularity::Crate => Some(MergeBehavior::Crate),
277 ImportGranularity::Module => Some(MergeBehavior::Module),
278 ImportGranularity::One => Some(MergeBehavior::One),
279 ImportGranularity::Item => None,
280 };
281 if !cfg.enforce_granularity {
282 let file_granularity = guess_granularity_from_scope(scope);
283 mb = match file_granularity {
284 ImportGranularityGuess::Unknown => mb,
285 ImportGranularityGuess::Item => None,
286 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
287 ImportGranularityGuess::ModuleOrItem => match mb {
289 Some(MergeBehavior::Module) | None => mb,
290 Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
293 },
294 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
295 ImportGranularityGuess::CrateOrModule => match mb {
296 Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
297 Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
298 },
299 ImportGranularityGuess::One => Some(MergeBehavior::One),
300 };
301 }
302
303 let use_tree = make.use_tree(path, None, alias, false);
304 if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
305 use_tree.wrap_in_tree_list();
306 }
307 let use_item = make::use_(None, None, use_tree);
308 for attr in scope.required_cfgs.iter().map(|attr| attr.syntax().clone()) {
309 syntax_editor.insert(Position::first_child_of(use_item.syntax()), attr);
310 }
311
312 if let Some(mb) = mb {
314 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
315 for existing_use in
316 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
317 {
318 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
319 syntax_editor.replace(existing_use.syntax(), merged.syntax());
320 return;
321 }
322 }
323 }
324 insert_use_with_editor_(scope, use_item, cfg.group, syntax_editor);
327}
328
329pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
330 if path.parent_path().is_some() {
332 return None;
333 }
334 let use_tree = path.syntax().parent().and_then(ast::UseTree::cast)?;
335 if use_tree.use_tree_list().is_some() || use_tree.star_token().is_some() {
336 return None;
337 }
338 if let Some(use_) = use_tree.syntax().parent().and_then(ast::Use::cast) {
339 return Some(Box::new(use_));
340 }
341 Some(Box::new(use_tree))
342}
343
344pub fn remove_path_if_in_use_stmt(path: &ast::Path) {
345 if let Some(node) = ast_to_remove_for_path_in_use_stmt(path) {
346 node.remove();
347 }
348}
349
350#[derive(Eq, PartialEq, PartialOrd, Ord)]
351enum ImportGroup {
352 Std,
354 ExternCrate,
355 ThisCrate,
356 ThisModule,
357 SuperModule,
358 One,
359}
360
361impl ImportGroup {
362 fn new(use_tree: &ast::UseTree) -> ImportGroup {
363 if use_tree.path().is_none() && use_tree.use_tree_list().is_some() {
364 return ImportGroup::One;
365 }
366
367 let Some(first_segment) = use_tree.path().as_ref().and_then(ast::Path::first_segment)
368 else {
369 return ImportGroup::ExternCrate;
370 };
371
372 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
373 match kind {
374 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
375 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
376 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
377 PathSegmentKind::Name(name) => match name.text().as_str() {
378 "std" => ImportGroup::Std,
379 "core" => ImportGroup::Std,
380 _ => ImportGroup::ExternCrate,
381 },
382 PathSegmentKind::SelfTypeKw => ImportGroup::ExternCrate,
384 PathSegmentKind::Type { .. } => ImportGroup::ExternCrate,
385 }
386 }
387}
388
389#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
390enum ImportGranularityGuess {
391 Unknown,
392 Item,
393 Module,
394 ModuleOrItem,
395 Crate,
396 CrateOrModule,
397 One,
398}
399
400fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
401 let use_stmt = |item| match item {
404 ast::Item::Use(use_) => {
405 let use_tree = use_.use_tree()?;
406 Some((use_tree, use_.visibility(), use_.attrs()))
407 }
408 _ => None,
409 };
410 let mut use_stmts = match &scope.kind {
411 ImportScopeKind::File(f) => f.items(),
412 ImportScopeKind::Module(m) => m.items(),
413 ImportScopeKind::Block(b) => b.items(),
414 }
415 .filter_map(use_stmt);
416 let mut res = ImportGranularityGuess::Unknown;
417 let Some((mut prev, mut prev_vis, mut prev_attrs)) = use_stmts.next() else { return res };
418
419 let is_tree_one_style =
420 |use_tree: &ast::UseTree| use_tree.path().is_none() && use_tree.use_tree_list().is_some();
421 let mut seen_one_style_groups = Vec::new();
422
423 loop {
424 if is_tree_one_style(&prev) {
425 if res != ImportGranularityGuess::One {
426 if res != ImportGranularityGuess::Unknown {
427 break ImportGranularityGuess::Unknown;
429 }
430
431 res = ImportGranularityGuess::One;
432 seen_one_style_groups.push((prev_vis.clone(), prev_attrs.clone()));
433 }
434 } else if let Some(use_tree_list) = prev.use_tree_list() {
435 if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
436 break ImportGranularityGuess::Crate;
438 } else {
439 res = ImportGranularityGuess::CrateOrModule;
441 }
442 }
443
444 let Some((curr, curr_vis, curr_attrs)) = use_stmts.next() else { break res };
445 if is_tree_one_style(&curr) {
446 if res != ImportGranularityGuess::One
447 || seen_one_style_groups.iter().any(|(prev_vis, prev_attrs)| {
448 eq_visibility(prev_vis.clone(), curr_vis.clone())
449 && eq_attrs(prev_attrs.clone(), curr_attrs.clone())
450 })
451 {
452 break ImportGranularityGuess::Unknown;
455 }
456 seen_one_style_groups.push((curr_vis.clone(), curr_attrs.clone()));
457 } else if eq_visibility(prev_vis, curr_vis.clone())
458 && eq_attrs(prev_attrs, curr_attrs.clone())
459 && let Some((prev_path, curr_path)) = prev.path().zip(curr.path())
460 && let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path)
461 {
462 if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
463 let prefix_c = prev_prefix.qualifiers().count();
464 let curr_c = curr_path.qualifiers().count() - prefix_c;
465 let prev_c = prev_path.qualifiers().count() - prefix_c;
466 if curr_c == 1 && prev_c == 1 {
467 break ImportGranularityGuess::Item;
469 } else {
470 res = ImportGranularityGuess::ModuleOrItem;
472 }
473 } else {
474 break ImportGranularityGuess::Module;
477 }
478 }
479 prev = curr;
480 prev_vis = curr_vis;
481 prev_attrs = curr_attrs;
482 }
483}
484
485fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
486 let scope_syntax = scope.as_syntax_node();
487 let insert_use_tree =
488 use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
489 let group = ImportGroup::new(&insert_use_tree);
490 let path_node_iter = scope_syntax
491 .children()
492 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
493 .flat_map(|(use_, node)| {
494 let tree = use_.use_tree()?;
495 Some((tree, node))
496 });
497
498 if group_imports {
499 let group_iter = path_node_iter
502 .clone()
503 .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
504 .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
505
506 let mut last = None;
508 let post_insert: Option<(_, SyntaxNode)> = group_iter
510 .inspect(|(.., node)| last = Some(node.clone()))
511 .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
512
513 if let Some((.., node)) = post_insert {
514 cov_mark::hit!(insert_group);
515 return ted::insert(ted::Position::before(node), use_item.syntax());
517 }
518 if let Some(node) = last {
519 cov_mark::hit!(insert_group_last);
520 return ted::insert(ted::Position::after(node), use_item.syntax());
522 }
523
524 let mut last = None;
527 let post_group = path_node_iter
529 .inspect(|(.., node)| last = Some(node.clone()))
530 .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
531 if let Some((.., node)) = post_group {
532 cov_mark::hit!(insert_group_new_group);
533 ted::insert(ted::Position::before(&node), use_item.syntax());
534 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
535 ted::insert(ted::Position::after(node), make::tokens::single_newline());
536 }
537 return;
538 }
539 if let Some(node) = last {
541 cov_mark::hit!(insert_group_no_group);
542 ted::insert(ted::Position::after(&node), use_item.syntax());
543 ted::insert(ted::Position::after(node), make::tokens::single_newline());
544 return;
545 }
546 } else {
547 if let Some((_, node)) = path_node_iter.last() {
549 cov_mark::hit!(insert_no_grouping_last);
550 ted::insert(ted::Position::after(node), use_item.syntax());
551 return;
552 }
553 }
554
555 let l_curly = match &scope.kind {
556 ImportScopeKind::File(_) => None,
557 ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
559 ImportScopeKind::Block(block) => block.l_curly_token(),
561 };
562 if let Some(last_inner_element) = scope_syntax
565 .children_with_tokens()
566 .skip(l_curly.is_some() as usize)
568 .take_while(|child| match child {
569 NodeOrToken::Node(node) => {
570 is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
571 }
572 NodeOrToken::Token(token) => {
573 [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
574 .contains(&token.kind())
575 }
576 })
577 .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
578 .last()
579 {
580 cov_mark::hit!(insert_empty_inner_attr);
581 ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
582 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
583 } else {
584 match l_curly {
585 Some(b) => {
586 cov_mark::hit!(insert_empty_module);
587 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
588 ted::insert(ted::Position::after(&b), use_item.syntax());
589 }
590 None => {
591 cov_mark::hit!(insert_empty_file);
592 ted::insert(
593 ted::Position::first_child_of(scope_syntax),
594 make::tokens::blank_line(),
595 );
596 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
597 }
598 }
599 }
600}
601
602fn insert_use_with_editor_(
603 scope: &ImportScope,
604 use_item: ast::Use,
605 group_imports: bool,
606 syntax_editor: &SyntaxEditor,
607) {
608 let make = syntax_editor.make();
609 let scope_syntax = scope.as_syntax_node();
610 let insert_use_tree =
611 use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
612 let group = ImportGroup::new(&insert_use_tree);
613 let path_node_iter = scope_syntax
614 .children()
615 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
616 .flat_map(|(use_, node)| {
617 let tree = use_.use_tree()?;
618 Some((tree, node))
619 });
620
621 if group_imports {
622 let group_iter = path_node_iter
625 .clone()
626 .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
627 .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
628
629 let mut last = None;
631 let post_insert: Option<(_, SyntaxNode)> = group_iter
633 .inspect(|(.., node)| last = Some(node.clone()))
634 .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
635
636 if let Some((.., node)) = post_insert {
637 cov_mark::hit!(insert_group);
638 return syntax_editor.insert(Position::before(node), use_item.syntax());
640 }
641 if let Some(node) = last {
642 cov_mark::hit!(insert_group_last);
643 return syntax_editor.insert(Position::after(node), use_item.syntax());
645 }
646
647 let mut last = None;
650 let post_group = path_node_iter
652 .inspect(|(.., node)| last = Some(node.clone()))
653 .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
654 if let Some((.., node)) = post_group {
655 cov_mark::hit!(insert_group_new_group);
656 syntax_editor.insert(Position::before(&node), use_item.syntax());
657 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
658 syntax_editor.insert(Position::after(node), make.whitespace("\n"));
659 }
660 return;
661 }
662 if let Some(node) = last {
664 cov_mark::hit!(insert_group_no_group);
665 syntax_editor.insert(Position::after(&node), use_item.syntax());
666 syntax_editor.insert(Position::after(node), make.whitespace("\n"));
667 return;
668 }
669 } else {
670 if let Some((_, node)) = path_node_iter.last() {
672 cov_mark::hit!(insert_no_grouping_last);
673 syntax_editor.insert(Position::after(node), use_item.syntax());
674 return;
675 }
676 }
677
678 let l_curly = match &scope.kind {
679 ImportScopeKind::File(_) => None,
680 ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
682 ImportScopeKind::Block(block) => block.l_curly_token(),
684 };
685 if let Some(last_inner_element) = scope_syntax
688 .children_with_tokens()
689 .skip(l_curly.is_some() as usize)
691 .take_while(|child| match child {
692 NodeOrToken::Node(node) => {
693 is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
694 }
695 NodeOrToken::Token(token) => {
696 [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
697 .contains(&token.kind())
698 }
699 })
700 .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
701 .last()
702 {
703 cov_mark::hit!(insert_empty_inner_attr);
704 syntax_editor.insert(Position::after(&last_inner_element), use_item.syntax());
705 syntax_editor.insert(Position::after(last_inner_element), make.whitespace("\n"));
706 } else {
707 match l_curly {
708 Some(b) => {
709 cov_mark::hit!(insert_empty_module);
710 syntax_editor.insert(Position::after(&b), make.whitespace("\n"));
711 syntax_editor.insert_with_whitespace(Position::after(&b), use_item.syntax());
712 }
713 None => {
714 cov_mark::hit!(insert_empty_file);
715 syntax_editor
716 .insert(Position::first_child_of(scope_syntax), make.whitespace("\n\n"));
717 syntax_editor.insert(Position::first_child_of(scope_syntax), use_item.syntax());
718 }
719 }
720 }
721}
722
723fn is_inner_attribute(node: SyntaxNode) -> bool {
724 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
725}