2 collections::{HashMap, HashSet},
6 use hir::{HasSource, ModuleSource};
8 assists::{AssistId, AssistKind},
10 defs::{Definition, NameClass, NameRefClass},
11 search::{FileReference, SearchScope},
15 algo::find_node_at_range,
18 edit::{AstNodeEdit, IndentLevel},
19 make, HasName, HasVisibility,
21 match_ast, ted, AstNode, SourceFile,
22 SyntaxKind::{self, WHITESPACE},
23 SyntaxNode, TextRange,
26 use crate::{AssistContext, Assists};
28 use super::remove_unused_param::range_to_remove;
30 // Assist: extract_module
32 // Extracts a selected region as separate module. All the references, visibility and imports are
36 // $0fn foo(name: i32) -> i32 {
40 // fn bar(name: i32) -> i32 {
47 // pub(crate) fn foo(name: i32) -> i32 {
52 // fn bar(name: i32) -> i32 {
56 pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
57 if ctx.has_empty_selection() {
61 let node = ctx.covering_element();
62 let node = match node {
63 syntax::NodeOrToken::Node(n) => n,
64 syntax::NodeOrToken::Token(t) => t.parent()?,
67 //If the selection is inside impl block, we need to place new module outside impl block,
68 //as impl blocks cannot contain modules
70 let mut impl_parent: Option<ast::Impl> = None;
71 let mut impl_child_count: usize = 0;
72 if let Some(parent_assoc_list) = node.parent() {
73 if let Some(parent_impl) = parent_assoc_list.parent() {
74 if let Some(impl_) = ast::Impl::cast(parent_impl) {
75 impl_child_count = parent_assoc_list.children().count();
76 impl_parent = Some(impl_);
81 let mut curr_parent_module: Option<ast::Module> = None;
82 if let Some(mod_syn_opt) = node.ancestors().find(|it| ast::Module::can_cast(it.kind())) {
83 curr_parent_module = ast::Module::cast(mod_syn_opt);
86 let mut module = extract_target(&node, ctx.selection_trimmed())?;
87 if module.body_items.is_empty() {
91 let old_item_indent = module.body_items[0].indent_level();
94 AssistId("extract_module", AssistKind::RefactorExtract),
98 //This takes place in three steps:
100 //- Firstly, we will update the references(usages) e.g. converting a
101 // function call bar() to modname::bar(), and similarly for other items
103 //- Secondly, changing the visibility of each item inside the newly selected module
104 // i.e. making a fn a() {} to pub(crate) fn a() {}
106 //- Thirdly, resolving all the imports this includes removing paths from imports
107 // outside the module, shifting/cloning them inside new module, or shifting the imports, or making
108 // new import statements
110 //We are getting item usages and record_fields together, record_fields
111 //for change_visibility and usages for first point mentioned above in the process
112 let (usages_to_be_processed, record_fields) = module.get_usages_and_record_fields(ctx);
114 let import_paths_to_be_removed = module.resolve_imports(curr_parent_module, ctx);
115 module.change_visibility(record_fields);
117 let mut body_items: Vec<String> = Vec::new();
118 let mut items_to_be_processed: Vec<ast::Item> = module.body_items.clone();
119 let mut new_item_indent = old_item_indent + 1;
121 if impl_parent.is_some() {
122 new_item_indent = old_item_indent + 2;
124 items_to_be_processed = [module.use_items.clone(), items_to_be_processed].concat();
127 for item in items_to_be_processed {
128 let item = item.indent(IndentLevel(1));
129 let mut indented_item = String::new();
130 format_to!(indented_item, "{new_item_indent}{item}");
131 body_items.push(indented_item);
134 let mut body = body_items.join("\n\n");
136 if let Some(impl_) = &impl_parent {
137 let mut impl_body_def = String::new();
139 if let Some(self_ty) = impl_.self_ty() {
141 let impl_indent = old_item_indent + 1;
144 "{impl_indent}impl {self_ty} {{\n{body}\n{impl_indent}}}",
147 body = impl_body_def;
149 // Add the import for enum/struct corresponding to given impl block
150 module.make_use_stmt_of_node_with_super(self_ty.syntax());
151 for item in module.use_items {
152 let item_indent = old_item_indent + 1;
153 body = format!("{item_indent}{item}\n\n{body}");
158 let mut module_def = String::new();
160 let module_name = module.name;
161 format_to!(module_def, "mod {module_name} {{\n{body}\n{old_item_indent}}}");
163 let mut usages_to_be_updated_for_curr_file = vec![];
164 for usages_to_be_updated_for_file in usages_to_be_processed {
165 if usages_to_be_updated_for_file.0 == ctx.file_id() {
166 usages_to_be_updated_for_curr_file = usages_to_be_updated_for_file.1;
169 builder.edit_file(usages_to_be_updated_for_file.0);
170 for usage_to_be_processed in usages_to_be_updated_for_file.1 {
171 builder.replace(usage_to_be_processed.0, usage_to_be_processed.1)
175 builder.edit_file(ctx.file_id());
176 for usage_to_be_processed in usages_to_be_updated_for_curr_file {
177 builder.replace(usage_to_be_processed.0, usage_to_be_processed.1)
180 for import_path_text_range in import_paths_to_be_removed {
181 builder.delete(import_path_text_range);
184 if let Some(impl_) = impl_parent {
185 // Remove complete impl block if it has only one child (as such it will be empty
186 // after deleting that child)
187 let node_to_be_removed = if impl_child_count == 1 {
190 //Remove selected node
194 builder.delete(node_to_be_removed.text_range());
195 // Remove preceding indentation from node
196 if let Some(range) = indent_range_before_given_node(node_to_be_removed) {
197 builder.delete(range);
200 builder.insert(impl_.syntax().text_range().end(), format!("\n\n{module_def}"));
202 builder.replace(module.text_range, module_def)
210 text_range: TextRange,
212 /// All items except use items.
213 body_items: Vec<ast::Item>,
214 /// Use items are kept separately as they help when the selection is inside an impl block,
215 /// we can directly take these items and keep them outside generated impl block inside
216 /// generated module.
217 use_items: Vec<ast::Item>,
220 fn extract_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Module> {
221 let selected_nodes = node
223 .filter(|node| selection_range.contains_range(node.text_range()))
224 .chain(iter::once(node.clone()));
225 let (use_items, body_items) = selected_nodes
226 .filter_map(ast::Item::cast)
227 .partition(|item| matches!(item, ast::Item::Use(..)));
229 Some(Module { text_range: selection_range, name: "modname", body_items, use_items })
233 fn get_usages_and_record_fields(
235 ctx: &AssistContext<'_>,
236 ) -> (HashMap<FileId, Vec<(TextRange, String)>>, Vec<SyntaxNode>) {
237 let mut adt_fields = Vec::new();
238 let mut refs: HashMap<FileId, Vec<(TextRange, String)>> = HashMap::new();
240 //Here impl is not included as each item inside impl will be tied to the parent of
241 //implementing block(a struct, enum, etc), if the parent is in selected module, it will
242 //get updated by ADT section given below or if it is not, then we dont need to do any operation
243 for item in &self.body_items {
245 match (item.syntax()) {
247 if let Some( nod ) = ctx.sema.to_def(&it) {
248 let node_def = Definition::Adt(nod);
249 self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
251 //Enum Fields are not allowed to explicitly specify pub, it is implied
253 ast::Adt::Struct(x) => {
254 if let Some(field_list) = x.field_list() {
256 ast::FieldList::RecordFieldList(record_field_list) => {
257 record_field_list.fields().for_each(|record_field| {
258 adt_fields.push(record_field.syntax().clone());
261 ast::FieldList::TupleFieldList(tuple_field_list) => {
262 tuple_field_list.fields().for_each(|tuple_field| {
263 adt_fields.push(tuple_field.syntax().clone());
269 ast::Adt::Union(x) => {
270 if let Some(record_field_list) = x.record_field_list() {
271 record_field_list.fields().for_each(|record_field| {
272 adt_fields.push(record_field.syntax().clone());
276 ast::Adt::Enum(_) => {},
280 ast::TypeAlias(it) => {
281 if let Some( nod ) = ctx.sema.to_def(&it) {
282 let node_def = Definition::TypeAlias(nod);
283 self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
287 if let Some( nod ) = ctx.sema.to_def(&it) {
288 let node_def = Definition::Const(nod);
289 self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
293 if let Some( nod ) = ctx.sema.to_def(&it) {
294 let node_def = Definition::Static(nod);
295 self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
299 if let Some( nod ) = ctx.sema.to_def(&it) {
300 let node_def = Definition::Function(nod);
301 self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
305 if let Some(nod) = ctx.sema.to_def(&it) {
306 self.expand_and_group_usages_file_wise(ctx, Definition::Macro(nod), &mut refs);
317 fn expand_and_group_usages_file_wise(
319 ctx: &AssistContext<'_>,
320 node_def: Definition,
321 refs_in_files: &mut HashMap<FileId, Vec<(TextRange, String)>>,
323 for (file_id, references) in node_def.usages(&ctx.sema).all() {
324 let source_file = ctx.sema.parse(file_id);
325 let usages_in_file = references
327 .filter_map(|usage| self.get_usage_to_be_processed(&source_file, usage));
328 refs_in_files.entry(file_id).or_default().extend(usages_in_file);
332 fn get_usage_to_be_processed(
334 source_file: &SourceFile,
335 FileReference { range, name, .. }: FileReference,
336 ) -> Option<(TextRange, String)> {
337 let path: ast::Path = find_node_at_range(source_file.syntax(), range)?;
339 for desc in path.syntax().descendants() {
340 if desc.to_string() == name.syntax().to_string()
341 && !self.text_range.contains_range(desc.text_range())
343 if let Some(name_ref) = ast::NameRef::cast(desc) {
344 let mod_name = self.name;
346 name_ref.syntax().text_range(),
347 format!("{mod_name}::{name_ref}"),
356 fn change_visibility(&mut self, record_fields: Vec<SyntaxNode>) {
357 let (mut replacements, record_field_parents, impls) =
358 get_replacements_for_visibilty_change(&mut self.body_items, false);
360 let mut impl_items: Vec<ast::Item> = impls
362 .flat_map(|impl_| impl_.syntax().descendants())
363 .filter_map(ast::Item::cast)
366 let (mut impl_item_replacements, _, _) =
367 get_replacements_for_visibilty_change(&mut impl_items, true);
369 replacements.append(&mut impl_item_replacements);
371 for (_, field_owner) in record_field_parents {
372 for desc in field_owner.descendants().filter_map(ast::RecordField::cast) {
373 let is_record_field_present =
374 record_fields.clone().into_iter().any(|x| x.to_string() == desc.to_string());
375 if is_record_field_present {
376 replacements.push((desc.visibility(), desc.syntax().clone()));
381 for (vis, syntax) in replacements {
382 let item = syntax.children_with_tokens().find(|node_or_token| {
383 match node_or_token.kind() {
384 // We're skipping comments, doc comments, and attribute macros that may precede the keyword
385 // that the visibility should be placed before.
386 SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE => false,
391 add_change_vis(vis, item);
397 curr_parent_module: Option<ast::Module>,
398 ctx: &AssistContext<'_>,
399 ) -> Vec<TextRange> {
400 let mut import_paths_to_be_removed: Vec<TextRange> = vec![];
401 let mut node_set: HashSet<String> = HashSet::new();
403 for item in self.body_items.clone() {
404 for x in item.syntax().descendants() {
405 if let Some(name) = ast::Name::cast(x.clone()) {
406 if let Some(name_classify) = NameClass::classify(&ctx.sema, &name) {
407 //Necessary to avoid two same names going through
408 if !node_set.contains(&name.syntax().to_string()) {
409 node_set.insert(name.syntax().to_string());
410 let def_opt: Option<Definition> = match name_classify {
411 NameClass::Definition(def) => Some(def),
415 if let Some(def) = def_opt {
416 if let Some(import_path) = self
417 .process_names_and_namerefs_for_import_resolve(
424 check_intersection_and_push(
425 &mut import_paths_to_be_removed,
434 if let Some(name_ref) = ast::NameRef::cast(x) {
435 if let Some(name_classify) = NameRefClass::classify(&ctx.sema, &name_ref) {
436 //Necessary to avoid two same names going through
437 if !node_set.contains(&name_ref.syntax().to_string()) {
438 node_set.insert(name_ref.syntax().to_string());
439 let def_opt: Option<Definition> = match name_classify {
440 NameRefClass::Definition(def) => Some(def),
444 if let Some(def) = def_opt {
445 if let Some(import_path) = self
446 .process_names_and_namerefs_for_import_resolve(
453 check_intersection_and_push(
454 &mut import_paths_to_be_removed,
465 import_paths_to_be_removed
468 fn process_names_and_namerefs_for_import_resolve(
471 node_syntax: &SyntaxNode,
472 curr_parent_module: &Option<ast::Module>,
473 ctx: &AssistContext<'_>,
474 ) -> Option<TextRange> {
475 //We only need to find in the current file
476 let selection_range = ctx.selection_trimmed();
477 let curr_file_id = ctx.file_id();
478 let search_scope = SearchScope::single_file(curr_file_id);
479 let usage_res = def.usages(&ctx.sema).in_scope(search_scope).all();
480 let file = ctx.sema.parse(curr_file_id);
482 let mut exists_inside_sel = false;
483 let mut exists_outside_sel = false;
484 for (_, refs) in usage_res.iter() {
485 let mut non_use_nodes_itr = refs.iter().filter_map(|x| {
486 if find_node_at_range::<ast::Use>(file.syntax(), x.range).is_none() {
487 let path_opt = find_node_at_range::<ast::Path>(file.syntax(), x.range);
496 .any(|x| !selection_range.contains_range(x.syntax().text_range()))
498 exists_outside_sel = true;
500 if non_use_nodes_itr.any(|x| selection_range.contains_range(x.syntax().text_range())) {
501 exists_inside_sel = true;
505 let source_exists_outside_sel_in_same_mod = does_source_exists_outside_sel_in_same_mod(
513 let use_stmt_opt: Option<ast::Use> = usage_res.into_iter().find_map(|(file_id, refs)| {
514 if file_id == curr_file_id {
517 .find_map(|fref| find_node_at_range(file.syntax(), fref.range))
523 let mut use_tree_str_opt: Option<Vec<ast::Path>> = None;
524 //Exists inside and outside selection
525 // - Use stmt for item is present -> get the use_tree_str and reconstruct the path in new
527 // - Use stmt for item is not present ->
528 //If it is not found, the definition is either ported inside new module or it stays
530 //- Def is inside: Nothing to import
531 //- Def is outside: Import it inside with super
533 //Exists inside selection but not outside -> Check for the import of it in original module,
534 //get the use_tree_str, reconstruct the use stmt in new module
536 let mut import_path_to_be_removed: Option<TextRange> = None;
537 if exists_inside_sel && exists_outside_sel {
538 //Changes to be made only inside new module
540 //If use_stmt exists, find the use_tree_str, reconstruct it inside new module
541 //If not, insert a use stmt with super and the given nameref
542 if let Some((use_tree_str, _)) =
543 self.process_use_stmt_for_import_resolve(use_stmt_opt, node_syntax)
545 use_tree_str_opt = Some(use_tree_str);
546 } else if source_exists_outside_sel_in_same_mod {
547 //Considered only after use_stmt is not present
548 //source_exists_outside_sel_in_same_mod | exists_outside_sel(exists_inside_sel =
549 //true for all cases)
550 // false | false -> Do nothing
551 // false | true -> If source is in selection -> nothing to do, If source is outside
552 // mod -> ust_stmt transversal
553 // true | false -> super import insertion
554 // true | true -> super import insertion
555 self.make_use_stmt_of_node_with_super(node_syntax);
557 } else if exists_inside_sel && !exists_outside_sel {
558 //Changes to be made inside new module, and remove import from outside
560 if let Some((mut use_tree_str, text_range_opt)) =
561 self.process_use_stmt_for_import_resolve(use_stmt_opt, node_syntax)
563 if let Some(text_range) = text_range_opt {
564 import_path_to_be_removed = Some(text_range);
567 if source_exists_outside_sel_in_same_mod {
568 if let Some(first_path_in_use_tree) = use_tree_str.last() {
569 let first_path_in_use_tree_str = first_path_in_use_tree.to_string();
570 if !first_path_in_use_tree_str.contains("super")
571 && !first_path_in_use_tree_str.contains("crate")
573 let super_path = make::ext::ident_path("super");
574 use_tree_str.push(super_path);
579 use_tree_str_opt = Some(use_tree_str);
580 } else if source_exists_outside_sel_in_same_mod {
581 self.make_use_stmt_of_node_with_super(node_syntax);
585 if let Some(use_tree_str) = use_tree_str_opt {
586 let mut use_tree_str = use_tree_str;
587 use_tree_str.reverse();
589 if !(!exists_outside_sel && exists_inside_sel && source_exists_outside_sel_in_same_mod)
591 if let Some(first_path_in_use_tree) = use_tree_str.first() {
592 let first_path_in_use_tree_str = first_path_in_use_tree.to_string();
593 if first_path_in_use_tree_str.contains("super") {
594 let super_path = make::ext::ident_path("super");
595 use_tree_str.insert(0, super_path)
601 make::use_(None, make::use_tree(make::join_paths(use_tree_str), None, None, false));
602 let item = ast::Item::from(use_);
603 self.use_items.insert(0, item);
606 import_path_to_be_removed
609 fn make_use_stmt_of_node_with_super(&mut self, node_syntax: &SyntaxNode) -> ast::Item {
610 let super_path = make::ext::ident_path("super");
611 let node_path = make::ext::ident_path(&node_syntax.to_string());
612 let use_ = make::use_(
614 make::use_tree(make::join_paths(vec![super_path, node_path]), None, None, false),
617 let item = ast::Item::from(use_);
618 self.use_items.insert(0, item.clone());
622 fn process_use_stmt_for_import_resolve(
624 use_stmt_opt: Option<ast::Use>,
625 node_syntax: &SyntaxNode,
626 ) -> Option<(Vec<ast::Path>, Option<TextRange>)> {
627 if let Some(use_stmt) = use_stmt_opt {
628 for desc in use_stmt.syntax().descendants() {
629 if let Some(path_seg) = ast::PathSegment::cast(desc) {
630 if path_seg.syntax().to_string() == node_syntax.to_string() {
631 let mut use_tree_str = vec![path_seg.parent_path()];
632 get_use_tree_paths_from_path(path_seg.parent_path(), &mut use_tree_str);
633 for ancs in path_seg.syntax().ancestors() {
634 //Here we are looking for use_tree with same string value as node
635 //passed above as the range_to_remove function looks for a comma and
636 //then includes it in the text range to remove it. But the comma only
637 //appears at the use_tree level
638 if let Some(use_tree) = ast::UseTree::cast(ancs) {
639 if use_tree.syntax().to_string() == node_syntax.to_string() {
642 Some(range_to_remove(use_tree.syntax())),
648 return Some((use_tree_str, None));
658 fn check_intersection_and_push(
659 import_paths_to_be_removed: &mut Vec<TextRange>,
660 import_path: TextRange,
662 if import_paths_to_be_removed.len() > 0 {
663 // Text ranges received here for imports are extended to the
664 // next/previous comma which can cause intersections among them
665 // and later deletion of these can cause panics similar
666 // to reported in #11766. So to mitigate it, we
667 // check for intersection between all current members
668 // and if it exists we combine both text ranges into
670 let r = import_paths_to_be_removed
672 .position(|it| it.intersect(import_path).is_some());
675 import_paths_to_be_removed[it] = import_paths_to_be_removed[it].cover(import_path)
677 None => import_paths_to_be_removed.push(import_path),
680 import_paths_to_be_removed.push(import_path);
684 fn does_source_exists_outside_sel_in_same_mod(
686 ctx: &AssistContext<'_>,
687 curr_parent_module: &Option<ast::Module>,
688 selection_range: TextRange,
689 curr_file_id: FileId,
691 let mut source_exists_outside_sel_in_same_mod = false;
693 Definition::Module(x) => {
694 let source = x.definition_source(ctx.db());
695 let have_same_parent;
696 if let Some(ast_module) = &curr_parent_module {
697 if let Some(hir_module) = x.parent(ctx.db()) {
699 compare_hir_and_ast_module(ast_module, hir_module, ctx).is_some();
701 let source_file_id = source.file_id.original_file(ctx.db());
702 have_same_parent = source_file_id == curr_file_id;
705 let source_file_id = source.file_id.original_file(ctx.db());
706 have_same_parent = source_file_id == curr_file_id;
709 if have_same_parent {
711 ModuleSource::Module(module_) => {
712 source_exists_outside_sel_in_same_mod =
713 !selection_range.contains_range(module_.syntax().text_range());
719 Definition::Function(x) => {
720 if let Some(source) = x.source(ctx.db()) {
721 let have_same_parent = if let Some(ast_module) = &curr_parent_module {
722 compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
724 let source_file_id = source.file_id.original_file(ctx.db());
725 source_file_id == curr_file_id
728 if have_same_parent {
729 source_exists_outside_sel_in_same_mod =
730 !selection_range.contains_range(source.value.syntax().text_range());
734 Definition::Adt(x) => {
735 if let Some(source) = x.source(ctx.db()) {
736 let have_same_parent = if let Some(ast_module) = &curr_parent_module {
737 compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
739 let source_file_id = source.file_id.original_file(ctx.db());
740 source_file_id == curr_file_id
743 if have_same_parent {
744 source_exists_outside_sel_in_same_mod =
745 !selection_range.contains_range(source.value.syntax().text_range());
749 Definition::Variant(x) => {
750 if let Some(source) = x.source(ctx.db()) {
751 let have_same_parent = if let Some(ast_module) = &curr_parent_module {
752 compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
754 let source_file_id = source.file_id.original_file(ctx.db());
755 source_file_id == curr_file_id
758 if have_same_parent {
759 source_exists_outside_sel_in_same_mod =
760 !selection_range.contains_range(source.value.syntax().text_range());
764 Definition::Const(x) => {
765 if let Some(source) = x.source(ctx.db()) {
766 let have_same_parent = if let Some(ast_module) = &curr_parent_module {
767 compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
769 let source_file_id = source.file_id.original_file(ctx.db());
770 source_file_id == curr_file_id
773 if have_same_parent {
774 source_exists_outside_sel_in_same_mod =
775 !selection_range.contains_range(source.value.syntax().text_range());
779 Definition::Static(x) => {
780 if let Some(source) = x.source(ctx.db()) {
781 let have_same_parent = if let Some(ast_module) = &curr_parent_module {
782 compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
784 let source_file_id = source.file_id.original_file(ctx.db());
785 source_file_id == curr_file_id
788 if have_same_parent {
789 source_exists_outside_sel_in_same_mod =
790 !selection_range.contains_range(source.value.syntax().text_range());
794 Definition::Trait(x) => {
795 if let Some(source) = x.source(ctx.db()) {
796 let have_same_parent = if let Some(ast_module) = &curr_parent_module {
797 compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
799 let source_file_id = source.file_id.original_file(ctx.db());
800 source_file_id == curr_file_id
803 if have_same_parent {
804 source_exists_outside_sel_in_same_mod =
805 !selection_range.contains_range(source.value.syntax().text_range());
809 Definition::TypeAlias(x) => {
810 if let Some(source) = x.source(ctx.db()) {
811 let have_same_parent = if let Some(ast_module) = &curr_parent_module {
812 compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
814 let source_file_id = source.file_id.original_file(ctx.db());
815 source_file_id == curr_file_id
818 if have_same_parent {
819 source_exists_outside_sel_in_same_mod =
820 !selection_range.contains_range(source.value.syntax().text_range());
827 source_exists_outside_sel_in_same_mod
830 fn get_replacements_for_visibilty_change(
831 items: &mut [ast::Item],
832 is_clone_for_updated: bool,
834 Vec<(Option<ast::Visibility>, SyntaxNode)>,
835 Vec<(Option<ast::Visibility>, SyntaxNode)>,
838 let mut replacements = Vec::new();
839 let mut record_field_parents = Vec::new();
840 let mut impls = Vec::new();
843 if !is_clone_for_updated {
844 *item = item.clone_for_update();
846 //Use stmts are ignored
848 ast::Item::Const(it) => replacements.push((it.visibility(), it.syntax().clone())),
849 ast::Item::Enum(it) => replacements.push((it.visibility(), it.syntax().clone())),
850 ast::Item::ExternCrate(it) => replacements.push((it.visibility(), it.syntax().clone())),
851 ast::Item::Fn(it) => replacements.push((it.visibility(), it.syntax().clone())),
852 //Associated item's visibility should not be changed
853 ast::Item::Impl(it) if it.for_token().is_none() => impls.push(it.clone()),
854 ast::Item::MacroDef(it) => replacements.push((it.visibility(), it.syntax().clone())),
855 ast::Item::Module(it) => replacements.push((it.visibility(), it.syntax().clone())),
856 ast::Item::Static(it) => replacements.push((it.visibility(), it.syntax().clone())),
857 ast::Item::Struct(it) => {
858 replacements.push((it.visibility(), it.syntax().clone()));
859 record_field_parents.push((it.visibility(), it.syntax().clone()));
861 ast::Item::Trait(it) => replacements.push((it.visibility(), it.syntax().clone())),
862 ast::Item::TypeAlias(it) => replacements.push((it.visibility(), it.syntax().clone())),
863 ast::Item::Union(it) => {
864 replacements.push((it.visibility(), it.syntax().clone()));
865 record_field_parents.push((it.visibility(), it.syntax().clone()));
871 (replacements, record_field_parents, impls)
874 fn get_use_tree_paths_from_path(
876 use_tree_str: &mut Vec<ast::Path>,
877 ) -> Option<&mut Vec<ast::Path>> {
878 path.syntax().ancestors().filter(|x| x.to_string() != path.to_string()).find_map(|x| {
879 if let Some(use_tree) = ast::UseTree::cast(x) {
880 if let Some(upper_tree_path) = use_tree.path() {
881 if upper_tree_path.to_string() != path.to_string() {
882 use_tree_str.push(upper_tree_path.clone());
883 get_use_tree_paths_from_path(upper_tree_path, use_tree_str);
884 return Some(use_tree);
894 fn add_change_vis(vis: Option<ast::Visibility>, node_or_token_opt: Option<syntax::SyntaxElement>) {
896 if let Some(node_or_token) = node_or_token_opt {
897 let pub_crate_vis = make::visibility_pub_crate().clone_for_update();
898 ted::insert(ted::Position::before(node_or_token), pub_crate_vis.syntax());
903 fn compare_hir_and_ast_module(
904 ast_module: &ast::Module,
905 hir_module: hir::Module,
906 ctx: &AssistContext<'_>,
908 let hir_mod_name = hir_module.name(ctx.db())?;
909 let ast_mod_name = ast_module.name()?;
910 if hir_mod_name.to_string() != ast_mod_name.to_string() {
917 fn indent_range_before_given_node(node: &SyntaxNode) -> Option<TextRange> {
918 node.siblings_with_tokens(syntax::Direction::Prev)
919 .find(|x| x.kind() == WHITESPACE)
920 .map(|x| x.text_range())
925 use crate::tests::{check_assist, check_assist_not_applicable};
930 fn test_not_applicable_without_selection() {
931 check_assist_not_applicable(
934 $0pub struct PublicStruct {
942 fn test_extract_module() {
946 mod thirdpartycrate {
949 pub struct SomeType2;
951 pub struct SomeType1;
955 use crate::thirdpartycrate::{nest::{SomeType, SomeType2}, SomeType1};
957 pub struct PublicStruct {
958 field: PrivateStruct,
963 pub fn new() -> Self {
964 Self { field: PrivateStruct::new(), field1: SomeType1 }
969 let _s = PrivateStruct::new();
973 $0struct PrivateStruct {
977 pub struct PrivateStruct1 {
983 PrivateStruct { inner: SomeType }
993 mod thirdpartycrate {
996 pub struct SomeType2;
998 pub struct SomeType1;
1002 use crate::thirdpartycrate::{nest::{SomeType2}, SomeType1};
1004 pub struct PublicStruct {
1005 field: modname::PrivateStruct,
1010 pub fn new() -> Self {
1011 Self { field: modname::PrivateStruct::new(), field1: SomeType1 }
1016 let _s = modname::PrivateStruct::new();
1017 let _a = modname::bar();
1021 use crate::thirdpartycrate::nest::SomeType;
1023 pub(crate) struct PrivateStruct {
1024 pub(crate) inner: SomeType,
1027 pub struct PrivateStruct1 {
1031 impl PrivateStruct {
1032 pub(crate) fn new() -> Self {
1033 PrivateStruct { inner: SomeType }
1037 pub(crate) fn bar() -> i32 {
1047 fn test_extract_module_for_function_only() {
1051 $0fn foo(name: i32) -> i32 {
1055 fn bar(name: i32) -> i32 {
1061 pub(crate) fn foo(name: i32) -> i32 {
1066 fn bar(name: i32) -> i32 {
1074 fn test_extract_module_for_impl_having_corresponding_adt_in_selection() {
1082 pub fn new_a() -> i32 {
1088 let _a = A::new_a();
1095 pub(crate) struct A {}
1098 pub fn new_a() -> i32 {
1105 let _a = modname::A::new_a();
1113 fn test_import_resolve_when_its_only_inside_selection() {
1118 pub struct PrivateStruct;
1119 pub struct PrivateStruct1;
1123 use super::foo::{PrivateStruct, PrivateStruct1};
1126 field: PrivateStruct,
1130 field: PrivateStruct1,
1136 pub struct PrivateStruct;
1137 pub struct PrivateStruct1;
1141 use super::foo::{PrivateStruct1};
1144 use super::super::foo::PrivateStruct;
1146 pub(crate) struct Strukt {
1147 pub(crate) field: PrivateStruct,
1152 field: PrivateStruct1,
1160 fn test_import_resolve_when_its_inside_and_outside_selection_and_source_not_in_same_mod() {
1165 pub struct PrivateStruct;
1169 use super::foo::PrivateStruct;
1172 field: PrivateStruct,
1176 field: PrivateStruct,
1182 pub struct PrivateStruct;
1186 use super::foo::PrivateStruct;
1189 use super::super::foo::PrivateStruct;
1191 pub(crate) struct Strukt {
1192 pub(crate) field: PrivateStruct,
1197 field: PrivateStruct,
1205 fn test_import_resolve_when_its_inside_and_outside_selection_and_source_is_in_same_mod() {
1210 pub struct PrivateStruct;
1213 field: PrivateStruct,
1217 field: PrivateStruct,
1223 pub struct PrivateStruct;
1226 use super::PrivateStruct;
1228 pub(crate) struct Strukt {
1229 pub(crate) field: PrivateStruct,
1234 field: PrivateStruct,
1242 fn test_extract_module_for_correspoding_adt_of_impl_present_in_same_mod_but_not_in_selection() {
1250 pub fn new_a() -> i32 {
1256 let _a = A::new_a();
1268 pub fn new_a() -> i32 {
1275 let _a = A::new_a();
1283 fn test_extract_module_for_impl_not_having_corresponding_adt_in_selection_and_not_in_same_mod_but_with_super(
1295 pub fn new_a() -> i32 {
1301 let _a = A::new_a();
1313 use super::super::foo::A;
1316 pub fn new_a() -> i32 {
1323 let _a = A::new_a();
1331 fn test_import_resolve_for_trait_bounds_on_function() {
1340 fn foo<T: JustATrait>(arg: T) -> T {
1344 impl JustATrait for A {}
1357 use super::JustATrait;
1359 pub(crate) struct A {}
1361 pub(crate) fn foo<T: JustATrait>(arg: T) -> T {
1365 impl JustATrait for A {}
1367 pub(crate) fn bar() {
1378 fn test_extract_module_for_module() {
1391 pub(crate) mod impl_play {
1401 fn test_extract_module_with_multiple_files() {
1408 use foo::PrivateStruct;
1411 field: PrivateStruct,
1420 pub struct PrivateStruct;
1425 use foo::PrivateStruct;
1428 field: PrivateStruct,
1435 pub(crate) struct Strukt1 {
1436 pub(crate) field: Strukt,
1445 fn test_extract_module_macro_rules() {
1466 fn test_do_not_apply_visibility_modifier_to_trait_impl_items() {
1476 $0impl ATrait for A {
1501 fn test_if_inside_impl_block_generate_module_outside() {
1523 pub(crate) fn foo() {}
1531 fn test_if_inside_impl_block_generate_module_outside_but_impl_block_having_one_child() {
1552 pub(crate) fn foo(x: B) {}
1560 fn test_issue_11766() {
1561 //https://github.com/rust-lang/rust-analyzer/issues/11766
1572 $0type A = (Foo, Bar);$0
1587 pub(crate) type A = (Foo, Bar);
1594 fn test_issue_12790() {
1598 $0/// A documented function
1599 fn documented_fn() {}
1601 // A commented function with a #[] attribute macro
1603 fn attribute_fn() {}
1605 // A normally commented function
1608 /// A documented Struct
1609 struct DocumentedStruct {
1613 /// Documented field
1623 struct MacroedStruct {
1627 /// Documented field
1636 struct NormalStruct {
1640 /// Documented field
1648 /// A documented type
1649 type DocumentedType = i32;
1653 type MacroedType = i32;
1655 /// A module to move
1664 /// A documented trait
1671 enum DocumentedEnum {
1675 B { x: i32, y: i32 }
1678 /// Documented const
1679 const MY_CONST: i32 = 0;$0
1683 /// A documented function
1684 pub(crate) fn documented_fn() {}
1686 // A commented function with a #[] attribute macro
1688 pub(crate) fn attribute_fn() {}
1690 // A normally commented function
1691 pub(crate) fn normal_fn() {}
1693 /// A documented Struct
1694 pub(crate) struct DocumentedStruct {
1698 /// Documented field
1708 pub(crate) struct MacroedStruct {
1712 /// Documented field
1721 pub(crate) struct NormalStruct {
1725 /// Documented field
1733 /// A documented type
1734 pub(crate) type DocumentedType = i32;
1738 pub(crate) type MacroedType = i32;
1740 /// A module to move
1741 pub(crate) mod module {}
1746 pub(crate) fn new() {}
1749 /// A documented trait
1750 pub(crate) trait DocTrait {
1756 pub(crate) enum DocumentedEnum {
1760 B { x: i32, y: i32 }
1763 /// Documented const
1764 pub(crate) const MY_CONST: i32 = 0;