Skip to content

Commit b0d2487

Browse files
authored
Merge pull request #20311 from Hmikihiro/migrate_convert_tuple_struct_to_named_struct
Migrate `convert_tuple_struct_to_named_struct` assist to use `SyntaxEditor`
2 parents e57f184 + ec02abf commit b0d2487

File tree

2 files changed

+118
-67
lines changed

2 files changed

+118
-67
lines changed

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

Lines changed: 100 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use either::Either;
2+
use hir::FileRangeWrapper;
23
use ide_db::defs::{Definition, NameRefClass};
4+
use std::ops::RangeInclusive;
35
use syntax::{
4-
SyntaxKind, SyntaxNode,
5-
ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility},
6-
match_ast, ted,
6+
SyntaxElement, SyntaxKind, SyntaxNode, T, TextSize,
7+
ast::{
8+
self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory,
9+
},
10+
match_ast,
11+
syntax_editor::{Element, Position, SyntaxEditor},
712
};
813

914
use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder};
@@ -71,66 +76,63 @@ pub(crate) fn convert_tuple_struct_to_named_struct(
7176
Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
7277
};
7378
let target = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range();
74-
79+
let syntax = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax());
7580
acc.add(
7681
AssistId::refactor_rewrite("convert_tuple_struct_to_named_struct"),
7782
"Convert to named struct",
7883
target,
7984
|edit| {
8085
let names = generate_names(tuple_fields.fields());
8186
edit_field_references(ctx, edit, tuple_fields.fields(), &names);
87+
let mut editor = edit.make_editor(syntax);
8288
edit_struct_references(ctx, edit, strukt_def, &names);
83-
edit_struct_def(ctx, edit, &strukt_or_variant, tuple_fields, names);
89+
edit_struct_def(&mut editor, &strukt_or_variant, tuple_fields, names);
90+
edit.add_file_edits(ctx.vfs_file_id(), editor);
8491
},
8592
)
8693
}
8794

8895
fn edit_struct_def(
89-
ctx: &AssistContext<'_>,
90-
edit: &mut SourceChangeBuilder,
96+
editor: &mut SyntaxEditor,
9197
strukt: &Either<ast::Struct, ast::Variant>,
9298
tuple_fields: ast::TupleFieldList,
9399
names: Vec<ast::Name>,
94100
) {
95101
let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| {
96-
let field = ast::make::record_field(f.visibility(), name, f.ty()?).clone_for_update();
97-
ted::insert_all(
98-
ted::Position::first_child_of(field.syntax()),
102+
let field = ast::make::record_field(f.visibility(), name, f.ty()?);
103+
let mut field_editor = SyntaxEditor::new(field.syntax().clone());
104+
field_editor.insert_all(
105+
Position::first_child_of(field.syntax()),
99106
f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(),
100107
);
101-
Some(field)
108+
ast::RecordField::cast(field_editor.finish().new_root().clone())
102109
});
103-
let record_fields = ast::make::record_field_list(record_fields);
104-
let tuple_fields_text_range = tuple_fields.syntax().text_range();
105-
106-
edit.edit_file(ctx.vfs_file_id());
110+
let make = SyntaxFactory::without_mappings();
111+
let record_fields = make.record_field_list(record_fields);
112+
let tuple_fields_before = Position::before(tuple_fields.syntax());
107113

108114
if let Either::Left(strukt) = strukt {
109115
if let Some(w) = strukt.where_clause() {
110-
edit.delete(w.syntax().text_range());
111-
edit.insert(
112-
tuple_fields_text_range.start(),
113-
ast::make::tokens::single_newline().text(),
114-
);
115-
edit.insert(tuple_fields_text_range.start(), w.syntax().text());
116+
editor.delete(w.syntax());
117+
let mut insert_element = Vec::new();
118+
insert_element.push(ast::make::tokens::single_newline().syntax_element());
119+
insert_element.push(w.syntax().clone_for_update().syntax_element());
116120
if w.syntax().last_token().is_none_or(|t| t.kind() != SyntaxKind::COMMA) {
117-
edit.insert(tuple_fields_text_range.start(), ",");
121+
insert_element.push(ast::make::token(T![,]).into());
118122
}
119-
edit.insert(
120-
tuple_fields_text_range.start(),
121-
ast::make::tokens::single_newline().text(),
122-
);
123+
insert_element.push(ast::make::tokens::single_newline().syntax_element());
124+
editor.insert_all(tuple_fields_before, insert_element);
123125
} else {
124-
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
126+
editor.insert(tuple_fields_before, ast::make::tokens::single_space());
125127
}
126128
if let Some(t) = strukt.semicolon_token() {
127-
edit.delete(t.text_range());
129+
editor.delete(t);
128130
}
129131
} else {
130-
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
132+
editor.insert(tuple_fields_before, ast::make::tokens::single_space());
131133
}
132134

133-
edit.replace(tuple_fields_text_range, record_fields.to_string());
135+
editor.replace(tuple_fields.syntax(), record_fields.syntax());
134136
}
135137

136138
fn edit_struct_references(
@@ -145,27 +147,22 @@ fn edit_struct_references(
145147
};
146148
let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
147149

148-
let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> {
150+
let edit_node = |node: SyntaxNode| -> Option<SyntaxNode> {
151+
let make = SyntaxFactory::without_mappings();
149152
match_ast! {
150153
match node {
151154
ast::TupleStructPat(tuple_struct_pat) => {
152-
let file_range = ctx.sema.original_range_opt(&node)?;
153-
edit.edit_file(file_range.file_id.file_id(ctx.db()));
154-
edit.replace(
155-
file_range.range,
156-
ast::make::record_pat_with_fields(
157-
tuple_struct_pat.path()?,
158-
ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
159-
|(pat, name)| {
160-
ast::make::record_pat_field(
161-
ast::make::name_ref(&name.to_string()),
162-
pat,
163-
)
164-
},
165-
), None),
166-
)
167-
.to_string(),
168-
);
155+
Some(make.record_pat_with_fields(
156+
tuple_struct_pat.path()?,
157+
ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
158+
|(pat, name)| {
159+
ast::make::record_pat_field(
160+
ast::make::name_ref(&name.to_string()),
161+
pat,
162+
)
163+
},
164+
), None),
165+
).syntax().clone())
169166
},
170167
// for tuple struct creations like Foo(42)
171168
ast::CallExpr(call_expr) => {
@@ -181,10 +178,8 @@ fn edit_struct_references(
181178
}
182179

183180
let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
184-
185-
edit.replace(
186-
ctx.sema.original_range(&node).range,
187-
ast::make::record_expr(
181+
Some(
182+
make.record_expr(
188183
path,
189184
ast::make::record_expr_field_list(arg_list.args().zip(names).map(
190185
|(expr, name)| {
@@ -194,25 +189,58 @@ fn edit_struct_references(
194189
)
195190
},
196191
)),
197-
)
198-
.to_string(),
199-
);
192+
).syntax().clone()
193+
)
200194
},
201195
_ => return None,
202196
}
203197
}
204-
Some(())
205198
};
206199

207200
for (file_id, refs) in usages {
208-
edit.edit_file(file_id.file_id(ctx.db()));
209-
for r in refs {
210-
for node in r.name.syntax().ancestors() {
211-
if edit_node(edit, node).is_some() {
212-
break;
201+
let source = ctx.sema.parse(file_id);
202+
let source = source.syntax();
203+
204+
let mut editor = edit.make_editor(source);
205+
for r in refs.iter().rev() {
206+
if let Some((old_node, new_node)) = r
207+
.name
208+
.syntax()
209+
.ancestors()
210+
.find_map(|node| Some((node.clone(), edit_node(node.clone())?)))
211+
{
212+
if let Some(old_node) = ctx.sema.original_syntax_node_rooted(&old_node) {
213+
editor.replace(old_node, new_node);
214+
} else {
215+
let FileRangeWrapper { file_id: _, range } = ctx.sema.original_range(&old_node);
216+
let parent = source.covering_element(range);
217+
match parent {
218+
SyntaxElement::Token(token) => {
219+
editor.replace(token, new_node.syntax_element());
220+
}
221+
SyntaxElement::Node(parent_node) => {
222+
// replace the part of macro
223+
// ```
224+
// foo!(a, Test::A(0));
225+
// ^^^^^^^^^^^^^^^ // parent_node
226+
// ^^^^^^^^^^ // replace_range
227+
// ```
228+
let start = parent_node
229+
.children_with_tokens()
230+
.find(|t| t.text_range().contains(range.start()));
231+
let end = parent_node
232+
.children_with_tokens()
233+
.find(|t| t.text_range().contains(range.end() - TextSize::new(1)));
234+
if let (Some(start), Some(end)) = (start, end) {
235+
let replace_range = RangeInclusive::new(start, end);
236+
editor.replace_all(replace_range, vec![new_node.into()]);
237+
}
238+
}
239+
}
213240
}
214241
}
215242
}
243+
edit.add_file_edits(file_id.file_id(ctx.db()), editor);
216244
}
217245
}
218246

@@ -230,22 +258,28 @@ fn edit_field_references(
230258
let def = Definition::Field(field);
231259
let usages = def.usages(&ctx.sema).all();
232260
for (file_id, refs) in usages {
233-
edit.edit_file(file_id.file_id(ctx.db()));
261+
let source = ctx.sema.parse(file_id);
262+
let source = source.syntax();
263+
let mut editor = edit.make_editor(source);
234264
for r in refs {
235-
if let Some(name_ref) = r.name.as_name_ref() {
236-
edit.replace(ctx.sema.original_range(name_ref.syntax()).range, name.text());
265+
if let Some(name_ref) = r.name.as_name_ref()
266+
&& let Some(original) = ctx.sema.original_ast_node(name_ref.clone())
267+
{
268+
editor.replace(original.syntax(), name.syntax());
237269
}
238270
}
271+
edit.add_file_edits(file_id.file_id(ctx.db()), editor);
239272
}
240273
}
241274
}
242275

243276
fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
277+
let make = SyntaxFactory::without_mappings();
244278
fields
245279
.enumerate()
246280
.map(|(i, _)| {
247281
let idx = i + 1;
248-
ast::make::name(&format!("field{idx}"))
282+
make.name(&format!("field{idx}"))
249283
})
250284
.collect()
251285
}
@@ -1013,8 +1047,7 @@ where
10131047
pub struct $0Foo(#[my_custom_attr] u32);
10141048
"#,
10151049
r#"
1016-
pub struct Foo { #[my_custom_attr]
1017-
field1: u32 }
1050+
pub struct Foo { #[my_custom_attr]field1: u32 }
10181051
"#,
10191052
);
10201053
}

crates/syntax/src/ast/syntax_factory/constructors.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,24 @@ impl SyntaxFactory {
939939
ast
940940
}
941941

942+
pub fn record_expr(
943+
&self,
944+
path: ast::Path,
945+
fields: ast::RecordExprFieldList,
946+
) -> ast::RecordExpr {
947+
let ast = make::record_expr(path.clone(), fields.clone()).clone_for_update();
948+
if let Some(mut mapping) = self.mappings() {
949+
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
950+
builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone());
951+
builder.map_node(
952+
fields.syntax().clone(),
953+
ast.record_expr_field_list().unwrap().syntax().clone(),
954+
);
955+
builder.finish(&mut mapping);
956+
}
957+
ast
958+
}
959+
942960
pub fn record_expr_field(
943961
&self,
944962
name: ast::NameRef,

0 commit comments

Comments
 (0)