mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-14 22:24:14 +00:00
Simplify fix structure
This commit is contained in:
parent
37aa68f050
commit
188ec3459e
4 changed files with 93 additions and 97 deletions
|
@ -6,10 +6,7 @@
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use hir::{
|
use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
|
||||||
diagnostics::{Diagnostic as HirDiagnostics, DiagnosticSinkBuilder},
|
|
||||||
Semantics,
|
|
||||||
};
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ra_db::SourceDatabase;
|
use ra_db::SourceDatabase;
|
||||||
use ra_ide_db::RootDatabase;
|
use ra_ide_db::RootDatabase;
|
||||||
|
@ -73,7 +70,7 @@ pub(crate) fn diagnostics(
|
||||||
.build(|d| {
|
.build(|d| {
|
||||||
res.borrow_mut().push(Diagnostic {
|
res.borrow_mut().push(Diagnostic {
|
||||||
message: d.message(),
|
message: d.message(),
|
||||||
range: sema.diagnostics_presentation_range(d).range,
|
range: sema.diagnostics_display_range(d).range,
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
fix: None,
|
fix: None,
|
||||||
})
|
})
|
||||||
|
@ -86,12 +83,9 @@ pub(crate) fn diagnostics(
|
||||||
res.into_inner()
|
res.into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diagnostic_with_fix<D: HirDiagnostics + DiagnosticWithFix>(
|
fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
|
||||||
d: &D,
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
) -> Diagnostic {
|
|
||||||
Diagnostic {
|
Diagnostic {
|
||||||
range: sema.diagnostics_presentation_range(d).range,
|
range: sema.diagnostics_display_range(d).range,
|
||||||
message: d.message(),
|
message: d.message(),
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
fix: d.fix(&sema),
|
fix: d.fix(&sema),
|
||||||
|
@ -120,8 +114,9 @@ fn check_unnecessary_braces_in_use_statement(
|
||||||
range: use_range,
|
range: use_range,
|
||||||
message: "Unnecessary braces in use statement".to_string(),
|
message: "Unnecessary braces in use statement".to_string(),
|
||||||
severity: Severity::WeakWarning,
|
severity: Severity::WeakWarning,
|
||||||
fix: Some((
|
fix: Some(Fix::new(
|
||||||
Fix::new("Remove unnecessary braces", SourceFileEdit { file_id, edit }.into()),
|
"Remove unnecessary braces",
|
||||||
|
SourceFileEdit { file_id, edit }.into(),
|
||||||
use_range,
|
use_range,
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
|
@ -165,11 +160,9 @@ fn check_struct_shorthand_initialization(
|
||||||
range: field_range,
|
range: field_range,
|
||||||
message: "Shorthand struct initialization".to_string(),
|
message: "Shorthand struct initialization".to_string(),
|
||||||
severity: Severity::WeakWarning,
|
severity: Severity::WeakWarning,
|
||||||
fix: Some((
|
fix: Some(Fix::new(
|
||||||
Fix::new(
|
|
||||||
"Use struct shorthand initialization",
|
"Use struct shorthand initialization",
|
||||||
SourceFileEdit { file_id, edit }.into(),
|
SourceFileEdit { file_id, edit }.into(),
|
||||||
),
|
|
||||||
field_range,
|
field_range,
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
|
@ -197,7 +190,7 @@ mod tests {
|
||||||
|
|
||||||
let (analysis, file_position) = analysis_and_position(ra_fixture_before);
|
let (analysis, file_position) = analysis_and_position(ra_fixture_before);
|
||||||
let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap();
|
let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap();
|
||||||
let (mut fix, fix_range) = diagnostic.fix.unwrap();
|
let mut fix = diagnostic.fix.unwrap();
|
||||||
let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
|
let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
|
||||||
let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
|
let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
|
||||||
let actual = {
|
let actual = {
|
||||||
|
@ -208,9 +201,10 @@ mod tests {
|
||||||
|
|
||||||
assert_eq_text!(&after, &actual);
|
assert_eq_text!(&after, &actual);
|
||||||
assert!(
|
assert!(
|
||||||
fix_range.start() <= file_position.offset && fix_range.end() >= file_position.offset,
|
fix.fix_trigger_range.start() <= file_position.offset
|
||||||
|
&& fix.fix_trigger_range.end() >= file_position.offset,
|
||||||
"diagnostic fix range {:?} does not touch cursor position {:?}",
|
"diagnostic fix range {:?} does not touch cursor position {:?}",
|
||||||
fix_range,
|
fix.fix_trigger_range,
|
||||||
file_position.offset
|
file_position.offset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -222,7 +216,7 @@ mod tests {
|
||||||
let (analysis, file_pos) = analysis_and_position(ra_fixture_before);
|
let (analysis, file_pos) = analysis_and_position(ra_fixture_before);
|
||||||
let current_file_id = file_pos.file_id;
|
let current_file_id = file_pos.file_id;
|
||||||
let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap();
|
let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap();
|
||||||
let mut fix = diagnostic.fix.unwrap().0;
|
let mut fix = diagnostic.fix.unwrap();
|
||||||
let edit = fix.source_change.source_file_edits.pop().unwrap();
|
let edit = fix.source_change.source_file_edits.pop().unwrap();
|
||||||
let changed_file_id = edit.file_id;
|
let changed_file_id = edit.file_id;
|
||||||
let before = analysis.file_text(changed_file_id).unwrap();
|
let before = analysis.file_text(changed_file_id).unwrap();
|
||||||
|
@ -513,7 +507,6 @@ fn test_fn() {
|
||||||
range: 0..8,
|
range: 0..8,
|
||||||
severity: Error,
|
severity: Error,
|
||||||
fix: Some(
|
fix: Some(
|
||||||
(
|
|
||||||
Fix {
|
Fix {
|
||||||
label: "Create module",
|
label: "Create module",
|
||||||
source_change: SourceChange {
|
source_change: SourceChange {
|
||||||
|
@ -528,9 +521,8 @@ fn test_fn() {
|
||||||
],
|
],
|
||||||
is_snippet: false,
|
is_snippet: false,
|
||||||
},
|
},
|
||||||
|
fix_trigger_range: 0..8,
|
||||||
},
|
},
|
||||||
0..8,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
//! Provides a way to derive fixes based on the diagnostic data.
|
//! Provides a way to attach fix actions to the
|
||||||
use crate::Fix;
|
use crate::Fix;
|
||||||
use ast::{edit::IndentLevel, make};
|
use ast::{edit::IndentLevel, make};
|
||||||
use hir::{
|
use hir::{
|
||||||
db::AstDatabase,
|
db::AstDatabase,
|
||||||
diagnostics::{MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule},
|
diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule},
|
||||||
HasSource, HirDisplay, Semantics, VariantDef,
|
HasSource, HirDisplay, Semantics, VariantDef,
|
||||||
};
|
};
|
||||||
use ra_db::FileId;
|
use ra_db::FileId;
|
||||||
|
@ -11,60 +11,60 @@ use ra_ide_db::{
|
||||||
source_change::{FileSystemEdit, SourceFileEdit},
|
source_change::{FileSystemEdit, SourceFileEdit},
|
||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
use ra_syntax::{algo, ast, AstNode, TextRange};
|
use ra_syntax::{algo, ast, AstNode};
|
||||||
use ra_text_edit::{TextEdit, TextEditBuilder};
|
use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||||
|
|
||||||
/// A trait to implement fot the Diagnostic that has a fix available.
|
/// A [Diagnostic] that potentially has a fix available.
|
||||||
pub trait DiagnosticWithFix {
|
///
|
||||||
/// Provides a fix with the fix range, if applicable in the current semantics.
|
/// [Diagnostic]: hir::diagnostics::Diagnostic
|
||||||
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)>;
|
pub trait DiagnosticWithFix: Diagnostic {
|
||||||
|
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticWithFix for UnresolvedModule {
|
impl DiagnosticWithFix for UnresolvedModule {
|
||||||
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> {
|
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
|
||||||
let fix = Fix::new(
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
|
let unresolved_module = self.decl.to_node(&root);
|
||||||
|
Some(Fix::new(
|
||||||
"Create module",
|
"Create module",
|
||||||
FileSystemEdit::CreateFile {
|
FileSystemEdit::CreateFile {
|
||||||
anchor: self.file.original_file(sema.db),
|
anchor: self.file.original_file(sema.db),
|
||||||
dst: self.candidate.clone(),
|
dst: self.candidate.clone(),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
unresolved_module.syntax().text_range(),
|
||||||
|
))
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
|
||||||
let unresolved_module = self.decl.to_node(&root);
|
|
||||||
Some((fix, unresolved_module.syntax().text_range()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticWithFix for NoSuchField {
|
impl DiagnosticWithFix for NoSuchField {
|
||||||
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> {
|
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
let record_expr_field = self.field.to_node(&root);
|
missing_record_expr_field_fix(
|
||||||
let fix =
|
&sema,
|
||||||
missing_struct_field_fix(&sema, self.file.original_file(sema.db), &record_expr_field)?;
|
self.file.original_file(sema.db),
|
||||||
Some((fix, record_expr_field.syntax().text_range()))
|
&self.field.to_node(&root),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticWithFix for MissingFields {
|
impl DiagnosticWithFix for MissingFields {
|
||||||
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> {
|
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
|
||||||
// Note that although we could add a diagnostics to
|
// Note that although we could add a diagnostics to
|
||||||
// fill the missing tuple field, e.g :
|
// fill the missing tuple field, e.g :
|
||||||
// `struct A(usize);`
|
// `struct A(usize);`
|
||||||
// `let a = A { 0: () }`
|
// `let a = A { 0: () }`
|
||||||
// but it is uncommon usage and it should not be encouraged.
|
// but it is uncommon usage and it should not be encouraged.
|
||||||
if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
|
if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
|
||||||
None
|
return None;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?;
|
let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?;
|
||||||
let mut new_field_list = old_field_list.clone();
|
let mut new_field_list = old_field_list.clone();
|
||||||
for f in self.missed_fields.iter() {
|
for f in self.missed_fields.iter() {
|
||||||
let field = make::record_expr_field(
|
let field =
|
||||||
make::name_ref(&f.to_string()),
|
make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
|
||||||
Some(make::expr_unit()),
|
|
||||||
);
|
|
||||||
new_field_list = new_field_list.append_field(&field);
|
new_field_list = new_field_list.append_field(&field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,31 +74,27 @@ impl DiagnosticWithFix for MissingFields {
|
||||||
.into_text_edit(&mut builder);
|
.into_text_edit(&mut builder);
|
||||||
builder.finish()
|
builder.finish()
|
||||||
};
|
};
|
||||||
Some((
|
Some(Fix::new(
|
||||||
Fix::new(
|
|
||||||
"Fill struct fields",
|
"Fill struct fields",
|
||||||
SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(),
|
SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(),
|
||||||
),
|
|
||||||
sema.original_range(&old_field_list.syntax()).range,
|
sema.original_range(&old_field_list.syntax()).range,
|
||||||
// old_field_list.syntax().text_range(),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticWithFix for MissingOkInTailExpr {
|
impl DiagnosticWithFix for MissingOkInTailExpr {
|
||||||
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> {
|
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
|
||||||
let root = sema.db.parse_or_expand(self.file)?;
|
let root = sema.db.parse_or_expand(self.file)?;
|
||||||
let tail_expr = self.expr.to_node(&root);
|
let tail_expr = self.expr.to_node(&root);
|
||||||
let tail_expr_range = tail_expr.syntax().text_range();
|
let tail_expr_range = tail_expr.syntax().text_range();
|
||||||
let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax()));
|
let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax()));
|
||||||
let source_change =
|
let source_change =
|
||||||
SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
|
SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
|
||||||
Some((Fix::new("Wrap with ok", source_change), tail_expr_range))
|
Some(Fix::new("Wrap with ok", source_change, tail_expr_range))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn missing_struct_field_fix(
|
fn missing_record_expr_field_fix(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
usage_file_id: FileId,
|
usage_file_id: FileId,
|
||||||
record_expr_field: &ast::RecordExprField,
|
record_expr_field: &ast::RecordExprField,
|
||||||
|
@ -159,8 +155,11 @@ fn missing_struct_field_fix(
|
||||||
file_id: def_file_id,
|
file_id: def_file_id,
|
||||||
edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
|
edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
|
||||||
};
|
};
|
||||||
let fix = Fix::new("Create field", source_change.into());
|
return Some(Fix::new(
|
||||||
return Some(fix);
|
"Create field",
|
||||||
|
source_change.into(),
|
||||||
|
record_expr_field.syntax().text_range(),
|
||||||
|
));
|
||||||
|
|
||||||
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
|
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
|
||||||
match field_def_list {
|
match field_def_list {
|
||||||
|
|
|
@ -105,20 +105,26 @@ pub struct Diagnostic {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub range: TextRange,
|
pub range: TextRange,
|
||||||
pub severity: Severity,
|
pub severity: Severity,
|
||||||
pub fix: Option<(Fix, TextRange)>,
|
pub fix: Option<Fix>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Fix {
|
pub struct Fix {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub source_change: SourceChange,
|
pub source_change: SourceChange,
|
||||||
|
/// Allows to trigger the fix only when the caret is in the range given
|
||||||
|
pub fix_trigger_range: TextRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fix {
|
impl Fix {
|
||||||
pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self {
|
pub fn new(
|
||||||
|
label: impl Into<String>,
|
||||||
|
source_change: SourceChange,
|
||||||
|
fix_trigger_range: TextRange,
|
||||||
|
) -> Self {
|
||||||
let label = label.into();
|
let label = label.into();
|
||||||
assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
|
assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
|
||||||
Self { label, source_change }
|
Self { label, source_change, fix_trigger_range }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -773,12 +773,11 @@ fn handle_fixes(
|
||||||
|
|
||||||
let diagnostics = snap.analysis.diagnostics(file_id, snap.config.experimental_diagnostics)?;
|
let diagnostics = snap.analysis.diagnostics(file_id, snap.config.experimental_diagnostics)?;
|
||||||
|
|
||||||
let fixes_from_diagnostics = diagnostics
|
for fix in diagnostics
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|d| d.fix)
|
.filter_map(|d| d.fix)
|
||||||
.filter(|(_fix, fix_range)| fix_range.intersect(range).is_some())
|
.filter(|fix| fix.fix_trigger_range.intersect(range).is_some())
|
||||||
.map(|(fix, _range)| fix);
|
{
|
||||||
for fix in fixes_from_diagnostics {
|
|
||||||
let title = fix.label;
|
let title = fix.label;
|
||||||
let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
|
let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
|
||||||
let action = lsp_ext::CodeAction {
|
let action = lsp_ext::CodeAction {
|
||||||
|
|
Loading…
Reference in a new issue