Skip to content

Commit c039810

Browse files
authored
Fix: Select correct insert position for disabled group import
The logic for importing with and without `group_imports` differed significantly when no previous group existed. This lead to the problem of using the wrong position when importing inside a module (#11585) or when inner attributes are involved. The existing code for grouped imports is better and takes these things into account. This PR changes the flow to use the pre-existing code for adding a new import group even for the non-grouped import settings. Some coverage markers are updated and the `group` is removed, since they are now invoked in both cases (grouping and no grouping). Tests are updated and two tests (empty module and inner attribute) are added. Fixes #11585
1 parent a1d684e commit c039810

File tree

2 files changed

+98
-62
lines changed

2 files changed

+98
-62
lines changed

crates/ide_db/src/imports/insert_use.rs

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -337,67 +337,65 @@ fn insert_use_(
337337
Some((path, has_tl, node))
338338
});
339339

340-
if !group_imports {
340+
if group_imports {
341+
// Iterator that discards anything thats not in the required grouping
342+
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
343+
let group_iter = path_node_iter
344+
.clone()
345+
.skip_while(|(path, ..)| ImportGroup::new(path) != group)
346+
.take_while(|(path, ..)| ImportGroup::new(path) == group);
347+
348+
// 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
349+
let mut last = None;
350+
// find the element that would come directly after our new import
351+
let post_insert: Option<(_, _, SyntaxNode)> = group_iter
352+
.inspect(|(.., node)| last = Some(node.clone()))
353+
.find(|&(ref path, has_tl, _)| {
354+
use_tree_path_cmp(insert_path, false, path, has_tl) != Ordering::Greater
355+
});
356+
357+
if let Some((.., node)) = post_insert {
358+
cov_mark::hit!(insert_group);
359+
// insert our import before that element
360+
return ted::insert(ted::Position::before(node), use_item.syntax());
361+
}
362+
if let Some(node) = last {
363+
cov_mark::hit!(insert_group_last);
364+
// there is no element after our new import, so append it to the end of the group
365+
return ted::insert(ted::Position::after(node), use_item.syntax());
366+
}
367+
368+
// the group we were looking for actually doesn't exist, so insert
369+
370+
let mut last = None;
371+
// find the group that comes after where we want to insert
372+
let post_group = path_node_iter
373+
.inspect(|(.., node)| last = Some(node.clone()))
374+
.find(|(p, ..)| ImportGroup::new(p) > group);
375+
if let Some((.., node)) = post_group {
376+
cov_mark::hit!(insert_group_new_group);
377+
ted::insert(ted::Position::before(&node), use_item.syntax());
378+
if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
379+
ted::insert(ted::Position::after(node), make::tokens::single_newline());
380+
}
381+
return;
382+
}
383+
// there is no such group, so append after the last one
384+
if let Some(node) = last {
385+
cov_mark::hit!(insert_group_no_group);
386+
ted::insert(ted::Position::after(&node), use_item.syntax());
387+
ted::insert(ted::Position::after(node), make::tokens::single_newline());
388+
return;
389+
}
390+
} else {
391+
// There exists a group, so append to the end of it
341392
if let Some((_, _, node)) = path_node_iter.last() {
342393
cov_mark::hit!(insert_no_grouping_last);
343394
ted::insert(ted::Position::after(node), use_item.syntax());
344-
} else {
345-
cov_mark::hit!(insert_no_grouping_last2);
346-
ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
347-
ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
395+
return;
348396
}
349-
return;
350-
}
351-
352-
// Iterator that discards anything thats not in the required grouping
353-
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
354-
let group_iter = path_node_iter
355-
.clone()
356-
.skip_while(|(path, ..)| ImportGroup::new(path) != group)
357-
.take_while(|(path, ..)| ImportGroup::new(path) == group);
358-
359-
// 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
360-
let mut last = None;
361-
// find the element that would come directly after our new import
362-
let post_insert: Option<(_, _, SyntaxNode)> = group_iter
363-
.inspect(|(.., node)| last = Some(node.clone()))
364-
.find(|&(ref path, has_tl, _)| {
365-
use_tree_path_cmp(insert_path, false, path, has_tl) != Ordering::Greater
366-
});
367-
368-
if let Some((.., node)) = post_insert {
369-
cov_mark::hit!(insert_group);
370-
// insert our import before that element
371-
return ted::insert(ted::Position::before(node), use_item.syntax());
372-
}
373-
if let Some(node) = last {
374-
cov_mark::hit!(insert_group_last);
375-
// there is no element after our new import, so append it to the end of the group
376-
return ted::insert(ted::Position::after(node), use_item.syntax());
377397
}
378398

379-
// the group we were looking for actually doesn't exist, so insert
380-
381-
let mut last = None;
382-
// find the group that comes after where we want to insert
383-
let post_group = path_node_iter
384-
.inspect(|(.., node)| last = Some(node.clone()))
385-
.find(|(p, ..)| ImportGroup::new(p) > group);
386-
if let Some((.., node)) = post_group {
387-
cov_mark::hit!(insert_group_new_group);
388-
ted::insert(ted::Position::before(&node), use_item.syntax());
389-
if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
390-
ted::insert(ted::Position::after(node), make::tokens::single_newline());
391-
}
392-
return;
393-
}
394-
// there is no such group, so append after the last one
395-
if let Some(node) = last {
396-
cov_mark::hit!(insert_group_no_group);
397-
ted::insert(ted::Position::after(&node), use_item.syntax());
398-
ted::insert(ted::Position::after(node), make::tokens::single_newline());
399-
return;
400-
}
401399
// there are no imports in this file at all
402400
if let Some(last_inner_element) = scope_syntax
403401
.children_with_tokens()
@@ -407,14 +405,14 @@ fn insert_use_(
407405
})
408406
.last()
409407
{
410-
cov_mark::hit!(insert_group_empty_inner_attr);
408+
cov_mark::hit!(insert_empty_inner_attr);
411409
ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
412410
ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
413411
return;
414412
}
415413
let l_curly = match scope {
416414
ImportScope::File(_) => {
417-
cov_mark::hit!(insert_group_empty_file);
415+
cov_mark::hit!(insert_empty_file);
418416
ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
419417
ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
420418
return;
@@ -426,7 +424,7 @@ fn insert_use_(
426424
};
427425
match l_curly {
428426
Some(b) => {
429-
cov_mark::hit!(insert_group_empty_module);
427+
cov_mark::hit!(insert_empty_module);
430428
ted::insert(ted::Position::after(&b), make::tokens::single_newline());
431429
ted::insert(ted::Position::after(&b), use_item.syntax());
432430
}

crates/ide_db/src/imports/insert_use/tests.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ use external_crate2::bar::A;",
8686

8787
#[test]
8888
fn insert_not_group_empty() {
89-
cov_mark::check!(insert_no_grouping_last2);
89+
cov_mark::check!(insert_empty_file);
9090
check_with_config(
9191
"use external_crate2::bar::A",
9292
r"",
@@ -103,6 +103,44 @@ fn insert_not_group_empty() {
103103
);
104104
}
105105

106+
#[test]
107+
fn insert_not_group_empty_module() {
108+
cov_mark::check!(insert_empty_module);
109+
check_with_config(
110+
"foo::bar",
111+
r"mod x {$0}",
112+
r"mod x {
113+
use foo::bar;
114+
}",
115+
&InsertUseConfig {
116+
granularity: ImportGranularity::Item,
117+
enforce_granularity: true,
118+
prefix_kind: PrefixKind::Plain,
119+
group: false,
120+
skip_glob_imports: true,
121+
},
122+
);
123+
}
124+
125+
#[test]
126+
fn insert_no_group_after_inner_attr() {
127+
cov_mark::check!(insert_empty_inner_attr);
128+
check_with_config(
129+
"foo::bar",
130+
r"#![allow(unused_imports)]",
131+
r"#![allow(unused_imports)]
132+
133+
use foo::bar;",
134+
&InsertUseConfig {
135+
granularity: ImportGranularity::Item,
136+
enforce_granularity: true,
137+
prefix_kind: PrefixKind::Plain,
138+
group: false,
139+
skip_glob_imports: true,
140+
},
141+
)
142+
}
143+
106144
#[test]
107145
fn insert_existing() {
108146
check_crate("std::fs", "use std::fs;", "use std::fs;")
@@ -321,7 +359,7 @@ fn main() {}",
321359

322360
#[test]
323361
fn insert_empty_file() {
324-
cov_mark::check!(insert_group_empty_file);
362+
cov_mark::check!(insert_empty_file);
325363
// empty files will get two trailing newlines
326364
// this is due to the test case insert_no_imports above
327365
check_crate(
@@ -335,7 +373,7 @@ fn insert_empty_file() {
335373

336374
#[test]
337375
fn insert_empty_module() {
338-
cov_mark::check!(insert_group_empty_module);
376+
cov_mark::check!(insert_empty_module);
339377
check(
340378
"foo::bar",
341379
r"
@@ -352,7 +390,7 @@ mod x {
352390

353391
#[test]
354392
fn insert_after_inner_attr() {
355-
cov_mark::check!(insert_group_empty_inner_attr);
393+
cov_mark::check!(insert_empty_inner_attr);
356394
check_crate(
357395
"foo::bar",
358396
r"#![allow(unused_imports)]",

0 commit comments

Comments
 (0)