diff options
Diffstat (limited to 'crates/ra_ide/src/diagnostics')
-rw-r--r-- | crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | 101 |
1 files changed, 50 insertions, 51 deletions
diff --git a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs index 1955e1521..57b54a61e 100644 --- a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs +++ b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | //! Provides a way to derive fixes based on the diagnostic data. | 1 | //! Provides a way to attach fix actions to the |
2 | use crate::Fix; | 2 | use crate::Fix; |
3 | use ast::{edit::IndentLevel, make}; | 3 | use ast::{edit::IndentLevel, make}; |
4 | use hir::{ | 4 | use hir::{ |
5 | db::AstDatabase, | 5 | db::AstDatabase, |
6 | diagnostics::{MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, | 6 | diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, |
7 | HasSource, HirDisplay, Semantics, VariantDef, | 7 | HasSource, HirDisplay, Semantics, VariantDef, |
8 | }; | 8 | }; |
9 | use ra_db::FileId; | 9 | use ra_db::FileId; |
@@ -11,94 +11,90 @@ use ra_ide_db::{ | |||
11 | source_change::{FileSystemEdit, SourceFileEdit}, | 11 | source_change::{FileSystemEdit, SourceFileEdit}, |
12 | RootDatabase, | 12 | RootDatabase, |
13 | }; | 13 | }; |
14 | use ra_syntax::{algo, ast, AstNode, TextRange}; | 14 | use ra_syntax::{algo, ast, AstNode}; |
15 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 15 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
16 | 16 | ||
17 | /// A trait to implement fot the Diagnostic that has a fix available. | 17 | /// A [Diagnostic] that potentially has a fix available. |
18 | pub trait DiagnosticWithFix { | 18 | /// |
19 | /// Provides a fix with the fix range, if applicable in the current semantics. | 19 | /// [Diagnostic]: hir::diagnostics::Diagnostic |
20 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)>; | 20 | pub trait DiagnosticWithFix: Diagnostic { |
21 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>; | ||
21 | } | 22 | } |
22 | 23 | ||
23 | impl DiagnosticWithFix for UnresolvedModule { | 24 | impl DiagnosticWithFix for UnresolvedModule { |
24 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { | 25 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { |
25 | let fix = Fix::new( | 26 | let root = sema.db.parse_or_expand(self.file)?; |
27 | let unresolved_module = self.decl.to_node(&root); | ||
28 | Some(Fix::new( | ||
26 | "Create module", | 29 | "Create module", |
27 | FileSystemEdit::CreateFile { | 30 | FileSystemEdit::CreateFile { |
28 | anchor: self.file.original_file(sema.db), | 31 | anchor: self.file.original_file(sema.db), |
29 | dst: self.candidate.clone(), | 32 | dst: self.candidate.clone(), |
30 | } | 33 | } |
31 | .into(), | 34 | .into(), |
32 | ); | 35 | unresolved_module.syntax().text_range(), |
33 | 36 | )) | |
34 | let root = sema.db.parse_or_expand(self.file)?; | ||
35 | let unresolved_module = self.decl.to_node(&root); | ||
36 | Some((fix, unresolved_module.syntax().text_range())) | ||
37 | } | 37 | } |
38 | } | 38 | } |
39 | 39 | ||
40 | impl DiagnosticWithFix for NoSuchField { | 40 | impl DiagnosticWithFix for NoSuchField { |
41 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { | 41 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { |
42 | let root = sema.db.parse_or_expand(self.file)?; | 42 | let root = sema.db.parse_or_expand(self.file)?; |
43 | let record_expr_field = self.field.to_node(&root); | 43 | missing_record_expr_field_fix( |
44 | let fix = | 44 | &sema, |
45 | missing_struct_field_fix(&sema, self.file.original_file(sema.db), &record_expr_field)?; | 45 | self.file.original_file(sema.db), |
46 | Some((fix, record_expr_field.syntax().text_range())) | 46 | &self.field.to_node(&root), |
47 | ) | ||
47 | } | 48 | } |
48 | } | 49 | } |
49 | 50 | ||
50 | impl DiagnosticWithFix for MissingFields { | 51 | impl DiagnosticWithFix for MissingFields { |
51 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { | 52 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { |
52 | // Note that although we could add a diagnostics to | 53 | // Note that although we could add a diagnostics to |
53 | // fill the missing tuple field, e.g : | 54 | // fill the missing tuple field, e.g : |
54 | // `struct A(usize);` | 55 | // `struct A(usize);` |
55 | // `let a = A { 0: () }` | 56 | // `let a = A { 0: () }` |
56 | // but it is uncommon usage and it should not be encouraged. | 57 | // but it is uncommon usage and it should not be encouraged. |
57 | if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | 58 | if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { |
58 | None | 59 | return None; |
59 | } else { | 60 | } |
60 | let root = sema.db.parse_or_expand(self.file)?; | ||
61 | let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; | ||
62 | let mut new_field_list = old_field_list.clone(); | ||
63 | for f in self.missed_fields.iter() { | ||
64 | let field = make::record_expr_field( | ||
65 | make::name_ref(&f.to_string()), | ||
66 | Some(make::expr_unit()), | ||
67 | ); | ||
68 | new_field_list = new_field_list.append_field(&field); | ||
69 | } | ||
70 | 61 | ||
71 | let edit = { | 62 | let root = sema.db.parse_or_expand(self.file)?; |
72 | let mut builder = TextEditBuilder::default(); | 63 | let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; |
73 | algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) | 64 | let mut new_field_list = old_field_list.clone(); |
74 | .into_text_edit(&mut builder); | 65 | for f in self.missed_fields.iter() { |
75 | builder.finish() | 66 | let field = |
76 | }; | 67 | make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); |
77 | Some(( | 68 | new_field_list = new_field_list.append_field(&field); |
78 | Fix::new( | ||
79 | "Fill struct fields", | ||
80 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), | ||
81 | ), | ||
82 | sema.original_range(&old_field_list.syntax()).range, | ||
83 | // old_field_list.syntax().text_range(), | ||
84 | )) | ||
85 | } | 69 | } |
70 | |||
71 | let edit = { | ||
72 | let mut builder = TextEditBuilder::default(); | ||
73 | algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) | ||
74 | .into_text_edit(&mut builder); | ||
75 | builder.finish() | ||
76 | }; | ||
77 | Some(Fix::new( | ||
78 | "Fill struct fields", | ||
79 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), | ||
80 | sema.original_range(&old_field_list.syntax()).range, | ||
81 | )) | ||
86 | } | 82 | } |
87 | } | 83 | } |
88 | 84 | ||
89 | impl DiagnosticWithFix for MissingOkInTailExpr { | 85 | impl DiagnosticWithFix for MissingOkInTailExpr { |
90 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { | 86 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { |
91 | let root = sema.db.parse_or_expand(self.file)?; | 87 | let root = sema.db.parse_or_expand(self.file)?; |
92 | let tail_expr = self.expr.to_node(&root); | 88 | let tail_expr = self.expr.to_node(&root); |
93 | let tail_expr_range = tail_expr.syntax().text_range(); | 89 | let tail_expr_range = tail_expr.syntax().text_range(); |
94 | let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); | 90 | let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); |
95 | let source_change = | 91 | let source_change = |
96 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); | 92 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); |
97 | Some((Fix::new("Wrap with ok", source_change), tail_expr_range)) | 93 | Some(Fix::new("Wrap with ok", source_change, tail_expr_range)) |
98 | } | 94 | } |
99 | } | 95 | } |
100 | 96 | ||
101 | fn missing_struct_field_fix( | 97 | fn missing_record_expr_field_fix( |
102 | sema: &Semantics<RootDatabase>, | 98 | sema: &Semantics<RootDatabase>, |
103 | usage_file_id: FileId, | 99 | usage_file_id: FileId, |
104 | record_expr_field: &ast::RecordExprField, | 100 | record_expr_field: &ast::RecordExprField, |
@@ -159,8 +155,11 @@ fn missing_struct_field_fix( | |||
159 | file_id: def_file_id, | 155 | file_id: def_file_id, |
160 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | 156 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), |
161 | }; | 157 | }; |
162 | let fix = Fix::new("Create field", source_change.into()); | 158 | return Some(Fix::new( |
163 | return Some(fix); | 159 | "Create field", |
160 | source_change.into(), | ||
161 | record_expr_field.syntax().text_range(), | ||
162 | )); | ||
164 | 163 | ||
165 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { | 164 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { |
166 | match field_def_list { | 165 | match field_def_list { |