mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 03:54:42 +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
					
				
					 2 changed files with 118 additions and 67 deletions
				
			
		|  | @ -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,26 +189,59 @@ 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( | ||||||
|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Shoyu Vanilla (Flint)
						Shoyu Vanilla (Flint)