Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #14752 - ponyii:fix/generate_derive_breaks_indentation, r=Veykril
fix: assists no longer break indentation
Fixes https://github.com/rust-lang/rust-analyzer/issues/14674
These are _ad hoc_ patches for a number of assists that can produce incorrectly indented code, namely:
- generate_derive
- add_missing_impl_members
- add_missing_default_members
Some general solution is required in future, as the same problem arises in many other assists, e.g.
- replace_derive_with...
- generate_default_from_enum...
- generate_default_from_new
- generate_delegate_methods
(the list is incomplete)
| -rw-r--r-- | crates/ide-assists/src/handlers/add_missing_impl_members.rs | 172 | ||||
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_derive.rs | 41 | ||||
| -rw-r--r-- | crates/ide-assists/src/utils.rs | 12 | ||||
| -rw-r--r-- | crates/syntax/src/ast/edit_in_place.rs | 2 |
4 files changed, 217 insertions, 10 deletions
diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 9e4e6c1daf..89e03a98d6 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -1346,8 +1346,8 @@ struct SomeStruct { } impl PartialEq for SomeStruct { $0fn ne(&self, other: &Self) -> bool { - !self.eq(other) - } + !self.eq(other) + } } "#, ); @@ -1511,11 +1511,175 @@ fn main() { struct S; impl Tr for S { fn method() { - ${0:todo!()} - } + ${0:todo!()} + } } } "#, ); } + + #[test] + fn test_add_missing_preserves_indentation() { + // in different modules + check_assist( + add_missing_impl_members, + r#" +mod m { + pub trait Foo { + const CONST_MULTILINE: ( + i32, + i32 + ); + + fn foo(&self); + } +} +struct S; +impl m::Foo for S { $0 }"#, + r#" +mod m { + pub trait Foo { + const CONST_MULTILINE: ( + i32, + i32 + ); + + fn foo(&self); + } +} +struct S; +impl m::Foo for S { + $0const CONST_MULTILINE: ( + i32, + i32 + ); + + fn foo(&self) { + todo!() + } +}"#, + ); + // in the same module + check_assist( + add_missing_impl_members, + r#" +mod m { + trait Foo { + type Output; + + const CONST: usize = 42; + const CONST_2: i32; + const CONST_MULTILINE: ( + i32, + i32 + ); + + fn foo(&self); + fn bar(&self); + fn baz(&self); + } + + struct S; + + impl Foo for S { + fn bar(&self) {} +$0 + } +}"#, + r#" +mod m { + trait Foo { + type Output; + + const CONST: usize = 42; + const CONST_2: i32; + const CONST_MULTILINE: ( + i32, + i32 + ); + + fn foo(&self); + fn bar(&self); + fn baz(&self); + } + + struct S; + + impl Foo for S { + fn bar(&self) {} + + $0type Output; + + const CONST_2: i32; + + const CONST_MULTILINE: ( + i32, + i32 + ); + + fn foo(&self) { + todo!() + } + + fn baz(&self) { + todo!() + } + + } +}"#, + ); + } + + #[test] + fn test_add_default_preserves_indentation() { + check_assist( + add_missing_default_members, + r#" +mod m { + pub trait Foo { + type Output; + + const CONST: usize = 42; + const CONST_2: i32; + const CONST_MULTILINE: = ( + i32, + i32, + ) = (3, 14); + + fn valid(some: u32) -> bool { false } + fn foo(some: u32) -> bool; + } +} +struct S; +impl m::Foo for S { $0 }"#, + r#" +mod m { + pub trait Foo { + type Output; + + const CONST: usize = 42; + const CONST_2: i32; + const CONST_MULTILINE: = ( + i32, + i32, + ) = (3, 14); + + fn valid(some: u32) -> bool { false } + fn foo(some: u32) -> bool; + } +} +struct S; +impl m::Foo for S { + $0const CONST: usize = 42; + + const CONST_MULTILINE: = ( + i32, + i32, + ) = (3, 14); + + fn valid(some: u32) -> bool { false } +}"#, + ) + } } diff --git a/crates/ide-assists/src/handlers/generate_derive.rs b/crates/ide-assists/src/handlers/generate_derive.rs index 339245b94e..78ac2eb30e 100644 --- a/crates/ide-assists/src/handlers/generate_derive.rs +++ b/crates/ide-assists/src/handlers/generate_derive.rs @@ -1,5 +1,5 @@ use syntax::{ - ast::{self, AstNode, HasAttrs}, + ast::{self, edit::IndentLevel, AstNode, HasAttrs}, SyntaxKind::{COMMENT, WHITESPACE}, TextSize, }; @@ -42,7 +42,12 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt .next(); match derive_attr { None => { - builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); + let indent_level = IndentLevel::from_node(nominal.syntax()); + builder.insert_snippet( + cap, + node_start, + format!("#[derive($0)]\n{indent_level}"), + ); } Some(tt) => { // Just move the cursor. @@ -84,6 +89,20 @@ mod tests { "struct Foo { $0 a: i32, }", "#[derive($0)]\nstruct Foo { a: i32, }", ); + check_assist( + generate_derive, + " +mod m { + struct Foo { a: i32,$0 } +} + ", + " +mod m { + #[derive($0)] + struct Foo { a: i32, } +} + ", + ); } #[test] @@ -111,6 +130,24 @@ struct Foo { a: i32$0, } struct Foo { a: i32, } ", ); + check_assist( + generate_derive, + " +mod m { + /// `Foo` is a pretty important struct. + /// It does stuff. + struct Foo { a: i32,$0 } +} + ", + " +mod m { + /// `Foo` is a pretty important struct. + /// It does stuff. + #[derive($0)] + struct Foo { a: i32, } +} + ", + ); } #[test] diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 77f4b0e3e0..8f7ea26306 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -9,8 +9,8 @@ use stdx::format_to; use syntax::{ ast::{ self, - edit::{self, AstNodeEdit}, - edit_in_place::{AttrsOwnerEdit, Removable}, + edit::{AstNodeEdit, IndentLevel}, + edit_in_place::{AttrsOwnerEdit, Indent, Removable}, make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace, }, ted, AstNode, AstToken, Direction, SourceFile, @@ -139,9 +139,11 @@ pub fn add_trait_assoc_items_to_impl( let transform = PathTransform::trait_impl(&target_scope, &source_scope, trait_, impl_.clone()); + let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1; let items = items.into_iter().map(|assoc_item| { transform.apply(assoc_item.syntax()); assoc_item.remove_attrs_and_docs(); + assoc_item.reindent_to(new_indent_level); assoc_item }); @@ -153,8 +155,10 @@ pub fn add_trait_assoc_items_to_impl( first_item.get_or_insert_with(|| item.clone()); match &item { ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { - let body = make::block_expr(None, Some(make::ext::expr_todo())) - .indent(edit::IndentLevel(1)); + let body = AstNodeEdit::indent( + &make::block_expr(None, Some(make::ext::expr_todo())), + new_indent_level, + ); ted::replace(fn_.get_or_create_body().syntax(), body.clone_for_update().syntax()) } ast::AssocItem::TypeAlias(type_alias) => { diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index a493c92e7d..995e8d8d15 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -465,6 +465,8 @@ impl ast::Impl { } impl ast::AssocItemList { + /// Attention! This function does align the first line of `item` with respect to `self`, + /// but it does _not_ change indentation of other lines (if any). pub fn add_item(&self, item: ast::AssocItem) { let (indent, position, whitespace) = match self.assoc_items().last() { Some(last_item) => ( |