1use std::{iter::once, ops::RangeInclusive};
2
3use hir::{HasSource, ModuleSource};
4use ide_db::{
5 FileId, FxHashMap, FxHashSet,
6 assists::AssistId,
7 defs::{Definition, NameClass, NameRefClass},
8 search::{FileReference, SearchScope},
9};
10use itertools::Itertools;
11use smallvec::SmallVec;
12use syntax::{
13 AstNode,
14 SyntaxKind::{self, WHITESPACE},
15 SyntaxNode, TextRange, TextSize,
16 algo::find_node_at_range,
17 ast::{
18 self, HasVisibility,
19 edit::{AstNodeEdit, IndentLevel},
20 make,
21 },
22 match_ast, ted,
23};
24
25use crate::{AssistContext, Assists};
26
27use super::remove_unused_param::range_to_remove;
28
29pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
56 if ctx.has_empty_selection() {
57 return None;
58 }
59
60 let node = ctx.covering_element();
61 let node = match node {
62 syntax::NodeOrToken::Node(n) => n,
63 syntax::NodeOrToken::Token(t) => t.parent()?,
64 };
65
66 let mut curr_parent_module: Option<ast::Module> = None;
67 if let Some(mod_syn_opt) = node.ancestors().find(|it| ast::Module::can_cast(it.kind())) {
68 curr_parent_module = ast::Module::cast(mod_syn_opt);
69 }
70
71 let selection_range = ctx.selection_trimmed();
72 let (mut module, module_text_range) = if let Some(item) = ast::Item::cast(node.clone()) {
73 let module = extract_single_target(&item);
74 (module, node.text_range())
75 } else {
76 let (module, range) = extract_child_target(&node, selection_range)?;
77 let module_text_range = range.start().text_range().cover(range.end().text_range());
78 (module, module_text_range)
79 };
80 if module.body_items.is_empty() {
81 return None;
82 }
83
84 let mut old_item_indent = module.body_items[0].indent_level();
85 let old_items: Vec<_> = module.use_items.iter().chain(&module.body_items).cloned().collect();
86
87 let mut impl_parent: Option<ast::Impl> = None;
91 let mut impl_child_count: usize = 0;
92 if let Some(parent_assoc_list) = module.body_items[0].syntax().parent()
93 && let Some(parent_impl) = parent_assoc_list.parent()
94 && let Some(impl_) = ast::Impl::cast(parent_impl)
95 {
96 impl_child_count = parent_assoc_list.children().count();
97 old_item_indent = impl_.indent_level();
98 impl_parent = Some(impl_);
99 }
100
101 acc.add(
102 AssistId::refactor_extract("extract_module"),
103 "Extract Module",
104 module_text_range,
105 |builder| {
106 let (usages_to_be_processed, record_fields, use_stmts_to_be_inserted) =
122 module.get_usages_and_record_fields(ctx, module_text_range);
123
124 builder.edit_file(ctx.vfs_file_id());
125 use_stmts_to_be_inserted.into_iter().for_each(|(_, use_stmt)| {
126 builder.insert(ctx.selection_trimmed().end(), format!("\n{use_stmt}"));
127 });
128
129 let import_items = module.resolve_imports(curr_parent_module, ctx);
130 module.change_visibility(record_fields);
131
132 let module_def = generate_module_def(&impl_parent, &module).indent(old_item_indent);
133
134 let mut usages_to_be_processed_for_cur_file = vec![];
135 for (file_id, usages) in usages_to_be_processed {
136 if file_id == ctx.vfs_file_id() {
137 usages_to_be_processed_for_cur_file = usages;
138 continue;
139 }
140 builder.edit_file(file_id);
141 for (text_range, usage) in usages {
142 builder.replace(text_range, usage)
143 }
144 }
145
146 builder.edit_file(ctx.vfs_file_id());
147 for (text_range, usage) in usages_to_be_processed_for_cur_file {
148 builder.replace(text_range, usage);
149 }
150
151 if let Some(impl_) = impl_parent {
152 let nodes_to_be_removed = if impl_child_count == old_items.len() {
155 vec![impl_.syntax()]
156 } else {
157 old_items.iter().map(|it| it.syntax()).collect()
159 };
160
161 for node_to_be_removed in nodes_to_be_removed {
162 builder.delete(node_to_be_removed.text_range());
163 if let Some(range) = indent_range_before_given_node(node_to_be_removed) {
165 builder.delete(range);
166 }
167 }
168
169 builder.insert(
170 impl_.syntax().text_range().end(),
171 format!("\n\n{old_item_indent}{module_def}"),
172 );
173 } else {
174 for import_item in import_items {
175 if !module_text_range.contains_range(import_item) {
176 builder.delete(import_item);
177 }
178 }
179 builder.replace(module_text_range, module_def.to_string())
180 }
181 },
182 )
183}
184
185fn generate_module_def(
186 parent_impl: &Option<ast::Impl>,
187 Module { name, body_items, use_items }: &Module,
188) -> ast::Module {
189 let items: Vec<_> = if let Some(impl_) = parent_impl.as_ref()
190 && let Some(self_ty) = impl_.self_ty()
191 {
192 let assoc_items = body_items
193 .iter()
194 .map(|item| item.syntax().clone())
195 .filter_map(ast::AssocItem::cast)
196 .map(|it| it.indent(IndentLevel(1)))
197 .collect_vec();
198 let assoc_item_list = make::assoc_item_list(Some(assoc_items)).clone_for_update();
199 let impl_ = impl_.reset_indent();
200 ted::replace(impl_.get_or_create_assoc_item_list().syntax(), assoc_item_list.syntax());
201 let use_impl = make_use_stmt_of_node_with_super(self_ty.syntax());
203 once(use_impl)
204 .chain(use_items.iter().cloned())
205 .chain(once(ast::Item::Impl(impl_)))
206 .collect()
207 } else {
208 use_items.iter().chain(body_items).cloned().collect()
209 };
210
211 let items = items.into_iter().map(|it| it.reset_indent().indent(IndentLevel(1))).collect_vec();
212 let module_body = make::item_list(Some(items));
213
214 let module_name = make::name(name);
215 make::mod_(module_name, Some(module_body))
216}
217
218fn make_use_stmt_of_node_with_super(node_syntax: &SyntaxNode) -> ast::Item {
219 let super_path = make::ext::ident_path("super");
220 let node_path = make::ext::ident_path(&node_syntax.to_string());
221 let use_ = make::use_(
222 None,
223 None,
224 make::use_tree(make::join_paths(vec![super_path, node_path]), None, None, false),
225 );
226
227 ast::Item::from(use_)
228}
229
230#[derive(Debug)]
231struct Module {
232 name: &'static str,
233 body_items: Vec<ast::Item>,
235 use_items: Vec<ast::Item>,
239}
240
241fn extract_single_target(node: &ast::Item) -> Module {
242 let (body_items, use_items) = if matches!(node, ast::Item::Use(_)) {
243 (Vec::new(), vec![node.clone()])
244 } else {
245 (vec![node.clone()], Vec::new())
246 };
247 let name = "modname";
248 Module { name, body_items, use_items }
249}
250
251fn extract_child_target(
252 node: &SyntaxNode,
253 selection_range: TextRange,
254) -> Option<(Module, RangeInclusive<SyntaxNode>)> {
255 let selected_nodes = node
256 .children()
257 .filter(|node| selection_range.contains_range(node.text_range()))
258 .filter_map(ast::Item::cast)
259 .collect_vec();
260 let start = selected_nodes.first()?.syntax().clone();
261 let end = selected_nodes.last()?.syntax().clone();
262 let (use_items, body_items): (Vec<ast::Item>, Vec<ast::Item>) =
263 selected_nodes.into_iter().partition(|item| matches!(item, ast::Item::Use(..)));
264 Some((Module { name: "modname", body_items, use_items }, start..=end))
265}
266
267impl Module {
268 fn get_usages_and_record_fields(
269 &self,
270 ctx: &AssistContext<'_>,
271 replace_range: TextRange,
272 ) -> (FxHashMap<FileId, Vec<(TextRange, String)>>, Vec<SyntaxNode>, FxHashMap<TextSize, ast::Use>)
273 {
274 let mut adt_fields = Vec::new();
275 let mut refs: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
276 let mut use_stmts_to_be_inserted = FxHashMap::default();
278
279 for item in &self.body_items {
284 match_ast! {
285 match (item.syntax()) {
286 ast::Adt(it) => {
287 if let Some( nod ) = ctx.sema.to_def(&it) {
288 let node_def = Definition::Adt(nod);
289 self.expand_and_group_usages_file_wise(ctx, replace_range,node_def, &mut refs, &mut use_stmts_to_be_inserted);
290
291 match it {
293 ast::Adt::Struct(x) => {
294 if let Some(field_list) = x.field_list() {
295 match field_list {
296 ast::FieldList::RecordFieldList(record_field_list) => {
297 record_field_list.fields().for_each(|record_field| {
298 adt_fields.push(record_field.syntax().clone());
299 });
300 },
301 ast::FieldList::TupleFieldList(tuple_field_list) => {
302 tuple_field_list.fields().for_each(|tuple_field| {
303 adt_fields.push(tuple_field.syntax().clone());
304 });
305 },
306 }
307 }
308 },
309 ast::Adt::Union(x) => {
310 if let Some(record_field_list) = x.record_field_list() {
311 record_field_list.fields().for_each(|record_field| {
312 adt_fields.push(record_field.syntax().clone());
313 });
314 }
315 },
316 ast::Adt::Enum(_) => {},
317 }
318 }
319 },
320 ast::TypeAlias(it) => {
321 if let Some( nod ) = ctx.sema.to_def(&it) {
322 let node_def = Definition::TypeAlias(nod);
323 self.expand_and_group_usages_file_wise(ctx,replace_range, node_def, &mut refs, &mut use_stmts_to_be_inserted);
324 }
325 },
326 ast::Const(it) => {
327 if let Some( nod ) = ctx.sema.to_def(&it) {
328 let node_def = Definition::Const(nod);
329 self.expand_and_group_usages_file_wise(ctx,replace_range, node_def, &mut refs, &mut use_stmts_to_be_inserted);
330 }
331 },
332 ast::Static(it) => {
333 if let Some( nod ) = ctx.sema.to_def(&it) {
334 let node_def = Definition::Static(nod);
335 self.expand_and_group_usages_file_wise(ctx,replace_range, node_def, &mut refs, &mut use_stmts_to_be_inserted);
336 }
337 },
338 ast::Fn(it) => {
339 if let Some( nod ) = ctx.sema.to_def(&it) {
340 let node_def = Definition::Function(nod);
341 self.expand_and_group_usages_file_wise(ctx,replace_range, node_def, &mut refs, &mut use_stmts_to_be_inserted);
342 }
343 },
344 ast::Macro(it) => {
345 if let Some(nod) = ctx.sema.to_def(&it) {
346 self.expand_and_group_usages_file_wise(ctx,replace_range, Definition::Macro(nod), &mut refs, &mut use_stmts_to_be_inserted);
347 }
348 },
349 _ => (),
350 }
351 }
352 }
353
354 (refs, adt_fields, use_stmts_to_be_inserted)
355 }
356
357 fn expand_and_group_usages_file_wise(
358 &self,
359 ctx: &AssistContext<'_>,
360 replace_range: TextRange,
361 node_def: Definition,
362 refs_in_files: &mut FxHashMap<FileId, Vec<(TextRange, String)>>,
363 use_stmts_to_be_inserted: &mut FxHashMap<TextSize, ast::Use>,
364 ) {
365 let mod_name = self.name;
366 let covering_node = match ctx.covering_element() {
367 syntax::NodeOrToken::Node(node) => node,
368 syntax::NodeOrToken::Token(tok) => tok.parent().unwrap(), };
370 let out_of_sel = |node: &SyntaxNode| !replace_range.contains_range(node.text_range());
371 let mut use_stmts_set = FxHashSet::default();
372
373 for (file_id, refs) in node_def.usages(&ctx.sema).all() {
374 let source_file = ctx.sema.parse(file_id);
375 let usages = refs.into_iter().filter_map(|FileReference { range, .. }| {
376 let name_ref = find_node_at_range::<ast::NameRef>(source_file.syntax(), range)?;
378
379 if out_of_sel(name_ref.syntax()) {
380 let new_ref = format!("{mod_name}::{name_ref}");
381 return Some((range, new_ref));
382 } else if let Some(use_) = name_ref.syntax().ancestors().find_map(ast::Use::cast) {
383 if use_.syntax().parent().is_some_and(|parent| parent == covering_node)
386 && use_stmts_set.insert(use_.syntax().text_range().start())
387 {
388 let use_ = use_stmts_to_be_inserted
389 .entry(use_.syntax().text_range().start())
390 .or_insert_with(|| use_.clone_subtree().clone_for_update());
391 for seg in use_
392 .syntax()
393 .descendants()
394 .filter_map(ast::NameRef::cast)
395 .filter(|seg| seg.syntax().to_string() == name_ref.to_string())
396 {
397 let new_ref = make::path_from_text(&format!("{mod_name}::{seg}"))
398 .clone_for_update();
399 ted::replace(seg.syntax().parent()?, new_ref.syntax());
400 }
401 }
402 }
403
404 None
405 });
406 refs_in_files.entry(file_id.file_id(ctx.db())).or_default().extend(usages);
407 }
408 }
409
410 fn change_visibility(&mut self, record_fields: Vec<SyntaxNode>) {
411 let (mut replacements, record_field_parents, impls) =
412 get_replacements_for_visibility_change(&mut self.body_items, false);
413
414 let mut impl_items = impls
415 .into_iter()
416 .flat_map(|impl_| impl_.syntax().descendants())
417 .filter_map(ast::Item::cast)
418 .collect_vec();
419
420 let (mut impl_item_replacements, _, _) =
421 get_replacements_for_visibility_change(&mut impl_items, true);
422
423 replacements.append(&mut impl_item_replacements);
424
425 for (_, field_owner) in record_field_parents {
426 for desc in field_owner.descendants().filter_map(ast::RecordField::cast) {
427 let is_record_field_present =
428 record_fields.clone().into_iter().any(|x| x.to_string() == desc.to_string());
429 if is_record_field_present {
430 replacements.push((desc.visibility(), desc.syntax().clone()));
431 }
432 }
433 }
434
435 for (vis, syntax) in replacements {
436 let item = syntax.children_with_tokens().find(|node_or_token| {
437 match node_or_token.kind() {
438 SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE => false,
441 _ => true,
442 }
443 });
444
445 add_change_vis(vis, item);
446 }
447 }
448
449 fn resolve_imports(
450 &mut self,
451 module: Option<ast::Module>,
452 ctx: &AssistContext<'_>,
453 ) -> Vec<TextRange> {
454 let mut imports_to_remove = vec![];
455 let mut node_set = FxHashSet::default();
456
457 for item in self.body_items.clone() {
458 item.syntax()
459 .descendants()
460 .filter_map(|x| {
461 if let Some(name) = ast::Name::cast(x.clone()) {
462 NameClass::classify(&ctx.sema, &name).and_then(|nc| match nc {
463 NameClass::Definition(def) => Some((name.syntax().clone(), def)),
464 _ => None,
465 })
466 } else if let Some(name_ref) = ast::NameRef::cast(x) {
467 NameRefClass::classify(&ctx.sema, &name_ref).and_then(|nc| match nc {
468 NameRefClass::Definition(def, _) => {
469 Some((name_ref.syntax().clone(), def))
470 }
471 _ => None,
472 })
473 } else {
474 None
475 }
476 })
477 .for_each(|(node, def)| {
478 if node_set.insert(node.to_string())
479 && let Some(import) = self.process_def_in_sel(def, &node, &module, ctx)
480 {
481 check_intersection_and_push(&mut imports_to_remove, import);
482 }
483 })
484 }
485
486 imports_to_remove
487 }
488
489 fn process_def_in_sel(
490 &mut self,
491 def: Definition,
492 use_node: &SyntaxNode,
493 curr_parent_module: &Option<ast::Module>,
494 ctx: &AssistContext<'_>,
495 ) -> Option<TextRange> {
496 let selection_range = ctx.selection_trimmed();
498 let file_id = ctx.file_id();
499 let usage_res = def.usages(&ctx.sema).in_scope(&SearchScope::single_file(file_id)).all();
500
501 let file = ctx.sema.parse(file_id);
502
503 let mut uses_exist_in_sel = false;
505 let mut uses_exist_out_sel = false;
506 'outside: for (_, refs) in usage_res.iter() {
507 for x in refs
508 .iter()
509 .filter(|x| find_node_at_range::<ast::Use>(file.syntax(), x.range).is_none())
510 .filter_map(|x| find_node_at_range::<ast::Path>(file.syntax(), x.range))
511 {
512 let in_selection = selection_range.contains_range(x.syntax().text_range());
513 uses_exist_in_sel |= in_selection;
514 uses_exist_out_sel |= !in_selection;
515
516 if uses_exist_in_sel && uses_exist_out_sel {
517 break 'outside;
518 }
519 }
520 }
521
522 let (def_in_mod, def_out_sel) = check_def_in_mod_and_out_sel(
523 def,
524 ctx,
525 curr_parent_module,
526 selection_range,
527 file_id.file_id(ctx.db()),
528 );
529
530 let use_stmt: Option<ast::Use> = usage_res
532 .into_iter()
533 .filter(|(use_file_id, _)| *use_file_id == file_id)
534 .flat_map(|(_, refs)| refs.into_iter().rev())
535 .find_map(|fref| find_node_at_range(file.syntax(), fref.range));
536 let use_stmt_not_in_sel = use_stmt.as_ref().is_some_and(|use_stmt| {
537 !selection_range.contains_range(use_stmt.syntax().text_range())
538 });
539
540 let mut use_tree_paths: Option<Vec<ast::Path>> = None;
541 let mut import_path_to_be_removed: Option<TextRange> = None;
554 if uses_exist_in_sel && uses_exist_out_sel {
555 match self.process_use_stmt_for_import_resolve(use_stmt, use_node) {
560 Some((use_tree_str, _)) => use_tree_paths = Some(use_tree_str),
561 None if def_in_mod && def_out_sel => {
562 let super_use_node = make_use_stmt_of_node_with_super(use_node);
571 self.use_items.insert(0, super_use_node);
572 }
573 None => {}
574 }
575 } else if uses_exist_in_sel && !uses_exist_out_sel {
576 if let Some((mut use_tree_str, text_range_opt)) =
579 self.process_use_stmt_for_import_resolve(use_stmt, use_node)
580 {
581 if let Some(text_range) = text_range_opt {
582 import_path_to_be_removed = Some(text_range);
583 }
584
585 if def_in_mod
586 && def_out_sel
587 && let Some(first_path_in_use_tree) = use_tree_str.last()
588 {
589 let first_path_in_use_tree_str = first_path_in_use_tree.to_string();
590 if !first_path_in_use_tree_str.contains("super")
591 && !first_path_in_use_tree_str.contains("crate")
592 {
593 let super_path = make::ext::ident_path("super");
594 use_tree_str.push(super_path);
595 }
596 }
597
598 use_tree_paths = Some(use_tree_str);
599 } else if def_in_mod && def_out_sel {
600 let super_use_node = make_use_stmt_of_node_with_super(use_node);
601 self.use_items.insert(0, super_use_node);
602 }
603 }
604
605 if let Some(mut use_tree_paths) = use_tree_paths {
606 use_tree_paths.reverse();
607
608 if (uses_exist_out_sel || !uses_exist_in_sel || !def_in_mod || !def_out_sel)
609 && let Some(first_path_in_use_tree) = use_tree_paths.first()
610 && first_path_in_use_tree.to_string().contains("super")
611 {
612 use_tree_paths.insert(0, make::ext::ident_path("super"));
613 }
614
615 let is_item = matches!(
616 def,
617 Definition::Macro(_)
618 | Definition::Module(_)
619 | Definition::Function(_)
620 | Definition::Adt(_)
621 | Definition::Const(_)
622 | Definition::Static(_)
623 | Definition::Trait(_)
624 | Definition::TypeAlias(_)
625 );
626
627 if (def_out_sel || !is_item) && use_stmt_not_in_sel {
628 let use_ = make::use_(
629 None,
630 None,
631 make::use_tree(make::join_paths(use_tree_paths), None, None, false),
632 );
633 self.use_items.insert(0, ast::Item::from(use_));
634 }
635 }
636
637 import_path_to_be_removed
638 }
639
640 fn process_use_stmt_for_import_resolve(
641 &self,
642 use_stmt: Option<ast::Use>,
643 node_syntax: &SyntaxNode,
644 ) -> Option<(Vec<ast::Path>, Option<TextRange>)> {
645 let use_stmt = use_stmt?;
646 for path_seg in use_stmt.syntax().descendants().filter_map(ast::PathSegment::cast) {
647 if path_seg.syntax().to_string() == node_syntax.to_string() {
648 let mut use_tree_str = vec![path_seg.parent_path()];
649 get_use_tree_paths_from_path(path_seg.parent_path(), &mut use_tree_str);
650
651 for use_tree in path_seg.syntax().ancestors().filter_map(ast::UseTree::cast) {
656 if use_tree.syntax().to_string() == node_syntax.to_string() {
657 return Some((use_tree_str, Some(range_to_remove(use_tree.syntax()))));
658 }
659 }
660
661 return Some((use_tree_str, None));
662 }
663 }
664
665 None
666 }
667}
668
669fn check_intersection_and_push(
670 import_paths_to_be_removed: &mut Vec<TextRange>,
671 mut import_path: TextRange,
672) {
673 let s: SmallVec<[_; 2]> = import_paths_to_be_removed
680 .iter_mut()
681 .positions(|it| it.intersect(import_path).is_some())
682 .collect();
683 for pos in s.into_iter().rev() {
684 let intersecting_path = import_paths_to_be_removed.swap_remove(pos);
685 import_path = import_path.cover(intersecting_path);
686 }
687 import_paths_to_be_removed.push(import_path);
688}
689
690fn check_def_in_mod_and_out_sel(
691 def: Definition,
692 ctx: &AssistContext<'_>,
693 curr_parent_module: &Option<ast::Module>,
694 selection_range: TextRange,
695 curr_file_id: FileId,
696) -> (bool, bool) {
697 macro_rules! check_item {
698 ($x:ident) => {
699 if let Some(source) = $x.source(ctx.db()) {
700 let have_same_parent = if let Some(ast_module) = &curr_parent_module {
701 ctx.sema.to_module_def(ast_module).is_some_and(|it| it == $x.module(ctx.db()))
702 } else {
703 source.file_id.original_file(ctx.db()).file_id(ctx.db()) == curr_file_id
704 };
705
706 let in_sel = !selection_range.contains_range(source.value.syntax().text_range());
707 return (have_same_parent, in_sel);
708 }
709 };
710 }
711
712 match def {
713 Definition::Module(x) => {
714 let source = x.definition_source(ctx.db());
715 let have_same_parent = match (&curr_parent_module, x.parent(ctx.db())) {
716 (Some(ast_module), Some(hir_module)) => {
717 ctx.sema.to_module_def(ast_module).is_some_and(|it| it == hir_module)
718 }
719 _ => source.file_id.original_file(ctx.db()).file_id(ctx.db()) == curr_file_id,
720 };
721
722 if have_same_parent && let ModuleSource::Module(module_) = source.value {
723 let in_sel = !selection_range.contains_range(module_.syntax().text_range());
724 return (have_same_parent, in_sel);
725 }
726
727 return (have_same_parent, false);
728 }
729 Definition::Function(x) => check_item!(x),
730 Definition::Adt(x) => check_item!(x),
731 Definition::Variant(x) => check_item!(x),
732 Definition::Const(x) => check_item!(x),
733 Definition::Static(x) => check_item!(x),
734 Definition::Trait(x) => check_item!(x),
735 Definition::TypeAlias(x) => check_item!(x),
736 _ => {}
737 }
738
739 (false, false)
740}
741
742fn get_replacements_for_visibility_change(
743 items: &mut [ast::Item],
744 is_clone_for_updated: bool,
745) -> (
746 Vec<(Option<ast::Visibility>, SyntaxNode)>,
747 Vec<(Option<ast::Visibility>, SyntaxNode)>,
748 Vec<ast::Impl>,
749) {
750 let mut replacements = Vec::new();
751 let mut record_field_parents = Vec::new();
752 let mut impls = Vec::new();
753
754 for item in items {
755 if !is_clone_for_updated {
756 *item = item.clone_for_update();
757 }
758 macro_rules! push_to_replacement {
760 ($it:ident) => {
761 replacements.push(($it.visibility(), $it.syntax().clone()))
762 };
763 }
764
765 match item {
766 ast::Item::Const(it) => push_to_replacement!(it),
767 ast::Item::Enum(it) => push_to_replacement!(it),
768 ast::Item::ExternCrate(it) => push_to_replacement!(it),
769 ast::Item::Fn(it) => push_to_replacement!(it),
770 ast::Item::Impl(it) if it.for_token().is_none() => impls.push(it.clone()),
772 ast::Item::MacroDef(it) => push_to_replacement!(it),
773 ast::Item::Module(it) => push_to_replacement!(it),
774 ast::Item::Static(it) => push_to_replacement!(it),
775 ast::Item::Struct(it) => {
776 push_to_replacement!(it);
777 record_field_parents.push((it.visibility(), it.syntax().clone()));
778 }
779 ast::Item::Trait(it) => push_to_replacement!(it),
780 ast::Item::TypeAlias(it) => push_to_replacement!(it),
781 ast::Item::Union(it) => {
782 push_to_replacement!(it);
783 record_field_parents.push((it.visibility(), it.syntax().clone()));
784 }
785 _ => (),
786 }
787 }
788
789 (replacements, record_field_parents, impls)
790}
791
792fn get_use_tree_paths_from_path(
793 path: ast::Path,
794 use_tree_str: &mut Vec<ast::Path>,
795) -> Option<&mut Vec<ast::Path>> {
796 path.syntax()
797 .ancestors()
798 .filter(|x| x.to_string() != path.to_string())
799 .filter_map(ast::UseTree::cast)
800 .find_map(|use_tree| {
801 if let Some(upper_tree_path) = use_tree.path()
802 && upper_tree_path.to_string() != path.to_string()
803 {
804 use_tree_str.push(upper_tree_path.clone());
805 get_use_tree_paths_from_path(upper_tree_path, use_tree_str);
806 return Some(use_tree);
807 }
808 None
809 })?;
810
811 Some(use_tree_str)
812}
813
814fn add_change_vis(vis: Option<ast::Visibility>, node_or_token_opt: Option<syntax::SyntaxElement>) {
815 if vis.is_none()
816 && let Some(node_or_token) = node_or_token_opt
817 {
818 let pub_crate_vis = make::visibility_pub_crate().clone_for_update();
819 ted::insert(ted::Position::before(node_or_token), pub_crate_vis.syntax());
820 }
821}
822
823fn indent_range_before_given_node(node: &SyntaxNode) -> Option<TextRange> {
824 node.siblings_with_tokens(syntax::Direction::Prev)
825 .find(|x| x.kind() == WHITESPACE)
826 .map(|x| x.text_range())
827}
828
829#[cfg(test)]
830mod tests {
831 use crate::tests::{check_assist, check_assist_not_applicable};
832
833 use super::*;
834
835 #[test]
836 fn test_not_applicable_without_selection() {
837 check_assist_not_applicable(
838 extract_module,
839 r"
840$0pub struct PublicStruct {
841 field: i32,
842}
843 ",
844 )
845 }
846
847 #[test]
848 fn test_extract_module() {
849 check_assist(
850 extract_module,
851 r"
852 mod thirdpartycrate {
853 pub mod nest {
854 pub struct SomeType;
855 pub struct SomeType2;
856 }
857 pub struct SomeType1;
858 }
859
860 mod bar {
861 use crate::thirdpartycrate::{nest::{SomeType, SomeType2}, SomeType1};
862
863 pub struct PublicStruct {
864 field: PrivateStruct,
865 field1: SomeType1,
866 }
867
868 impl PublicStruct {
869 pub fn new() -> Self {
870 Self { field: PrivateStruct::new(), field1: SomeType1 }
871 }
872 }
873
874 fn foo() {
875 let _s = PrivateStruct::new();
876 let _a = bar();
877 }
878
879$0struct PrivateStruct {
880 inner: SomeType,
881}
882
883pub struct PrivateStruct1 {
884 pub inner: i32,
885}
886
887impl PrivateStruct {
888 fn new() -> Self {
889 PrivateStruct { inner: SomeType }
890 }
891}
892
893fn bar() -> i32 {
894 2
895}$0
896 }
897 ",
898 r"
899 mod thirdpartycrate {
900 pub mod nest {
901 pub struct SomeType;
902 pub struct SomeType2;
903 }
904 pub struct SomeType1;
905 }
906
907 mod bar {
908 use crate::thirdpartycrate::{nest::{SomeType2}, SomeType1};
909
910 pub struct PublicStruct {
911 field: modname::PrivateStruct,
912 field1: SomeType1,
913 }
914
915 impl PublicStruct {
916 pub fn new() -> Self {
917 Self { field: modname::PrivateStruct::new(), field1: SomeType1 }
918 }
919 }
920
921 fn foo() {
922 let _s = modname::PrivateStruct::new();
923 let _a = modname::bar();
924 }
925
926mod modname {
927 use crate::thirdpartycrate::nest::SomeType;
928
929 pub(crate) struct PrivateStruct {
930 pub(crate) inner: SomeType,
931 }
932
933 pub struct PrivateStruct1 {
934 pub inner: i32,
935 }
936
937 impl PrivateStruct {
938 pub(crate) fn new() -> Self {
939 PrivateStruct { inner: SomeType }
940 }
941 }
942
943 pub(crate) fn bar() -> i32 {
944 2
945 }
946}
947 }
948 ",
949 );
950 }
951
952 #[test]
953 fn test_extract_module_for_function_only() {
954 check_assist(
955 extract_module,
956 r"
957$0fn foo(name: i32) -> i32 {
958 name + 1
959}$0
960
961 fn bar(name: i32) -> i32 {
962 name + 2
963 }
964 ",
965 r"
966mod modname {
967 pub(crate) fn foo(name: i32) -> i32 {
968 name + 1
969 }
970}
971
972 fn bar(name: i32) -> i32 {
973 name + 2
974 }
975 ",
976 )
977 }
978
979 #[test]
980 fn test_extract_module_for_impl_having_corresponding_adt_in_selection() {
981 check_assist(
982 extract_module,
983 r"
984 mod impl_play {
985$0struct A {}
986
987impl A {
988 pub fn new_a() -> i32 {
989 2
990 }
991}$0
992
993 fn a() {
994 let _a = A::new_a();
995 }
996 }
997 ",
998 r"
999 mod impl_play {
1000mod modname {
1001 pub(crate) struct A {}
1002
1003 impl A {
1004 pub fn new_a() -> i32 {
1005 2
1006 }
1007 }
1008}
1009
1010 fn a() {
1011 let _a = modname::A::new_a();
1012 }
1013 }
1014 ",
1015 )
1016 }
1017
1018 #[test]
1019 fn test_import_resolve_when_its_only_inside_selection() {
1020 check_assist(
1021 extract_module,
1022 r"
1023 mod foo {
1024 pub struct PrivateStruct;
1025 pub struct PrivateStruct1;
1026 }
1027
1028 mod bar {
1029 use super::foo::{PrivateStruct, PrivateStruct1};
1030
1031$0struct Strukt {
1032 field: PrivateStruct,
1033}$0
1034
1035 struct Strukt1 {
1036 field: PrivateStruct1,
1037 }
1038 }
1039 ",
1040 r"
1041 mod foo {
1042 pub struct PrivateStruct;
1043 pub struct PrivateStruct1;
1044 }
1045
1046 mod bar {
1047 use super::foo::{PrivateStruct1};
1048
1049mod modname {
1050 use super::super::foo::PrivateStruct;
1051
1052 pub(crate) struct Strukt {
1053 pub(crate) field: PrivateStruct,
1054 }
1055}
1056
1057 struct Strukt1 {
1058 field: PrivateStruct1,
1059 }
1060 }
1061 ",
1062 )
1063 }
1064
1065 #[test]
1066 fn test_import_resolve_when_its_inside_and_outside_selection_and_source_not_in_same_mod() {
1067 check_assist(
1068 extract_module,
1069 r"
1070 mod foo {
1071 pub struct PrivateStruct;
1072 }
1073
1074 mod bar {
1075 use super::foo::PrivateStruct;
1076
1077$0struct Strukt {
1078 field: PrivateStruct,
1079}$0
1080
1081 struct Strukt1 {
1082 field: PrivateStruct,
1083 }
1084 }
1085 ",
1086 r"
1087 mod foo {
1088 pub struct PrivateStruct;
1089 }
1090
1091 mod bar {
1092 use super::foo::PrivateStruct;
1093
1094mod modname {
1095 use super::super::foo::PrivateStruct;
1096
1097 pub(crate) struct Strukt {
1098 pub(crate) field: PrivateStruct,
1099 }
1100}
1101
1102 struct Strukt1 {
1103 field: PrivateStruct,
1104 }
1105 }
1106 ",
1107 )
1108 }
1109
1110 #[test]
1111 fn test_import_resolve_when_its_inside_and_outside_selection_and_source_is_in_same_mod() {
1112 check_assist(
1113 extract_module,
1114 r"
1115 mod bar {
1116 pub struct PrivateStruct;
1117
1118$0struct Strukt {
1119 field: PrivateStruct,
1120}$0
1121
1122 struct Strukt1 {
1123 field: PrivateStruct,
1124 }
1125 }
1126 ",
1127 r"
1128 mod bar {
1129 pub struct PrivateStruct;
1130
1131mod modname {
1132 use super::PrivateStruct;
1133
1134 pub(crate) struct Strukt {
1135 pub(crate) field: PrivateStruct,
1136 }
1137}
1138
1139 struct Strukt1 {
1140 field: PrivateStruct,
1141 }
1142 }
1143 ",
1144 )
1145 }
1146
1147 #[test]
1148 fn test_extract_module_for_corresponding_adt_of_impl_present_in_same_mod_but_not_in_selection()
1149 {
1150 check_assist(
1151 extract_module,
1152 r"
1153 mod impl_play {
1154 struct A {}
1155
1156$0impl A {
1157 pub fn new_a() -> i32 {
1158 2
1159 }
1160}$0
1161
1162 fn a() {
1163 let _a = A::new_a();
1164 }
1165 }
1166 ",
1167 r"
1168 mod impl_play {
1169 struct A {}
1170
1171mod modname {
1172 use super::A;
1173
1174 impl A {
1175 pub fn new_a() -> i32 {
1176 2
1177 }
1178 }
1179}
1180
1181 fn a() {
1182 let _a = A::new_a();
1183 }
1184 }
1185 ",
1186 )
1187 }
1188
1189 #[test]
1190 fn test_extract_module_for_impl_not_having_corresponding_adt_in_selection_and_not_in_same_mod_but_with_super()
1191 {
1192 check_assist(
1193 extract_module,
1194 r"
1195 mod foo {
1196 pub struct A {}
1197 }
1198 mod impl_play {
1199 use super::foo::A;
1200
1201$0impl A {
1202 pub fn new_a() -> i32 {
1203 2
1204 }
1205}$0
1206
1207 fn a() {
1208 let _a = A::new_a();
1209 }
1210 }
1211 ",
1212 r"
1213 mod foo {
1214 pub struct A {}
1215 }
1216 mod impl_play {
1217 use super::foo::A;
1218
1219mod modname {
1220 use super::super::foo::A;
1221
1222 impl A {
1223 pub fn new_a() -> i32 {
1224 2
1225 }
1226 }
1227}
1228
1229 fn a() {
1230 let _a = A::new_a();
1231 }
1232 }
1233 ",
1234 )
1235 }
1236
1237 #[test]
1238 fn test_import_resolve_for_trait_bounds_on_function() {
1239 check_assist(
1240 extract_module,
1241 r"
1242 mod impl_play2 {
1243 trait JustATrait {}
1244
1245$0struct A {}
1246
1247fn foo<T: JustATrait>(arg: T) -> T {
1248 arg
1249}
1250
1251impl JustATrait for A {}
1252
1253fn bar() {
1254 let a = A {};
1255 foo(a);
1256}$0
1257 }
1258 ",
1259 r"
1260 mod impl_play2 {
1261 trait JustATrait {}
1262
1263mod modname {
1264 use super::JustATrait;
1265
1266 pub(crate) struct A {}
1267
1268 pub(crate) fn foo<T: JustATrait>(arg: T) -> T {
1269 arg
1270 }
1271
1272 impl JustATrait for A {}
1273
1274 pub(crate) fn bar() {
1275 let a = A {};
1276 foo(a);
1277 }
1278}
1279 }
1280 ",
1281 )
1282 }
1283
1284 #[test]
1285 fn test_extract_module_for_module() {
1286 check_assist(
1287 extract_module,
1288 r"
1289 mod impl_play2 {
1290$0mod impl_play {
1291 pub struct A {}
1292}$0
1293 }
1294 ",
1295 r"
1296 mod impl_play2 {
1297mod modname {
1298 pub(crate) mod impl_play {
1299 pub struct A {}
1300 }
1301}
1302 }
1303 ",
1304 )
1305 }
1306
1307 #[test]
1308 fn test_extract_module_with_multiple_files() {
1309 check_assist(
1310 extract_module,
1311 r"
1312 //- /main.rs
1313 mod foo;
1314
1315 use foo::PrivateStruct;
1316
1317 pub struct Strukt {
1318 field: PrivateStruct,
1319 }
1320
1321 fn main() {
1322 $0struct Strukt1 {
1323 field: Strukt,
1324 }$0
1325 }
1326 //- /foo.rs
1327 pub struct PrivateStruct;
1328 ",
1329 r"
1330 mod foo;
1331
1332 use foo::PrivateStruct;
1333
1334 pub struct Strukt {
1335 field: PrivateStruct,
1336 }
1337
1338 fn main() {
1339 mod modname {
1340 use super::Strukt;
1341
1342 pub(crate) struct Strukt1 {
1343 pub(crate) field: Strukt,
1344 }
1345 }
1346 }
1347 ",
1348 )
1349 }
1350
1351 #[test]
1352 fn test_extract_module_macro_rules() {
1353 check_assist(
1354 extract_module,
1355 r"
1356$0macro_rules! m {
1357 () => {};
1358}$0
1359m! {}
1360 ",
1361 r"
1362mod modname {
1363 macro_rules! m {
1364 () => {};
1365 }
1366}
1367modname::m! {}
1368 ",
1369 );
1370 }
1371
1372 #[test]
1373 fn test_do_not_apply_visibility_modifier_to_trait_impl_items() {
1374 check_assist(
1375 extract_module,
1376 r"
1377 trait ATrait {
1378 fn function();
1379 }
1380
1381 struct A {}
1382
1383$0impl ATrait for A {
1384 fn function() {}
1385}$0
1386 ",
1387 r"
1388 trait ATrait {
1389 fn function();
1390 }
1391
1392 struct A {}
1393
1394mod modname {
1395 use super::A;
1396
1397 use super::ATrait;
1398
1399 impl ATrait for A {
1400 fn function() {}
1401 }
1402}
1403 ",
1404 )
1405 }
1406
1407 #[test]
1408 fn test_if_inside_impl_block_generate_module_outside() {
1409 check_assist(
1410 extract_module,
1411 r"struct A {}
1412
1413 impl A {
1414 $0fn foo() {}$0
1415 fn bar() {}
1416 }
1417 ",
1418 r"struct A {}
1419
1420 impl A {
1421 fn bar() {}
1422 }
1423
1424 mod modname {
1425 use super::A;
1426
1427 impl A {
1428 pub(crate) fn foo() {}
1429 }
1430 }
1431 ",
1432 );
1433
1434 check_assist(
1435 extract_module,
1436 r"struct A {}
1437
1438 impl A {
1439 $0fn foo() {}
1440 fn bar() {}$0
1441 fn baz() {}
1442 }
1443 ",
1444 r"struct A {}
1445
1446 impl A {
1447 fn baz() {}
1448 }
1449
1450 mod modname {
1451 use super::A;
1452
1453 impl A {
1454 pub(crate) fn foo() {}
1455
1456 pub(crate) fn bar() {}
1457 }
1458 }
1459 ",
1460 )
1461 }
1462
1463 #[test]
1464 fn test_if_inside_impl_block_generate_module_outside_but_impl_block_having_one_child() {
1465 check_assist(
1466 extract_module,
1467 r"struct A {}
1468 struct B {}
1469
1470 impl A {
1471$0fn foo(x: B) {}$0
1472 }
1473 ",
1474 r"struct A {}
1475 struct B {}
1476
1477 mod modname {
1478 use super::A;
1479
1480 use super::B;
1481
1482 impl A {
1483 pub(crate) fn foo(x: B) {}
1484 }
1485 }
1486 ",
1487 )
1488 }
1489
1490 #[test]
1491 fn test_issue_11766() {
1492 check_assist(
1494 extract_module,
1495 r"
1496 mod x {
1497 pub struct Foo;
1498 pub struct Bar;
1499 }
1500
1501 use x::{Bar, Foo};
1502
1503 $0type A = (Foo, Bar);$0
1504 ",
1505 r"
1506 mod x {
1507 pub struct Foo;
1508 pub struct Bar;
1509 }
1510
1511 use x::{};
1512
1513 mod modname {
1514 use super::x::Bar;
1515
1516 use super::x::Foo;
1517
1518 pub(crate) type A = (Foo, Bar);
1519 }
1520 ",
1521 )
1522 }
1523
1524 #[test]
1525 fn test_issue_12790() {
1526 check_assist(
1527 extract_module,
1528 r"
1529 $0/// A documented function
1530 fn documented_fn() {}
1531
1532 // A commented function with a #[] attribute macro
1533 #[cfg(test)]
1534 fn attribute_fn() {}
1535
1536 // A normally commented function
1537 fn normal_fn() {}
1538
1539 /// A documented Struct
1540 struct DocumentedStruct {
1541 // Normal field
1542 x: i32,
1543
1544 /// Documented field
1545 y: i32,
1546
1547 // Macroed field
1548 #[cfg(test)]
1549 z: i32,
1550 }
1551
1552 // A macroed Struct
1553 #[cfg(test)]
1554 struct MacroedStruct {
1555 // Normal field
1556 x: i32,
1557
1558 /// Documented field
1559 y: i32,
1560
1561 // Macroed field
1562 #[cfg(test)]
1563 z: i32,
1564 }
1565
1566 // A normal Struct
1567 struct NormalStruct {
1568 // Normal field
1569 x: i32,
1570
1571 /// Documented field
1572 y: i32,
1573
1574 // Macroed field
1575 #[cfg(test)]
1576 z: i32,
1577 }
1578
1579 /// A documented type
1580 type DocumentedType = i32;
1581
1582 // A macroed type
1583 #[cfg(test)]
1584 type MacroedType = i32;
1585
1586 /// A module to move
1587 mod module {}
1588
1589 /// An impl to move
1590 impl NormalStruct {
1591 /// A method
1592 fn new() {}
1593 }
1594
1595 /// A documented trait
1596 trait DocTrait {
1597 /// Inner function
1598 fn doc() {}
1599 }
1600
1601 /// An enum
1602 enum DocumentedEnum {
1603 /// A variant
1604 A,
1605 /// Another variant
1606 B { x: i32, y: i32 }
1607 }
1608
1609 /// Documented const
1610 const MY_CONST: i32 = 0;$0
1611 ",
1612 r"
1613 mod modname {
1614 /// A documented function
1615 pub(crate) fn documented_fn() {}
1616
1617 // A commented function with a #[] attribute macro
1618 #[cfg(test)]
1619 pub(crate) fn attribute_fn() {}
1620
1621 // A normally commented function
1622 pub(crate) fn normal_fn() {}
1623
1624 /// A documented Struct
1625 pub(crate) struct DocumentedStruct {
1626 // Normal field
1627 pub(crate) x: i32,
1628
1629 /// Documented field
1630 pub(crate) y: i32,
1631
1632 // Macroed field
1633 #[cfg(test)]
1634 pub(crate) z: i32,
1635 }
1636
1637 // A macroed Struct
1638 #[cfg(test)]
1639 pub(crate) struct MacroedStruct {
1640 // Normal field
1641 pub(crate) x: i32,
1642
1643 /// Documented field
1644 pub(crate) y: i32,
1645
1646 // Macroed field
1647 #[cfg(test)]
1648 pub(crate) z: i32,
1649 }
1650
1651 // A normal Struct
1652 pub(crate) struct NormalStruct {
1653 // Normal field
1654 pub(crate) x: i32,
1655
1656 /// Documented field
1657 pub(crate) y: i32,
1658
1659 // Macroed field
1660 #[cfg(test)]
1661 pub(crate) z: i32,
1662 }
1663
1664 /// A documented type
1665 pub(crate) type DocumentedType = i32;
1666
1667 // A macroed type
1668 #[cfg(test)]
1669 pub(crate) type MacroedType = i32;
1670
1671 /// A module to move
1672 pub(crate) mod module {}
1673
1674 /// An impl to move
1675 impl NormalStruct {
1676 /// A method
1677 pub(crate) fn new() {}
1678 }
1679
1680 /// A documented trait
1681 pub(crate) trait DocTrait {
1682 /// Inner function
1683 fn doc() {}
1684 }
1685
1686 /// An enum
1687 pub(crate) enum DocumentedEnum {
1688 /// A variant
1689 A,
1690 /// Another variant
1691 B { x: i32, y: i32 }
1692 }
1693
1694 /// Documented const
1695 pub(crate) const MY_CONST: i32 = 0;
1696 }
1697 ",
1698 )
1699 }
1700
1701 #[test]
1702 fn test_merge_multiple_intersections() {
1703 check_assist(
1704 extract_module,
1705 r#"
1706mod dep {
1707 pub struct A;
1708 pub struct B;
1709 pub struct C;
1710}
1711
1712use dep::{A, B, C};
1713
1714$0struct S {
1715 inner: A,
1716 state: C,
1717 condvar: B,
1718}$0
1719"#,
1720 r#"
1721mod dep {
1722 pub struct A;
1723 pub struct B;
1724 pub struct C;
1725}
1726
1727use dep::{};
1728
1729mod modname {
1730 use super::dep::B;
1731
1732 use super::dep::C;
1733
1734 use super::dep::A;
1735
1736 pub(crate) struct S {
1737 pub(crate) inner: A,
1738 pub(crate) state: C,
1739 pub(crate) condvar: B,
1740 }
1741}
1742"#,
1743 );
1744 }
1745
1746 #[test]
1747 fn test_remove_import_path_inside_selection() {
1748 check_assist(
1749 extract_module,
1750 r#"
1751$0struct Point;
1752impl Point {
1753 pub const fn direction(self, other: Self) -> Option<Direction> {
1754 Some(Vertical)
1755 }
1756}
1757
1758pub enum Direction {
1759 Horizontal,
1760 Vertical,
1761}
1762use Direction::{Horizontal, Vertical};$0
1763
1764fn main() {
1765 let x = Vertical;
1766}
1767"#,
1768 r#"
1769mod modname {
1770 use Direction::{Horizontal, Vertical};
1771
1772 pub(crate) struct Point;
1773
1774 impl Point {
1775 pub const fn direction(self, other: Self) -> Option<Direction> {
1776 Some(Vertical)
1777 }
1778 }
1779
1780 pub enum Direction {
1781 Horizontal,
1782 Vertical,
1783 }
1784}
1785use modname::Direction::{Horizontal, Vertical};
1786
1787fn main() {
1788 let x = Vertical;
1789}
1790"#,
1791 );
1792 }
1793
1794 #[test]
1795 fn test_miss_select_item() {
1796 check_assist(
1797 extract_module,
1798 r#"
1799mod foo {
1800 mod $0bar {
1801 fn foo(){}$0
1802 }
1803}
1804"#,
1805 r#"
1806mod foo {
1807 mod modname {
1808 pub(crate) mod bar {
1809 fn foo(){}
1810 }
1811 }
1812}
1813"#,
1814 )
1815 }
1816}