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, 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/// 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
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
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: &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            // We use the user's setting to infer if this is module or item.
223            ImportGranularityGuess::ModuleOrItem => match mb {
224                Some(MergeBehavior::Module) | None => mb,
225                // There isn't really a way to decide between module or item here, so we just pick one.
226                // FIXME: Maybe it is possible to infer based on semantic analysis?
227                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    // merge into existing imports if possible
251    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    // either we weren't allowed to merge or there is no import that fits the merge conditions
263    // so look for the place we have to insert to
264    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            // We use the user's setting to infer if this is module or item.
289            ImportGranularityGuess::ModuleOrItem => match mb {
290                Some(MergeBehavior::Module) | None => mb,
291                // There isn't really a way to decide between module or item here, so we just pick one.
292                // FIXME: Maybe it is possible to infer based on semantic analysis?
293                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    // merge into existing imports if possible
316    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    // either we weren't allowed to merge or there is no import that fits the merge conditions
328    // so look for the place we have to insert to
329    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    // FIXME: improve this
334    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    // the order here defines the order of new group inserts
356    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            // these aren't valid use paths, so fall back to something random
386            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    // The idea is simple, just check each import as well as the import and its precedent together for
405    // whether they fulfill a granularity criteria.
406    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                    // This scope has a mix of one-style and other style imports.
431                    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                // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
440                break ImportGranularityGuess::Crate;
441            } else {
442                // Could still be crate-style so continue looking.
443                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                // This scope has either a mix of one-style and other style imports or
456                // multiple one-style imports with the same visibility and attributes.
457                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                    // Same prefix, only differing in the last segment and no use tree lists so this has to be of item style.
471                    break ImportGranularityGuess::Item;
472                } else {
473                    // Same prefix and no use tree list but differs in more than one segment at the end. This might be module style still.
474                    res = ImportGranularityGuess::ModuleOrItem;
475                }
476            } else {
477                // Same prefix with item tree lists, has to be module style as it
478                // can't be crate style since the trees wouldn't share a prefix then.
479                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        // Iterator that discards anything that's not in the required grouping
503        // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
504        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        // 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
510        let mut last = None;
511        // find the element that would come directly after our new import
512        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            // insert our import before that element
519            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            // there is no element after our new import, so append it to the end of the group
524            return ted::insert(ted::Position::after(node), use_item.syntax());
525        }
526
527        // the group we were looking for actually doesn't exist, so insert
528
529        let mut last = None;
530        // find the group that comes after where we want to insert
531        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        // there is no such group, so append after the last one
543        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        // There exists a group, so append to the end of it
551        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        // don't insert the imports before the item list/block expr's opening curly brace
561        ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
562        // don't insert the imports before the item list's opening curly brace
563        ImportScopeKind::Block(block) => block.l_curly_token(),
564    };
565    // there are no imports in this file at all
566    // so put the import after all inner module attributes and possible license header comments
567    if let Some(last_inner_element) = scope_syntax
568        .children_with_tokens()
569        // skip the curly brace
570        .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        // Iterator that discards anything that's not in the required grouping
626        // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
627        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        // 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
633        let mut last = None;
634        // find the element that would come directly after our new import
635        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            // insert our import before that element
642            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            // there is no element after our new import, so append it to the end of the group
647            return syntax_editor.insert(Position::after(node), use_item.syntax());
648        }
649
650        // the group we were looking for actually doesn't exist, so insert
651
652        let mut last = None;
653        // find the group that comes after where we want to insert
654        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        // there is no such group, so append after the last one
666        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        // There exists a group, so append to the end of it
674        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        // don't insert the imports before the item list/block expr's opening curly brace
684        ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
685        // don't insert the imports before the item list's opening curly brace
686        ImportScopeKind::Block(block) => block.l_curly_token(),
687    };
688    // there are no imports in this file at all
689    // so put the import after all inner module attributes and possible license header comments
690    if let Some(last_inner_element) = scope_syntax
691        .children_with_tokens()
692        // skip the curly brace
693        .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}