mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
Merge pull request #20311 from Hmikihiro/migrate_convert_tuple_struct_to_named_struct
Migrate `convert_tuple_struct_to_named_struct` assist to use `SyntaxEditor`
This commit is contained in:
commit
b0d2487ea6
@ -1,9 +1,14 @@
|
|||||||
use either::Either;
|
use either::Either;
|
||||||
|
use hir::FileRangeWrapper;
|
||||||
use ide_db::defs::{Definition, NameRefClass};
|
use ide_db::defs::{Definition, NameRefClass};
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
SyntaxKind, SyntaxNode,
|
SyntaxElement, SyntaxKind, SyntaxNode, T, TextSize,
|
||||||
ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility},
|
ast::{
|
||||||
match_ast, ted,
|
self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory,
|
||||||
|
},
|
||||||
|
match_ast,
|
||||||
|
syntax_editor::{Element, Position, SyntaxEditor},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder};
|
use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder};
|
||||||
@ -71,7 +76,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct(
|
|||||||
Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
|
Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
|
||||||
};
|
};
|
||||||
let target = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range();
|
let target = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range();
|
||||||
|
let syntax = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax());
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId::refactor_rewrite("convert_tuple_struct_to_named_struct"),
|
AssistId::refactor_rewrite("convert_tuple_struct_to_named_struct"),
|
||||||
"Convert to named struct",
|
"Convert to named struct",
|
||||||
@ -79,58 +84,55 @@ pub(crate) fn convert_tuple_struct_to_named_struct(
|
|||||||
|edit| {
|
|edit| {
|
||||||
let names = generate_names(tuple_fields.fields());
|
let names = generate_names(tuple_fields.fields());
|
||||||
edit_field_references(ctx, edit, tuple_fields.fields(), &names);
|
edit_field_references(ctx, edit, tuple_fields.fields(), &names);
|
||||||
|
let mut editor = edit.make_editor(syntax);
|
||||||
edit_struct_references(ctx, edit, strukt_def, &names);
|
edit_struct_references(ctx, edit, strukt_def, &names);
|
||||||
edit_struct_def(ctx, edit, &strukt_or_variant, tuple_fields, names);
|
edit_struct_def(&mut editor, &strukt_or_variant, tuple_fields, names);
|
||||||
|
edit.add_file_edits(ctx.vfs_file_id(), editor);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_struct_def(
|
fn edit_struct_def(
|
||||||
ctx: &AssistContext<'_>,
|
editor: &mut SyntaxEditor,
|
||||||
edit: &mut SourceChangeBuilder,
|
|
||||||
strukt: &Either<ast::Struct, ast::Variant>,
|
strukt: &Either<ast::Struct, ast::Variant>,
|
||||||
tuple_fields: ast::TupleFieldList,
|
tuple_fields: ast::TupleFieldList,
|
||||||
names: Vec<ast::Name>,
|
names: Vec<ast::Name>,
|
||||||
) {
|
) {
|
||||||
let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| {
|
let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| {
|
||||||
let field = ast::make::record_field(f.visibility(), name, f.ty()?).clone_for_update();
|
let field = ast::make::record_field(f.visibility(), name, f.ty()?);
|
||||||
ted::insert_all(
|
let mut field_editor = SyntaxEditor::new(field.syntax().clone());
|
||||||
ted::Position::first_child_of(field.syntax()),
|
field_editor.insert_all(
|
||||||
|
Position::first_child_of(field.syntax()),
|
||||||
f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(),
|
f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(),
|
||||||
);
|
);
|
||||||
Some(field)
|
ast::RecordField::cast(field_editor.finish().new_root().clone())
|
||||||
});
|
});
|
||||||
let record_fields = ast::make::record_field_list(record_fields);
|
let make = SyntaxFactory::without_mappings();
|
||||||
let tuple_fields_text_range = tuple_fields.syntax().text_range();
|
let record_fields = make.record_field_list(record_fields);
|
||||||
|
let tuple_fields_before = Position::before(tuple_fields.syntax());
|
||||||
edit.edit_file(ctx.vfs_file_id());
|
|
||||||
|
|
||||||
if let Either::Left(strukt) = strukt {
|
if let Either::Left(strukt) = strukt {
|
||||||
if let Some(w) = strukt.where_clause() {
|
if let Some(w) = strukt.where_clause() {
|
||||||
edit.delete(w.syntax().text_range());
|
editor.delete(w.syntax());
|
||||||
edit.insert(
|
let mut insert_element = Vec::new();
|
||||||
tuple_fields_text_range.start(),
|
insert_element.push(ast::make::tokens::single_newline().syntax_element());
|
||||||
ast::make::tokens::single_newline().text(),
|
insert_element.push(w.syntax().clone_for_update().syntax_element());
|
||||||
);
|
|
||||||
edit.insert(tuple_fields_text_range.start(), w.syntax().text());
|
|
||||||
if w.syntax().last_token().is_none_or(|t| t.kind() != SyntaxKind::COMMA) {
|
if w.syntax().last_token().is_none_or(|t| t.kind() != SyntaxKind::COMMA) {
|
||||||
edit.insert(tuple_fields_text_range.start(), ",");
|
insert_element.push(ast::make::token(T![,]).into());
|
||||||
}
|
}
|
||||||
edit.insert(
|
insert_element.push(ast::make::tokens::single_newline().syntax_element());
|
||||||
tuple_fields_text_range.start(),
|
editor.insert_all(tuple_fields_before, insert_element);
|
||||||
ast::make::tokens::single_newline().text(),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
|
editor.insert(tuple_fields_before, ast::make::tokens::single_space());
|
||||||
}
|
}
|
||||||
if let Some(t) = strukt.semicolon_token() {
|
if let Some(t) = strukt.semicolon_token() {
|
||||||
edit.delete(t.text_range());
|
editor.delete(t);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
|
editor.insert(tuple_fields_before, ast::make::tokens::single_space());
|
||||||
}
|
}
|
||||||
|
|
||||||
edit.replace(tuple_fields_text_range, record_fields.to_string());
|
editor.replace(tuple_fields.syntax(), record_fields.syntax());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_struct_references(
|
fn edit_struct_references(
|
||||||
@ -145,15 +147,12 @@ fn edit_struct_references(
|
|||||||
};
|
};
|
||||||
let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
|
let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
|
||||||
|
|
||||||
let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> {
|
let edit_node = |node: SyntaxNode| -> Option<SyntaxNode> {
|
||||||
|
let make = SyntaxFactory::without_mappings();
|
||||||
match_ast! {
|
match_ast! {
|
||||||
match node {
|
match node {
|
||||||
ast::TupleStructPat(tuple_struct_pat) => {
|
ast::TupleStructPat(tuple_struct_pat) => {
|
||||||
let file_range = ctx.sema.original_range_opt(&node)?;
|
Some(make.record_pat_with_fields(
|
||||||
edit.edit_file(file_range.file_id.file_id(ctx.db()));
|
|
||||||
edit.replace(
|
|
||||||
file_range.range,
|
|
||||||
ast::make::record_pat_with_fields(
|
|
||||||
tuple_struct_pat.path()?,
|
tuple_struct_pat.path()?,
|
||||||
ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
|
ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
|
||||||
|(pat, name)| {
|
|(pat, name)| {
|
||||||
@ -163,9 +162,7 @@ fn edit_struct_references(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
), None),
|
), None),
|
||||||
)
|
).syntax().clone())
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
// for tuple struct creations like Foo(42)
|
// for tuple struct creations like Foo(42)
|
||||||
ast::CallExpr(call_expr) => {
|
ast::CallExpr(call_expr) => {
|
||||||
@ -181,10 +178,8 @@ fn edit_struct_references(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
|
let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
|
||||||
|
Some(
|
||||||
edit.replace(
|
make.record_expr(
|
||||||
ctx.sema.original_range(&node).range,
|
|
||||||
ast::make::record_expr(
|
|
||||||
path,
|
path,
|
||||||
ast::make::record_expr_field_list(arg_list.args().zip(names).map(
|
ast::make::record_expr_field_list(arg_list.args().zip(names).map(
|
||||||
|(expr, name)| {
|
|(expr, name)| {
|
||||||
@ -194,27 +189,60 @@ fn edit_struct_references(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
).syntax().clone()
|
||||||
)
|
)
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (file_id, refs) in usages {
|
for (file_id, refs) in usages {
|
||||||
edit.edit_file(file_id.file_id(ctx.db()));
|
let source = ctx.sema.parse(file_id);
|
||||||
for r in refs {
|
let source = source.syntax();
|
||||||
for node in r.name.syntax().ancestors() {
|
|
||||||
if edit_node(edit, node).is_some() {
|
let mut editor = edit.make_editor(source);
|
||||||
break;
|
for r in refs.iter().rev() {
|
||||||
|
if let Some((old_node, new_node)) = r
|
||||||
|
.name
|
||||||
|
.syntax()
|
||||||
|
.ancestors()
|
||||||
|
.find_map(|node| Some((node.clone(), edit_node(node.clone())?)))
|
||||||
|
{
|
||||||
|
if let Some(old_node) = ctx.sema.original_syntax_node_rooted(&old_node) {
|
||||||
|
editor.replace(old_node, new_node);
|
||||||
|
} else {
|
||||||
|
let FileRangeWrapper { file_id: _, range } = ctx.sema.original_range(&old_node);
|
||||||
|
let parent = source.covering_element(range);
|
||||||
|
match parent {
|
||||||
|
SyntaxElement::Token(token) => {
|
||||||
|
editor.replace(token, new_node.syntax_element());
|
||||||
|
}
|
||||||
|
SyntaxElement::Node(parent_node) => {
|
||||||
|
// replace the part of macro
|
||||||
|
// ```
|
||||||
|
// foo!(a, Test::A(0));
|
||||||
|
// ^^^^^^^^^^^^^^^ // parent_node
|
||||||
|
// ^^^^^^^^^^ // replace_range
|
||||||
|
// ```
|
||||||
|
let start = parent_node
|
||||||
|
.children_with_tokens()
|
||||||
|
.find(|t| t.text_range().contains(range.start()));
|
||||||
|
let end = parent_node
|
||||||
|
.children_with_tokens()
|
||||||
|
.find(|t| t.text_range().contains(range.end() - TextSize::new(1)));
|
||||||
|
if let (Some(start), Some(end)) = (start, end) {
|
||||||
|
let replace_range = RangeInclusive::new(start, end);
|
||||||
|
editor.replace_all(replace_range, vec![new_node.into()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
edit.add_file_edits(file_id.file_id(ctx.db()), editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn edit_field_references(
|
fn edit_field_references(
|
||||||
ctx: &AssistContext<'_>,
|
ctx: &AssistContext<'_>,
|
||||||
@ -230,22 +258,28 @@ fn edit_field_references(
|
|||||||
let def = Definition::Field(field);
|
let def = Definition::Field(field);
|
||||||
let usages = def.usages(&ctx.sema).all();
|
let usages = def.usages(&ctx.sema).all();
|
||||||
for (file_id, refs) in usages {
|
for (file_id, refs) in usages {
|
||||||
edit.edit_file(file_id.file_id(ctx.db()));
|
let source = ctx.sema.parse(file_id);
|
||||||
|
let source = source.syntax();
|
||||||
|
let mut editor = edit.make_editor(source);
|
||||||
for r in refs {
|
for r in refs {
|
||||||
if let Some(name_ref) = r.name.as_name_ref() {
|
if let Some(name_ref) = r.name.as_name_ref()
|
||||||
edit.replace(ctx.sema.original_range(name_ref.syntax()).range, name.text());
|
&& let Some(original) = ctx.sema.original_ast_node(name_ref.clone())
|
||||||
|
{
|
||||||
|
editor.replace(original.syntax(), name.syntax());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
edit.add_file_edits(file_id.file_id(ctx.db()), editor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
|
fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
|
||||||
|
let make = SyntaxFactory::without_mappings();
|
||||||
fields
|
fields
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, _)| {
|
.map(|(i, _)| {
|
||||||
let idx = i + 1;
|
let idx = i + 1;
|
||||||
ast::make::name(&format!("field{idx}"))
|
make.name(&format!("field{idx}"))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@ -1013,8 +1047,7 @@ where
|
|||||||
pub struct $0Foo(#[my_custom_attr] u32);
|
pub struct $0Foo(#[my_custom_attr] u32);
|
||||||
"#,
|
"#,
|
||||||
r#"
|
r#"
|
||||||
pub struct Foo { #[my_custom_attr]
|
pub struct Foo { #[my_custom_attr]field1: u32 }
|
||||||
field1: u32 }
|
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -939,6 +939,24 @@ impl SyntaxFactory {
|
|||||||
ast
|
ast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn record_expr(
|
||||||
|
&self,
|
||||||
|
path: ast::Path,
|
||||||
|
fields: ast::RecordExprFieldList,
|
||||||
|
) -> ast::RecordExpr {
|
||||||
|
let ast = make::record_expr(path.clone(), fields.clone()).clone_for_update();
|
||||||
|
if let Some(mut mapping) = self.mappings() {
|
||||||
|
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||||
|
builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone());
|
||||||
|
builder.map_node(
|
||||||
|
fields.syntax().clone(),
|
||||||
|
ast.record_expr_field_list().unwrap().syntax().clone(),
|
||||||
|
);
|
||||||
|
builder.finish(&mut mapping);
|
||||||
|
}
|
||||||
|
ast
|
||||||
|
}
|
||||||
|
|
||||||
pub fn record_expr_field(
|
pub fn record_expr_field(
|
||||||
&self,
|
&self,
|
||||||
name: ast::NameRef,
|
name: ast::NameRef,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user