From e24e22f288eba33928a9e579f13653d6f04fcdfa Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sat, 3 Oct 2020 17:34:52 +0300 Subject: Add fix for incorrect case diagnostic --- crates/hir/src/code_model.rs | 40 +++++++++-------------- crates/hir/src/diagnostics.rs | 3 +- crates/hir_ty/src/diagnostics.rs | 2 +- crates/ide/src/diagnostics.rs | 64 +++++++++++++++++++++++++++++++++++++ crates/ide/src/diagnostics/fixes.rs | 20 ++++++++++-- crates/ide/src/references.rs | 2 +- crates/ide/src/references/rename.rs | 7 ++++ crates/syntax/src/ptr.rs | 4 +++ 8 files changed, 112 insertions(+), 30 deletions(-) diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index 19ea26e36..c134356ef 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs @@ -257,34 +257,22 @@ impl ModuleDef { } pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { - match self { + let id = match self { ModuleDef::Adt(it) => match it { - Adt::Struct(it) => { - hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink) - } - Adt::Enum(it) => hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink), - Adt::Union(it) => hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink), + Adt::Struct(it) => it.id.into(), + Adt::Enum(it) => it.id.into(), + Adt::Union(it) => it.id.into(), }, - ModuleDef::Trait(it) => { - hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink) - } - ModuleDef::Function(it) => { - hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink) - } - ModuleDef::TypeAlias(it) => { - hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink) - } - ModuleDef::Module(it) => { - hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink) - } - ModuleDef::Const(it) => { - hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink) - } - ModuleDef::Static(it) => { - hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink) - } + ModuleDef::Trait(it) => it.id.into(), + ModuleDef::Function(it) => it.id.into(), + ModuleDef::TypeAlias(it) => it.id.into(), + ModuleDef::Module(it) => it.id.into(), + ModuleDef::Const(it) => it.id.into(), + ModuleDef::Static(it) => it.id.into(), _ => return, - } + }; + + hir_ty::diagnostics::validate_module_item(db, id, sink) } } @@ -389,6 +377,8 @@ impl Module { let crate_def_map = db.crate_def_map(self.id.krate); crate_def_map.add_diagnostics(db.upcast(), self.id.local_id, sink); for decl in self.declarations(db) { + decl.diagnostics(db, sink); + match decl { crate::ModuleDef::Function(f) => f.diagnostics(db, sink), crate::ModuleDef::Module(m) => { diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 363164b9b..da2b40849 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -2,5 +2,6 @@ pub use hir_def::diagnostics::UnresolvedModule; pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder}; pub use hir_ty::diagnostics::{ - MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField, + IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, + NoSuchField, }; diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs index bd370e3b2..66762b90e 100644 --- a/crates/hir_ty/src/diagnostics.rs +++ b/crates/hir_ty/src/diagnostics.rs @@ -298,7 +298,7 @@ impl Diagnostic for IncorrectCase { } fn is_experimental(&self) -> bool { - true + false } } diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index f5d627b6e..71ab98c1f 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -96,6 +96,9 @@ pub(crate) fn diagnostics( .on::(|d| { res.borrow_mut().push(diagnostic_with_fix(d, &sema)); }) + .on::(|d| { + res.borrow_mut().push(warning_with_fix(d, &sema)); + }) // Only collect experimental diagnostics when they're enabled. .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) .filter(|diag| !config.disabled.contains(diag.code().as_str())); @@ -130,6 +133,16 @@ fn diagnostic_with_fix(d: &D, sema: &Semantics(d: &D, sema: &Semantics) -> Diagnostic { + Diagnostic { + // name: Some(d.name().into()), + range: sema.diagnostics_display_range(d).range, + message: d.message(), + severity: Severity::WeakWarning, + fix: d.fix(&sema), + } +} + fn check_unnecessary_braces_in_use_statement( acc: &mut Vec, file_id: FileId, @@ -253,6 +266,37 @@ mod tests { ); } + /// Similar to `check_fix`, but applies all the available fixes. + fn check_fixes(ra_fixture_before: &str, ra_fixture_after: &str) { + let after = trim_indent(ra_fixture_after); + + let (analysis, file_position) = fixture::position(ra_fixture_before); + let diagnostic = analysis + .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) + .unwrap() + .pop() + .unwrap(); + let fix = diagnostic.fix.unwrap(); + let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); + let actual = { + let mut actual = target_file_contents.to_string(); + // Go from the last one to the first one, so that ranges won't be affected by previous edits. + for edit in fix.source_change.source_file_edits.iter().rev() { + edit.edit.apply(&mut actual); + } + actual + }; + + assert_eq_text!(&after, &actual); + assert!( + fix.fix_trigger_range.start() <= file_position.offset + && fix.fix_trigger_range.end() >= file_position.offset, + "diagnostic fix range {:?} does not touch cursor position {:?}", + fix.fix_trigger_range, + file_position.offset + ); + } + /// Checks that a diagnostic applies to the file containing the `<|>` cursor marker /// which has a fix that can apply to other files. fn check_apply_diagnostic_fix_in_other_file(ra_fixture_before: &str, ra_fixture_after: &str) { @@ -790,4 +834,24 @@ struct Foo { let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); assert!(!diagnostics.is_empty()); } + + #[test] + fn test_rename_incorrect_case() { + check_fixes( + r#" +pub struct test_struct<|> { one: i32 } + +pub fn some_fn(val: test_struct) -> test_struct { + test_struct { one: val.one + 1 } +} +"#, + r#" +pub struct TestStruct { one: i32 } + +pub fn some_fn(val: TestStruct) -> TestStruct { + TestStruct { one: val.one + 1 } +} +"#, + ); + } } diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index 68ae1c239..286ef0785 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs @@ -3,7 +3,10 @@ use base_db::FileId; use hir::{ db::AstDatabase, - diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, + diagnostics::{ + Diagnostic, IncorrectCase, MissingFields, MissingOkInTailExpr, NoSuchField, + UnresolvedModule, + }, HasSource, HirDisplay, Semantics, VariantDef, }; use ide_db::{ @@ -17,7 +20,7 @@ use syntax::{ }; use text_edit::TextEdit; -use crate::diagnostics::Fix; +use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition}; /// A [Diagnostic] that potentially has a fix available. /// @@ -99,6 +102,19 @@ impl DiagnosticWithFix for MissingOkInTailExpr { } } +impl DiagnosticWithFix for IncorrectCase { + fn fix(&self, sema: &Semantics) -> Option { + let file_id = self.file.original_file(sema.db); + let offset = self.ident.text_range().start(); + let file_position = FilePosition { file_id, offset }; + + let rename_changes = rename_with_semantics(sema, file_position, &self.suggested_text)?; + + let label = format!("Rename to {}", self.suggested_text); + Some(Fix::new(&label, rename_changes.info, rename_changes.range)) + } +} + fn missing_record_expr_field_fix( sema: &Semantics, usage_file_id: FileId, diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index f65a05ea3..88e2f2db3 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -9,7 +9,7 @@ //! at the index that the match starts at and its tree parent is //! resolved to the search element definition, we get a reference. -mod rename; +pub(crate) mod rename; use hir::Semantics; use ide_db::{ diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index f3b5cfc8c..f9a11e43d 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs @@ -42,7 +42,14 @@ pub(crate) fn rename( new_name: &str, ) -> Result, RenameError> { let sema = Semantics::new(db); + rename_with_semantics(&sema, position, new_name) +} +pub(crate) fn rename_with_semantics( + sema: &Semantics, + position: FilePosition, + new_name: &str, +) -> Result, RenameError> { match lex_single_syntax_kind(new_name) { Some(res) => match res { (SyntaxKind::IDENT, _) => (), diff --git a/crates/syntax/src/ptr.rs b/crates/syntax/src/ptr.rs index d3fb7a5d9..34e20464a 100644 --- a/crates/syntax/src/ptr.rs +++ b/crates/syntax/src/ptr.rs @@ -23,6 +23,10 @@ impl SyntaxNodePtr { SyntaxNodePtr { range: node.text_range(), kind: node.kind() } } + pub fn text_range(&self) -> TextRange { + self.range.clone() + } + pub fn to_node(&self, root: &SyntaxNode) -> SyntaxNode { assert!(root.parent().is_none()); successors(Some(root.clone()), |node| { -- cgit v1.2.3