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) {
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 {
109                        return Some(ImportScope {
110                            kind: ImportScopeKind::Block(b),
111                            required_cfgs,
112                        });
113                    }
114                    required_cfgs.extend(has_attrs.attrs().filter(|attr| {
115                        attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
116                    }));
117                }
118            }
119        }
120        None
121    }
122
123    pub fn as_syntax_node(&self) -> &SyntaxNode {
124        match &self.kind {
125            ImportScopeKind::File(file) => file.syntax(),
126            ImportScopeKind::Module(item_list) => item_list.syntax(),
127            ImportScopeKind::Block(block) => block.syntax(),
128        }
129    }
130
131    pub fn clone_for_update(&self) -> Self {
132        Self {
133            kind: match &self.kind {
134                ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()),
135                ImportScopeKind::Module(item_list) => {
136                    ImportScopeKind::Module(item_list.clone_for_update())
137                }
138                ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()),
139            },
140            required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(),
141        }
142    }
143}
144
145/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
146pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
147    insert_use_with_alias_option(scope, path, cfg, None);
148}
149
150/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
151pub fn insert_use_with_editor(
152    scope: &ImportScope,
153    path: ast::Path,
154    cfg: &InsertUseConfig,
155    syntax_editor: &mut SyntaxEditor,
156    syntax_factory: &SyntaxFactory,
157) {
158    insert_use_with_alias_option_with_editor(scope, path, cfg, None, syntax_editor, syntax_factory);
159}
160
161pub fn insert_use_as_alias(
162    scope: &ImportScope,
163    path: ast::Path,
164    cfg: &InsertUseConfig,
165    edition: span::Edition,
166) {
167    let text: &str = "use foo as _";
168    let parse = syntax::SourceFile::parse(text, edition);
169    let node = parse
170        .tree()
171        .syntax()
172        .descendants()
173        .find_map(ast::UseTree::cast)
174        .expect("Failed to make ast node `Rename`");
175    let alias = node.rename();
176
177    insert_use_with_alias_option(scope, path, cfg, alias);
178}
179
180fn insert_use_with_alias_option(
181    scope: &ImportScope,
182    path: ast::Path,
183    cfg: &InsertUseConfig,
184    alias: Option<ast::Rename>,
185) {
186    let _p = tracing::info_span!("insert_use_with_alias_option").entered();
187    let mut mb = match cfg.granularity {
188        ImportGranularity::Crate => Some(MergeBehavior::Crate),
189        ImportGranularity::Module => Some(MergeBehavior::Module),
190        ImportGranularity::One => Some(MergeBehavior::One),
191        ImportGranularity::Item => None,
192    };
193    if !cfg.enforce_granularity {
194        let file_granularity = guess_granularity_from_scope(scope);
195        mb = match file_granularity {
196            ImportGranularityGuess::Unknown => mb,
197            ImportGranularityGuess::Item => None,
198            ImportGranularityGuess::Module => Some(MergeBehavior::Module),
199            // We use the user's setting to infer if this is module or item.
200            ImportGranularityGuess::ModuleOrItem => match mb {
201                Some(MergeBehavior::Module) | None => mb,
202                // There isn't really a way to decide between module or item here, so we just pick one.
203                // FIXME: Maybe it is possible to infer based on semantic analysis?
204                Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
205            },
206            ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
207            ImportGranularityGuess::CrateOrModule => match mb {
208                Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
209                Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
210            },
211            ImportGranularityGuess::One => Some(MergeBehavior::One),
212        };
213    }
214
215    let mut use_tree = make::use_tree(path, None, alias, false);
216    if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
217        use_tree = use_tree.clone_for_update();
218        use_tree.wrap_in_tree_list();
219    }
220    let use_item = make::use_(None, None, use_tree).clone_for_update();
221    for attr in
222        scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
223    {
224        ted::insert(ted::Position::first_child_of(use_item.syntax()), attr);
225    }
226
227    // merge into existing imports if possible
228    if let Some(mb) = mb {
229        let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
230        for existing_use in
231            scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
232        {
233            if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
234                ted::replace(existing_use.syntax(), merged.syntax());
235                return;
236            }
237        }
238    }
239    // either we weren't allowed to merge or there is no import that fits the merge conditions
240    // so look for the place we have to insert to
241    insert_use_(scope, use_item, cfg.group);
242}
243
244fn insert_use_with_alias_option_with_editor(
245    scope: &ImportScope,
246    path: ast::Path,
247    cfg: &InsertUseConfig,
248    alias: Option<ast::Rename>,
249    syntax_editor: &mut SyntaxEditor,
250    syntax_factory: &SyntaxFactory,
251) {
252    let _p = tracing::info_span!("insert_use_with_alias_option").entered();
253    let mut mb = match cfg.granularity {
254        ImportGranularity::Crate => Some(MergeBehavior::Crate),
255        ImportGranularity::Module => Some(MergeBehavior::Module),
256        ImportGranularity::One => Some(MergeBehavior::One),
257        ImportGranularity::Item => None,
258    };
259    if !cfg.enforce_granularity {
260        let file_granularity = guess_granularity_from_scope(scope);
261        mb = match file_granularity {
262            ImportGranularityGuess::Unknown => mb,
263            ImportGranularityGuess::Item => None,
264            ImportGranularityGuess::Module => Some(MergeBehavior::Module),
265            // We use the user's setting to infer if this is module or item.
266            ImportGranularityGuess::ModuleOrItem => match mb {
267                Some(MergeBehavior::Module) | None => mb,
268                // There isn't really a way to decide between module or item here, so we just pick one.
269                // FIXME: Maybe it is possible to infer based on semantic analysis?
270                Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
271            },
272            ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
273            ImportGranularityGuess::CrateOrModule => match mb {
274                Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
275                Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
276            },
277            ImportGranularityGuess::One => Some(MergeBehavior::One),
278        };
279    }
280
281    let use_tree = syntax_factory.use_tree(path, None, alias, false);
282    if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
283        use_tree.wrap_in_tree_list();
284    }
285    let use_item = make::use_(None, None, use_tree).clone_for_update();
286    for attr in
287        scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
288    {
289        syntax_editor.insert(Position::first_child_of(use_item.syntax()), attr);
290    }
291
292    // merge into existing imports if possible
293    if let Some(mb) = mb {
294        let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
295        for existing_use in
296            scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
297        {
298            if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
299                syntax_editor.replace(existing_use.syntax(), merged.syntax());
300                return;
301            }
302        }
303    }
304    // either we weren't allowed to merge or there is no import that fits the merge conditions
305    // so look for the place we have to insert to
306    insert_use_with_editor_(scope, use_item, cfg.group, syntax_editor, syntax_factory);
307}
308
309pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
310    // FIXME: improve this
311    if path.parent_path().is_some() {
312        return None;
313    }
314    let use_tree = path.syntax().parent().and_then(ast::UseTree::cast)?;
315    if use_tree.use_tree_list().is_some() || use_tree.star_token().is_some() {
316        return None;
317    }
318    if let Some(use_) = use_tree.syntax().parent().and_then(ast::Use::cast) {
319        return Some(Box::new(use_));
320    }
321    Some(Box::new(use_tree))
322}
323
324pub fn remove_path_if_in_use_stmt(path: &ast::Path) {
325    if let Some(node) = ast_to_remove_for_path_in_use_stmt(path) {
326        node.remove();
327    }
328}
329
330#[derive(Eq, PartialEq, PartialOrd, Ord)]
331enum ImportGroup {
332    // the order here defines the order of new group inserts
333    Std,
334    ExternCrate,
335    ThisCrate,
336    ThisModule,
337    SuperModule,
338    One,
339}
340
341impl ImportGroup {
342    fn new(use_tree: &ast::UseTree) -> ImportGroup {
343        if use_tree.path().is_none() && use_tree.use_tree_list().is_some() {
344            return ImportGroup::One;
345        }
346
347        let Some(first_segment) = use_tree.path().as_ref().and_then(ast::Path::first_segment)
348        else {
349            return ImportGroup::ExternCrate;
350        };
351
352        let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
353        match kind {
354            PathSegmentKind::SelfKw => ImportGroup::ThisModule,
355            PathSegmentKind::SuperKw => ImportGroup::SuperModule,
356            PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
357            PathSegmentKind::Name(name) => match name.text().as_str() {
358                "std" => ImportGroup::Std,
359                "core" => ImportGroup::Std,
360                _ => ImportGroup::ExternCrate,
361            },
362            // these aren't valid use paths, so fall back to something random
363            PathSegmentKind::SelfTypeKw => ImportGroup::ExternCrate,
364            PathSegmentKind::Type { .. } => ImportGroup::ExternCrate,
365        }
366    }
367}
368
369#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
370enum ImportGranularityGuess {
371    Unknown,
372    Item,
373    Module,
374    ModuleOrItem,
375    Crate,
376    CrateOrModule,
377    One,
378}
379
380fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
381    // The idea is simple, just check each import as well as the import and its precedent together for
382    // whether they fulfill a granularity criteria.
383    let use_stmt = |item| match item {
384        ast::Item::Use(use_) => {
385            let use_tree = use_.use_tree()?;
386            Some((use_tree, use_.visibility(), use_.attrs()))
387        }
388        _ => None,
389    };
390    let mut use_stmts = match &scope.kind {
391        ImportScopeKind::File(f) => f.items(),
392        ImportScopeKind::Module(m) => m.items(),
393        ImportScopeKind::Block(b) => b.items(),
394    }
395    .filter_map(use_stmt);
396    let mut res = ImportGranularityGuess::Unknown;
397    let Some((mut prev, mut prev_vis, mut prev_attrs)) = use_stmts.next() else { return res };
398
399    let is_tree_one_style =
400        |use_tree: &ast::UseTree| use_tree.path().is_none() && use_tree.use_tree_list().is_some();
401    let mut seen_one_style_groups = Vec::new();
402
403    loop {
404        if is_tree_one_style(&prev) {
405            if res != ImportGranularityGuess::One {
406                if res != ImportGranularityGuess::Unknown {
407                    // This scope has a mix of one-style and other style imports.
408                    break ImportGranularityGuess::Unknown;
409                }
410
411                res = ImportGranularityGuess::One;
412                seen_one_style_groups.push((prev_vis.clone(), prev_attrs.clone()));
413            }
414        } else if let Some(use_tree_list) = prev.use_tree_list() {
415            if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
416                // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
417                break ImportGranularityGuess::Crate;
418            } else {
419                // Could still be crate-style so continue looking.
420                res = ImportGranularityGuess::CrateOrModule;
421            }
422        }
423
424        let Some((curr, curr_vis, curr_attrs)) = use_stmts.next() else { break res };
425        if is_tree_one_style(&curr) {
426            if res != ImportGranularityGuess::One
427                || seen_one_style_groups.iter().any(|(prev_vis, prev_attrs)| {
428                    eq_visibility(prev_vis.clone(), curr_vis.clone())
429                        && eq_attrs(prev_attrs.clone(), curr_attrs.clone())
430                })
431            {
432                // This scope has either a mix of one-style and other style imports or
433                // multiple one-style imports with the same visibility and attributes.
434                break ImportGranularityGuess::Unknown;
435            }
436            seen_one_style_groups.push((curr_vis.clone(), curr_attrs.clone()));
437        } else if eq_visibility(prev_vis, curr_vis.clone())
438            && eq_attrs(prev_attrs, curr_attrs.clone())
439            && let Some((prev_path, curr_path)) = prev.path().zip(curr.path())
440            && let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path)
441        {
442            if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
443                let prefix_c = prev_prefix.qualifiers().count();
444                let curr_c = curr_path.qualifiers().count() - prefix_c;
445                let prev_c = prev_path.qualifiers().count() - prefix_c;
446                if curr_c == 1 && prev_c == 1 {
447                    // Same prefix, only differing in the last segment and no use tree lists so this has to be of item style.
448                    break ImportGranularityGuess::Item;
449                } else {
450                    // Same prefix and no use tree list but differs in more than one segment at the end. This might be module style still.
451                    res = ImportGranularityGuess::ModuleOrItem;
452                }
453            } else {
454                // Same prefix with item tree lists, has to be module style as it
455                // can't be crate style since the trees wouldn't share a prefix then.
456                break ImportGranularityGuess::Module;
457            }
458        }
459        prev = curr;
460        prev_vis = curr_vis;
461        prev_attrs = curr_attrs;
462    }
463}
464
465fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
466    let scope_syntax = scope.as_syntax_node();
467    let insert_use_tree =
468        use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
469    let group = ImportGroup::new(&insert_use_tree);
470    let path_node_iter = scope_syntax
471        .children()
472        .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
473        .flat_map(|(use_, node)| {
474            let tree = use_.use_tree()?;
475            Some((tree, node))
476        });
477
478    if group_imports {
479        // Iterator that discards anything that's not in the required grouping
480        // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
481        let group_iter = path_node_iter
482            .clone()
483            .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
484            .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
485
486        // 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
487        let mut last = None;
488        // find the element that would come directly after our new import
489        let post_insert: Option<(_, SyntaxNode)> = group_iter
490            .inspect(|(.., node)| last = Some(node.clone()))
491            .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
492
493        if let Some((.., node)) = post_insert {
494            cov_mark::hit!(insert_group);
495            // insert our import before that element
496            return ted::insert(ted::Position::before(node), use_item.syntax());
497        }
498        if let Some(node) = last {
499            cov_mark::hit!(insert_group_last);
500            // there is no element after our new import, so append it to the end of the group
501            return ted::insert(ted::Position::after(node), use_item.syntax());
502        }
503
504        // the group we were looking for actually doesn't exist, so insert
505
506        let mut last = None;
507        // find the group that comes after where we want to insert
508        let post_group = path_node_iter
509            .inspect(|(.., node)| last = Some(node.clone()))
510            .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
511        if let Some((.., node)) = post_group {
512            cov_mark::hit!(insert_group_new_group);
513            ted::insert(ted::Position::before(&node), use_item.syntax());
514            if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
515                ted::insert(ted::Position::after(node), make::tokens::single_newline());
516            }
517            return;
518        }
519        // there is no such group, so append after the last one
520        if let Some(node) = last {
521            cov_mark::hit!(insert_group_no_group);
522            ted::insert(ted::Position::after(&node), use_item.syntax());
523            ted::insert(ted::Position::after(node), make::tokens::single_newline());
524            return;
525        }
526    } else {
527        // There exists a group, so append to the end of it
528        if let Some((_, node)) = path_node_iter.last() {
529            cov_mark::hit!(insert_no_grouping_last);
530            ted::insert(ted::Position::after(node), use_item.syntax());
531            return;
532        }
533    }
534
535    let l_curly = match &scope.kind {
536        ImportScopeKind::File(_) => None,
537        // don't insert the imports before the item list/block expr's opening curly brace
538        ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
539        // don't insert the imports before the item list's opening curly brace
540        ImportScopeKind::Block(block) => block.l_curly_token(),
541    };
542    // there are no imports in this file at all
543    // so put the import after all inner module attributes and possible license header comments
544    if let Some(last_inner_element) = scope_syntax
545        .children_with_tokens()
546        // skip the curly brace
547        .skip(l_curly.is_some() as usize)
548        .take_while(|child| match child {
549            NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
550            NodeOrToken::Token(token) => {
551                [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
552                    .contains(&token.kind())
553            }
554        })
555        .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
556        .last()
557    {
558        cov_mark::hit!(insert_empty_inner_attr);
559        ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
560        ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
561    } else {
562        match l_curly {
563            Some(b) => {
564                cov_mark::hit!(insert_empty_module);
565                ted::insert(ted::Position::after(&b), make::tokens::single_newline());
566                ted::insert(ted::Position::after(&b), use_item.syntax());
567            }
568            None => {
569                cov_mark::hit!(insert_empty_file);
570                ted::insert(
571                    ted::Position::first_child_of(scope_syntax),
572                    make::tokens::blank_line(),
573                );
574                ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
575            }
576        }
577    }
578}
579
580fn insert_use_with_editor_(
581    scope: &ImportScope,
582    use_item: ast::Use,
583    group_imports: bool,
584    syntax_editor: &mut SyntaxEditor,
585    syntax_factory: &SyntaxFactory,
586) {
587    let scope_syntax = scope.as_syntax_node();
588    let insert_use_tree =
589        use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
590    let group = ImportGroup::new(&insert_use_tree);
591    let path_node_iter = scope_syntax
592        .children()
593        .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
594        .flat_map(|(use_, node)| {
595            let tree = use_.use_tree()?;
596            Some((tree, node))
597        });
598
599    if group_imports {
600        // Iterator that discards anything that's not in the required grouping
601        // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
602        let group_iter = path_node_iter
603            .clone()
604            .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
605            .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
606
607        // 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
608        let mut last = None;
609        // find the element that would come directly after our new import
610        let post_insert: Option<(_, SyntaxNode)> = group_iter
611            .inspect(|(.., node)| last = Some(node.clone()))
612            .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
613
614        if let Some((.., node)) = post_insert {
615            cov_mark::hit!(insert_group);
616            // insert our import before that element
617            return syntax_editor.insert(Position::before(node), use_item.syntax());
618        }
619        if let Some(node) = last {
620            cov_mark::hit!(insert_group_last);
621            // there is no element after our new import, so append it to the end of the group
622            return syntax_editor.insert(Position::after(node), use_item.syntax());
623        }
624
625        // the group we were looking for actually doesn't exist, so insert
626
627        let mut last = None;
628        // find the group that comes after where we want to insert
629        let post_group = path_node_iter
630            .inspect(|(.., node)| last = Some(node.clone()))
631            .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
632        if let Some((.., node)) = post_group {
633            cov_mark::hit!(insert_group_new_group);
634            syntax_editor.insert(Position::before(&node), use_item.syntax());
635            if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
636                syntax_editor.insert(Position::after(node), syntax_factory.whitespace("\n"));
637            }
638            return;
639        }
640        // there is no such group, so append after the last one
641        if let Some(node) = last {
642            cov_mark::hit!(insert_group_no_group);
643            syntax_editor.insert(Position::after(&node), use_item.syntax());
644            syntax_editor.insert(Position::after(node), syntax_factory.whitespace("\n"));
645            return;
646        }
647    } else {
648        // There exists a group, so append to the end of it
649        if let Some((_, node)) = path_node_iter.last() {
650            cov_mark::hit!(insert_no_grouping_last);
651            syntax_editor.insert(Position::after(node), use_item.syntax());
652            return;
653        }
654    }
655
656    let l_curly = match &scope.kind {
657        ImportScopeKind::File(_) => None,
658        // don't insert the imports before the item list/block expr's opening curly brace
659        ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
660        // don't insert the imports before the item list's opening curly brace
661        ImportScopeKind::Block(block) => block.l_curly_token(),
662    };
663    // there are no imports in this file at all
664    // so put the import after all inner module attributes and possible license header comments
665    if let Some(last_inner_element) = scope_syntax
666        .children_with_tokens()
667        // skip the curly brace
668        .skip(l_curly.is_some() as usize)
669        .take_while(|child| match child {
670            NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
671            NodeOrToken::Token(token) => {
672                [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
673                    .contains(&token.kind())
674            }
675        })
676        .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
677        .last()
678    {
679        cov_mark::hit!(insert_empty_inner_attr);
680        syntax_editor.insert(Position::after(&last_inner_element), use_item.syntax());
681        syntax_editor.insert(Position::after(last_inner_element), syntax_factory.whitespace("\n"));
682    } else {
683        match l_curly {
684            Some(b) => {
685                cov_mark::hit!(insert_empty_module);
686                syntax_editor.insert(Position::after(&b), syntax_factory.whitespace("\n"));
687                syntax_editor.insert(Position::after(&b), use_item.syntax());
688            }
689            None => {
690                cov_mark::hit!(insert_empty_file);
691                syntax_editor.insert(
692                    Position::first_child_of(scope_syntax),
693                    syntax_factory.whitespace("\n\n"),
694                );
695                syntax_editor.insert(Position::first_child_of(scope_syntax), use_item.syntax());
696            }
697        }
698    }
699}
700
701fn is_inner_attribute(node: SyntaxNode) -> bool {
702    ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
703}