Unnamed repository; edit this file 'description' to name the repository.
feat: add quickfix for redundant_assoc_item diagnostic
Young-Flash 2024-01-01
parent cf52c4b · commit 613774e
-rw-r--r--crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs115
1 files changed, 90 insertions, 25 deletions
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 c202264bb5..9992a0f82c 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
@@ -1,5 +1,12 @@
-use hir::{Const, Function, HasSource, TypeAlias};
-use ide_db::base_db::FileRange;
+use hir::{db::ExpandDatabase, Const, Function, HasSource, HirDisplay, TypeAlias};
+use ide_db::{
+ assists::{Assist, AssistId, AssistKind},
+ base_db::FileRange,
+ label::Label,
+ source_change::SourceChangeBuilder,
+};
+use syntax::{AstNode, SyntaxKind};
+use text_edit::TextRange;
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
@@ -10,35 +17,48 @@ pub(crate) fn trait_impl_redundant_assoc_item(
ctx: &DiagnosticsContext<'_>,
d: &hir::TraitImplRedundantAssocItems,
) -> Diagnostic {
+ let db = ctx.sema.db;
let name = d.assoc_item.0.clone();
+ let redundant_assoc_item_name = name.display(db);
let assoc_item = d.assoc_item.1;
- let db = ctx.sema.db;
let default_range = d.impl_.syntax_node_ptr().text_range();
let trait_name = d.trait_.name(db).to_smol_str();
- let (redundant_item_name, diagnostic_range) = match assoc_item {
- hir::AssocItem::Function(id) => (
- format!("`fn {}`", name.display(db)),
- Function::from(id)
- .source(db)
- .map(|it| it.syntax().value.text_range())
- .unwrap_or(default_range),
- ),
- hir::AssocItem::Const(id) => (
- format!("`const {}`", name.display(db)),
- Const::from(id)
- .source(db)
- .map(|it| it.syntax().value.text_range())
- .unwrap_or(default_range),
- ),
- hir::AssocItem::TypeAlias(id) => (
- format!("`type {}`", name.display(db)),
- TypeAlias::from(id)
- .source(db)
- .map(|it| it.syntax().value.text_range())
- .unwrap_or(default_range),
- ),
+ let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item {
+ hir::AssocItem::Function(id) => {
+ let function = Function::from(id);
+ (
+ format!("`fn {}`", redundant_assoc_item_name),
+ function
+ .source(db)
+ .map(|it| it.syntax().value.text_range())
+ .unwrap_or(default_range),
+ format!("\n {};", function.display(db).to_string()),
+ )
+ }
+ hir::AssocItem::Const(id) => {
+ let constant = Const::from(id);
+ (
+ format!("`const {}`", redundant_assoc_item_name),
+ constant
+ .source(db)
+ .map(|it| it.syntax().value.text_range())
+ .unwrap_or(default_range),
+ format!("\n {};", constant.display(db).to_string()),
+ )
+ }
+ hir::AssocItem::TypeAlias(id) => {
+ let type_alias = TypeAlias::from(id);
+ (
+ format!("`type {}`", redundant_assoc_item_name),
+ type_alias
+ .source(db)
+ .map(|it| it.syntax().value.text_range())
+ .unwrap_or(default_range),
+ format!("\n type {};", type_alias.name(ctx.sema.db).to_smol_str()),
+ )
+ }
};
Diagnostic::new(
@@ -46,6 +66,51 @@ pub(crate) fn trait_impl_redundant_assoc_item(
format!("{redundant_item_name} is not a member of trait `{trait_name}`"),
FileRange { file_id: d.file_id.file_id().unwrap(), range: diagnostic_range },
)
+ .with_fixes(quickfix_for_redundant_assoc_item(
+ ctx,
+ d,
+ redundant_item_def,
+ diagnostic_range,
+ ))
+}
+
+/// add assoc item into the trait def body
+fn quickfix_for_redundant_assoc_item(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::TraitImplRedundantAssocItems,
+ redundant_item_def: String,
+ range: TextRange,
+) -> Option<Vec<Assist>> {
+ let add_assoc_item_def = |builder: &mut SourceChangeBuilder| -> Option<()> {
+ let db = ctx.sema.db;
+ let root = db.parse_or_expand(d.file_id);
+ // don't modify trait def in outer crate
+ let current_crate = ctx.sema.scope(&d.impl_.syntax_node_ptr().to_node(&root))?.krate();
+ let trait_def_crate = d.trait_.module(db).krate();
+ if trait_def_crate != current_crate {
+ return None;
+ }
+ let trait_def = d.trait_.source(db)?.value;
+ let where_to_insert = trait_def
+ .syntax()
+ .descendants_with_tokens()
+ .find(|it| it.kind() == SyntaxKind::L_CURLY)
+ .map(|it| it.text_range())?;
+
+ Some(builder.insert(where_to_insert.end(), redundant_item_def))
+ };
+ let file_id = d.file_id.file_id()?;
+ let mut source_change_builder = SourceChangeBuilder::new(file_id);
+ add_assoc_item_def(&mut source_change_builder)?;
+
+ Some(vec![Assist {
+ id: AssistId("add assoc item def into trait def", AssistKind::QuickFix),
+ label: Label::new("Add assoc item def into trait def".to_string()),
+ group: None,
+ target: range,
+ source_change: Some(source_change_builder.finish()),
+ trigger_signature_help: false,
+ }])
}
#[cfg(test)]