mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-01 07:48:45 +00:00
Merge #11865
11865: Fix: Select correct insert position for disabled group import r=jonasbb a=jonasbb 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 Co-authored-by: Jonas Bushart <jonas@bushart.org>
This commit is contained in:
commit
46d7ee68f2
2 changed files with 116 additions and 83 deletions
|
@ -337,67 +337,65 @@ fn insert_use_(
|
||||||
Some((path, has_tl, node))
|
Some((path, has_tl, node))
|
||||||
});
|
});
|
||||||
|
|
||||||
if !group_imports {
|
if group_imports {
|
||||||
|
// Iterator that discards anything thats not in the required grouping
|
||||||
|
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
|
||||||
|
let group_iter = path_node_iter
|
||||||
|
.clone()
|
||||||
|
.skip_while(|(path, ..)| ImportGroup::new(path) != group)
|
||||||
|
.take_while(|(path, ..)| ImportGroup::new(path) == group);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
let mut last = None;
|
||||||
|
// find the element that would come directly after our new import
|
||||||
|
let post_insert: Option<(_, _, SyntaxNode)> = group_iter
|
||||||
|
.inspect(|(.., node)| last = Some(node.clone()))
|
||||||
|
.find(|&(ref path, has_tl, _)| {
|
||||||
|
use_tree_path_cmp(insert_path, false, path, has_tl) != Ordering::Greater
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((.., node)) = post_insert {
|
||||||
|
cov_mark::hit!(insert_group);
|
||||||
|
// insert our import before that element
|
||||||
|
return ted::insert(ted::Position::before(node), use_item.syntax());
|
||||||
|
}
|
||||||
|
if let Some(node) = last {
|
||||||
|
cov_mark::hit!(insert_group_last);
|
||||||
|
// there is no element after our new import, so append it to the end of the group
|
||||||
|
return ted::insert(ted::Position::after(node), use_item.syntax());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the group we were looking for actually doesn't exist, so insert
|
||||||
|
|
||||||
|
let mut last = None;
|
||||||
|
// find the group that comes after where we want to insert
|
||||||
|
let post_group = path_node_iter
|
||||||
|
.inspect(|(.., node)| last = Some(node.clone()))
|
||||||
|
.find(|(p, ..)| ImportGroup::new(p) > group);
|
||||||
|
if let Some((.., node)) = post_group {
|
||||||
|
cov_mark::hit!(insert_group_new_group);
|
||||||
|
ted::insert(ted::Position::before(&node), use_item.syntax());
|
||||||
|
if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
|
||||||
|
ted::insert(ted::Position::after(node), make::tokens::single_newline());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// there is no such group, so append after the last one
|
||||||
|
if let Some(node) = last {
|
||||||
|
cov_mark::hit!(insert_group_no_group);
|
||||||
|
ted::insert(ted::Position::after(&node), use_item.syntax());
|
||||||
|
ted::insert(ted::Position::after(node), make::tokens::single_newline());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// There exists a group, so append to the end of it
|
||||||
if let Some((_, _, node)) = path_node_iter.last() {
|
if let Some((_, _, node)) = path_node_iter.last() {
|
||||||
cov_mark::hit!(insert_no_grouping_last);
|
cov_mark::hit!(insert_no_grouping_last);
|
||||||
ted::insert(ted::Position::after(node), use_item.syntax());
|
ted::insert(ted::Position::after(node), use_item.syntax());
|
||||||
} else {
|
return;
|
||||||
cov_mark::hit!(insert_no_grouping_last2);
|
|
||||||
ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
|
|
||||||
ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterator that discards anything thats not in the required grouping
|
|
||||||
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
|
|
||||||
let group_iter = path_node_iter
|
|
||||||
.clone()
|
|
||||||
.skip_while(|(path, ..)| ImportGroup::new(path) != group)
|
|
||||||
.take_while(|(path, ..)| ImportGroup::new(path) == group);
|
|
||||||
|
|
||||||
// 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
|
|
||||||
let mut last = None;
|
|
||||||
// find the element that would come directly after our new import
|
|
||||||
let post_insert: Option<(_, _, SyntaxNode)> = group_iter
|
|
||||||
.inspect(|(.., node)| last = Some(node.clone()))
|
|
||||||
.find(|&(ref path, has_tl, _)| {
|
|
||||||
use_tree_path_cmp(insert_path, false, path, has_tl) != Ordering::Greater
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some((.., node)) = post_insert {
|
|
||||||
cov_mark::hit!(insert_group);
|
|
||||||
// insert our import before that element
|
|
||||||
return ted::insert(ted::Position::before(node), use_item.syntax());
|
|
||||||
}
|
|
||||||
if let Some(node) = last {
|
|
||||||
cov_mark::hit!(insert_group_last);
|
|
||||||
// there is no element after our new import, so append it to the end of the group
|
|
||||||
return ted::insert(ted::Position::after(node), use_item.syntax());
|
|
||||||
}
|
|
||||||
|
|
||||||
// the group we were looking for actually doesn't exist, so insert
|
|
||||||
|
|
||||||
let mut last = None;
|
|
||||||
// find the group that comes after where we want to insert
|
|
||||||
let post_group = path_node_iter
|
|
||||||
.inspect(|(.., node)| last = Some(node.clone()))
|
|
||||||
.find(|(p, ..)| ImportGroup::new(p) > group);
|
|
||||||
if let Some((.., node)) = post_group {
|
|
||||||
cov_mark::hit!(insert_group_new_group);
|
|
||||||
ted::insert(ted::Position::before(&node), use_item.syntax());
|
|
||||||
if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
|
|
||||||
ted::insert(ted::Position::after(node), make::tokens::single_newline());
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// there is no such group, so append after the last one
|
|
||||||
if let Some(node) = last {
|
|
||||||
cov_mark::hit!(insert_group_no_group);
|
|
||||||
ted::insert(ted::Position::after(&node), use_item.syntax());
|
|
||||||
ted::insert(ted::Position::after(node), make::tokens::single_newline());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// there are no imports in this file at all
|
// there are no imports in this file at all
|
||||||
if let Some(last_inner_element) = scope_syntax
|
if let Some(last_inner_element) = scope_syntax
|
||||||
.children_with_tokens()
|
.children_with_tokens()
|
||||||
|
@ -407,14 +405,14 @@ fn insert_use_(
|
||||||
})
|
})
|
||||||
.last()
|
.last()
|
||||||
{
|
{
|
||||||
cov_mark::hit!(insert_group_empty_inner_attr);
|
cov_mark::hit!(insert_empty_inner_attr);
|
||||||
ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
|
ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
|
||||||
ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
|
ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let l_curly = match scope {
|
let l_curly = match scope {
|
||||||
ImportScope::File(_) => {
|
ImportScope::File(_) => {
|
||||||
cov_mark::hit!(insert_group_empty_file);
|
cov_mark::hit!(insert_empty_file);
|
||||||
ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
|
ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
|
||||||
ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
|
ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
|
||||||
return;
|
return;
|
||||||
|
@ -426,7 +424,7 @@ fn insert_use_(
|
||||||
};
|
};
|
||||||
match l_curly {
|
match l_curly {
|
||||||
Some(b) => {
|
Some(b) => {
|
||||||
cov_mark::hit!(insert_group_empty_module);
|
cov_mark::hit!(insert_empty_module);
|
||||||
ted::insert(ted::Position::after(&b), make::tokens::single_newline());
|
ted::insert(ted::Position::after(&b), make::tokens::single_newline());
|
||||||
ted::insert(ted::Position::after(&b), use_item.syntax());
|
ted::insert(ted::Position::after(&b), use_item.syntax());
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,25 +84,6 @@ use external_crate2::bar::A;",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn insert_not_group_empty() {
|
|
||||||
cov_mark::check!(insert_no_grouping_last2);
|
|
||||||
check_with_config(
|
|
||||||
"use external_crate2::bar::A",
|
|
||||||
r"",
|
|
||||||
r"use external_crate2::bar::A;
|
|
||||||
|
|
||||||
",
|
|
||||||
&InsertUseConfig {
|
|
||||||
granularity: ImportGranularity::Item,
|
|
||||||
enforce_granularity: true,
|
|
||||||
prefix_kind: PrefixKind::Plain,
|
|
||||||
group: false,
|
|
||||||
skip_glob_imports: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_existing() {
|
fn insert_existing() {
|
||||||
check_crate("std::fs", "use std::fs;", "use std::fs;")
|
check_crate("std::fs", "use std::fs;", "use std::fs;")
|
||||||
|
@ -321,7 +302,9 @@ fn main() {}",
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_empty_file() {
|
fn insert_empty_file() {
|
||||||
cov_mark::check!(insert_group_empty_file);
|
cov_mark::check_count!(insert_empty_file, 2);
|
||||||
|
|
||||||
|
// Default configuration
|
||||||
// empty files will get two trailing newlines
|
// empty files will get two trailing newlines
|
||||||
// this is due to the test case insert_no_imports above
|
// this is due to the test case insert_no_imports above
|
||||||
check_crate(
|
check_crate(
|
||||||
|
@ -330,12 +313,30 @@ fn insert_empty_file() {
|
||||||
r"use foo::bar;
|
r"use foo::bar;
|
||||||
|
|
||||||
",
|
",
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// "not group" configuration
|
||||||
|
check_with_config(
|
||||||
|
"use external_crate2::bar::A",
|
||||||
|
r"",
|
||||||
|
r"use external_crate2::bar::A;
|
||||||
|
|
||||||
|
",
|
||||||
|
&InsertUseConfig {
|
||||||
|
granularity: ImportGranularity::Item,
|
||||||
|
enforce_granularity: true,
|
||||||
|
prefix_kind: PrefixKind::Plain,
|
||||||
|
group: false,
|
||||||
|
skip_glob_imports: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_empty_module() {
|
fn insert_empty_module() {
|
||||||
cov_mark::check!(insert_group_empty_module);
|
cov_mark::check_count!(insert_empty_module, 2);
|
||||||
|
|
||||||
|
// Default configuration
|
||||||
check(
|
check(
|
||||||
"foo::bar",
|
"foo::bar",
|
||||||
r"
|
r"
|
||||||
|
@ -347,19 +348,53 @@ mod x {
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
ImportGranularity::Item,
|
ImportGranularity::Item,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// "not group" configuration
|
||||||
|
check_with_config(
|
||||||
|
"foo::bar",
|
||||||
|
r"mod x {$0}",
|
||||||
|
r"mod x {
|
||||||
|
use foo::bar;
|
||||||
|
}",
|
||||||
|
&InsertUseConfig {
|
||||||
|
granularity: ImportGranularity::Item,
|
||||||
|
enforce_granularity: true,
|
||||||
|
prefix_kind: PrefixKind::Plain,
|
||||||
|
group: false,
|
||||||
|
skip_glob_imports: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_after_inner_attr() {
|
fn insert_after_inner_attr() {
|
||||||
cov_mark::check!(insert_group_empty_inner_attr);
|
cov_mark::check_count!(insert_empty_inner_attr, 2);
|
||||||
|
|
||||||
|
// Default configuration
|
||||||
check_crate(
|
check_crate(
|
||||||
"foo::bar",
|
"foo::bar",
|
||||||
r"#![allow(unused_imports)]",
|
r"#![allow(unused_imports)]",
|
||||||
r"#![allow(unused_imports)]
|
r"#![allow(unused_imports)]
|
||||||
|
|
||||||
use foo::bar;",
|
use foo::bar;",
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// "not group" configuration
|
||||||
|
check_with_config(
|
||||||
|
"foo::bar",
|
||||||
|
r"#![allow(unused_imports)]",
|
||||||
|
r"#![allow(unused_imports)]
|
||||||
|
|
||||||
|
use foo::bar;",
|
||||||
|
&InsertUseConfig {
|
||||||
|
granularity: ImportGranularity::Item,
|
||||||
|
enforce_granularity: true,
|
||||||
|
prefix_kind: PrefixKind::Plain,
|
||||||
|
group: false,
|
||||||
|
skip_glob_imports: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue