mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-12-23 08:48:08 +00:00
Merge 7e3532889d into 3aecf081f6
This commit is contained in:
commit
06d64422b8
3 changed files with 602 additions and 0 deletions
580
crates/ide-assists/src/handlers/move_use_to_top_level.rs
Normal file
580
crates/ide-assists/src/handlers/move_use_to_top_level.rs
Normal file
|
|
@ -0,0 +1,580 @@
|
|||
use crate::assist_context::{AssistContext, Assists};
|
||||
use ide_db::FileId;
|
||||
use ide_db::assists::AssistId;
|
||||
use ide_db::imports::insert_use::{ImportScope, ImportScopeKind};
|
||||
use ide_db::source_change::SourceChangeBuilder;
|
||||
use syntax::{
|
||||
AstNode, AstToken,
|
||||
ast::{
|
||||
BlockExpr, HasModuleItem, Item, Module, SourceFile, Use, Whitespace, edit_in_place::Indent,
|
||||
make,
|
||||
},
|
||||
syntax_editor::{Position, SyntaxEditor},
|
||||
};
|
||||
|
||||
// Assist: move_use_to_top_level
|
||||
//
|
||||
// Moves a use statement from a nested scope to the top level.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// use std::collections::HashMap$0;
|
||||
// let map = HashMap::new();
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// use std::collections::HashMap;
|
||||
//
|
||||
// fn main() {
|
||||
// let map = HashMap::new();
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn move_use_to_top_level(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
let use_item = ctx.find_node_at_offset::<Use>()?;
|
||||
|
||||
if !is_use_item_movable_to_top(&use_item) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let scope = ImportScope::find_insert_use_container(use_item.syntax(), &ctx.sema)?;
|
||||
|
||||
acc.add(
|
||||
AssistId::refactor_rewrite("move_use_to_top_level"),
|
||||
"Move use statement to top-level",
|
||||
use_item.syntax().text_range(),
|
||||
|builder| {
|
||||
let file_id = ctx.vfs_file_id();
|
||||
move_use_to_top(file_id, builder, scope, &use_item);
|
||||
cleanup_original_location(file_id, builder, &use_item);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn is_use_item_movable_to_top(use_item: &Use) -> bool {
|
||||
use_item.syntax().ancestors().any(|ancestor| {
|
||||
BlockExpr::cast(ancestor.clone()).is_some()
|
||||
&& !ancestor.ancestors().any(|a| Module::cast(a).is_some())
|
||||
})
|
||||
}
|
||||
|
||||
fn move_use_to_top(
|
||||
file_id: FileId,
|
||||
builder: &mut SourceChangeBuilder,
|
||||
scope: ImportScope,
|
||||
use_item: &Use,
|
||||
) {
|
||||
let source_file = extract_source_file_from_scope(&scope);
|
||||
let mut editor = builder.make_editor(source_file.syntax());
|
||||
|
||||
let use_item = use_item.clone_for_update();
|
||||
remove_indents_from_use_item(&use_item);
|
||||
insert_use_item_at_top(&mut editor, source_file, &use_item);
|
||||
|
||||
builder.add_file_edits(file_id, editor);
|
||||
}
|
||||
|
||||
fn remove_indents_from_use_item(use_item: &Use) {
|
||||
use_item.dedent(use_item.indent_level());
|
||||
}
|
||||
|
||||
fn insert_use_item_at_top(editor: &mut SyntaxEditor, source_file: SourceFile, use_item: &Use) {
|
||||
let last_top_level_use: Option<Item> = find_last_top_level_use(&source_file);
|
||||
|
||||
match last_top_level_use {
|
||||
Some(last_use) => insert_after_existing_use(editor, &last_use, use_item),
|
||||
None => insert_at_file_beginning(editor, &source_file, use_item),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_source_file_from_scope(scope: &ImportScope) -> SourceFile {
|
||||
match &scope.kind {
|
||||
ImportScopeKind::File(file) => file.clone(),
|
||||
ImportScopeKind::Module(module) => module
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(SourceFile::cast)
|
||||
.expect("Module must be inside a source file"),
|
||||
ImportScopeKind::Block(block) => block
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(SourceFile::cast)
|
||||
.expect("Block must be inside a source file"),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_last_top_level_use(source_file: &SourceFile) -> Option<Item> {
|
||||
source_file.items().take_while(|item| Use::cast(item.syntax().clone()).is_some()).last()
|
||||
}
|
||||
|
||||
fn insert_after_existing_use(editor: &mut SyntaxEditor, last_use: &Item, use_item: &Use) {
|
||||
editor.insert_all(
|
||||
Position::after(last_use.syntax()),
|
||||
vec![make::tokens::whitespace("\n").into(), use_item.syntax().clone().into()],
|
||||
);
|
||||
}
|
||||
|
||||
fn insert_at_file_beginning(editor: &mut SyntaxEditor, source_file: &SourceFile, use_item: &Use) {
|
||||
if let Some(first_item) = source_file.items().next() {
|
||||
editor.insert_all(
|
||||
Position::before(first_item.syntax()),
|
||||
vec![use_item.syntax().clone().into(), make::tokens::whitespace("\n\n").into()],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup_original_location(file_id: FileId, builder: &mut SourceChangeBuilder, use_item: &Use) {
|
||||
if let Some(parent) = use_item.syntax().parent() {
|
||||
let mut editor = builder.make_editor(&parent);
|
||||
editor.delete(use_item.syntax());
|
||||
|
||||
if let Some(ws) = use_item
|
||||
.syntax()
|
||||
.next_sibling_or_token()
|
||||
.and_then(|token| token.into_token())
|
||||
.and_then(Whitespace::cast)
|
||||
.filter(|ws| ws.syntax().text().starts_with('\n'))
|
||||
{
|
||||
editor.delete(ws.syntax());
|
||||
}
|
||||
|
||||
builder.add_file_edits(file_id, editor);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
use super::*;
|
||||
|
||||
// 1. Basic functionality
|
||||
#[test]
|
||||
fn test_move_use_from_function() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_from_closure() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
let closure = || {
|
||||
use std::collections::HashMap$0;
|
||||
HashMap::new()
|
||||
};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let closure = || {
|
||||
HashMap::new()
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_from_block() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
{
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
{
|
||||
let map = HashMap::new();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Use statement variations
|
||||
#[test]
|
||||
fn test_move_use_with_rename() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
use std::collections::HashMap as Map$0;
|
||||
let map = Map::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::HashMap as Map;
|
||||
|
||||
fn main() {
|
||||
let map = Map::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_with_glob() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
use std::collections::*$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::*;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_with_grouped_imports() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
use std::collections::{HashMap, Vec}$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::{HashMap, Vec};
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_preserves_visibility() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
pub use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
pub use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_with_attributes() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
#[allow(unused)]
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
#[allow(unused)]
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_with_comments() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
// comment1
|
||||
// comment2
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
// comment1
|
||||
// comment2
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Context variations
|
||||
#[test]
|
||||
fn test_move_use_with_existing_imports() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn main() {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::fmt::Debug;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_from_method() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn bar(&self) {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::HashMap;
|
||||
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn bar(&self) {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_from_trait_method() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
trait Foo {
|
||||
fn bar(&self) {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::HashMap;
|
||||
|
||||
trait Foo {
|
||||
fn bar(&self) {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_ignores_other_local_uses() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
use std::fmt::Debug;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
use std::fmt::Debug;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_with_comments_above() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
// comment
|
||||
|
||||
fn main() {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
// comment
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Edge cases
|
||||
#[test]
|
||||
fn test_move_use_from_const() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
const FOO: usize = {
|
||||
use std::mem::size_of$0;
|
||||
size_of::<i32>()
|
||||
};
|
||||
"#,
|
||||
r#"
|
||||
use std::mem::size_of;
|
||||
|
||||
const FOO: usize = {
|
||||
size_of::<i32>()
|
||||
};
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_use_from_static() {
|
||||
check_assist(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
static FOO: usize = {
|
||||
use std::mem::size_of$0;
|
||||
size_of::<i32>()
|
||||
};
|
||||
"#,
|
||||
r#"
|
||||
use std::mem::size_of;
|
||||
|
||||
static FOO: usize = {
|
||||
size_of::<i32>()
|
||||
};
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Test utilities
|
||||
#[test]
|
||||
fn test_target_range() {
|
||||
check_assist_target(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
"use std::collections::HashMap;",
|
||||
);
|
||||
}
|
||||
|
||||
// 6. Negative tests - when assist should NOT be applicable
|
||||
#[test]
|
||||
fn test_not_applicable_when_use_already_at_top() {
|
||||
check_assist_not_applicable(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
use std::collections::HashMap$0;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_applicable_when_use_in_module_scope() {
|
||||
check_assist_not_applicable(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
mod foo {
|
||||
use std::collections::HashMap$0;
|
||||
|
||||
fn bar() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_applicable_in_module_function() {
|
||||
check_assist_not_applicable(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
mod a {
|
||||
fn foo() {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_applicable_when_not_in_use_statement() {
|
||||
check_assist_not_applicable(
|
||||
move_use_to_top_level,
|
||||
r#"
|
||||
fn main() {
|
||||
let map$0 = HashMap::new();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -196,6 +196,7 @@ mod handlers {
|
|||
mod move_guard;
|
||||
mod move_module_to_file;
|
||||
mod move_to_mod_rs;
|
||||
mod move_use_to_top_level;
|
||||
mod normalize_import;
|
||||
mod number_representation;
|
||||
mod promote_local_to_const;
|
||||
|
|
@ -338,6 +339,7 @@ mod handlers {
|
|||
move_guard::move_guard_to_arm_body,
|
||||
move_module_to_file::move_module_to_file,
|
||||
move_to_mod_rs::move_to_mod_rs,
|
||||
move_use_to_top_level::move_use_to_top_level,
|
||||
normalize_import::normalize_import,
|
||||
number_representation::reformat_number_literal,
|
||||
promote_local_to_const::promote_local_to_const,
|
||||
|
|
|
|||
|
|
@ -2826,6 +2826,26 @@ fn t() {}
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_move_use_to_top_level() {
|
||||
check_doc_test(
|
||||
"move_use_to_top_level",
|
||||
r#####"
|
||||
fn main() {
|
||||
use std::collections::HashMap$0;
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_normalize_import() {
|
||||
check_doc_test(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue