Skip to content

Commit 256104d

Browse files
committed
Extract ImportAssets out of auto_import
1 parent 3d13939 commit 256104d

File tree

3 files changed

+273
-241
lines changed

3 files changed

+273
-241
lines changed

crates/assists/src/handlers/auto_import.rs

Lines changed: 18 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
1-
use std::collections::BTreeSet;
2-
3-
use either::Either;
4-
use hir::{
5-
AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6-
Type,
7-
};
8-
use ide_db::{imports_locator, RootDatabase};
9-
use insert_use::ImportScope;
10-
use rustc_hash::FxHashSet;
11-
use syntax::{
12-
ast::{self, AstNode},
13-
SyntaxNode,
14-
};
15-
161
use crate::{
17-
utils::insert_use, utils::mod_path_to_ast, AssistContext, AssistId, AssistKind, Assists,
18-
GroupLabel,
2+
utils::import_assets::{ImportAssets, ImportCandidate},
3+
utils::{insert_use, mod_path_to_ast, ImportScope},
4+
AssistContext, AssistId, AssistKind, Assists, GroupLabel,
195
};
206

217
// Assist: auto_import
@@ -38,16 +24,16 @@ use crate::{
3824
// # pub mod std { pub mod collections { pub struct HashMap { } } }
3925
// ```
4026
pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41-
let auto_import_assets = AutoImportAssets::new(ctx)?;
42-
let proposed_imports = auto_import_assets.search_for_imports(ctx);
27+
let auto_import_assets = ImportAssets::new(&ctx)?;
28+
let proposed_imports = auto_import_assets.search_for_imports(&ctx.sema, &ctx.config.insert_use);
4329
if proposed_imports.is_empty() {
4430
return None;
4531
}
4632

47-
let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
48-
let group = auto_import_assets.get_import_group_message();
33+
let range = ctx.sema.original_range(auto_import_assets.syntax_under_caret()).range;
34+
let group = import_group_message(auto_import_assets.import_candidate());
4935
let scope =
50-
ImportScope::find_insert_use_container(&auto_import_assets.syntax_under_caret, ctx)?;
36+
ImportScope::find_insert_use_container(auto_import_assets.syntax_under_caret(), ctx)?;
5137
let syntax = scope.as_syntax_node();
5238
for import in proposed_imports {
5339
acc.add_group(
@@ -65,227 +51,18 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
6551
Some(())
6652
}
6753

68-
#[derive(Debug)]
69-
struct AutoImportAssets {
70-
import_candidate: ImportCandidate,
71-
module_with_name_to_import: Module,
72-
syntax_under_caret: SyntaxNode,
73-
}
74-
75-
impl AutoImportAssets {
76-
fn new(ctx: &AssistContext) -> Option<Self> {
77-
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
78-
Self::for_regular_path(path_under_caret, &ctx)
79-
} else {
80-
Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx)
54+
fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
55+
let name = match import_candidate {
56+
ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
57+
ImportCandidate::QualifierStart(qualifier_start) => format!("Import {}", qualifier_start),
58+
ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => {
59+
format!("Import a trait for item {}", trait_assoc_item_name)
8160
}
82-
}
83-
84-
fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
85-
let syntax_under_caret = method_call.syntax().to_owned();
86-
let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
87-
Some(Self {
88-
import_candidate: ImportCandidate::for_method_call(&ctx.sema, &method_call)?,
89-
module_with_name_to_import,
90-
syntax_under_caret,
91-
})
92-
}
93-
94-
fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
95-
let syntax_under_caret = path_under_caret.syntax().to_owned();
96-
if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
97-
return None;
61+
ImportCandidate::TraitMethod(_, trait_method_name) => {
62+
format!("Import a trait for method {}", trait_method_name)
9863
}
99-
100-
let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
101-
Some(Self {
102-
import_candidate: ImportCandidate::for_regular_path(&ctx.sema, &path_under_caret)?,
103-
module_with_name_to_import,
104-
syntax_under_caret,
105-
})
106-
}
107-
108-
fn get_search_query(&self) -> &str {
109-
match &self.import_candidate {
110-
ImportCandidate::UnqualifiedName(name) => name,
111-
ImportCandidate::QualifierStart(qualifier_start) => qualifier_start,
112-
ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name,
113-
ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name,
114-
}
115-
}
116-
117-
fn get_import_group_message(&self) -> GroupLabel {
118-
let name = match &self.import_candidate {
119-
ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
120-
ImportCandidate::QualifierStart(qualifier_start) => {
121-
format!("Import {}", qualifier_start)
122-
}
123-
ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => {
124-
format!("Import a trait for item {}", trait_assoc_item_name)
125-
}
126-
ImportCandidate::TraitMethod(_, trait_method_name) => {
127-
format!("Import a trait for method {}", trait_method_name)
128-
}
129-
};
130-
GroupLabel(name)
131-
}
132-
133-
fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> {
134-
let _p = profile::span("auto_import::search_for_imports");
135-
let db = ctx.db();
136-
let current_crate = self.module_with_name_to_import.krate();
137-
imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
138-
.into_iter()
139-
.filter_map(|candidate| match &self.import_candidate {
140-
ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
141-
let located_assoc_item = match candidate {
142-
Either::Left(ModuleDef::Function(located_function)) => located_function
143-
.as_assoc_item(db)
144-
.map(|assoc| assoc.container(db))
145-
.and_then(Self::assoc_to_trait),
146-
Either::Left(ModuleDef::Const(located_const)) => located_const
147-
.as_assoc_item(db)
148-
.map(|assoc| assoc.container(db))
149-
.and_then(Self::assoc_to_trait),
150-
_ => None,
151-
}?;
152-
153-
let mut trait_candidates = FxHashSet::default();
154-
trait_candidates.insert(located_assoc_item.into());
155-
156-
assoc_item_type
157-
.iterate_path_candidates(
158-
db,
159-
current_crate,
160-
&trait_candidates,
161-
None,
162-
|_, assoc| Self::assoc_to_trait(assoc.container(db)),
163-
)
164-
.map(ModuleDef::from)
165-
.map(Either::Left)
166-
}
167-
ImportCandidate::TraitMethod(function_callee, _) => {
168-
let located_assoc_item =
169-
if let Either::Left(ModuleDef::Function(located_function)) = candidate {
170-
located_function
171-
.as_assoc_item(db)
172-
.map(|assoc| assoc.container(db))
173-
.and_then(Self::assoc_to_trait)
174-
} else {
175-
None
176-
}?;
177-
178-
let mut trait_candidates = FxHashSet::default();
179-
trait_candidates.insert(located_assoc_item.into());
180-
181-
function_callee
182-
.iterate_method_candidates(
183-
db,
184-
current_crate,
185-
&trait_candidates,
186-
None,
187-
|_, function| {
188-
Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
189-
},
190-
)
191-
.map(ModuleDef::from)
192-
.map(Either::Left)
193-
}
194-
_ => Some(candidate),
195-
})
196-
.filter_map(|candidate| match candidate {
197-
Either::Left(module_def) => self.module_with_name_to_import.find_use_path_prefixed(
198-
db,
199-
module_def,
200-
ctx.config.insert_use.prefix_kind,
201-
),
202-
Either::Right(macro_def) => self.module_with_name_to_import.find_use_path_prefixed(
203-
db,
204-
macro_def,
205-
ctx.config.insert_use.prefix_kind,
206-
),
207-
})
208-
.filter(|use_path| !use_path.segments.is_empty())
209-
.take(20)
210-
.collect::<BTreeSet<_>>()
211-
}
212-
213-
fn assoc_to_trait(assoc: AssocItemContainer) -> Option<Trait> {
214-
if let AssocItemContainer::Trait(extracted_trait) = assoc {
215-
Some(extracted_trait)
216-
} else {
217-
None
218-
}
219-
}
220-
}
221-
222-
#[derive(Debug)]
223-
enum ImportCandidate {
224-
/// Simple name like 'HashMap'
225-
UnqualifiedName(String),
226-
/// First part of the qualified name.
227-
/// For 'std::collections::HashMap', that will be 'std'.
228-
QualifierStart(String),
229-
/// A trait associated function (with no self parameter) or associated constant.
230-
/// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type
231-
/// and `String` is the `test_function`
232-
TraitAssocItem(Type, String),
233-
/// A trait method with self parameter.
234-
/// For 'test_enum.test_method()', `Type` is the `test_enum` expression type
235-
/// and `String` is the `test_method`
236-
TraitMethod(Type, String),
237-
}
238-
239-
impl ImportCandidate {
240-
fn for_method_call(
241-
sema: &Semantics<RootDatabase>,
242-
method_call: &ast::MethodCallExpr,
243-
) -> Option<Self> {
244-
if sema.resolve_method_call(method_call).is_some() {
245-
return None;
246-
}
247-
Some(Self::TraitMethod(
248-
sema.type_of_expr(&method_call.receiver()?)?,
249-
method_call.name_ref()?.syntax().to_string(),
250-
))
251-
}
252-
253-
fn for_regular_path(
254-
sema: &Semantics<RootDatabase>,
255-
path_under_caret: &ast::Path,
256-
) -> Option<Self> {
257-
if sema.resolve_path(path_under_caret).is_some() {
258-
return None;
259-
}
260-
261-
let segment = path_under_caret.segment()?;
262-
if let Some(qualifier) = path_under_caret.qualifier() {
263-
let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
264-
let qualifier_start_path =
265-
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
266-
if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
267-
let qualifier_resolution = if qualifier_start_path == qualifier {
268-
qualifier_start_resolution
269-
} else {
270-
sema.resolve_path(&qualifier)?
271-
};
272-
if let PathResolution::Def(ModuleDef::Adt(assoc_item_path)) = qualifier_resolution {
273-
Some(ImportCandidate::TraitAssocItem(
274-
assoc_item_path.ty(sema.db),
275-
segment.syntax().to_string(),
276-
))
277-
} else {
278-
None
279-
}
280-
} else {
281-
Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string()))
282-
}
283-
} else {
284-
Some(ImportCandidate::UnqualifiedName(
285-
segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(),
286-
))
287-
}
288-
}
64+
};
65+
GroupLabel(name)
28966
}
29067

29168
#[cfg(test)]

crates/assists/src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Assorted functions shared by several assists.
22
pub(crate) mod insert_use;
3+
pub(crate) mod import_assets;
34

45
use std::{iter, ops};
56

0 commit comments

Comments
 (0)