From 3bc26ba4aa152686f10a0cf97986e4c6b7d25def Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:12:44 -0500 Subject: [PATCH 1/4] minor: Add `item_enum` constructor to `SyntaxFactory` I recursively added all constructors it depends on. I also changed the old `make::` constructors to support more of the grammar. --- .../ide-assists/src/handlers/bool_to_enum.rs | 6 +- .../src/handlers/generate_enum_variant.rs | 2 +- .../crates/syntax/src/ast/edit_in_place.rs | 8 +- .../crates/syntax/src/ast/make.rs | 28 ++- .../src/ast/syntax_factory/constructors.rs | 207 +++++++++++++++++- 5 files changed, 242 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs index 605fd1405239..f699899066b7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -512,9 +512,11 @@ fn make_bool_enum(make_pub: bool) -> ast::Enum { let enum_def = make::enum_( if make_pub { Some(make::visibility_pub()) } else { None }, make::name("Bool"), + None, + None, make::variant_list(vec![ - make::variant(make::name("True"), None), - make::variant(make::name("False"), None), + make::variant(None, make::name("True"), None, None), + make::variant(None, make::name("False"), None, None), ]), ) .clone_for_update(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs index 5d5845912103..985d14d22afb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs @@ -137,7 +137,7 @@ fn make_variant( parent: PathParent, ) -> ast::Variant { let field_list = parent.make_field_list(ctx); - make::variant(make::name(&name_ref.text()), field_list) + make::variant(None, make::name(&name_ref.text()), field_list, None) } fn make_record_field_list( diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs index f1286e7aa213..8ec794bfa454 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs @@ -1173,7 +1173,7 @@ mod tests { #[test] fn add_variant_to_empty_enum() { - let variant = make::variant(make::name("Bar"), None).clone_for_update(); + let variant = make::variant(None, make::name("Bar"), None, None).clone_for_update(); check_add_variant( r#" @@ -1190,7 +1190,7 @@ enum Foo { #[test] fn add_variant_to_non_empty_enum() { - let variant = make::variant(make::name("Baz"), None).clone_for_update(); + let variant = make::variant(None, make::name("Baz"), None, None).clone_for_update(); check_add_variant( r#" @@ -1211,10 +1211,12 @@ enum Foo { #[test] fn add_variant_with_tuple_field_list() { let variant = make::variant( + None, make::name("Baz"), Some(ast::FieldList::TupleFieldList(make::tuple_field_list(std::iter::once( make::tuple_field(None, make::ty("bool")), )))), + None, ) .clone_for_update(); @@ -1237,10 +1239,12 @@ enum Foo { #[test] fn add_variant_with_record_field_list() { let variant = make::variant( + None, make::name("Baz"), Some(ast::FieldList::RecordFieldList(make::record_field_list(std::iter::once( make::record_field(None, make::name("x"), make::ty("bool")), )))), + None, ) .clone_for_update(); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index 7eb3e08f5410..05c2a8354da6 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -1053,7 +1053,17 @@ pub fn variant_list(variants: impl IntoIterator) -> ast::Va ast_from_text(&format!("enum f {{ {variants} }}")) } -pub fn variant(name: ast::Name, field_list: Option) -> ast::Variant { +pub fn variant( + visibility: Option, + name: ast::Name, + field_list: Option, + discriminant: Option, +) -> ast::Variant { + let visibility = match visibility { + None => String::new(), + Some(it) => format!("{it} "), + }; + let field_list = match field_list { None => String::new(), Some(it) => match it { @@ -1061,7 +1071,12 @@ pub fn variant(name: ast::Name, field_list: Option) -> ast::Vari ast::FieldList::TupleFieldList(tuple) => format!("{tuple}"), }, }; - ast_from_text(&format!("enum f {{ {name}{field_list} }}")) + + let discriminant = match discriminant { + Some(it) => format!(" = {it}"), + None => String::new(), + }; + ast_from_text(&format!("enum f {{ {visibility}{name}{field_list}{discriminant} }}")) } pub fn fn_( @@ -1122,6 +1137,8 @@ pub fn struct_( pub fn enum_( visibility: Option, enum_name: ast::Name, + generic_param_list: Option, + where_clause: Option, variant_list: ast::VariantList, ) -> ast::Enum { let visibility = match visibility { @@ -1129,7 +1146,12 @@ pub fn enum_( Some(it) => format!("{it} "), }; - ast_from_text(&format!("{visibility}enum {enum_name} {variant_list}")) + let generic_params = generic_param_list.map(|it| it.to_string()).unwrap_or_default(); + let where_clause = where_clause.map(|it| format!(" {it}")).unwrap_or_default(); + + ast_from_text(&format!( + "{visibility}enum {enum_name}{generic_params}{where_clause} {variant_list}" + )) } pub fn attr_outer(meta: ast::Meta) -> ast::Attr { diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 54f17bd721d5..88e9a93cd289 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use crate::{ - ast::{self, make, HasName, HasTypeBounds}, + ast::{self, make, HasGenericParams, HasName, HasTypeBounds, HasVisibility}, syntax_editor::SyntaxMappingBuilder, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, }; @@ -169,6 +169,211 @@ impl SyntaxFactory { ast } + pub fn record_field_list( + &self, + fields: impl IntoIterator, + ) -> ast::RecordFieldList { + let fields: Vec = fields.into_iter().collect(); + let input: Vec<_> = fields.iter().map(|it| it.syntax().clone()).collect(); + let ast = make::record_field_list(fields).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + + builder.map_children(input.into_iter(), ast.fields().map(|it| it.syntax().clone())); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn record_field( + &self, + visibility: Option, + name: ast::Name, + ty: ast::Type, + ) -> ast::RecordField { + let ast = + make::record_field(visibility.clone(), name.clone(), ty.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn tuple_field_list( + &self, + fields: impl IntoIterator, + ) -> ast::TupleFieldList { + let fields: Vec = fields.into_iter().collect(); + let input: Vec<_> = fields.iter().map(|it| it.syntax().clone()).collect(); + let ast = make::tuple_field_list(fields).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + + builder.map_children(input.into_iter(), ast.fields().map(|it| it.syntax().clone())); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn tuple_field( + &self, + visibility: Option, + ty: ast::Type, + ) -> ast::TupleField { + let ast = make::tuple_field(visibility.clone(), ty.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn item_enum( + &self, + visibility: Option, + name: ast::Name, + generic_param_list: Option, + where_clause: Option, + variant_list: ast::VariantList, + ) -> ast::Enum { + let ast = make::enum_( + visibility.clone(), + name.clone(), + generic_param_list.clone(), + where_clause.clone(), + variant_list.clone(), + ) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + + if let Some(generic_param_list) = generic_param_list { + builder.map_node( + generic_param_list.syntax().clone(), + ast.generic_param_list().unwrap().syntax().clone(), + ); + } + + if let Some(where_clause) = where_clause { + builder.map_node( + where_clause.syntax().clone(), + ast.where_clause().unwrap().syntax().clone(), + ); + } + + builder.map_node( + variant_list.syntax().clone(), + ast.variant_list().unwrap().syntax().clone(), + ); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn variant_list( + &self, + variants: impl IntoIterator, + ) -> ast::VariantList { + let variants: Vec = variants.into_iter().collect(); + let input: Vec<_> = variants.iter().map(|it| it.syntax().clone()).collect(); + let ast = make::variant_list(variants).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + + builder.map_children(input.into_iter(), ast.variants().map(|it| it.syntax().clone())); + + builder.finish(&mut mapping); + } + + ast + } + + pub fn variant( + &self, + visibility: Option, + name: ast::Name, + field_list: Option, + discriminant: Option, + ) -> ast::Variant { + let ast = make::variant( + visibility.clone(), + name.clone(), + field_list.clone(), + discriminant.clone(), + ) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + + if let Some(field_list) = field_list { + builder.map_node( + field_list.syntax().clone(), + ast.field_list().unwrap().syntax().clone(), + ); + } + + if let Some(discriminant) = discriminant { + builder + .map_node(discriminant.syntax().clone(), ast.expr().unwrap().syntax().clone()); + } + + builder.finish(&mut mapping); + } + + ast + } + pub fn token_tree( &self, delimiter: SyntaxKind, From d9bb8fcab15d1c92d51218e7953913ca614e3282 Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:50:02 -0500 Subject: [PATCH 2/4] minor: Add `whitespace` constructor to `SyntaxFactory` --- .../crates/syntax/src/ast/syntax_factory/constructors.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 88e9a93cd289..640d262a3e7c 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -403,4 +403,8 @@ impl SyntaxFactory { pub fn token(&self, kind: SyntaxKind) -> SyntaxToken { make::token(kind) } + + pub fn whitespace(&self, text: &str) -> ast::SyntaxToken { + make::tokens::whitespace(text) + } } From 26e7e4f7487a0b7b0db06a3d9e0ea209d4f4abcf Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:33:32 -0500 Subject: [PATCH 3/4] minor: Add `ty_infer` constructor to `SyntaxFactory` --- .../crates/syntax/src/ast/syntax_factory/constructors.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 640d262a3e7c..aa894ef633a1 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -18,6 +18,14 @@ impl SyntaxFactory { make::ty(text).clone_for_update() } + pub fn ty_infer(&self) -> ast::InferType { + let ast::Type::InferType(ast) = make::ty_placeholder().clone_for_update() else { + unreachable!() + }; + + ast + } + pub fn type_param( &self, name: ast::Name, From 3b781667eb0d50acb40eec7431be7a1677597d8f Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:11:33 -0500 Subject: [PATCH 4/4] minor: Migrate `generate_enum_variant` to `SyntaxEditor` --- .../src/handlers/generate_enum_variant.rs | 170 ++++++--------- .../crates/syntax/src/ast/edit_in_place.rs | 128 ----------- .../crates/syntax/src/syntax_editor/edits.rs | 206 +++++++++++++++++- 3 files changed, 275 insertions(+), 229 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs index 985d14d22afb..bb08cb904ead 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs @@ -1,7 +1,7 @@ use hir::{HasSource, HirDisplay, InRealFile}; use ide_db::assists::{AssistId, AssistKind}; use syntax::{ - ast::{self, make, HasArgList}, + ast::{self, syntax_factory::SyntaxFactory, HasArgList}, match_ast, AstNode, SyntaxNode, }; @@ -33,7 +33,7 @@ use crate::assist_context::{AssistContext, Assists}; // ``` pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let path: ast::Path = ctx.find_node_at_offset()?; - let parent = path_parent(&path)?; + let parent = PathParent::new(&path)?; if ctx.sema.resolve_path(&path).is_some() { // No need to generate anything if the path resolves @@ -46,14 +46,32 @@ pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) return None; } - if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) = + let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) = ctx.sema.resolve_path(&path.qualifier()?) - { - let target = path.syntax().text_range(); - return add_variant_to_accumulator(acc, ctx, target, e, &name_ref, parent); - } + else { + return None; + }; - None + let target = path.syntax().text_range(); + let name_ref: &ast::NameRef = &name_ref; + let db = ctx.db(); + let InRealFile { file_id, value: enum_node } = e.source(db)?.original_ast_node_rooted(db)?; + + acc.add( + AssistId("generate_enum_variant", AssistKind::Generate), + "Generate variant", + target, + |builder| { + let mut editor = builder.make_editor(enum_node.syntax()); + let make = SyntaxFactory::new(); + let field_list = parent.make_field_list(ctx, &make); + let variant = make.variant(None, make.name(&name_ref.text()), field_list, None); + if let Some(it) = enum_node.variant_list() { + it.add_variant(&mut editor, &variant); + } + builder.add_file_edits(file_id, editor); + }, + ) } #[derive(Debug)] @@ -65,6 +83,20 @@ enum PathParent { } impl PathParent { + fn new(path: &ast::Path) -> Option { + let parent = path.syntax().parent()?; + + match_ast! { + match parent { + ast::PathExpr(it) => Some(PathParent::PathExpr(it)), + ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)), + ast::PathPat(it) => Some(PathParent::PathPat(it)), + ast::UseTree(it) => Some(PathParent::UseTree(it)), + _ => None + } + } + } + fn syntax(&self) -> &SyntaxNode { match self { PathParent::PathExpr(it) => it.syntax(), @@ -74,97 +106,49 @@ impl PathParent { } } - fn make_field_list(&self, ctx: &AssistContext<'_>) -> Option { + fn make_field_list( + &self, + ctx: &AssistContext<'_>, + make: &SyntaxFactory, + ) -> Option { let scope = ctx.sema.scope(self.syntax())?; match self { PathParent::PathExpr(it) => { - if let Some(call_expr) = it.syntax().parent().and_then(ast::CallExpr::cast) { - make_tuple_field_list(call_expr, ctx, &scope) - } else { - None - } + let call_expr = ast::CallExpr::cast(it.syntax().parent()?)?; + let args = call_expr.arg_list()?.args(); + let tuple_fields = args.map(|arg| { + let ty = + expr_ty(ctx, make, arg, &scope).unwrap_or_else(|| make.ty_infer().into()); + make.tuple_field(None, ty) + }); + Some(make.tuple_field_list(tuple_fields).into()) + } + PathParent::RecordExpr(it) => { + let fields = it.record_expr_field_list()?.fields(); + let record_fields = fields.map(|field| { + let name = name_from_field(make, &field); + + let ty = field + .expr() + .and_then(|it| expr_ty(ctx, make, it, &scope)) + .unwrap_or_else(|| make.ty_infer().into()); + + make.record_field(None, name, ty) + }); + Some(make.record_field_list(record_fields).into()) } - PathParent::RecordExpr(it) => make_record_field_list(it, ctx, &scope), PathParent::UseTree(_) | PathParent::PathPat(_) => None, } } } -fn path_parent(path: &ast::Path) -> Option { - let parent = path.syntax().parent()?; - - match_ast! { - match parent { - ast::PathExpr(it) => Some(PathParent::PathExpr(it)), - ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)), - ast::PathPat(it) => Some(PathParent::PathPat(it)), - ast::UseTree(it) => Some(PathParent::UseTree(it)), - _ => None - } - } -} - -fn add_variant_to_accumulator( - acc: &mut Assists, - ctx: &AssistContext<'_>, - target: syntax::TextRange, - adt: hir::Enum, - name_ref: &ast::NameRef, - parent: PathParent, -) -> Option<()> { - let db = ctx.db(); - let InRealFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node_rooted(db)?; - - acc.add( - AssistId("generate_enum_variant", AssistKind::Generate), - "Generate variant", - target, - |builder| { - builder.edit_file(file_id.file_id()); - let node = builder.make_mut(enum_node); - let variant = make_variant(ctx, name_ref, parent); - if let Some(it) = node.variant_list() { - it.add_variant(variant.clone_for_update()) - } - }, - ) -} - -fn make_variant( - ctx: &AssistContext<'_>, - name_ref: &ast::NameRef, - parent: PathParent, -) -> ast::Variant { - let field_list = parent.make_field_list(ctx); - make::variant(None, make::name(&name_ref.text()), field_list, None) -} - -fn make_record_field_list( - record: &ast::RecordExpr, - ctx: &AssistContext<'_>, - scope: &hir::SemanticsScope<'_>, -) -> Option { - let fields = record.record_expr_field_list()?.fields(); - let record_fields = fields.map(|field| { - let name = name_from_field(&field); - - let ty = field - .expr() - .and_then(|it| expr_ty(ctx, it, scope)) - .unwrap_or_else(make::ty_placeholder); - - make::record_field(None, name, ty) - }); - Some(make::record_field_list(record_fields).into()) -} - -fn name_from_field(field: &ast::RecordExprField) -> ast::Name { +fn name_from_field(make: &SyntaxFactory, field: &ast::RecordExprField) -> ast::Name { let text = match field.name_ref() { Some(it) => it.to_string(), None => name_from_field_shorthand(field).unwrap_or("unknown".to_owned()), }; - make::name(&text) + make.name(&text) } fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option { @@ -175,27 +159,15 @@ fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option { Some(path.as_single_name_ref()?.to_string()) } -fn make_tuple_field_list( - call_expr: ast::CallExpr, - ctx: &AssistContext<'_>, - scope: &hir::SemanticsScope<'_>, -) -> Option { - let args = call_expr.arg_list()?.args(); - let tuple_fields = args.map(|arg| { - let ty = expr_ty(ctx, arg, scope).unwrap_or_else(make::ty_placeholder); - make::tuple_field(None, ty) - }); - Some(make::tuple_field_list(tuple_fields).into()) -} - fn expr_ty( ctx: &AssistContext<'_>, + make: &SyntaxFactory, arg: ast::Expr, scope: &hir::SemanticsScope<'_>, ) -> Option { let ty = ctx.sema.type_of_expr(&arg).map(|it| it.adjusted())?; let text = ty.display_source_code(ctx.db(), scope.module().into(), false).ok()?; - Some(make::ty(&text)) + Some(make.ty(&text)) } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs index 8ec794bfa454..ffe9f16cfd52 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs @@ -909,30 +909,6 @@ fn get_or_insert_comma_after(syntax: &SyntaxNode) -> SyntaxToken { } } -impl ast::VariantList { - pub fn add_variant(&self, variant: ast::Variant) { - let (indent, position) = match self.variants().last() { - Some(last_item) => ( - IndentLevel::from_node(last_item.syntax()), - Position::after(get_or_insert_comma_after(last_item.syntax())), - ), - None => match self.l_curly_token() { - Some(l_curly) => { - normalize_ws_between_braces(self.syntax()); - (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly)) - } - None => (IndentLevel::single(), Position::last_child_of(self.syntax())), - }, - }; - let elements: Vec = vec![ - make::tokens::whitespace(&format!("{}{indent}", "\n")).into(), - variant.syntax().clone().into(), - ast::make::token(T![,]).into(), - ]; - ted::insert_all(position, elements); - } -} - fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> { let l = node .children_with_tokens() @@ -1055,8 +1031,6 @@ mod tests { use std::fmt; use parser::Edition; - use stdx::trim_indent; - use test_utils::assert_eq_text; use crate::SourceFile; @@ -1170,106 +1144,4 @@ mod tests { check("let a: u8 = 3;", "let a = 3;", None); check("let a: = 3;", "let a = 3;", None); } - - #[test] - fn add_variant_to_empty_enum() { - let variant = make::variant(None, make::name("Bar"), None, None).clone_for_update(); - - check_add_variant( - r#" -enum Foo {} -"#, - r#" -enum Foo { - Bar, -} -"#, - variant, - ); - } - - #[test] - fn add_variant_to_non_empty_enum() { - let variant = make::variant(None, make::name("Baz"), None, None).clone_for_update(); - - check_add_variant( - r#" -enum Foo { - Bar, -} -"#, - r#" -enum Foo { - Bar, - Baz, -} -"#, - variant, - ); - } - - #[test] - fn add_variant_with_tuple_field_list() { - let variant = make::variant( - None, - make::name("Baz"), - Some(ast::FieldList::TupleFieldList(make::tuple_field_list(std::iter::once( - make::tuple_field(None, make::ty("bool")), - )))), - None, - ) - .clone_for_update(); - - check_add_variant( - r#" -enum Foo { - Bar, -} -"#, - r#" -enum Foo { - Bar, - Baz(bool), -} -"#, - variant, - ); - } - - #[test] - fn add_variant_with_record_field_list() { - let variant = make::variant( - None, - make::name("Baz"), - Some(ast::FieldList::RecordFieldList(make::record_field_list(std::iter::once( - make::record_field(None, make::name("x"), make::ty("bool")), - )))), - None, - ) - .clone_for_update(); - - check_add_variant( - r#" -enum Foo { - Bar, -} -"#, - r#" -enum Foo { - Bar, - Baz { x: bool }, -} -"#, - variant, - ); - } - - fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { - let enum_ = ast_mut_from_text::(before); - if let Some(it) = enum_.variant_list() { - it.add_variant(variant) - } - let after = enum_.to_string(); - assert_eq_text!(&trim_indent(expected.trim()), &trim_indent(after.trim())); - } } diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs index 73196f5cb1be..8069fdd06f74 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs @@ -1,8 +1,12 @@ //! Structural editing for ast using `SyntaxEditor` use crate::{ - ast::make, ast::AstNode, ast::Fn, ast::GenericParam, ast::HasGenericParams, ast::HasName, - syntax_editor::Position, syntax_editor::SyntaxEditor, SyntaxKind, + ast::{ + self, edit::IndentLevel, make, syntax_factory::SyntaxFactory, AstNode, Fn, GenericParam, + HasGenericParams, HasName, + }, + syntax_editor::{Position, SyntaxEditor}, + Direction, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T, }; impl SyntaxEditor { @@ -70,3 +74,201 @@ impl SyntaxEditor { } } } + +fn get_or_insert_comma_after(editor: &mut SyntaxEditor, syntax: &SyntaxNode) -> SyntaxToken { + let make = SyntaxFactory::without_mappings(); + match syntax + .siblings_with_tokens(Direction::Next) + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == T![,]) + { + Some(it) => it, + None => { + let comma = make.token(T![,]); + editor.insert(Position::after(syntax), &comma); + comma + } + } +} + +impl ast::VariantList { + pub fn add_variant(&self, editor: &mut SyntaxEditor, variant: &ast::Variant) { + let make = SyntaxFactory::without_mappings(); + let (indent, position) = match self.variants().last() { + Some(last_item) => ( + IndentLevel::from_node(last_item.syntax()), + Position::after(get_or_insert_comma_after(editor, last_item.syntax())), + ), + None => match self.l_curly_token() { + Some(l_curly) => { + normalize_ws_between_braces(editor, self.syntax()); + (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly)) + } + None => (IndentLevel::single(), Position::last_child_of(self.syntax())), + }, + }; + let elements: Vec = vec![ + make.whitespace(&format!("{}{indent}", "\n")).into(), + variant.syntax().clone().into(), + make.token(T![,]).into(), + ]; + editor.insert_all(position, elements); + } +} + +fn normalize_ws_between_braces(editor: &mut SyntaxEditor, node: &SyntaxNode) -> Option<()> { + let make = SyntaxFactory::without_mappings(); + let l = node + .children_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == T!['{'])?; + let r = node + .children_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == T!['}'])?; + + let indent = IndentLevel::from_node(node); + + match l.next_sibling_or_token() { + Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => { + if ws.next_sibling_or_token()?.into_token()? == r { + editor.replace(ws, make.whitespace(&format!("\n{indent}"))); + } + } + Some(ws) if ws.kind() == T!['}'] => { + editor.insert(Position::after(l), make.whitespace(&format!("\n{indent}"))); + } + _ => (), + } + Some(()) +} + +#[cfg(test)] +mod tests { + use parser::Edition; + use stdx::trim_indent; + use test_utils::assert_eq_text; + + use crate::SourceFile; + + use super::*; + + fn ast_from_text(text: &str) -> N { + let parse = SourceFile::parse(text, Edition::CURRENT); + let node = match parse.tree().syntax().descendants().find_map(N::cast) { + Some(it) => it, + None => { + let node = std::any::type_name::(); + panic!("Failed to make ast node `{node}` from text {text}") + } + }; + let node = node.clone_subtree(); + assert_eq!(node.syntax().text_range().start(), 0.into()); + node + } + + #[test] + fn add_variant_to_empty_enum() { + let make = SyntaxFactory::without_mappings(); + let variant = make.variant(None, make.name("Bar"), None, None); + + check_add_variant( + r#" +enum Foo {} +"#, + r#" +enum Foo { + Bar, +} +"#, + variant, + ); + } + + #[test] + fn add_variant_to_non_empty_enum() { + let make = SyntaxFactory::without_mappings(); + let variant = make.variant(None, make.name("Baz"), None, None); + + check_add_variant( + r#" +enum Foo { + Bar, +} +"#, + r#" +enum Foo { + Bar, + Baz, +} +"#, + variant, + ); + } + + #[test] + fn add_variant_with_tuple_field_list() { + let make = SyntaxFactory::without_mappings(); + let variant = make.variant( + None, + make.name("Baz"), + Some(make.tuple_field_list([make.tuple_field(None, make.ty("bool"))]).into()), + None, + ); + + check_add_variant( + r#" +enum Foo { + Bar, +} +"#, + r#" +enum Foo { + Bar, + Baz(bool), +} +"#, + variant, + ); + } + + #[test] + fn add_variant_with_record_field_list() { + let make = SyntaxFactory::without_mappings(); + let variant = make.variant( + None, + make.name("Baz"), + Some( + make.record_field_list([make.record_field(None, make.name("x"), make.ty("bool"))]) + .into(), + ), + None, + ); + + check_add_variant( + r#" +enum Foo { + Bar, +} +"#, + r#" +enum Foo { + Bar, + Baz { x: bool }, +} +"#, + variant, + ); + } + + fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { + let enum_ = ast_from_text::(before); + let mut editor = SyntaxEditor::new(enum_.syntax().clone()); + if let Some(it) = enum_.variant_list() { + it.add_variant(&mut editor, &variant) + } + let edit = editor.finish(); + let after = edit.new_root.to_string(); + assert_eq_text!(&trim_indent(expected.trim()), &trim_indent(after.trim())); + } +}