mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-26 20:09:19 +00:00
Add fix for incorrect case diagnostic
This commit is contained in:
parent
17f1026c46
commit
e24e22f288
8 changed files with 112 additions and 30 deletions
|
@ -257,34 +257,22 @@ impl ModuleDef {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
|
pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
|
||||||
match self {
|
let id = match self {
|
||||||
ModuleDef::Adt(it) => match it {
|
ModuleDef::Adt(it) => match it {
|
||||||
Adt::Struct(it) => {
|
Adt::Struct(it) => it.id.into(),
|
||||||
hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink)
|
Adt::Enum(it) => it.id.into(),
|
||||||
}
|
Adt::Union(it) => it.id.into(),
|
||||||
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),
|
|
||||||
},
|
},
|
||||||
ModuleDef::Trait(it) => {
|
ModuleDef::Trait(it) => it.id.into(),
|
||||||
hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink)
|
ModuleDef::Function(it) => it.id.into(),
|
||||||
}
|
ModuleDef::TypeAlias(it) => it.id.into(),
|
||||||
ModuleDef::Function(it) => {
|
ModuleDef::Module(it) => it.id.into(),
|
||||||
hir_ty::diagnostics::validate_module_item(db, it.id.into(), sink)
|
ModuleDef::Const(it) => it.id.into(),
|
||||||
}
|
ModuleDef::Static(it) => it.id.into(),
|
||||||
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)
|
|
||||||
}
|
|
||||||
_ => return,
|
_ => 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);
|
let crate_def_map = db.crate_def_map(self.id.krate);
|
||||||
crate_def_map.add_diagnostics(db.upcast(), self.id.local_id, sink);
|
crate_def_map.add_diagnostics(db.upcast(), self.id.local_id, sink);
|
||||||
for decl in self.declarations(db) {
|
for decl in self.declarations(db) {
|
||||||
|
decl.diagnostics(db, sink);
|
||||||
|
|
||||||
match decl {
|
match decl {
|
||||||
crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
|
crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
|
||||||
crate::ModuleDef::Module(m) => {
|
crate::ModuleDef::Module(m) => {
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
pub use hir_def::diagnostics::UnresolvedModule;
|
pub use hir_def::diagnostics::UnresolvedModule;
|
||||||
pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder};
|
pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder};
|
||||||
pub use hir_ty::diagnostics::{
|
pub use hir_ty::diagnostics::{
|
||||||
MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField,
|
IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr,
|
||||||
|
NoSuchField,
|
||||||
};
|
};
|
||||||
|
|
|
@ -298,7 +298,7 @@ impl Diagnostic for IncorrectCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_experimental(&self) -> bool {
|
fn is_experimental(&self) -> bool {
|
||||||
true
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,9 @@ pub(crate) fn diagnostics(
|
||||||
.on::<hir::diagnostics::NoSuchField, _>(|d| {
|
.on::<hir::diagnostics::NoSuchField, _>(|d| {
|
||||||
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
|
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.
|
// Only collect experimental diagnostics when they're enabled.
|
||||||
.filter(|diag| !(diag.is_experimental() && config.disable_experimental))
|
.filter(|diag| !(diag.is_experimental() && config.disable_experimental))
|
||||||
.filter(|diag| !config.disabled.contains(diag.code().as_str()));
|
.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(
|
fn check_unnecessary_braces_in_use_statement(
|
||||||
acc: &mut Vec<Diagnostic>,
|
acc: &mut Vec<Diagnostic>,
|
||||||
file_id: FileId,
|
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
|
/// Checks that a diagnostic applies to the file containing the `<|>` cursor marker
|
||||||
/// which has a fix that can apply to other files.
|
/// 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) {
|
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();
|
let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
|
||||||
assert!(!diagnostics.is_empty());
|
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 }
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
use base_db::FileId;
|
use base_db::FileId;
|
||||||
use hir::{
|
use hir::{
|
||||||
db::AstDatabase,
|
db::AstDatabase,
|
||||||
diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule},
|
diagnostics::{
|
||||||
|
Diagnostic, IncorrectCase, MissingFields, MissingOkInTailExpr, NoSuchField,
|
||||||
|
UnresolvedModule,
|
||||||
|
},
|
||||||
HasSource, HirDisplay, Semantics, VariantDef,
|
HasSource, HirDisplay, Semantics, VariantDef,
|
||||||
};
|
};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
|
@ -17,7 +20,7 @@ use syntax::{
|
||||||
};
|
};
|
||||||
use text_edit::TextEdit;
|
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.
|
/// 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(
|
fn missing_record_expr_field_fix(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
usage_file_id: FileId,
|
usage_file_id: FileId,
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
//! at the index that the match starts at and its tree parent is
|
//! at the index that the match starts at and its tree parent is
|
||||||
//! resolved to the search element definition, we get a reference.
|
//! resolved to the search element definition, we get a reference.
|
||||||
|
|
||||||
mod rename;
|
pub(crate) mod rename;
|
||||||
|
|
||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
|
|
|
@ -42,7 +42,14 @@ pub(crate) fn rename(
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
||||||
let sema = Semantics::new(db);
|
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) {
|
match lex_single_syntax_kind(new_name) {
|
||||||
Some(res) => match res {
|
Some(res) => match res {
|
||||||
(SyntaxKind::IDENT, _) => (),
|
(SyntaxKind::IDENT, _) => (),
|
||||||
|
|
|
@ -23,6 +23,10 @@ impl SyntaxNodePtr {
|
||||||
SyntaxNodePtr { range: node.text_range(), kind: node.kind() }
|
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 {
|
pub fn to_node(&self, root: &SyntaxNode) -> SyntaxNode {
|
||||||
assert!(root.parent().is_none());
|
assert!(root.parent().is_none());
|
||||||
successors(Some(root.clone()), |node| {
|
successors(Some(root.clone()), |node| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue