Add fix for incorrect case diagnostic

This commit is contained in:
Igor Aleksanov 2020-10-03 17:34:52 +03:00
parent 17f1026c46
commit e24e22f288
8 changed files with 112 additions and 30 deletions

View File

@ -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) => {

View File

@ -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,
};

View File

@ -298,7 +298,7 @@ impl Diagnostic for IncorrectCase {
}
fn is_experimental(&self) -> bool {
true
false
}
}

View File

@ -96,6 +96,9 @@ pub(crate) fn diagnostics(
.on::<hir::diagnostics::NoSuchField, _>(|d| {
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
})
.on::<hir::diagnostics::IncorrectCase, _>(|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: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabas
}
}
fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> 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<Diagnostic>,
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 }
}
"#,
);
}
}

View File

@ -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<RootDatabase>) -> Option<Fix> {
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<RootDatabase>,
usage_file_id: FileId,

View File

@ -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::{

View File

@ -42,7 +42,14 @@ pub(crate) fn rename(
new_name: &str,
) -> Result<RangeInfo<SourceChange>, RenameError> {
let sema = Semantics::new(db);
rename_with_semantics(&sema, position, new_name)
}
pub(crate) fn rename_with_semantics(
sema: &Semantics<RootDatabase>,
position: FilePosition,
new_name: &str,
) -> Result<RangeInfo<SourceChange>, RenameError> {
match lex_single_syntax_kind(new_name) {
Some(res) => match res {
(SyntaxKind::IDENT, _) => (),

View File

@ -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| {