mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-06 10:18:53 +00:00
1699 lines
49 KiB
Rust
1699 lines
49 KiB
Rust
use std::iter;
|
|
|
|
use hir::{HirFileIdExt, ModuleSource};
|
|
use ide_db::{
|
|
assists::{AssistId, AssistKind},
|
|
base_db::FileId,
|
|
defs::{Definition, NameClass, NameRefClass},
|
|
helpers::item_name,
|
|
items_locator::items_with_name,
|
|
search::{FileReference, SearchScope},
|
|
FxHashMap, FxHashSet,
|
|
};
|
|
use itertools::Itertools;
|
|
use smallvec::SmallVec;
|
|
use stdx::format_to;
|
|
use syntax::{
|
|
algo::find_node_at_range,
|
|
ast::{
|
|
self,
|
|
edit::{AstNodeEdit, IndentLevel},
|
|
make, HasVisibility,
|
|
},
|
|
match_ast, ted, AstNode, SourceFile,
|
|
SyntaxKind::{self, WHITESPACE},
|
|
SyntaxNode, TextRange,
|
|
};
|
|
|
|
use crate::{AssistContext, Assists};
|
|
|
|
use super::remove_unused_param::range_to_remove;
|
|
|
|
// Assist: extract_module
|
|
//
|
|
// Extracts a selected region as separate module. All the references, visibility and imports are
|
|
// resolved.
|
|
//
|
|
// ```
|
|
// $0fn foo(name: i32) -> i32 {
|
|
// name + 1
|
|
// }$0
|
|
//
|
|
// fn bar(name: i32) -> i32 {
|
|
// name + 2
|
|
// }
|
|
// ```
|
|
// ->
|
|
// ```
|
|
// mod modname {
|
|
// pub(crate) fn foo(name: i32) -> i32 {
|
|
// name + 1
|
|
// }
|
|
// }
|
|
//
|
|
// fn bar(name: i32) -> i32 {
|
|
// name + 2
|
|
// }
|
|
// ```
|
|
pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
|
if ctx.has_empty_selection() {
|
|
return None;
|
|
}
|
|
|
|
let node = ctx.covering_element();
|
|
let node = match node {
|
|
syntax::NodeOrToken::Node(n) => n,
|
|
syntax::NodeOrToken::Token(t) => t.parent()?,
|
|
};
|
|
|
|
//If the selection is inside impl block, we need to place new module outside impl block,
|
|
//as impl blocks cannot contain modules
|
|
|
|
let mut impl_parent: Option<ast::Impl> = None;
|
|
let mut impl_child_count: usize = 0;
|
|
if let Some(parent_assoc_list) = node.parent() {
|
|
if let Some(parent_impl) = parent_assoc_list.parent() {
|
|
if let Some(impl_) = ast::Impl::cast(parent_impl) {
|
|
impl_child_count = parent_assoc_list.children().count();
|
|
impl_parent = Some(impl_);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut curr_parent_module: Option<ast::Module> = None;
|
|
if let Some(mod_syn_opt) = node.ancestors().find(|it| ast::Module::can_cast(it.kind())) {
|
|
curr_parent_module = ast::Module::cast(mod_syn_opt);
|
|
}
|
|
|
|
let mut module = extract_target(&node, ctx.selection_trimmed())?;
|
|
if module.body_items.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let old_item_indent = module.body_items[0].indent_level();
|
|
|
|
acc.add(
|
|
AssistId("extract_module", AssistKind::RefactorExtract),
|
|
"Extract Module",
|
|
module.text_range,
|
|
|builder| {
|
|
//This takes place in three steps:
|
|
//
|
|
//- Firstly, we will update the references(usages) e.g. converting a
|
|
// function call bar() to modname::bar(), and similarly for other items
|
|
//
|
|
//- Secondly, changing the visibility of each item inside the newly selected module
|
|
// i.e. making a fn a() {} to pub(crate) fn a() {}
|
|
//
|
|
//- Thirdly, resolving all the imports this includes removing paths from imports
|
|
// outside the module, shifting/cloning them inside new module, or shifting the imports, or making
|
|
// new import statements
|
|
|
|
//We are getting item usages and record_fields together, record_fields
|
|
//for change_visibility and usages for first point mentioned above in the process
|
|
let (usages_to_be_processed, record_fields) = module.get_usages_and_record_fields(ctx);
|
|
|
|
let import_paths_to_be_removed = module.resolve_imports(curr_parent_module, ctx);
|
|
module.change_visibility(record_fields);
|
|
|
|
let mut body_items: Vec<String> = Vec::new();
|
|
let mut items_to_be_processed: Vec<ast::Item> = module.body_items.clone();
|
|
|
|
let new_item_indent = if impl_parent.is_some() {
|
|
old_item_indent + 2
|
|
} else {
|
|
items_to_be_processed = [module.use_items.clone(), items_to_be_processed].concat();
|
|
old_item_indent + 1
|
|
};
|
|
|
|
for item in items_to_be_processed {
|
|
let item = item.indent(IndentLevel(1));
|
|
let mut indented_item = String::new();
|
|
format_to!(indented_item, "{new_item_indent}{item}");
|
|
body_items.push(indented_item);
|
|
}
|
|
|
|
let mut body = body_items.join("\n\n");
|
|
|
|
if let Some(impl_) = &impl_parent {
|
|
if let Some(self_ty) = impl_.self_ty() {
|
|
let impl_indent = old_item_indent + 1;
|
|
let mut impl_body_def = String::new();
|
|
format_to!(
|
|
impl_body_def,
|
|
"{impl_indent}impl {self_ty} {{\n{body}\n{impl_indent}}}",
|
|
);
|
|
body = impl_body_def;
|
|
|
|
// Add the import for enum/struct corresponding to given impl block
|
|
module.make_use_stmt_of_node_with_super(self_ty.syntax());
|
|
for item in module.use_items {
|
|
let item_indent = old_item_indent + 1;
|
|
body = format!("{item_indent}{item}\n\n{body}");
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut module_def = String::new();
|
|
let module_name = module.name;
|
|
format_to!(module_def, "mod {module_name} {{\n{body}\n{old_item_indent}}}");
|
|
|
|
let mut usages_to_be_updated_for_curr_file = vec![];
|
|
for usages_to_be_updated_for_file in usages_to_be_processed {
|
|
if usages_to_be_updated_for_file.0 == ctx.file_id() {
|
|
usages_to_be_updated_for_curr_file = usages_to_be_updated_for_file.1;
|
|
continue;
|
|
}
|
|
builder.edit_file(usages_to_be_updated_for_file.0);
|
|
for usage_to_be_processed in usages_to_be_updated_for_file.1 {
|
|
builder.replace(usage_to_be_processed.0, usage_to_be_processed.1)
|
|
}
|
|
}
|
|
|
|
builder.edit_file(ctx.file_id());
|
|
for usage_to_be_processed in usages_to_be_updated_for_curr_file {
|
|
builder.replace(usage_to_be_processed.0, usage_to_be_processed.1)
|
|
}
|
|
|
|
if let Some(impl_) = impl_parent {
|
|
// Remove complete impl block if it has only one child (as such it will be empty
|
|
// after deleting that child)
|
|
let node_to_be_removed = if impl_child_count == 1 {
|
|
impl_.syntax()
|
|
} else {
|
|
//Remove selected node
|
|
&node
|
|
};
|
|
|
|
builder.delete(node_to_be_removed.text_range());
|
|
// Remove preceding indentation from node
|
|
if let Some(range) = indent_range_before_given_node(node_to_be_removed) {
|
|
builder.delete(range);
|
|
}
|
|
|
|
builder.insert(impl_.syntax().text_range().end(), format!("\n\n{module_def}"));
|
|
} else {
|
|
for import_path_text_range in import_paths_to_be_removed {
|
|
if module.text_range.intersect(import_path_text_range).is_some() {
|
|
module.text_range = module.text_range.cover(import_path_text_range);
|
|
} else {
|
|
builder.delete(import_path_text_range);
|
|
}
|
|
}
|
|
|
|
builder.replace(module.text_range, module_def)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Module {
|
|
text_range: TextRange,
|
|
name: &'static str,
|
|
/// All items except use items.
|
|
body_items: Vec<ast::Item>,
|
|
/// Use items are kept separately as they help when the selection is inside an impl block,
|
|
/// we can directly take these items and keep them outside generated impl block inside
|
|
/// generated module.
|
|
use_items: Vec<ast::Item>,
|
|
}
|
|
|
|
fn extract_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Module> {
|
|
let selected_nodes = node
|
|
.children()
|
|
.filter(|node| selection_range.contains_range(node.text_range()))
|
|
.chain(iter::once(node.clone()));
|
|
let (use_items, body_items) = selected_nodes
|
|
.filter_map(ast::Item::cast)
|
|
.partition(|item| matches!(item, ast::Item::Use(..)));
|
|
|
|
Some(Module { text_range: selection_range, name: "modname", body_items, use_items })
|
|
}
|
|
|
|
impl Module {
|
|
fn get_usages_and_record_fields(
|
|
&self,
|
|
ctx: &AssistContext<'_>,
|
|
) -> (FxHashMap<FileId, Vec<(TextRange, String)>>, Vec<SyntaxNode>) {
|
|
let mut adt_fields = Vec::new();
|
|
let mut refs: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
|
|
|
|
//Here impl is not included as each item inside impl will be tied to the parent of
|
|
//implementing block(a struct, enum, etc), if the parent is in selected module, it will
|
|
//get updated by ADT section given below or if it is not, then we dont need to do any operation
|
|
for item in &self.body_items {
|
|
match_ast! {
|
|
match (item.syntax()) {
|
|
ast::Adt(it) => {
|
|
if let Some( nod ) = ctx.sema.to_def(&it) {
|
|
let node_def = Definition::Adt(nod);
|
|
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
|
|
|
|
//Enum Fields are not allowed to explicitly specify pub, it is implied
|
|
match it {
|
|
ast::Adt::Struct(x) => {
|
|
if let Some(field_list) = x.field_list() {
|
|
match field_list {
|
|
ast::FieldList::RecordFieldList(record_field_list) => {
|
|
record_field_list.fields().for_each(|record_field| {
|
|
adt_fields.push(record_field.syntax().clone());
|
|
});
|
|
},
|
|
ast::FieldList::TupleFieldList(tuple_field_list) => {
|
|
tuple_field_list.fields().for_each(|tuple_field| {
|
|
adt_fields.push(tuple_field.syntax().clone());
|
|
});
|
|
},
|
|
}
|
|
}
|
|
},
|
|
ast::Adt::Union(x) => {
|
|
if let Some(record_field_list) = x.record_field_list() {
|
|
record_field_list.fields().for_each(|record_field| {
|
|
adt_fields.push(record_field.syntax().clone());
|
|
});
|
|
}
|
|
},
|
|
ast::Adt::Enum(_) => {},
|
|
}
|
|
}
|
|
},
|
|
ast::TypeAlias(it) => {
|
|
if let Some( nod ) = ctx.sema.to_def(&it) {
|
|
let node_def = Definition::TypeAlias(nod);
|
|
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
|
|
}
|
|
},
|
|
ast::Const(it) => {
|
|
if let Some( nod ) = ctx.sema.to_def(&it) {
|
|
let node_def = Definition::Const(nod);
|
|
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
|
|
}
|
|
},
|
|
ast::Static(it) => {
|
|
if let Some( nod ) = ctx.sema.to_def(&it) {
|
|
let node_def = Definition::Static(nod);
|
|
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
|
|
}
|
|
},
|
|
ast::Fn(it) => {
|
|
if let Some( nod ) = ctx.sema.to_def(&it) {
|
|
let node_def = Definition::Function(nod);
|
|
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
|
|
}
|
|
},
|
|
ast::Macro(it) => {
|
|
if let Some(nod) = ctx.sema.to_def(&it) {
|
|
self.expand_and_group_usages_file_wise(ctx, Definition::Macro(nod), &mut refs);
|
|
}
|
|
},
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
(refs, adt_fields)
|
|
}
|
|
|
|
fn expand_and_group_usages_file_wise(
|
|
&self,
|
|
ctx: &AssistContext<'_>,
|
|
node_def: Definition,
|
|
refs_in_files: &mut FxHashMap<FileId, Vec<(TextRange, String)>>,
|
|
) {
|
|
for (file_id, references) in node_def.usages(&ctx.sema).all() {
|
|
let source_file = ctx.sema.parse(file_id);
|
|
let usages_in_file = references
|
|
.into_iter()
|
|
.filter_map(|usage| self.get_usage_to_be_processed(&source_file, usage));
|
|
refs_in_files.entry(file_id).or_default().extend(usages_in_file);
|
|
}
|
|
}
|
|
|
|
fn get_usage_to_be_processed(
|
|
&self,
|
|
source_file: &SourceFile,
|
|
FileReference { range, name, .. }: FileReference,
|
|
) -> Option<(TextRange, String)> {
|
|
let path: ast::Path = find_node_at_range(source_file.syntax(), range)?;
|
|
|
|
for desc in path.syntax().descendants() {
|
|
if desc.to_string() == name.syntax().to_string()
|
|
&& !self.text_range.contains_range(desc.text_range())
|
|
{
|
|
if let Some(name_ref) = ast::NameRef::cast(desc) {
|
|
let mod_name = self.name;
|
|
return Some((
|
|
name_ref.syntax().text_range(),
|
|
format!("{mod_name}::{name_ref}"),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn change_visibility(&mut self, record_fields: Vec<SyntaxNode>) {
|
|
let (mut replacements, record_field_parents, impls) =
|
|
get_replacements_for_visibility_change(&mut self.body_items, false);
|
|
|
|
let mut impl_items: Vec<ast::Item> = impls
|
|
.into_iter()
|
|
.flat_map(|impl_| impl_.syntax().descendants())
|
|
.filter_map(ast::Item::cast)
|
|
.collect();
|
|
|
|
let (mut impl_item_replacements, _, _) =
|
|
get_replacements_for_visibility_change(&mut impl_items, true);
|
|
|
|
replacements.append(&mut impl_item_replacements);
|
|
|
|
for (_, field_owner) in record_field_parents {
|
|
for desc in field_owner.descendants().filter_map(ast::RecordField::cast) {
|
|
let is_record_field_present =
|
|
record_fields.clone().into_iter().any(|x| x.to_string() == desc.to_string());
|
|
if is_record_field_present {
|
|
replacements.push((desc.visibility(), desc.syntax().clone()));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (vis, syntax) in replacements {
|
|
let item = syntax.children_with_tokens().find(|node_or_token| {
|
|
match node_or_token.kind() {
|
|
// We're skipping comments, doc comments, and attribute macros that may precede the keyword
|
|
// that the visibility should be placed before.
|
|
SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE => false,
|
|
_ => true,
|
|
}
|
|
});
|
|
|
|
add_change_vis(vis, item);
|
|
}
|
|
}
|
|
|
|
fn resolve_imports(
|
|
&mut self,
|
|
module: Option<ast::Module>,
|
|
ctx: &AssistContext<'_>,
|
|
) -> Vec<TextRange> {
|
|
let mut imports_to_remove = vec![];
|
|
let mut node_set = FxHashSet::default();
|
|
|
|
for item in self.body_items.clone() {
|
|
item.syntax()
|
|
.descendants()
|
|
.filter_map(|x| {
|
|
if let Some(name) = ast::Name::cast(x.clone()) {
|
|
NameClass::classify(&ctx.sema, &name).and_then(|nc| match nc {
|
|
NameClass::Definition(def) => Some((name.syntax().clone(), def)),
|
|
_ => None,
|
|
})
|
|
} else if let Some(name_ref) = ast::NameRef::cast(x) {
|
|
NameRefClass::classify(&ctx.sema, &name_ref).and_then(|nc| match nc {
|
|
NameRefClass::Definition(def) => Some((name_ref.syntax().clone(), def)),
|
|
_ => None,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.for_each(|(node, def)| {
|
|
if node_set.insert(node.to_string()) {
|
|
if let Some(import) = self.process_def_in_sel(def, &node, &module, ctx) {
|
|
check_intersection_and_push(&mut imports_to_remove, import);
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
imports_to_remove
|
|
}
|
|
|
|
fn process_def_in_sel(
|
|
&mut self,
|
|
def: Definition,
|
|
use_node: &SyntaxNode,
|
|
curr_parent_module: &Option<ast::Module>,
|
|
ctx: &AssistContext<'_>,
|
|
) -> Option<TextRange> {
|
|
//We only need to find in the current file
|
|
let selection_range = ctx.selection_trimmed();
|
|
let file_id = ctx.file_id();
|
|
let usage_res = def.usages(&ctx.sema).in_scope(&SearchScope::single_file(file_id)).all();
|
|
let file = ctx.sema.parse(file_id);
|
|
|
|
// track uses which does not exists in `Use`
|
|
let mut exists_inside_sel = false;
|
|
let mut exists_outside_sel = false;
|
|
'outside: for (_, refs) in usage_res.iter() {
|
|
for x in refs
|
|
.iter()
|
|
.filter(|x| find_node_at_range::<ast::Use>(file.syntax(), x.range).is_none())
|
|
.filter_map(|x| find_node_at_range::<ast::Path>(file.syntax(), x.range))
|
|
{
|
|
let in_selectin = selection_range.contains_range(x.syntax().text_range());
|
|
exists_inside_sel |= in_selectin;
|
|
exists_outside_sel |= !in_selectin;
|
|
|
|
if exists_inside_sel && exists_outside_sel {
|
|
break 'outside;
|
|
}
|
|
}
|
|
}
|
|
|
|
let (def_in_mod, def_out_sel) =
|
|
check_def_in_mod_and_out_sel(def, ctx, curr_parent_module, selection_range, file_id);
|
|
|
|
// Find use stmt that use def in current file
|
|
let use_stmt: Option<ast::Use> = usage_res
|
|
.into_iter()
|
|
.filter(|(use_file_id, _)| *use_file_id == file_id)
|
|
.flat_map(|(_, refs)| refs.into_iter().rev())
|
|
.find_map(|fref| find_node_at_range(file.syntax(), fref.range));
|
|
|
|
let mut use_tree_str_opt: Option<Vec<ast::Path>> = None;
|
|
//Exists inside and outside selection
|
|
// - Use stmt for item is present -> get the use_tree_str and reconstruct the path in new
|
|
// module
|
|
// - Use stmt for item is not present ->
|
|
//If it is not found, the definition is either ported inside new module or it stays
|
|
//outside:
|
|
//- Def is inside: Nothing to import
|
|
//- Def is outside: Import it inside with super
|
|
|
|
//Exists inside selection but not outside -> Check for the import of it in original module,
|
|
//get the use_tree_str, reconstruct the use stmt in new module
|
|
|
|
let mut import_path_to_be_removed: Option<TextRange> = None;
|
|
if exists_inside_sel && exists_outside_sel {
|
|
//Changes to be made only inside new module
|
|
|
|
//If use_stmt exists, find the use_tree_str, reconstruct it inside new module
|
|
//If not, insert a use stmt with super and the given nameref
|
|
match self.process_use_stmt_for_import_resolve(use_stmt, use_node) {
|
|
Some((use_tree_str, _)) => use_tree_str_opt = Some(use_tree_str),
|
|
None if def_in_mod && def_out_sel => {
|
|
//Considered only after use_stmt is not present
|
|
//def_in_mod && def_out_sel | exists_outside_sel(exists_inside_sel =
|
|
//true for all cases)
|
|
// false | false -> Do nothing
|
|
// false | true -> If source is in selection -> nothing to do, If source is outside
|
|
// mod -> ust_stmt transversal
|
|
// true | false -> super import insertion
|
|
// true | true -> super import insertion
|
|
self.make_use_stmt_of_node_with_super(use_node);
|
|
}
|
|
None => {}
|
|
}
|
|
} else if exists_inside_sel && !exists_outside_sel {
|
|
//Changes to be made inside new module, and remove import from outside
|
|
|
|
if let Some((mut use_tree_str, text_range_opt)) =
|
|
self.process_use_stmt_for_import_resolve(use_stmt, use_node)
|
|
{
|
|
if let Some(text_range) = text_range_opt {
|
|
import_path_to_be_removed = Some(text_range);
|
|
}
|
|
|
|
if def_in_mod && def_out_sel {
|
|
if let Some(first_path_in_use_tree) = use_tree_str.last() {
|
|
let first_path_in_use_tree_str = first_path_in_use_tree.to_string();
|
|
if !first_path_in_use_tree_str.contains("super")
|
|
&& !first_path_in_use_tree_str.contains("crate")
|
|
{
|
|
let super_path = make::ext::ident_path("super");
|
|
use_tree_str.push(super_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
use_tree_str_opt = Some(use_tree_str);
|
|
} else if def_in_mod && def_out_sel {
|
|
self.make_use_stmt_of_node_with_super(use_node);
|
|
}
|
|
}
|
|
|
|
if let Some(use_tree_str) = use_tree_str_opt {
|
|
let mut use_tree_str = use_tree_str;
|
|
use_tree_str.reverse();
|
|
|
|
if exists_outside_sel || !exists_inside_sel || !def_in_mod || !def_out_sel {
|
|
if let Some(first_path_in_use_tree) = use_tree_str.first() {
|
|
if first_path_in_use_tree.to_string().contains("super") {
|
|
use_tree_str.insert(0, make::ext::ident_path("super"));
|
|
}
|
|
}
|
|
}
|
|
|
|
let use_ =
|
|
make::use_(None, make::use_tree(make::join_paths(use_tree_str), None, None, false));
|
|
let item = ast::Item::from(use_);
|
|
|
|
let is_item = match def {
|
|
Definition::Macro(_) => true,
|
|
Definition::Module(_) => true,
|
|
Definition::Function(_) => true,
|
|
Definition::Adt(_) => true,
|
|
Definition::Const(_) => true,
|
|
Definition::Static(_) => true,
|
|
Definition::Trait(_) => true,
|
|
Definition::TraitAlias(_) => true,
|
|
Definition::TypeAlias(_) => true,
|
|
_ => false,
|
|
};
|
|
|
|
if def_out_sel || !is_item {
|
|
self.use_items.insert(0, item.clone());
|
|
}
|
|
}
|
|
|
|
import_path_to_be_removed
|
|
}
|
|
|
|
fn make_use_stmt_of_node_with_super(&mut self, node_syntax: &SyntaxNode) -> ast::Item {
|
|
let super_path = make::ext::ident_path("super");
|
|
let node_path = make::ext::ident_path(&node_syntax.to_string());
|
|
let use_ = make::use_(
|
|
None,
|
|
make::use_tree(make::join_paths(vec![super_path, node_path]), None, None, false),
|
|
);
|
|
|
|
let item = ast::Item::from(use_);
|
|
self.use_items.insert(0, item.clone());
|
|
item
|
|
}
|
|
|
|
fn process_use_stmt_for_import_resolve(
|
|
&self,
|
|
use_stmt: Option<ast::Use>,
|
|
node_syntax: &SyntaxNode,
|
|
) -> Option<(Vec<ast::Path>, Option<TextRange>)> {
|
|
let use_stmt = use_stmt?;
|
|
for path_seg in use_stmt.syntax().descendants().filter_map(ast::PathSegment::cast) {
|
|
if path_seg.syntax().to_string() == node_syntax.to_string() {
|
|
let mut use_tree_str = vec![path_seg.parent_path()];
|
|
get_use_tree_paths_from_path(path_seg.parent_path(), &mut use_tree_str);
|
|
|
|
//Here we are looking for use_tree with same string value as node
|
|
//passed above as the range_to_remove function looks for a comma and
|
|
//then includes it in the text range to remove it. But the comma only
|
|
//appears at the use_tree level
|
|
for use_tree in path_seg.syntax().ancestors().filter_map(ast::UseTree::cast) {
|
|
if use_tree.syntax().to_string() == node_syntax.to_string() {
|
|
return Some((use_tree_str, Some(range_to_remove(use_tree.syntax()))));
|
|
}
|
|
}
|
|
|
|
return Some((use_tree_str, None));
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
fn check_intersection_and_push(
|
|
import_paths_to_be_removed: &mut Vec<TextRange>,
|
|
mut import_path: TextRange,
|
|
) {
|
|
// Text ranges received here for imports are extended to the
|
|
// next/previous comma which can cause intersections among them
|
|
// and later deletion of these can cause panics similar
|
|
// to reported in #11766. So to mitigate it, we
|
|
// check for intersection between all current members
|
|
// and combine all such ranges into one.
|
|
let s: SmallVec<[_; 2]> = import_paths_to_be_removed
|
|
.iter_mut()
|
|
.positions(|it| it.intersect(import_path).is_some())
|
|
.collect();
|
|
for pos in s.into_iter().rev() {
|
|
let intersecting_path = import_paths_to_be_removed.swap_remove(pos);
|
|
import_path = import_path.cover(intersecting_path);
|
|
}
|
|
import_paths_to_be_removed.push(import_path);
|
|
}
|
|
|
|
fn check_def_in_mod_and_out_sel(
|
|
def: Definition,
|
|
ctx: &AssistContext<'_>,
|
|
curr_parent_module: &Option<ast::Module>,
|
|
selection_range: TextRange,
|
|
curr_file_id: FileId,
|
|
) -> (bool, bool) {
|
|
macro_rules! check_item {
|
|
($x:ident) => {
|
|
if let Some(source) = $x.source(ctx.db()) {
|
|
let have_same_parent = if let Some(ast_module) = &curr_parent_module {
|
|
ctx.sema.to_module_def(ast_module).is_some_and(|it| it == $x.module(ctx.db()))
|
|
} else {
|
|
source.file_id.original_file(ctx.db()) == curr_file_id
|
|
};
|
|
|
|
let in_sel = !selection_range.contains_range(source.value.syntax().text_range());
|
|
return (have_same_parent, in_sel);
|
|
}
|
|
};
|
|
}
|
|
|
|
match def {
|
|
Definition::Module(x) => {
|
|
let source = x.definition_source(ctx.db());
|
|
let have_same_parent = match (&curr_parent_module, x.parent(ctx.db())) {
|
|
(Some(ast_module), Some(hir_module)) => {
|
|
ctx.sema.to_module_def(ast_module).is_some_and(|it| it == hir_module)
|
|
}
|
|
_ => source.file_id.original_file(ctx.db()) == curr_file_id,
|
|
};
|
|
|
|
if have_same_parent {
|
|
if let ModuleSource::Module(module_) = source.value {
|
|
let in_sel = !selection_range.contains_range(module_.syntax().text_range());
|
|
return (have_same_parent, in_sel);
|
|
}
|
|
}
|
|
|
|
return (have_same_parent, false);
|
|
}
|
|
Definition::Function(x) => check_item!(x),
|
|
Definition::Adt(x) => check_item!(x),
|
|
Definition::Variant(x) => check_item!(x),
|
|
Definition::Const(x) => check_item!(x),
|
|
Definition::Static(x) => check_item!(x),
|
|
Definition::Trait(x) => check_item!(x),
|
|
Definition::TypeAlias(x) => check_item!(x),
|
|
_ => {}
|
|
}
|
|
|
|
(false, false)
|
|
}
|
|
|
|
fn get_replacements_for_visibility_change(
|
|
items: &mut [ast::Item],
|
|
is_clone_for_updated: bool,
|
|
) -> (
|
|
Vec<(Option<ast::Visibility>, SyntaxNode)>,
|
|
Vec<(Option<ast::Visibility>, SyntaxNode)>,
|
|
Vec<ast::Impl>,
|
|
) {
|
|
let mut replacements = Vec::new();
|
|
let mut record_field_parents = Vec::new();
|
|
let mut impls = Vec::new();
|
|
|
|
for item in items {
|
|
if !is_clone_for_updated {
|
|
*item = item.clone_for_update();
|
|
}
|
|
//Use stmts are ignored
|
|
macro_rules! push_to_replacement {
|
|
($it:ident) => {
|
|
replacements.push(($it.visibility(), $it.syntax().clone()))
|
|
};
|
|
}
|
|
|
|
match item {
|
|
ast::Item::Const(it) => push_to_replacement!(it),
|
|
ast::Item::Enum(it) => push_to_replacement!(it),
|
|
ast::Item::ExternCrate(it) => push_to_replacement!(it),
|
|
ast::Item::Fn(it) => push_to_replacement!(it),
|
|
//Associated item's visibility should not be changed
|
|
ast::Item::Impl(it) if it.for_token().is_none() => impls.push(it.clone()),
|
|
ast::Item::MacroDef(it) => push_to_replacement!(it),
|
|
ast::Item::Module(it) => push_to_replacement!(it),
|
|
ast::Item::Static(it) => push_to_replacement!(it),
|
|
ast::Item::Struct(it) => {
|
|
push_to_replacement!(it);
|
|
record_field_parents.push((it.visibility(), it.syntax().clone()));
|
|
}
|
|
ast::Item::Trait(it) => push_to_replacement!(it),
|
|
ast::Item::TypeAlias(it) => push_to_replacement!(it),
|
|
ast::Item::Union(it) => {
|
|
push_to_replacement!(it);
|
|
record_field_parents.push((it.visibility(), it.syntax().clone()));
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
(replacements, record_field_parents, impls)
|
|
}
|
|
|
|
fn get_use_tree_paths_from_path(
|
|
path: ast::Path,
|
|
use_tree_str: &mut Vec<ast::Path>,
|
|
) -> Option<&mut Vec<ast::Path>> {
|
|
path.syntax()
|
|
.ancestors()
|
|
.filter(|x| x.to_string() != path.to_string())
|
|
.filter_map(ast::UseTree::cast)
|
|
.find_map(|use_tree| {
|
|
if let Some(upper_tree_path) = use_tree.path() {
|
|
if upper_tree_path.to_string() != path.to_string() {
|
|
use_tree_str.push(upper_tree_path.clone());
|
|
get_use_tree_paths_from_path(upper_tree_path, use_tree_str);
|
|
return Some(use_tree);
|
|
}
|
|
}
|
|
None
|
|
})?;
|
|
|
|
Some(use_tree_str)
|
|
}
|
|
|
|
fn add_change_vis(vis: Option<ast::Visibility>, node_or_token_opt: Option<syntax::SyntaxElement>) {
|
|
if vis.is_none() {
|
|
if let Some(node_or_token) = node_or_token_opt {
|
|
let pub_crate_vis = make::visibility_pub_crate().clone_for_update();
|
|
ted::insert(ted::Position::before(node_or_token), pub_crate_vis.syntax());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn indent_range_before_given_node(node: &SyntaxNode) -> Option<TextRange> {
|
|
node.siblings_with_tokens(syntax::Direction::Prev)
|
|
.find(|x| x.kind() == WHITESPACE)
|
|
.map(|x| x.text_range())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_not_applicable_without_selection() {
|
|
check_assist_not_applicable(
|
|
extract_module,
|
|
r"
|
|
$0pub struct PublicStruct {
|
|
field: i32,
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_module() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod thirdpartycrate {
|
|
pub mod nest {
|
|
pub struct SomeType;
|
|
pub struct SomeType2;
|
|
}
|
|
pub struct SomeType1;
|
|
}
|
|
|
|
mod bar {
|
|
use crate::thirdpartycrate::{nest::{SomeType, SomeType2}, SomeType1};
|
|
|
|
pub struct PublicStruct {
|
|
field: PrivateStruct,
|
|
field1: SomeType1,
|
|
}
|
|
|
|
impl PublicStruct {
|
|
pub fn new() -> Self {
|
|
Self { field: PrivateStruct::new(), field1: SomeType1 }
|
|
}
|
|
}
|
|
|
|
fn foo() {
|
|
let _s = PrivateStruct::new();
|
|
let _a = bar();
|
|
}
|
|
|
|
$0struct PrivateStruct {
|
|
inner: SomeType,
|
|
}
|
|
|
|
pub struct PrivateStruct1 {
|
|
pub inner: i32,
|
|
}
|
|
|
|
impl PrivateStruct {
|
|
fn new() -> Self {
|
|
PrivateStruct { inner: SomeType }
|
|
}
|
|
}
|
|
|
|
fn bar() -> i32 {
|
|
2
|
|
}$0
|
|
}
|
|
",
|
|
r"
|
|
mod thirdpartycrate {
|
|
pub mod nest {
|
|
pub struct SomeType;
|
|
pub struct SomeType2;
|
|
}
|
|
pub struct SomeType1;
|
|
}
|
|
|
|
mod bar {
|
|
use crate::thirdpartycrate::{nest::{SomeType2}, SomeType1};
|
|
|
|
pub struct PublicStruct {
|
|
field: modname::PrivateStruct,
|
|
field1: SomeType1,
|
|
}
|
|
|
|
impl PublicStruct {
|
|
pub fn new() -> Self {
|
|
Self { field: modname::PrivateStruct::new(), field1: SomeType1 }
|
|
}
|
|
}
|
|
|
|
fn foo() {
|
|
let _s = modname::PrivateStruct::new();
|
|
let _a = modname::bar();
|
|
}
|
|
|
|
mod modname {
|
|
use crate::thirdpartycrate::nest::SomeType;
|
|
|
|
pub(crate) struct PrivateStruct {
|
|
pub(crate) inner: SomeType,
|
|
}
|
|
|
|
pub struct PrivateStruct1 {
|
|
pub inner: i32,
|
|
}
|
|
|
|
impl PrivateStruct {
|
|
pub(crate) fn new() -> Self {
|
|
PrivateStruct { inner: SomeType }
|
|
}
|
|
}
|
|
|
|
pub(crate) fn bar() -> i32 {
|
|
2
|
|
}
|
|
}
|
|
}
|
|
",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_module_for_function_only() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
$0fn foo(name: i32) -> i32 {
|
|
name + 1
|
|
}$0
|
|
|
|
fn bar(name: i32) -> i32 {
|
|
name + 2
|
|
}
|
|
",
|
|
r"
|
|
mod modname {
|
|
pub(crate) fn foo(name: i32) -> i32 {
|
|
name + 1
|
|
}
|
|
}
|
|
|
|
fn bar(name: i32) -> i32 {
|
|
name + 2
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_module_for_impl_having_corresponding_adt_in_selection() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod impl_play {
|
|
$0struct A {}
|
|
|
|
impl A {
|
|
pub fn new_a() -> i32 {
|
|
2
|
|
}
|
|
}$0
|
|
|
|
fn a() {
|
|
let _a = A::new_a();
|
|
}
|
|
}
|
|
",
|
|
r"
|
|
mod impl_play {
|
|
mod modname {
|
|
pub(crate) struct A {}
|
|
|
|
impl A {
|
|
pub fn new_a() -> i32 {
|
|
2
|
|
}
|
|
}
|
|
}
|
|
|
|
fn a() {
|
|
let _a = modname::A::new_a();
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_import_resolve_when_its_only_inside_selection() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod foo {
|
|
pub struct PrivateStruct;
|
|
pub struct PrivateStruct1;
|
|
}
|
|
|
|
mod bar {
|
|
use super::foo::{PrivateStruct, PrivateStruct1};
|
|
|
|
$0struct Strukt {
|
|
field: PrivateStruct,
|
|
}$0
|
|
|
|
struct Strukt1 {
|
|
field: PrivateStruct1,
|
|
}
|
|
}
|
|
",
|
|
r"
|
|
mod foo {
|
|
pub struct PrivateStruct;
|
|
pub struct PrivateStruct1;
|
|
}
|
|
|
|
mod bar {
|
|
use super::foo::{PrivateStruct1};
|
|
|
|
mod modname {
|
|
use super::super::foo::PrivateStruct;
|
|
|
|
pub(crate) struct Strukt {
|
|
pub(crate) field: PrivateStruct,
|
|
}
|
|
}
|
|
|
|
struct Strukt1 {
|
|
field: PrivateStruct1,
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_import_resolve_when_its_inside_and_outside_selection_and_source_not_in_same_mod() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod foo {
|
|
pub struct PrivateStruct;
|
|
}
|
|
|
|
mod bar {
|
|
use super::foo::PrivateStruct;
|
|
|
|
$0struct Strukt {
|
|
field: PrivateStruct,
|
|
}$0
|
|
|
|
struct Strukt1 {
|
|
field: PrivateStruct,
|
|
}
|
|
}
|
|
",
|
|
r"
|
|
mod foo {
|
|
pub struct PrivateStruct;
|
|
}
|
|
|
|
mod bar {
|
|
use super::foo::PrivateStruct;
|
|
|
|
mod modname {
|
|
use super::super::foo::PrivateStruct;
|
|
|
|
pub(crate) struct Strukt {
|
|
pub(crate) field: PrivateStruct,
|
|
}
|
|
}
|
|
|
|
struct Strukt1 {
|
|
field: PrivateStruct,
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_import_resolve_when_its_inside_and_outside_selection_and_source_is_in_same_mod() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod bar {
|
|
pub struct PrivateStruct;
|
|
|
|
$0struct Strukt {
|
|
field: PrivateStruct,
|
|
}$0
|
|
|
|
struct Strukt1 {
|
|
field: PrivateStruct,
|
|
}
|
|
}
|
|
",
|
|
r"
|
|
mod bar {
|
|
pub struct PrivateStruct;
|
|
|
|
mod modname {
|
|
use super::PrivateStruct;
|
|
|
|
pub(crate) struct Strukt {
|
|
pub(crate) field: PrivateStruct,
|
|
}
|
|
}
|
|
|
|
struct Strukt1 {
|
|
field: PrivateStruct,
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_module_for_corresponding_adt_of_impl_present_in_same_mod_but_not_in_selection()
|
|
{
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod impl_play {
|
|
struct A {}
|
|
|
|
$0impl A {
|
|
pub fn new_a() -> i32 {
|
|
2
|
|
}
|
|
}$0
|
|
|
|
fn a() {
|
|
let _a = A::new_a();
|
|
}
|
|
}
|
|
",
|
|
r"
|
|
mod impl_play {
|
|
struct A {}
|
|
|
|
mod modname {
|
|
use super::A;
|
|
|
|
impl A {
|
|
pub fn new_a() -> i32 {
|
|
2
|
|
}
|
|
}
|
|
}
|
|
|
|
fn a() {
|
|
let _a = A::new_a();
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_module_for_impl_not_having_corresponding_adt_in_selection_and_not_in_same_mod_but_with_super(
|
|
) {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod foo {
|
|
pub struct A {}
|
|
}
|
|
mod impl_play {
|
|
use super::foo::A;
|
|
|
|
$0impl A {
|
|
pub fn new_a() -> i32 {
|
|
2
|
|
}
|
|
}$0
|
|
|
|
fn a() {
|
|
let _a = A::new_a();
|
|
}
|
|
}
|
|
",
|
|
r"
|
|
mod foo {
|
|
pub struct A {}
|
|
}
|
|
mod impl_play {
|
|
use super::foo::A;
|
|
|
|
mod modname {
|
|
use super::super::foo::A;
|
|
|
|
impl A {
|
|
pub fn new_a() -> i32 {
|
|
2
|
|
}
|
|
}
|
|
}
|
|
|
|
fn a() {
|
|
let _a = A::new_a();
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_import_resolve_for_trait_bounds_on_function() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod impl_play2 {
|
|
trait JustATrait {}
|
|
|
|
$0struct A {}
|
|
|
|
fn foo<T: JustATrait>(arg: T) -> T {
|
|
arg
|
|
}
|
|
|
|
impl JustATrait for A {}
|
|
|
|
fn bar() {
|
|
let a = A {};
|
|
foo(a);
|
|
}$0
|
|
}
|
|
",
|
|
r"
|
|
mod impl_play2 {
|
|
trait JustATrait {}
|
|
|
|
mod modname {
|
|
use super::JustATrait;
|
|
|
|
pub(crate) struct A {}
|
|
|
|
pub(crate) fn foo<T: JustATrait>(arg: T) -> T {
|
|
arg
|
|
}
|
|
|
|
impl JustATrait for A {}
|
|
|
|
pub(crate) fn bar() {
|
|
let a = A {};
|
|
foo(a);
|
|
}
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_module_for_module() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod impl_play2 {
|
|
$0mod impl_play {
|
|
pub struct A {}
|
|
}$0
|
|
}
|
|
",
|
|
r"
|
|
mod impl_play2 {
|
|
mod modname {
|
|
pub(crate) mod impl_play {
|
|
pub struct A {}
|
|
}
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_module_with_multiple_files() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
//- /main.rs
|
|
mod foo;
|
|
|
|
use foo::PrivateStruct;
|
|
|
|
pub struct Strukt {
|
|
field: PrivateStruct,
|
|
}
|
|
|
|
fn main() {
|
|
$0struct Strukt1 {
|
|
field: Strukt,
|
|
}$0
|
|
}
|
|
//- /foo.rs
|
|
pub struct PrivateStruct;
|
|
",
|
|
r"
|
|
mod foo;
|
|
|
|
use foo::PrivateStruct;
|
|
|
|
pub struct Strukt {
|
|
field: PrivateStruct,
|
|
}
|
|
|
|
fn main() {
|
|
mod modname {
|
|
use super::Strukt;
|
|
|
|
pub(crate) struct Strukt1 {
|
|
pub(crate) field: Strukt,
|
|
}
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_module_macro_rules() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
$0macro_rules! m {
|
|
() => {};
|
|
}$0
|
|
m! {}
|
|
",
|
|
r"
|
|
mod modname {
|
|
macro_rules! m {
|
|
() => {};
|
|
}
|
|
}
|
|
modname::m! {}
|
|
",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_do_not_apply_visibility_modifier_to_trait_impl_items() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
trait ATrait {
|
|
fn function();
|
|
}
|
|
|
|
struct A {}
|
|
|
|
$0impl ATrait for A {
|
|
fn function() {}
|
|
}$0
|
|
",
|
|
r"
|
|
trait ATrait {
|
|
fn function();
|
|
}
|
|
|
|
struct A {}
|
|
|
|
mod modname {
|
|
use super::A;
|
|
|
|
use super::ATrait;
|
|
|
|
impl ATrait for A {
|
|
fn function() {}
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_if_inside_impl_block_generate_module_outside() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
struct A {}
|
|
|
|
impl A {
|
|
$0fn foo() {}$0
|
|
fn bar() {}
|
|
}
|
|
",
|
|
r"
|
|
struct A {}
|
|
|
|
impl A {
|
|
fn bar() {}
|
|
}
|
|
|
|
mod modname {
|
|
use super::A;
|
|
|
|
impl A {
|
|
pub(crate) fn foo() {}
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_if_inside_impl_block_generate_module_outside_but_impl_block_having_one_child() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
struct A {}
|
|
struct B {}
|
|
|
|
impl A {
|
|
$0fn foo(x: B) {}$0
|
|
}
|
|
",
|
|
r"
|
|
struct A {}
|
|
struct B {}
|
|
|
|
mod modname {
|
|
use super::B;
|
|
|
|
use super::A;
|
|
|
|
impl A {
|
|
pub(crate) fn foo(x: B) {}
|
|
}
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_issue_11766() {
|
|
//https://github.com/rust-lang/rust-analyzer/issues/11766
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
mod x {
|
|
pub struct Foo;
|
|
pub struct Bar;
|
|
}
|
|
|
|
use x::{Bar, Foo};
|
|
|
|
$0type A = (Foo, Bar);$0
|
|
",
|
|
r"
|
|
mod x {
|
|
pub struct Foo;
|
|
pub struct Bar;
|
|
}
|
|
|
|
use x::{};
|
|
|
|
mod modname {
|
|
use super::x::Bar;
|
|
|
|
use super::x::Foo;
|
|
|
|
pub(crate) type A = (Foo, Bar);
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_issue_12790() {
|
|
check_assist(
|
|
extract_module,
|
|
r"
|
|
$0/// A documented function
|
|
fn documented_fn() {}
|
|
|
|
// A commented function with a #[] attribute macro
|
|
#[cfg(test)]
|
|
fn attribute_fn() {}
|
|
|
|
// A normally commented function
|
|
fn normal_fn() {}
|
|
|
|
/// A documented Struct
|
|
struct DocumentedStruct {
|
|
// Normal field
|
|
x: i32,
|
|
|
|
/// Documented field
|
|
y: i32,
|
|
|
|
// Macroed field
|
|
#[cfg(test)]
|
|
z: i32,
|
|
}
|
|
|
|
// A macroed Struct
|
|
#[cfg(test)]
|
|
struct MacroedStruct {
|
|
// Normal field
|
|
x: i32,
|
|
|
|
/// Documented field
|
|
y: i32,
|
|
|
|
// Macroed field
|
|
#[cfg(test)]
|
|
z: i32,
|
|
}
|
|
|
|
// A normal Struct
|
|
struct NormalStruct {
|
|
// Normal field
|
|
x: i32,
|
|
|
|
/// Documented field
|
|
y: i32,
|
|
|
|
// Macroed field
|
|
#[cfg(test)]
|
|
z: i32,
|
|
}
|
|
|
|
/// A documented type
|
|
type DocumentedType = i32;
|
|
|
|
// A macroed type
|
|
#[cfg(test)]
|
|
type MacroedType = i32;
|
|
|
|
/// A module to move
|
|
mod module {}
|
|
|
|
/// An impl to move
|
|
impl NormalStruct {
|
|
/// A method
|
|
fn new() {}
|
|
}
|
|
|
|
/// A documented trait
|
|
trait DocTrait {
|
|
/// Inner function
|
|
fn doc() {}
|
|
}
|
|
|
|
/// An enum
|
|
enum DocumentedEnum {
|
|
/// A variant
|
|
A,
|
|
/// Another variant
|
|
B { x: i32, y: i32 }
|
|
}
|
|
|
|
/// Documented const
|
|
const MY_CONST: i32 = 0;$0
|
|
",
|
|
r"
|
|
mod modname {
|
|
/// A documented function
|
|
pub(crate) fn documented_fn() {}
|
|
|
|
// A commented function with a #[] attribute macro
|
|
#[cfg(test)]
|
|
pub(crate) fn attribute_fn() {}
|
|
|
|
// A normally commented function
|
|
pub(crate) fn normal_fn() {}
|
|
|
|
/// A documented Struct
|
|
pub(crate) struct DocumentedStruct {
|
|
// Normal field
|
|
pub(crate) x: i32,
|
|
|
|
/// Documented field
|
|
pub(crate) y: i32,
|
|
|
|
// Macroed field
|
|
#[cfg(test)]
|
|
pub(crate) z: i32,
|
|
}
|
|
|
|
// A macroed Struct
|
|
#[cfg(test)]
|
|
pub(crate) struct MacroedStruct {
|
|
// Normal field
|
|
pub(crate) x: i32,
|
|
|
|
/// Documented field
|
|
pub(crate) y: i32,
|
|
|
|
// Macroed field
|
|
#[cfg(test)]
|
|
pub(crate) z: i32,
|
|
}
|
|
|
|
// A normal Struct
|
|
pub(crate) struct NormalStruct {
|
|
// Normal field
|
|
pub(crate) x: i32,
|
|
|
|
/// Documented field
|
|
pub(crate) y: i32,
|
|
|
|
// Macroed field
|
|
#[cfg(test)]
|
|
pub(crate) z: i32,
|
|
}
|
|
|
|
/// A documented type
|
|
pub(crate) type DocumentedType = i32;
|
|
|
|
// A macroed type
|
|
#[cfg(test)]
|
|
pub(crate) type MacroedType = i32;
|
|
|
|
/// A module to move
|
|
pub(crate) mod module {}
|
|
|
|
/// An impl to move
|
|
impl NormalStruct {
|
|
/// A method
|
|
pub(crate) fn new() {}
|
|
}
|
|
|
|
/// A documented trait
|
|
pub(crate) trait DocTrait {
|
|
/// Inner function
|
|
fn doc() {}
|
|
}
|
|
|
|
/// An enum
|
|
pub(crate) enum DocumentedEnum {
|
|
/// A variant
|
|
A,
|
|
/// Another variant
|
|
B { x: i32, y: i32 }
|
|
}
|
|
|
|
/// Documented const
|
|
pub(crate) const MY_CONST: i32 = 0;
|
|
}
|
|
",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_merge_multiple_intersections() {
|
|
check_assist(
|
|
extract_module,
|
|
r#"
|
|
mod dep {
|
|
pub struct A;
|
|
pub struct B;
|
|
pub struct C;
|
|
}
|
|
|
|
use dep::{A, B, C};
|
|
|
|
$0struct S {
|
|
inner: A,
|
|
state: C,
|
|
condvar: B,
|
|
}$0
|
|
"#,
|
|
r#"
|
|
mod dep {
|
|
pub struct A;
|
|
pub struct B;
|
|
pub struct C;
|
|
}
|
|
|
|
use dep::{};
|
|
|
|
mod modname {
|
|
use super::dep::B;
|
|
|
|
use super::dep::C;
|
|
|
|
use super::dep::A;
|
|
|
|
pub(crate) struct S {
|
|
pub(crate) inner: A,
|
|
pub(crate) state: C,
|
|
pub(crate) condvar: B,
|
|
}
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_import_path_inside_selection() {
|
|
check_assist(
|
|
extract_module,
|
|
r#"
|
|
$0struct Point;
|
|
impl Point {
|
|
pub const fn direction(self, other: Self) -> Option<Direction> {
|
|
Some(Vertical)
|
|
}
|
|
}
|
|
|
|
pub enum Direction {
|
|
Horizontal,
|
|
Vertical,
|
|
}
|
|
use Direction::{Horizontal, Vertical};$0
|
|
|
|
fn main() {
|
|
let x = Vertical;
|
|
}
|
|
"#,
|
|
r#"
|
|
"#,
|
|
);
|
|
}
|
|
}
|