rust-analyzer/crates/ide_assists/src/handlers/extract_module.rs

1531 lines
48 KiB
Rust
Raw Normal View History

2021-08-16 08:15:10 +00:00
use std::collections::{HashMap, HashSet};
use hir::{HasSource, ModuleSource};
2021-08-16 08:15:10 +00:00
use ide_db::{
assists::{AssistId, AssistKind},
base_db::FileId,
defs::{Definition, NameClass, NameRefClass},
search::{FileReference, SearchScope},
};
use stdx::format_to;
use syntax::{
algo::find_node_at_range,
ast::{
self,
edit::{AstNodeEdit, IndentLevel},
make, HasName, HasVisibility,
},
match_ast, ted, AstNode, SourceFile,
SyntaxKind::WHITESPACE,
SyntaxNode, TextRange,
2021-08-16 08:15:10 +00:00
};
use crate::{AssistContext, Assists};
use super::remove_unused_param::range_to_remove;
// Assist: extract_module
//
// Extracts a selected region as seperate module. All the references, visibility and imports are
// resolved.
//
// ```
// $0fn foo(name: i32) -> i32 {
2021-08-16 08:15:10 +00:00
// name + 1
// }$0
2021-08-16 08:15:10 +00:00
//
// 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<()> {
2021-10-16 10:39:55 +00:00
if ctx.has_empty_selection() {
2021-08-16 08:15:10 +00:00
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_);
}
}
}
2021-08-16 08:15:10 +00:00
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);
}
2021-10-16 10:39:55 +00:00
let mut module = extract_target(&node, ctx.selection_trimmed())?;
2022-03-28 16:31:07 +00:00
if module.body_items.is_empty() {
2021-08-16 08:15:10 +00:00
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| {
2022-03-28 16:31:07 +00:00
//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 statemnts
//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);
2022-03-28 14:10:13 +00:00
module.change_visibility(record_fields);
2022-03-28 11:31:18 +00:00
let mut body_items: Vec<String> = Vec::new();
let mut items_to_be_processed: Vec<ast::Item> = module.body_items.clone();
let mut new_item_indent = old_item_indent + 1;
if impl_parent.is_some() {
new_item_indent = old_item_indent + 2;
} else {
items_to_be_processed = [module.use_items.clone(), items_to_be_processed].concat();
}
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.to_string());
body_items.push(indented_item);
}
2021-08-16 08:15:10 +00:00
let mut body = body_items.join("\n\n");
if let Some(impl_) = &impl_parent {
let mut impl_body_def = String::new();
if let Some(self_ty) = impl_.self_ty() {
format_to!(
impl_body_def,
"{}impl {} {{\n{}\n{}}}",
old_item_indent + 1,
self_ty.to_string(),
body,
old_item_indent + 1
);
body = impl_body_def;
// Add the import for enum/struct corresponding to given impl block
2022-03-28 14:10:13 +00:00
module.make_use_stmt_of_node_with_super(self_ty.syntax());
for item in module.use_items {
let mut indented_item = String::new();
format_to!(indented_item, "{}{}", old_item_indent + 1, item.to_string());
body = format!("{}\n\n{}", indented_item, body);
}
}
}
2021-08-16 08:15:10 +00:00
let mut module_def = String::new();
2021-08-16 08:15:10 +00:00
format_to!(module_def, "mod {} {{\n{}\n{}}}", module.name, body, old_item_indent);
2021-08-16 08:15:10 +00:00
let mut usages_to_be_updated_for_curr_file = vec![];
for usages_to_be_updated_for_file in usages_to_be_processed {
2021-10-16 10:39:55 +00:00
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;
2021-08-16 08:15:10 +00:00
}
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)
2021-08-16 08:15:10 +00:00
}
}
2021-10-16 10:39:55 +00:00
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)
}
for import_path_text_range in import_paths_to_be_removed {
builder.delete(import_path_text_range);
}
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)
2022-03-12 15:17:53 +00:00
let node_to_be_removed = if impl_child_count == 1 {
impl_.syntax()
} else {
//Remove selected node
2022-03-12 15:17:53 +00:00
&node
};
builder.delete(node_to_be_removed.text_range());
// Remove preceding indentation from node
2022-03-12 12:04:13 +00:00
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 {
builder.replace(module.text_range, module_def)
}
2021-08-16 08:15:10 +00:00
},
)
}
#[derive(Debug)]
struct Module {
text_range: TextRange,
name: String,
body_items: Vec<ast::Item>, // All items except use items
use_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
2021-08-16 08:15:10 +00:00
}
fn extract_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Module> {
let mut use_items = vec![];
let mut body_items: Vec<ast::Item> = node
2021-08-16 08:15:10 +00:00
.children()
2022-03-28 16:31:07 +00:00
.filter(|child| selection_range.contains_range(child.text_range()))
.filter_map(|child| match ast::Item::cast(child) {
Some(it @ ast::Item::Use(_)) => {
use_items.push(it);
None
2021-08-16 08:15:10 +00:00
}
2022-03-28 16:31:07 +00:00
item => item,
2021-08-16 08:15:10 +00:00
})
.collect();
if let Some(node_item) = ast::Item::cast(node.clone()) {
body_items.push(node_item);
}
Some(Module { text_range: selection_range, name: "modname".to_string(), body_items, use_items })
2021-08-16 08:15:10 +00:00
}
impl Module {
fn get_usages_and_record_fields(
&self,
ctx: &AssistContext,
) -> (HashMap<FileId, Vec<(TextRange, String)>>, Vec<SyntaxNode>) {
let mut adt_fields = Vec::new();
2021-08-16 08:15:10 +00:00
let mut refs: HashMap<FileId, Vec<(TextRange, String)>> = HashMap::new();
//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
self.body_items.iter().cloned().for_each(|item| {
2021-08-16 08:15:10 +00:00
match_ast! {
match (item.syntax()) {
ast::Adt(it) => {
if let Some( nod ) = ctx.sema.to_def(&it) {
2022-03-12 12:43:53 +00:00
let node_def = Definition::Adt(nod);
2021-08-16 08:15:10 +00:00
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());
});
}
},
2021-08-16 08:15:10 +00:00
ast::Adt::Enum(_) => {},
}
}
},
ast::TypeAlias(it) => {
if let Some( nod ) = ctx.sema.to_def(&it) {
2022-03-12 12:43:53 +00:00
let node_def = Definition::TypeAlias(nod);
2021-08-16 08:15:10 +00:00
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
}
},
ast::Const(it) => {
if let Some( nod ) = ctx.sema.to_def(&it) {
2022-03-12 12:43:53 +00:00
let node_def = Definition::Const(nod);
2021-08-16 08:15:10 +00:00
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
}
},
ast::Static(it) => {
if let Some( nod ) = ctx.sema.to_def(&it) {
2022-03-12 12:43:53 +00:00
let node_def = Definition::Static(nod);
2021-08-16 08:15:10 +00:00
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
}
},
ast::Fn(it) => {
if let Some( nod ) = ctx.sema.to_def(&it) {
2022-03-12 12:43:53 +00:00
let node_def = Definition::Function(nod);
2021-08-16 08:15:10 +00:00
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);
}
},
2021-08-16 08:15:10 +00:00
_ => (),
}
}
});
2022-03-12 13:56:26 +00:00
(refs, adt_fields)
2021-08-16 08:15:10 +00:00
}
fn expand_and_group_usages_file_wise(
&self,
ctx: &AssistContext,
node_def: Definition,
refs: &mut HashMap<FileId, Vec<(TextRange, String)>>,
) {
for (file_id, references) in node_def.usages(&ctx.sema).all() {
if let Some(file_refs) = refs.get_mut(&file_id) {
let mut usages = self.expand_ref_to_usages(references, ctx, file_id);
file_refs.append(&mut usages);
} else {
refs.insert(file_id, self.expand_ref_to_usages(references, ctx, file_id));
}
}
}
fn expand_ref_to_usages(
&self,
refs: Vec<FileReference>,
ctx: &AssistContext,
file_id: FileId,
) -> Vec<(TextRange, String)> {
let source_file = ctx.sema.parse(file_id);
let mut usages_to_be_processed_for_file = Vec::new();
for usage in refs {
if let Some(x) = self.get_usage_to_be_processed(&source_file, usage) {
usages_to_be_processed_for_file.push(x);
}
}
usages_to_be_processed_for_file
}
fn get_usage_to_be_processed(
&self,
source_file: &SourceFile,
FileReference { range, name, .. }: FileReference,
) -> Option<(TextRange, String)> {
2022-03-28 16:31:07 +00:00
let path: ast::Path = find_node_at_range(source_file.syntax(), range)?;
2021-08-16 08:15:10 +00:00
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) {
return Some((
name_ref.syntax().text_range(),
2021-12-17 15:46:20 +00:00
format!("{}::{}", self.name, name_ref),
2021-08-16 08:15:10 +00:00
));
}
}
}
None
}
2022-03-28 14:10:13 +00:00
fn change_visibility(&mut self, record_fields: Vec<SyntaxNode>) {
let (mut replacements, record_field_parents, impls) =
get_replacements_for_visibilty_change(&mut self.body_items, false);
2021-08-16 08:15:10 +00:00
2022-01-02 10:03:34 +00:00
let mut impl_items = Vec::new();
for impl_ in impls {
let mut this_impl_items = Vec::new();
for node in impl_.syntax().descendants() {
if let Some(item) = ast::Item::cast(node) {
this_impl_items.push(item);
}
}
2021-08-16 08:15:10 +00:00
2021-12-17 15:35:10 +00:00
impl_items.append(&mut this_impl_items);
2022-01-02 10:03:34 +00:00
}
2021-08-16 08:15:10 +00:00
2022-03-28 14:10:13 +00:00
let (mut impl_item_replacements, _, _) =
get_replacements_for_visibilty_change(&mut impl_items, true);
2021-08-16 08:15:10 +00:00
replacements.append(&mut impl_item_replacements);
record_field_parents.into_iter().for_each(|x| {
2022-03-12 12:35:31 +00:00
x.1.descendants().filter_map(ast::RecordField::cast).for_each(|desc| {
let is_record_field_present =
record_fields.clone().into_iter().any(|x| x.to_string() == desc.to_string());
2021-08-16 08:15:10 +00:00
if is_record_field_present {
2021-12-17 15:35:10 +00:00
replacements.push((desc.visibility(), desc.syntax().clone()));
2021-08-16 08:15:10 +00:00
}
});
});
replacements.into_iter().for_each(|(vis, syntax)| {
add_change_vis(vis, syntax.first_child_or_token());
});
}
fn resolve_imports(
&mut self,
curr_parent_module: Option<ast::Module>,
ctx: &AssistContext,
) -> Vec<TextRange> {
let mut import_paths_to_be_removed: Vec<TextRange> = vec![];
let mut node_set: HashSet<String> = HashSet::new();
self.body_items.clone().into_iter().for_each(|item| {
item.syntax().descendants().for_each(|x| {
if let Some(name) = ast::Name::cast(x.clone()) {
if let Some(name_classify) = NameClass::classify(&ctx.sema, &name) {
//Necessary to avoid two same names going through
if !node_set.contains(&name.syntax().to_string()) {
node_set.insert(name.syntax().to_string());
let def_opt: Option<Definition> = match name_classify {
NameClass::Definition(def) => Some(def),
_ => None,
};
if let Some(def) = def_opt {
if let Some(import_path) = self
.process_names_and_namerefs_for_import_resolve(
def,
name.syntax(),
&curr_parent_module,
ctx,
)
{
import_paths_to_be_removed.push(import_path);
}
}
}
}
}
if let Some(name_ref) = ast::NameRef::cast(x) {
if let Some(name_classify) = NameRefClass::classify(&ctx.sema, &name_ref) {
//Necessary to avoid two same names going through
if !node_set.contains(&name_ref.syntax().to_string()) {
node_set.insert(name_ref.syntax().to_string());
let def_opt: Option<Definition> = match name_classify {
NameRefClass::Definition(def) => Some(def),
_ => None,
};
if let Some(def) = def_opt {
if let Some(import_path) = self
.process_names_and_namerefs_for_import_resolve(
def,
name_ref.syntax(),
&curr_parent_module,
ctx,
)
{
import_paths_to_be_removed.push(import_path);
}
}
}
}
}
});
});
import_paths_to_be_removed
}
fn process_names_and_namerefs_for_import_resolve(
&mut self,
def: Definition,
node_syntax: &SyntaxNode,
curr_parent_module: &Option<ast::Module>,
ctx: &AssistContext,
) -> Option<TextRange> {
//We only need to find in the current file
2021-10-16 10:39:55 +00:00
let selection_range = ctx.selection_trimmed();
let curr_file_id = ctx.file_id();
let search_scope = SearchScope::single_file(curr_file_id);
2021-08-16 08:15:10 +00:00
let usage_res = def.usages(&ctx.sema).in_scope(search_scope).all();
2021-10-16 10:39:55 +00:00
let file = ctx.sema.parse(curr_file_id);
2021-08-16 08:15:10 +00:00
let mut exists_inside_sel = false;
let mut exists_outside_sel = false;
usage_res.clone().into_iter().for_each(|x| {
let mut non_use_nodes_itr = (&x.1).iter().filter_map(|x| {
2021-08-16 08:15:10 +00:00
if find_node_at_range::<ast::Use>(file.syntax(), x.range).is_none() {
let path_opt = find_node_at_range::<ast::Path>(file.syntax(), x.range);
return path_opt;
}
None
});
if non_use_nodes_itr
.clone()
.any(|x| !selection_range.contains_range(x.syntax().text_range()))
2021-08-16 08:15:10 +00:00
{
exists_outside_sel = true;
}
if non_use_nodes_itr.any(|x| selection_range.contains_range(x.syntax().text_range())) {
2021-08-16 08:15:10 +00:00
exists_inside_sel = true;
}
});
let source_exists_outside_sel_in_same_mod = does_source_exists_outside_sel_in_same_mod(
def,
ctx,
curr_parent_module,
selection_range,
curr_file_id,
);
let use_stmt_opt: Option<ast::Use> = usage_res.into_iter().find_map(|x| {
let file_id = x.0;
let mut use_opt: Option<ast::Use> = None;
2021-10-16 10:39:55 +00:00
if file_id == curr_file_id {
(&x.1).iter().for_each(|x| {
2021-08-16 08:15:10 +00:00
let node_opt: Option<ast::Use> = find_node_at_range(file.syntax(), x.range);
if let Some(node) = node_opt {
2021-12-17 15:35:10 +00:00
use_opt = Some(node);
2021-08-16 08:15:10 +00:00
}
});
}
2022-03-12 13:56:26 +00:00
use_opt
2021-08-16 08:15:10 +00:00
});
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
if let Some((use_tree_str, _)) =
self.process_use_stmt_for_import_resolve(use_stmt_opt, node_syntax)
{
use_tree_str_opt = Some(use_tree_str);
} else if source_exists_outside_sel_in_same_mod {
//Considered only after use_stmt is not present
//source_exists_outside_sel_in_same_mod | 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(node_syntax);
}
} else if exists_inside_sel && !exists_outside_sel {
//Changes to be made inside new module, and remove import from outside
if let Some((use_tree_str, text_range_opt)) =
self.process_use_stmt_for_import_resolve(use_stmt_opt, node_syntax)
{
if let Some(text_range) = text_range_opt {
import_path_to_be_removed = Some(text_range);
}
use_tree_str_opt = Some(use_tree_str);
} else if source_exists_outside_sel_in_same_mod {
self.make_use_stmt_of_node_with_super(node_syntax);
}
}
if let Some(use_tree_str) = use_tree_str_opt {
2021-12-17 15:35:10 +00:00
let mut use_tree_str = use_tree_str;
2021-08-16 08:15:10 +00:00
use_tree_str.reverse();
if use_tree_str[0].to_string().contains("super") {
let super_path = make::ext::ident_path("super");
use_tree_str.insert(0, super_path)
}
let use_ =
make::use_(None, make::use_tree(make::join_paths(use_tree_str), None, None, false));
2022-03-28 16:31:07 +00:00
let item = ast::Item::from(use_);
self.use_items.insert(0, item);
2021-08-16 08:15:10 +00:00
}
import_path_to_be_removed
}
2022-03-28 14:10:13 +00:00
fn make_use_stmt_of_node_with_super(&mut self, node_syntax: &SyntaxNode) -> ast::Item {
2021-08-16 08:15:10 +00:00
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),
);
2022-03-28 14:10:13 +00:00
let item = ast::Item::from(use_);
self.use_items.insert(0, item.clone());
item
2021-08-16 08:15:10 +00:00
}
fn process_use_stmt_for_import_resolve(
&self,
use_stmt_opt: Option<ast::Use>,
node_syntax: &SyntaxNode,
) -> Option<(Vec<ast::Path>, Option<TextRange>)> {
if let Some(use_stmt) = use_stmt_opt {
for desc in use_stmt.syntax().descendants() {
if let Some(path_seg) = ast::PathSegment::cast(desc) {
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);
for ancs in path_seg.syntax().ancestors() {
//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
if let Some(use_tree) = ast::UseTree::cast(ancs) {
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 does_source_exists_outside_sel_in_same_mod(
def: Definition,
ctx: &AssistContext,
curr_parent_module: &Option<ast::Module>,
selection_range: TextRange,
curr_file_id: FileId,
) -> bool {
let mut source_exists_outside_sel_in_same_mod = false;
match def {
Definition::Module(x) => {
let source = x.definition_source(ctx.db());
let have_same_parent;
if let Some(ast_module) = &curr_parent_module {
if let Some(hir_module) = x.parent(ctx.db()) {
have_same_parent =
2022-03-12 12:04:13 +00:00
compare_hir_and_ast_module(ast_module, hir_module, ctx).is_some();
2021-08-16 08:15:10 +00:00
} else {
let source_file_id = source.file_id.original_file(ctx.db());
have_same_parent = source_file_id == curr_file_id;
}
} else {
let source_file_id = source.file_id.original_file(ctx.db());
have_same_parent = source_file_id == curr_file_id;
}
2021-08-16 08:15:10 +00:00
if have_same_parent {
match source.value {
ModuleSource::Module(module_) => {
source_exists_outside_sel_in_same_mod =
!selection_range.contains_range(module_.syntax().text_range());
2021-08-16 08:15:10 +00:00
}
_ => {}
2021-08-16 08:15:10 +00:00
}
}
}
Definition::Function(x) => {
if let Some(source) = x.source(ctx.db()) {
2022-03-12 15:17:53 +00:00
let have_same_parent = if let Some(ast_module) = &curr_parent_module {
compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
} else {
let source_file_id = source.file_id.original_file(ctx.db());
2022-03-12 15:17:53 +00:00
source_file_id == curr_file_id
};
2021-08-16 08:15:10 +00:00
if have_same_parent {
source_exists_outside_sel_in_same_mod =
!selection_range.contains_range(source.value.syntax().text_range());
2021-08-16 08:15:10 +00:00
}
}
}
Definition::Adt(x) => {
if let Some(source) = x.source(ctx.db()) {
2022-03-12 15:17:53 +00:00
let have_same_parent = if let Some(ast_module) = &curr_parent_module {
compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
} else {
let source_file_id = source.file_id.original_file(ctx.db());
2022-03-12 15:17:53 +00:00
source_file_id == curr_file_id
};
2021-08-16 08:15:10 +00:00
if have_same_parent {
source_exists_outside_sel_in_same_mod =
!selection_range.contains_range(source.value.syntax().text_range());
2021-08-16 08:15:10 +00:00
}
}
}
Definition::Variant(x) => {
if let Some(source) = x.source(ctx.db()) {
2022-03-12 15:17:53 +00:00
let have_same_parent = if let Some(ast_module) = &curr_parent_module {
compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
} else {
let source_file_id = source.file_id.original_file(ctx.db());
2022-03-12 15:17:53 +00:00
source_file_id == curr_file_id
};
2021-08-16 08:15:10 +00:00
if have_same_parent {
source_exists_outside_sel_in_same_mod =
!selection_range.contains_range(source.value.syntax().text_range());
2021-08-16 08:15:10 +00:00
}
}
}
Definition::Const(x) => {
if let Some(source) = x.source(ctx.db()) {
2022-03-12 15:17:53 +00:00
let have_same_parent = if let Some(ast_module) = &curr_parent_module {
compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
} else {
let source_file_id = source.file_id.original_file(ctx.db());
2022-03-12 15:17:53 +00:00
source_file_id == curr_file_id
};
2021-08-16 08:15:10 +00:00
if have_same_parent {
source_exists_outside_sel_in_same_mod =
!selection_range.contains_range(source.value.syntax().text_range());
2021-08-16 08:15:10 +00:00
}
}
}
Definition::Static(x) => {
if let Some(source) = x.source(ctx.db()) {
2022-03-12 15:17:53 +00:00
let have_same_parent = if let Some(ast_module) = &curr_parent_module {
compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
} else {
let source_file_id = source.file_id.original_file(ctx.db());
2022-03-12 15:17:53 +00:00
source_file_id == curr_file_id
};
2021-08-16 08:15:10 +00:00
if have_same_parent {
source_exists_outside_sel_in_same_mod =
!selection_range.contains_range(source.value.syntax().text_range());
2021-08-16 08:15:10 +00:00
}
}
}
Definition::Trait(x) => {
if let Some(source) = x.source(ctx.db()) {
2022-03-12 15:17:53 +00:00
let have_same_parent = if let Some(ast_module) = &curr_parent_module {
compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
} else {
let source_file_id = source.file_id.original_file(ctx.db());
2022-03-12 15:17:53 +00:00
source_file_id == curr_file_id
};
2021-08-16 08:15:10 +00:00
if have_same_parent {
source_exists_outside_sel_in_same_mod =
!selection_range.contains_range(source.value.syntax().text_range());
2021-08-16 08:15:10 +00:00
}
}
}
Definition::TypeAlias(x) => {
if let Some(source) = x.source(ctx.db()) {
2022-03-12 15:17:53 +00:00
let have_same_parent = if let Some(ast_module) = &curr_parent_module {
compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
} else {
let source_file_id = source.file_id.original_file(ctx.db());
2022-03-12 15:17:53 +00:00
source_file_id == curr_file_id
};
2021-08-16 08:15:10 +00:00
if have_same_parent {
source_exists_outside_sel_in_same_mod =
!selection_range.contains_range(source.value.syntax().text_range());
2021-08-16 08:15:10 +00:00
}
}
}
2021-08-16 08:15:10 +00:00
_ => {}
}
2022-03-12 13:56:26 +00:00
source_exists_outside_sel_in_same_mod
2021-08-16 08:15:10 +00:00
}
fn get_replacements_for_visibilty_change(
2022-03-28 14:10:13 +00:00
items: &mut [ast::Item],
2021-08-16 08:15:10 +00:00
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();
items.into_iter().for_each(|item| {
if !is_clone_for_updated {
2022-03-28 14:10:13 +00:00
*item = item.clone_for_update();
2021-08-16 08:15:10 +00:00
}
//Use stmts are ignored
match item {
2021-12-17 15:35:10 +00:00
ast::Item::Const(it) => replacements.push((it.visibility(), it.syntax().clone())),
ast::Item::Enum(it) => replacements.push((it.visibility(), it.syntax().clone())),
ast::Item::ExternCrate(it) => replacements.push((it.visibility(), it.syntax().clone())),
ast::Item::Fn(it) => replacements.push((it.visibility(), it.syntax().clone())),
//Associated item's visibility should not be changed
2022-03-28 14:10:13 +00:00
ast::Item::Impl(it) if it.for_token().is_none() => impls.push(it.clone()),
2021-12-17 15:35:10 +00:00
ast::Item::MacroDef(it) => replacements.push((it.visibility(), it.syntax().clone())),
ast::Item::Module(it) => replacements.push((it.visibility(), it.syntax().clone())),
ast::Item::Static(it) => replacements.push((it.visibility(), it.syntax().clone())),
2021-08-16 08:15:10 +00:00
ast::Item::Struct(it) => {
2021-12-17 15:35:10 +00:00
replacements.push((it.visibility(), it.syntax().clone()));
record_field_parents.push((it.visibility(), it.syntax().clone()));
2021-08-16 08:15:10 +00:00
}
2021-12-17 15:35:10 +00:00
ast::Item::Trait(it) => replacements.push((it.visibility(), it.syntax().clone())),
ast::Item::TypeAlias(it) => replacements.push((it.visibility(), it.syntax().clone())),
2021-08-16 08:15:10 +00:00
ast::Item::Union(it) => {
2021-12-17 15:35:10 +00:00
replacements.push((it.visibility(), it.syntax().clone()));
record_field_parents.push((it.visibility(), it.syntax().clone()));
2021-08-16 08:15:10 +00:00
}
_ => (),
}
});
2022-03-28 14:10:13 +00:00
(replacements, record_field_parents, impls)
2021-08-16 08:15:10 +00:00
}
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()).find_map(|x| {
2021-12-17 15:35:10 +00:00
if let Some(use_tree) = ast::UseTree::cast(x) {
2021-08-16 08:15:10 +00:00
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)
}
2022-03-28 11:31:18 +00:00
fn add_change_vis(vis: Option<ast::Visibility>, node_or_token_opt: Option<syntax::SyntaxElement>) {
if vis.is_none() {
2021-08-16 08:15:10 +00:00
if let Some(node_or_token) = node_or_token_opt {
let pub_crate_vis = make::visibility_pub_crate().clone_for_update();
2022-03-28 11:31:18 +00:00
ted::insert(ted::Position::before(node_or_token), pub_crate_vis.syntax());
2021-08-16 08:15:10 +00:00
}
}
}
fn compare_hir_and_ast_module(
ast_module: &ast::Module,
hir_module: hir::Module,
ctx: &AssistContext,
) -> Option<()> {
let hir_mod_name = hir_module.name(ctx.db())?;
let ast_mod_name = ast_module.name()?;
if hir_mod_name.to_string() != ast_mod_name.to_string() {
return None;
}
2022-03-12 13:56:26 +00:00
Some(())
2021-08-16 08:15:10 +00:00
}
fn indent_range_before_given_node(node: &SyntaxNode) -> Option<TextRange> {
2022-03-12 13:56:26 +00:00
node.siblings_with_tokens(syntax::Direction::Prev)
.find(|x| x.kind() == WHITESPACE)
.map(|x| x.text_range())
}
2021-08-16 08:15:10 +00:00
#[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,
}
2021-08-16 08:15:10 +00:00
",
)
}
#[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,
}
2021-08-16 08:15:10 +00:00
pub struct PrivateStruct1 {
pub inner: i32,
}
2021-08-16 08:15:10 +00:00
impl PrivateStruct {
fn new() -> Self {
PrivateStruct { inner: SomeType }
}
}
2021-08-16 08:15:10 +00:00
fn bar() -> i32 {
2
}$0
2021-08-16 08:15:10 +00:00
}
",
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;
2021-08-16 08:15:10 +00:00
pub(crate) struct PrivateStruct {
pub(crate) inner: SomeType,
}
2021-08-16 08:15:10 +00:00
pub struct PrivateStruct1 {
pub inner: i32,
}
2021-08-16 08:15:10 +00:00
impl PrivateStruct {
pub(crate) fn new() -> Self {
PrivateStruct { inner: SomeType }
}
}
2021-08-16 08:15:10 +00:00
pub(crate) fn bar() -> i32 {
2
}
}
2021-08-16 08:15:10 +00:00
}
",
);
}
#[test]
fn test_extract_module_for_function_only() {
check_assist(
extract_module,
r"
$0fn foo(name: i32) -> i32 {
name + 1
}$0
2021-08-16 08:15:10 +00:00
fn bar(name: i32) -> i32 {
name + 2
}
",
r"
mod modname {
pub(crate) fn foo(name: i32) -> i32 {
name + 1
}
}
2021-08-16 08:15:10 +00:00
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 {}
2021-08-16 08:15:10 +00:00
impl A {
pub fn new_a() -> i32 {
2
}
}$0
2021-08-16 08:15:10 +00:00
fn a() {
let _a = A::new_a();
}
}
",
r"
mod impl_play {
mod modname {
pub(crate) struct A {}
2021-08-16 08:15:10 +00:00
impl A {
pub fn new_a() -> i32 {
2
}
}
}
2021-08-16 08:15:10 +00:00
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
2021-08-16 08:15:10 +00:00
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;
2021-08-16 08:15:10 +00:00
pub(crate) struct Strukt {
pub(crate) field: PrivateStruct,
}
}
2021-08-16 08:15:10 +00:00
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
2021-08-16 08:15:10 +00:00
struct Strukt1 {
field: PrivateStruct,
}
}
",
r"
mod foo {
pub struct PrivateStruct;
}
mod bar {
use super::foo::PrivateStruct;
mod modname {
use super::super::foo::PrivateStruct;
2021-08-16 08:15:10 +00:00
pub(crate) struct Strukt {
pub(crate) field: PrivateStruct,
}
}
2021-08-16 08:15:10 +00:00
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
2021-08-16 08:15:10 +00:00
struct Strukt1 {
field: PrivateStruct,
}
}
",
r"
mod bar {
pub struct PrivateStruct;
mod modname {
use super::PrivateStruct;
2021-08-16 08:15:10 +00:00
pub(crate) struct Strukt {
pub(crate) field: PrivateStruct,
}
}
2021-08-16 08:15:10 +00:00
struct Strukt1 {
field: PrivateStruct,
}
}
",
)
}
#[test]
fn test_extract_module_for_correspoding_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
2021-08-16 08:15:10 +00:00
fn a() {
let _a = A::new_a();
}
}
",
r"
mod impl_play {
struct A {}
mod modname {
use super::A;
2021-08-16 08:15:10 +00:00
impl A {
pub fn new_a() -> i32 {
2
}
}
}
2021-08-16 08:15:10 +00:00
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
2021-08-16 08:15:10 +00:00
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;
2021-08-16 08:15:10 +00:00
impl A {
pub fn new_a() -> i32 {
2
}
}
}
2021-08-16 08:15:10 +00:00
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 {}
2021-08-16 08:15:10 +00:00
fn foo<T: JustATrait>(arg: T) -> T {
arg
}
2021-08-16 08:15:10 +00:00
impl JustATrait for A {}
2021-08-16 08:15:10 +00:00
fn bar() {
let a = A {};
foo(a);
}$0
2021-08-16 08:15:10 +00:00
}
",
r"
mod impl_play2 {
trait JustATrait {}
mod modname {
use super::JustATrait;
2021-08-16 08:15:10 +00:00
pub(crate) struct A {}
2021-08-16 08:15:10 +00:00
pub(crate) fn foo<T: JustATrait>(arg: T) -> T {
arg
}
2021-08-16 08:15:10 +00:00
impl JustATrait for A {}
2021-08-16 08:15:10 +00:00
pub(crate) fn bar() {
let a = A {};
foo(a);
}
}
2021-08-16 08:15:10 +00:00
}
",
)
}
#[test]
fn test_extract_module_for_module() {
check_assist(
extract_module,
r"
mod impl_play2 {
$0mod impl_play {
pub struct A {}
}$0
2021-08-16 08:15:10 +00:00
}
",
r"
mod impl_play2 {
mod modname {
pub(crate) mod impl_play {
pub struct A {}
}
}
2021-08-16 08:15:10 +00:00
}
",
)
}
#[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 {
2021-08-16 08:15:10 +00:00
field: Strukt,
}$0
2021-08-16 08:15:10 +00:00
}
//- /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) {}
}
}
",
)
}
2021-08-16 08:15:10 +00:00
}