diff --git a/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs b/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs index fc2648efb407..769dba74c6d2 100644 --- a/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs +++ b/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs @@ -6,6 +6,7 @@ use ide_db::{ source_change::SourceChangeBuilder, }; use syntax::ToSmolStr; +use syntax::ast::edit::AstNodeEdit; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; @@ -23,6 +24,7 @@ pub(crate) fn trait_impl_redundant_assoc_item( let default_range = d.impl_.syntax_node_ptr().text_range(); let trait_name = d.trait_.name(db).display_no_db(ctx.edition).to_smolstr(); + let indent_level = d.trait_.source(db).map_or(0, |it| it.value.indent_level().0) + 1; let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item { hir::AssocItem::Function(id) => { @@ -30,7 +32,7 @@ pub(crate) fn trait_impl_redundant_assoc_item( ( format!("`fn {redundant_assoc_item_name}`"), function.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range), - format!("\n {};", function.display(db, ctx.display_target)), + format!("\n{};", function.display(db, ctx.display_target)), ) } hir::AssocItem::Const(id) => { @@ -38,7 +40,7 @@ pub(crate) fn trait_impl_redundant_assoc_item( ( format!("`const {redundant_assoc_item_name}`"), constant.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range), - format!("\n {};", constant.display(db, ctx.display_target)), + format!("\n{};", constant.display(db, ctx.display_target)), ) } hir::AssocItem::TypeAlias(id) => { @@ -46,10 +48,8 @@ pub(crate) fn trait_impl_redundant_assoc_item( ( format!("`type {redundant_assoc_item_name}`"), type_alias.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range), - format!( - "\n type {};", - type_alias.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr() - ), + // FIXME cannot generate generic parameter and bounds + format!("\ntype {};", type_alias.name(ctx.sema.db).display_no_db(ctx.edition)), ) } }; @@ -65,7 +65,7 @@ pub(crate) fn trait_impl_redundant_assoc_item( .with_fixes(quickfix_for_redundant_assoc_item( ctx, d, - redundant_item_def, + stdx::indent_string(redundant_item_def, indent_level).into(), diagnostic_range, )) } @@ -191,6 +191,89 @@ impl Marker for Foo { ) } + #[test] + fn quickfix_indentations() { + check_fix( + r#" +mod indent { + trait Marker { + fn boo(); + } + struct Foo; + impl Marker for Foo { + fn$0 bar(_a: i32, _b: T) -> String {} + fn boo() {} + } +} + "#, + r#" +mod indent { + trait Marker { + fn bar(_a: i32, _b: T) -> String + where + T: Copy,; + fn boo(); + } + struct Foo; + impl Marker for Foo { + fn bar(_a: i32, _b: T) -> String {} + fn boo() {} + } +} + "#, + ); + + check_fix( + r#" +mod indent { + trait Marker { + fn foo () {} + } + struct Foo; + impl Marker for Foo { + const FLAG: bool$0 = false; + } +} + "#, + r#" +mod indent { + trait Marker { + const FLAG: bool; + fn foo () {} + } + struct Foo; + impl Marker for Foo { + const FLAG: bool = false; + } +} + "#, + ); + + check_fix( + r#" +mod indent { + trait Marker { + } + struct Foo; + impl Marker for Foo { + type T = i32;$0 + } +} + "#, + r#" +mod indent { + trait Marker { + type T; + } + struct Foo; + impl Marker for Foo { + type T = i32; + } +} + "#, + ); + } + #[test] fn quickfix_dont_work() { check_no_fix( diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 978c50d807bc..03b0a5c92b04 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs @@ -1,5 +1,6 @@ //! Missing batteries for standard libraries. +use std::borrow::Cow; use std::io as sio; use std::process::Command; use std::{cmp::Ordering, ops, time::Instant}; @@ -214,6 +215,29 @@ pub fn trim_indent(mut text: &str) -> String { .collect() } +/// Indent non empty lines, including the first line +#[must_use] +pub fn indent_string<'a, S>(s: S, indent_level: u8) -> Cow<'a, str> +where + Cow<'a, str>: From, +{ + let s = Cow::from(s); + if indent_level == 0 || s.is_empty() { + return s; + } + let indent_str = " ".repeat(indent_level as usize); + s.split_inclusive("\n") + .map(|line| { + if line.trim_end().is_empty() { + Cow::Borrowed(line) + } else { + format!("{indent_str}{line}").into() + } + }) + .collect::() + .into() +} + pub fn equal_range_by(slice: &[T], mut key: F) -> ops::Range where F: FnMut(&T) -> Ordering,