Skip to content

Commit fcd4010

Browse files
committed
In extract_module.rs, generate ast::Module instead of String
1 parent 529d3b9 commit fcd4010

File tree

2 files changed

+126
-66
lines changed

2 files changed

+126
-66
lines changed

crates/ide-assists/src/handlers/extract_module.rs

Lines changed: 109 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use std::iter;
1+
use std::ops::RangeInclusive;
22

3-
use either::Either;
43
use hir::{HasSource, ModuleSource};
54
use ide_db::{
65
FileId, FxHashMap, FxHashSet,
@@ -82,7 +81,15 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
8281
curr_parent_module = ast::Module::cast(mod_syn_opt);
8382
}
8483

85-
let mut module = extract_target(&node, ctx.selection_trimmed())?;
84+
let selection_range = ctx.selection_trimmed();
85+
let (mut module, module_text_range) = if let Some(item) = ast::Item::cast(node.clone()) {
86+
let module = extract_single_target(&item);
87+
(module, node.text_range())
88+
} else {
89+
let (module, range) = extract_child_target(&node, selection_range)?;
90+
let module_text_range = range.start().text_range().cover(range.end().text_range());
91+
(module, module_text_range)
92+
};
8693
if module.body_items.is_empty() {
8794
return None;
8895
}
@@ -92,7 +99,7 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
9299
acc.add(
93100
AssistId::refactor_extract("extract_module"),
94101
"Extract Module",
95-
module.text_range,
102+
module_text_range,
96103
|builder| {
97104
//This takes place in three steps:
98105
//
@@ -110,17 +117,17 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
110117
//for change_visibility and usages for first point mentioned above in the process
111118

112119
let (usages_to_be_processed, record_fields, use_stmts_to_be_inserted) =
113-
module.get_usages_and_record_fields(ctx);
120+
module.get_usages_and_record_fields(ctx, module_text_range);
114121

115122
builder.edit_file(ctx.vfs_file_id());
116123
use_stmts_to_be_inserted.into_iter().for_each(|(_, use_stmt)| {
117124
builder.insert(ctx.selection_trimmed().end(), format!("\n{use_stmt}"));
118125
});
119126

120-
let import_paths_to_be_removed = module.resolve_imports(curr_parent_module, ctx);
127+
let import_items = module.resolve_imports(curr_parent_module, ctx);
121128
module.change_visibility(record_fields);
122129

123-
let module_def = generate_module_def(&impl_parent, &mut module, old_item_indent);
130+
let module_def = generate_module_def(&impl_parent, module, old_item_indent).to_string();
124131

125132
let mut usages_to_be_processed_for_cur_file = vec![];
126133
for (file_id, usages) in usages_to_be_processed {
@@ -157,54 +164,63 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
157164

158165
builder.insert(impl_.syntax().text_range().end(), format!("\n\n{module_def}"));
159166
} else {
160-
for import_path_text_range in import_paths_to_be_removed {
161-
if module.text_range.intersect(import_path_text_range).is_some() {
162-
module.text_range = module.text_range.cover(import_path_text_range);
163-
} else {
164-
builder.delete(import_path_text_range);
167+
for import_item in import_items {
168+
if !module_text_range.contains_range(import_item) {
169+
builder.delete(import_item);
165170
}
166171
}
167-
168-
builder.replace(module.text_range, module_def)
172+
builder.replace(module_text_range, module_def)
169173
}
170174
},
171175
)
172176
}
173177

174178
fn generate_module_def(
175179
parent_impl: &Option<ast::Impl>,
176-
module: &mut Module,
180+
module: Module,
177181
old_indent: IndentLevel,
178-
) -> String {
179-
let (items_to_be_processed, new_item_indent) = if parent_impl.is_some() {
180-
(Either::Left(module.body_items.iter()), old_indent + 2)
182+
) -> ast::Module {
183+
let Module { name, body_items, use_items } = module;
184+
let items = if let Some(self_ty) = parent_impl.as_ref().and_then(|imp| imp.self_ty()) {
185+
let assoc_items = body_items
186+
.into_iter()
187+
.map(|item| item.syntax().clone())
188+
.filter_map(ast::AssocItem::cast)
189+
.map(|it| it.indent(IndentLevel(1)))
190+
.collect_vec();
191+
let assoc_item_list = make::assoc_item_list(Some(assoc_items));
192+
let impl_ = make::impl_(None, None, None, self_ty.clone(), None, Some(assoc_item_list));
193+
// Add the import for enum/struct corresponding to given impl block
194+
let use_impl = make_use_stmt_of_node_with_super(self_ty.syntax());
195+
let mut module_body_items = use_items;
196+
module_body_items.insert(0, use_impl);
197+
module_body_items.push(ast::Item::Impl(impl_));
198+
module_body_items
181199
} else {
182-
(Either::Right(module.use_items.iter().chain(module.body_items.iter())), old_indent + 1)
200+
[use_items, body_items].concat()
183201
};
184202

185-
let mut body = items_to_be_processed
186-
.map(|item| item.indent(IndentLevel(1)))
187-
.map(|item| format!("{new_item_indent}{item}"))
188-
.join("\n\n");
203+
let items = items.into_iter().map(|it| it.reset_indent().indent(IndentLevel(1))).collect_vec();
204+
let module_body = make::item_list(Some(items));
189205

190-
if let Some(self_ty) = parent_impl.as_ref().and_then(|imp| imp.self_ty()) {
191-
let impl_indent = old_indent + 1;
192-
body = format!("{impl_indent}impl {self_ty} {{\n{body}\n{impl_indent}}}");
206+
let module_name = make::name(name);
207+
make::mod_(module_name, Some(module_body)).indent(old_indent)
208+
}
193209

194-
// Add the import for enum/struct corresponding to given impl block
195-
module.make_use_stmt_of_node_with_super(self_ty.syntax());
196-
for item in module.use_items.iter() {
197-
body = format!("{impl_indent}{item}\n\n{body}");
198-
}
199-
}
210+
fn make_use_stmt_of_node_with_super(node_syntax: &SyntaxNode) -> ast::Item {
211+
let super_path = make::ext::ident_path("super");
212+
let node_path = make::ext::ident_path(&node_syntax.to_string());
213+
let use_ = make::use_(
214+
None,
215+
None,
216+
make::use_tree(make::join_paths(vec![super_path, node_path]), None, None, false),
217+
);
200218

201-
let module_name = module.name;
202-
format!("mod {module_name} {{\n{body}\n{old_indent}}}")
219+
ast::Item::from(use_)
203220
}
204221

205222
#[derive(Debug)]
206223
struct Module {
207-
text_range: TextRange,
208224
name: &'static str,
209225
/// All items except use items.
210226
body_items: Vec<ast::Item>,
@@ -214,22 +230,37 @@ struct Module {
214230
use_items: Vec<ast::Item>,
215231
}
216232

217-
fn extract_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Module> {
233+
fn extract_single_target(node: &ast::Item) -> Module {
234+
let (body_items, use_items) = if matches!(node, ast::Item::Use(_)) {
235+
(Vec::new(), vec![node.clone()])
236+
} else {
237+
(vec![node.clone()], Vec::new())
238+
};
239+
let name = "modname";
240+
Module { name, body_items, use_items }
241+
}
242+
243+
fn extract_child_target(
244+
node: &SyntaxNode,
245+
selection_range: TextRange,
246+
) -> Option<(Module, RangeInclusive<SyntaxNode>)> {
218247
let selected_nodes = node
219248
.children()
220249
.filter(|node| selection_range.contains_range(node.text_range()))
221-
.chain(iter::once(node.clone()));
222-
let (use_items, body_items) = selected_nodes
223250
.filter_map(ast::Item::cast)
224-
.partition(|item| matches!(item, ast::Item::Use(..)));
225-
226-
Some(Module { text_range: selection_range, name: "modname", body_items, use_items })
251+
.collect_vec();
252+
let start = selected_nodes.first()?.syntax().clone();
253+
let end = selected_nodes.last()?.syntax().clone();
254+
let (use_items, body_items): (Vec<ast::Item>, Vec<ast::Item>) =
255+
selected_nodes.into_iter().partition(|item| matches!(item, ast::Item::Use(..)));
256+
Some((Module { name: "modname", body_items, use_items }, start..=end))
227257
}
228258

229259
impl Module {
230260
fn get_usages_and_record_fields(
231261
&self,
232262
ctx: &AssistContext<'_>,
263+
replace_range: TextRange,
233264
) -> (FxHashMap<FileId, Vec<(TextRange, String)>>, Vec<SyntaxNode>, FxHashMap<TextSize, ast::Use>)
234265
{
235266
let mut adt_fields = Vec::new();
@@ -247,7 +278,7 @@ impl Module {
247278
ast::Adt(it) => {
248279
if let Some( nod ) = ctx.sema.to_def(&it) {
249280
let node_def = Definition::Adt(nod);
250-
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted);
281+
self.expand_and_group_usages_file_wise(ctx, replace_range,node_def, &mut refs, &mut use_stmts_to_be_inserted);
251282

252283
//Enum Fields are not allowed to explicitly specify pub, it is implied
253284
match it {
@@ -281,30 +312,30 @@ impl Module {
281312
ast::TypeAlias(it) => {
282313
if let Some( nod ) = ctx.sema.to_def(&it) {
283314
let node_def = Definition::TypeAlias(nod);
284-
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted);
315+
self.expand_and_group_usages_file_wise(ctx,replace_range, node_def, &mut refs, &mut use_stmts_to_be_inserted);
285316
}
286317
},
287318
ast::Const(it) => {
288319
if let Some( nod ) = ctx.sema.to_def(&it) {
289320
let node_def = Definition::Const(nod);
290-
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted);
321+
self.expand_and_group_usages_file_wise(ctx,replace_range, node_def, &mut refs, &mut use_stmts_to_be_inserted);
291322
}
292323
},
293324
ast::Static(it) => {
294325
if let Some( nod ) = ctx.sema.to_def(&it) {
295326
let node_def = Definition::Static(nod);
296-
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted);
327+
self.expand_and_group_usages_file_wise(ctx,replace_range, node_def, &mut refs, &mut use_stmts_to_be_inserted);
297328
}
298329
},
299330
ast::Fn(it) => {
300331
if let Some( nod ) = ctx.sema.to_def(&it) {
301332
let node_def = Definition::Function(nod);
302-
self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs, &mut use_stmts_to_be_inserted);
333+
self.expand_and_group_usages_file_wise(ctx,replace_range, node_def, &mut refs, &mut use_stmts_to_be_inserted);
303334
}
304335
},
305336
ast::Macro(it) => {
306337
if let Some(nod) = ctx.sema.to_def(&it) {
307-
self.expand_and_group_usages_file_wise(ctx, Definition::Macro(nod), &mut refs, &mut use_stmts_to_be_inserted);
338+
self.expand_and_group_usages_file_wise(ctx,replace_range, Definition::Macro(nod), &mut refs, &mut use_stmts_to_be_inserted);
308339
}
309340
},
310341
_ => (),
@@ -318,6 +349,7 @@ impl Module {
318349
fn expand_and_group_usages_file_wise(
319350
&self,
320351
ctx: &AssistContext<'_>,
352+
replace_range: TextRange,
321353
node_def: Definition,
322354
refs_in_files: &mut FxHashMap<FileId, Vec<(TextRange, String)>>,
323355
use_stmts_to_be_inserted: &mut FxHashMap<TextSize, ast::Use>,
@@ -327,7 +359,7 @@ impl Module {
327359
syntax::NodeOrToken::Node(node) => node,
328360
syntax::NodeOrToken::Token(tok) => tok.parent().unwrap(), // won't panic
329361
};
330-
let out_of_sel = |node: &SyntaxNode| !self.text_range.contains_range(node.text_range());
362+
let out_of_sel = |node: &SyntaxNode| !replace_range.contains_range(node.text_range());
331363
let mut use_stmts_set = FxHashSet::default();
332364

333365
for (file_id, refs) in node_def.usages(&ctx.sema).all() {
@@ -527,7 +559,8 @@ impl Module {
527559
// mod -> ust_stmt transversal
528560
// true | false -> super import insertion
529561
// true | true -> super import insertion
530-
self.make_use_stmt_of_node_with_super(use_node);
562+
let super_use_node = make_use_stmt_of_node_with_super(use_node);
563+
self.use_items.insert(0, super_use_node);
531564
}
532565
None => {}
533566
}
@@ -556,7 +589,8 @@ impl Module {
556589

557590
use_tree_paths = Some(use_tree_str);
558591
} else if def_in_mod && def_out_sel {
559-
self.make_use_stmt_of_node_with_super(use_node);
592+
let super_use_node = make_use_stmt_of_node_with_super(use_node);
593+
self.use_items.insert(0, super_use_node);
560594
}
561595
}
562596

@@ -596,20 +630,6 @@ impl Module {
596630
import_path_to_be_removed
597631
}
598632

599-
fn make_use_stmt_of_node_with_super(&mut self, node_syntax: &SyntaxNode) -> ast::Item {
600-
let super_path = make::ext::ident_path("super");
601-
let node_path = make::ext::ident_path(&node_syntax.to_string());
602-
let use_ = make::use_(
603-
None,
604-
None,
605-
make::use_tree(make::join_paths(vec![super_path, node_path]), None, None, false),
606-
);
607-
608-
let item = ast::Item::from(use_);
609-
self.use_items.insert(0, item.clone());
610-
item
611-
}
612-
613633
fn process_use_stmt_for_import_resolve(
614634
&self,
615635
use_stmt: Option<ast::Use>,
@@ -1424,10 +1444,10 @@ $0fn foo(x: B) {}$0
14241444
struct B {}
14251445
14261446
mod modname {
1427-
use super::B;
1428-
14291447
use super::A;
14301448
1449+
use super::B;
1450+
14311451
impl A {
14321452
pub(crate) fn foo(x: B) {}
14331453
}
@@ -1739,4 +1759,27 @@ fn main() {
17391759
"#,
17401760
);
17411761
}
1762+
1763+
#[test]
1764+
fn test_miss_select_item() {
1765+
check_assist(
1766+
extract_module,
1767+
r#"
1768+
mod foo {
1769+
mod $0bar {
1770+
fn foo(){}$0
1771+
}
1772+
}
1773+
"#,
1774+
r#"
1775+
mod foo {
1776+
mod modname {
1777+
pub(crate) mod bar {
1778+
fn foo(){}
1779+
}
1780+
}
1781+
}
1782+
"#,
1783+
)
1784+
}
17421785
}

crates/syntax/src/ast/make.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,23 @@ pub fn ty_fn_ptr<I: Iterator<Item = Param>>(
231231
}
232232
}
233233

234+
pub fn item_list(body: Option<Vec<ast::Item>>) -> ast::ItemList {
235+
let is_break_braces = body.is_some();
236+
let body_newline = if is_break_braces { "\n" } else { "" };
237+
let body_indent = if is_break_braces { " " } else { "" };
238+
239+
let body = match body {
240+
Some(bd) => bd.iter().map(|elem| elem.to_string()).join("\n\n "),
241+
None => String::new(),
242+
};
243+
ast_from_text(&format!("mod C {{{body_newline}{body_indent}{body}{body_newline}}}"))
244+
}
245+
246+
pub fn mod_(name: ast::Name, body: Option<ast::ItemList>) -> ast::Module {
247+
let body = body.map_or(";".to_owned(), |body| format!(" {body}"));
248+
ast_from_text(&format!("mod {name}{body}"))
249+
}
250+
234251
pub fn assoc_item_list(body: Option<Vec<ast::AssocItem>>) -> ast::AssocItemList {
235252
let is_break_braces = body.is_some();
236253
let body_newline = if is_break_braces { "\n".to_owned() } else { String::new() };

0 commit comments

Comments
 (0)