rust/crates/ra_assists/src/handlers/auto_import.rs

294 lines
6.8 KiB
Rust
Raw Normal View History

2020-02-07 08:10:19 -06:00
use ra_ide_db::imports_locator::ImportsLocator;
2020-02-09 07:30:27 -06:00
use ra_syntax::ast::{self, AstNode};
use crate::{
2020-02-09 07:30:27 -06:00
assist_ctx::{Assist, AssistCtx},
2020-02-07 15:35:34 -06:00
insert_use_statement, AssistId,
};
2020-02-07 08:12:51 -06:00
use std::collections::BTreeSet;
// Assist: auto_import
//
// If the name is unresolved, provides all possible imports for it.
//
// ```
// fn main() {
// let map = HashMap<|>::new();
// }
2020-02-06 11:10:25 -06:00
// # pub mod std { pub mod collections { pub struct HashMap { } } }
// ```
// ->
// ```
// use std::collections::HashMap;
//
// fn main() {
2020-02-06 11:10:25 -06:00
// let map = HashMap::new();
// }
2020-02-06 11:10:25 -06:00
// # pub mod std { pub mod collections { pub struct HashMap { } } }
// ```
2020-02-06 10:17:51 -06:00
pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
let path_under_caret: ast::Path = ctx.find_node_at_offset()?;
if path_under_caret.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
return None;
}
let module = path_under_caret.syntax().ancestors().find_map(ast::Module::cast);
let position = match module.and_then(|it| it.item_list()) {
Some(item_list) => item_list.syntax().clone(),
None => {
let current_file =
path_under_caret.syntax().ancestors().find_map(ast::SourceFile::cast)?;
current_file.syntax().clone()
}
};
2020-01-24 01:33:18 -06:00
let source_analyzer = ctx.source_analyzer(&position, None);
let module_with_name_to_import = source_analyzer.module()?;
let name_ref_to_import =
path_under_caret.syntax().descendants().find_map(ast::NameRef::cast)?;
if source_analyzer
.resolve_path(ctx.db, &name_ref_to_import.syntax().ancestors().find_map(ast::Path::cast)?)
.is_some()
{
2020-01-24 01:33:18 -06:00
return None;
}
let name_to_import = name_ref_to_import.syntax().to_string();
let proposed_imports = ImportsLocator::new(ctx.db)
2020-02-03 05:56:03 -06:00
.find_imports(&name_to_import)
2020-01-24 01:33:18 -06:00
.into_iter()
.filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def))
.filter(|use_path| !use_path.segments.is_empty())
.take(20)
2020-02-07 08:12:51 -06:00
.collect::<BTreeSet<_>>();
if proposed_imports.is_empty() {
return None;
}
2020-02-09 07:30:27 -06:00
let mut group = ctx.add_assist_group(format!("Import {}", name_to_import));
for import in proposed_imports {
group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
2020-02-09 09:13:29 -06:00
edit.target(path_to_import_syntax.text_range());
2020-02-09 07:30:27 -06:00
insert_use_statement(
&position,
&path_under_caret.syntax(),
2020-02-09 07:30:27 -06:00
&import,
edit.text_edit_builder(),
);
});
}
group.finish()
}
#[cfg(test)]
mod tests {
2020-02-09 09:13:29 -06:00
use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
2020-02-07 08:12:51 -06:00
use super::*;
2020-01-26 16:16:18 -06:00
#[test]
fn applicable_when_found_an_import() {
2020-02-06 10:17:51 -06:00
check_assist(
2020-01-26 16:16:18 -06:00
auto_import,
r"
<|>PubStruct
2020-01-26 16:16:18 -06:00
pub mod PubMod {
pub struct PubStruct;
}
",
r"
<|>use PubMod::PubStruct;
PubStruct
2020-01-26 16:16:18 -06:00
pub mod PubMod {
pub struct PubStruct;
}
2020-01-26 16:16:18 -06:00
",
);
}
2020-02-02 02:28:16 -06:00
#[test]
fn auto_imports_are_merged() {
2020-02-06 10:17:51 -06:00
check_assist(
2020-02-02 02:28:16 -06:00
auto_import,
r"
use PubMod::PubStruct1;
struct Test {
test: Pub<|>Struct2<u8>,
}
2020-02-02 02:28:16 -06:00
pub mod PubMod {
pub struct PubStruct1;
pub struct PubStruct2<T> {
_t: T,
}
2020-02-02 02:28:16 -06:00
}
",
r"
use PubMod::{PubStruct2, PubStruct1};
struct Test {
test: Pub<|>Struct2<u8>,
}
2020-02-02 02:28:16 -06:00
pub mod PubMod {
pub struct PubStruct1;
pub struct PubStruct2<T> {
_t: T,
}
2020-02-02 02:28:16 -06:00
}
",
);
}
#[test]
2020-01-26 16:16:18 -06:00
fn applicable_when_found_multiple_imports() {
2020-02-06 10:17:51 -06:00
check_assist(
auto_import,
2020-01-26 16:16:18 -06:00
r"
PubSt<|>ruct
2020-01-26 16:16:18 -06:00
pub mod PubMod1 {
pub struct PubStruct;
}
pub mod PubMod2 {
pub struct PubStruct;
}
pub mod PubMod3 {
pub struct PubStruct;
}
",
r"
use PubMod1::PubStruct;
PubSt<|>ruct
2020-01-26 16:16:18 -06:00
pub mod PubMod1 {
pub struct PubStruct;
}
pub mod PubMod2 {
pub struct PubStruct;
}
2020-01-26 16:16:18 -06:00
pub mod PubMod3 {
pub struct PubStruct;
}
",
);
}
#[test]
fn not_applicable_for_already_imported_types() {
2020-02-06 10:17:51 -06:00
check_assist_not_applicable(
2020-01-26 16:16:18 -06:00
auto_import,
r"
use PubMod::PubStruct;
PubStruct<|>
2020-01-26 16:16:18 -06:00
pub mod PubMod {
pub struct PubStruct;
}
",
);
}
#[test]
2020-01-26 16:16:18 -06:00
fn not_applicable_for_types_with_private_paths() {
2020-02-06 10:17:51 -06:00
check_assist_not_applicable(
auto_import,
2020-01-26 16:16:18 -06:00
r"
PrivateStruct<|>
pub mod PubMod {
struct PrivateStruct;
}
2020-01-26 16:16:18 -06:00
",
);
}
2020-01-26 16:16:18 -06:00
#[test]
fn not_applicable_when_no_imports_found() {
2020-02-06 10:17:51 -06:00
check_assist_not_applicable(
2020-01-26 16:16:18 -06:00
auto_import,
"
PubStruct<|>",
);
}
#[test]
fn not_applicable_in_import_statements() {
2020-02-06 10:17:51 -06:00
check_assist_not_applicable(
auto_import,
2020-01-26 16:16:18 -06:00
r"
use PubStruct<|>;
pub mod PubMod {
pub struct PubStruct;
}",
);
}
2020-02-01 14:13:02 -06:00
#[test]
fn function_import() {
2020-02-06 10:17:51 -06:00
check_assist(
2020-02-01 14:13:02 -06:00
auto_import,
r"
test_function<|>
pub mod PubMod {
pub fn test_function() {};
}
",
r"
use PubMod::test_function;
test_function<|>
pub mod PubMod {
pub fn test_function() {};
}
",
);
}
2020-02-09 09:13:29 -06:00
#[test]
fn auto_import_target() {
check_assist_target(
auto_import,
r"
struct AssistInfo {
group_label: Option<<|>GroupLabel>,
}
mod m { pub struct GroupLabel; }
",
"GroupLabel",
)
}
#[test]
fn not_applicable_when_path_start_is_imported() {
check_assist_not_applicable(
auto_import,
r"
pub mod mod1 {
pub mod mod2 {
pub mod mod3 {
pub struct TestStruct;
}
}
}
use mod1::mod2;
fn main() {
mod2::mod3::TestStruct<|>
}
",
);
}
}