Skip to main content

ide_db/imports/
insert_use.rs

1//! Handle syntactic aspects of inserting a new `use` item.
2#[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/// How imports should be grouped into use statements.
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub enum ImportGranularity {
31    /// Merge imports from the same crate into a single use statement.
32    Crate,
33    /// Merge imports from the same module into a single use statement.
34    Module,
35    /// Flatten imports so that each has its own use statement.
36    Item,
37    /// Merge all imports into a single use statement as long as they have the same visibility
38    /// and attributes.
39    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    /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
75    /// Returns the original source node inside attributes.
76    pub fn find_insert_use_container(
77        position: &SyntaxNode,
78        sema: &Semantics<'_, RootDatabase>,
79    ) -> Option<Self> {
80        // The closest block expression ancestor
81        let mut block = None;
82        let mut required_cfgs = Vec::new();
83        // Walk up the ancestor tree searching for a suitable node to do insertions on
84        // with special handling on cfg-gated items, in which case we want to insert imports locally
85        // or FIXME: annotate inserted imports with the same cfg
86        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                // early return is important here, if we can't find the original module
91                // in the input there is no way for us to insert an import anywhere.
92                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
168/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
169pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
170    insert_use_with_alias_option(scope, path, cfg, None);
171}
172
173/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
174pub 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            // We use the user's setting to infer if this is module or item.
222            ImportGranularityGuess::ModuleOrItem => match mb {
223                Some(MergeBehavior::Module) | None => mb,
224                // There isn't really a way to decide between module or item here, so we just pick one.
225                // FIXME: Maybe it is possible to infer based on semantic analysis?
226                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    // merge into existing imports if possible
250    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    // either we weren't allowed to merge or there is no import that fits the merge conditions
262    // so look for the place we have to insert to
263    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            // We use the user's setting to infer if this is module or item.
288            ImportGranularityGuess::ModuleOrItem => match mb {
289                Some(MergeBehavior::Module) | None => mb,
290                // There isn't really a way to decide between module or item here, so we just pick one.
291                // FIXME: Maybe it is possible to infer based on semantic analysis?
292                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    // merge into existing imports if possible
313    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    // either we weren't allowed to merge or there is no import that fits the merge conditions
325    // so look for the place we have to insert to
326    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    // FIXME: improve this
331    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    // the order here defines the order of new group inserts
353    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            // these aren't valid use paths, so fall back to something random
383            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    // The idea is simple, just check each import as well as the import and its precedent together for
402    // whether they fulfill a granularity criteria.
403    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                    // This scope has a mix of one-style and other style imports.
428                    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                // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
437                break ImportGranularityGuess::Crate;
438            } else {
439                // Could still be crate-style so continue looking.
440                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                // This scope has either a mix of one-style and other style imports or
453                // multiple one-style imports with the same visibility and attributes.
454                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                    // Same prefix, only differing in the last segment and no use tree lists so this has to be of item style.
468                    break ImportGranularityGuess::Item;
469                } else {
470                    // Same prefix and no use tree list but differs in more than one segment at the end. This might be module style still.
471                    res = ImportGranularityGuess::ModuleOrItem;
472                }
473            } else {
474                // Same prefix with item tree lists, has to be module style as it
475                // can't be crate style since the trees wouldn't share a prefix then.
476                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        // Iterator that discards anything that's not in the required grouping
500        // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
501        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        // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
507        let mut last = None;
508        // find the element that would come directly after our new import
509        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            // insert our import before that element
516            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            // there is no element after our new import, so append it to the end of the group
521            return ted::insert(ted::Position::after(node), use_item.syntax());
522        }
523
524        // the group we were looking for actually doesn't exist, so insert
525
526        let mut last = None;
527        // find the group that comes after where we want to insert
528        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        // there is no such group, so append after the last one
540        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        // There exists a group, so append to the end of it
548        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        // don't insert the imports before the item list/block expr's opening curly brace
558        ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
559        // don't insert the imports before the item list's opening curly brace
560        ImportScopeKind::Block(block) => block.l_curly_token(),
561    };
562    // there are no imports in this file at all
563    // so put the import after all inner module attributes and possible license header comments
564    if let Some(last_inner_element) = scope_syntax
565        .children_with_tokens()
566        // skip the curly brace
567        .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        // Iterator that discards anything that's not in the required grouping
623        // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
624        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        // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
630        let mut last = None;
631        // find the element that would come directly after our new import
632        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            // insert our import before that element
639            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            // there is no element after our new import, so append it to the end of the group
644            return syntax_editor.insert(Position::after(node), use_item.syntax());
645        }
646
647        // the group we were looking for actually doesn't exist, so insert
648
649        let mut last = None;
650        // find the group that comes after where we want to insert
651        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        // there is no such group, so append after the last one
663        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        // There exists a group, so append to the end of it
671        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        // don't insert the imports before the item list/block expr's opening curly brace
681        ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
682        // don't insert the imports before the item list's opening curly brace
683        ImportScopeKind::Block(block) => block.l_curly_token(),
684    };
685    // there are no imports in this file at all
686    // so put the import after all inner module attributes and possible license header comments
687    if let Some(last_inner_element) = scope_syntax
688        .children_with_tokens()
689        // skip the curly brace
690        .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}