diff options
author | Kirill Bulatov <mail4score@gmail.com> | 2020-08-10 22:55:57 +0100 |
---|---|---|
committer | Kirill Bulatov <mail4score@gmail.com> | 2020-08-11 13:09:08 +0100 |
commit | c8cad76d25f7fab856c9646b70122e0f9f7d7218 (patch) | |
tree | 67091cef7037bbde1fc0d2da599562775a954f49 /crates/ra_ide/src/diagnostics | |
parent | 29fbc8e02180aac1f4d7819a9626206aa64028a0 (diff) |
Improve the ide diagnostics trait API
Diffstat (limited to 'crates/ra_ide/src/diagnostics')
-rw-r--r-- | crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | 163 |
1 files changed, 143 insertions, 20 deletions
diff --git a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs index 8578a90ec..56d454ac6 100644 --- a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs +++ b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | |||
@@ -1,46 +1,169 @@ | |||
1 | use crate::Fix; | ||
2 | use ast::{edit::IndentLevel, make}; | ||
1 | use hir::{ | 3 | use hir::{ |
2 | db::AstDatabase, | 4 | db::AstDatabase, |
3 | diagnostics::{MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, | 5 | diagnostics::{MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, |
6 | HasSource, HirDisplay, Semantics, VariantDef, | ||
4 | }; | 7 | }; |
5 | use ra_syntax::ast; | 8 | use ra_db::FileId; |
9 | use ra_ide_db::{ | ||
10 | source_change::{FileSystemEdit, SourceFileEdit}, | ||
11 | RootDatabase, | ||
12 | }; | ||
13 | use ra_syntax::{algo, ast, AstNode, TextRange}; | ||
14 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
6 | 15 | ||
7 | // TODO kb | 16 | // TODO kb |
8 | pub trait DiagnosticWithFix { | 17 | pub trait DiagnosticWithFix { |
9 | type AST; | 18 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)>; |
10 | fn fix_source(&self, db: &dyn AstDatabase) -> Option<Self::AST>; | ||
11 | } | 19 | } |
12 | 20 | ||
13 | impl DiagnosticWithFix for UnresolvedModule { | 21 | impl DiagnosticWithFix for UnresolvedModule { |
14 | type AST = ast::Module; | 22 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { |
15 | fn fix_source(&self, db: &dyn AstDatabase) -> Option<Self::AST> { | 23 | let fix = Fix::new( |
16 | let root = db.parse_or_expand(self.file)?; | 24 | "Create module", |
17 | Some(self.decl.to_node(&root)) | 25 | FileSystemEdit::CreateFile { |
26 | anchor: self.file.original_file(sema.db), | ||
27 | dst: self.candidate.clone(), | ||
28 | } | ||
29 | .into(), | ||
30 | ); | ||
31 | |||
32 | let root = sema.db.parse_or_expand(self.file)?; | ||
33 | let unresolved_module = self.decl.to_node(&root); | ||
34 | Some((fix, unresolved_module.syntax().text_range())) | ||
18 | } | 35 | } |
19 | } | 36 | } |
20 | 37 | ||
21 | impl DiagnosticWithFix for NoSuchField { | 38 | impl DiagnosticWithFix for NoSuchField { |
22 | type AST = ast::RecordExprField; | 39 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { |
23 | 40 | let root = sema.db.parse_or_expand(self.file)?; | |
24 | fn fix_source(&self, db: &dyn AstDatabase) -> Option<Self::AST> { | 41 | let record_expr_field = self.field.to_node(&root); |
25 | let root = db.parse_or_expand(self.file)?; | 42 | let fix = |
26 | Some(self.field.to_node(&root)) | 43 | missing_struct_field_fix(&sema, self.file.original_file(sema.db), &record_expr_field)?; |
44 | Some((fix, record_expr_field.syntax().text_range())) | ||
27 | } | 45 | } |
28 | } | 46 | } |
29 | 47 | ||
30 | impl DiagnosticWithFix for MissingFields { | 48 | impl DiagnosticWithFix for MissingFields { |
31 | type AST = ast::RecordExpr; | 49 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { |
50 | // Note that although we could add a diagnostics to | ||
51 | // fill the missing tuple field, e.g : | ||
52 | // `struct A(usize);` | ||
53 | // `let a = A { 0: () }` | ||
54 | // but it is uncommon usage and it should not be encouraged. | ||
55 | if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | ||
56 | None | ||
57 | } else { | ||
58 | let root = sema.db.parse_or_expand(self.file)?; | ||
59 | let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; | ||
60 | let mut new_field_list = old_field_list.clone(); | ||
61 | for f in self.missed_fields.iter() { | ||
62 | let field = make::record_expr_field( | ||
63 | make::name_ref(&f.to_string()), | ||
64 | Some(make::expr_unit()), | ||
65 | ); | ||
66 | new_field_list = new_field_list.append_field(&field); | ||
67 | } | ||
32 | 68 | ||
33 | fn fix_source(&self, db: &dyn AstDatabase) -> Option<Self::AST> { | 69 | let edit = { |
34 | let root = db.parse_or_expand(self.file)?; | 70 | let mut builder = TextEditBuilder::default(); |
35 | Some(self.field_list_parent.to_node(&root)) | 71 | algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) |
72 | .into_text_edit(&mut builder); | ||
73 | builder.finish() | ||
74 | }; | ||
75 | Some(( | ||
76 | Fix::new( | ||
77 | "Fill struct fields", | ||
78 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), | ||
79 | ), | ||
80 | sema.original_range(&old_field_list.syntax()).range, | ||
81 | // old_field_list.syntax().text_range(), | ||
82 | )) | ||
83 | } | ||
36 | } | 84 | } |
37 | } | 85 | } |
38 | 86 | ||
39 | impl DiagnosticWithFix for MissingOkInTailExpr { | 87 | impl DiagnosticWithFix for MissingOkInTailExpr { |
40 | type AST = ast::Expr; | 88 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { |
89 | let root = sema.db.parse_or_expand(self.file)?; | ||
90 | let tail_expr = self.expr.to_node(&root); | ||
91 | let tail_expr_range = tail_expr.syntax().text_range(); | ||
92 | let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); | ||
93 | let source_change = | ||
94 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); | ||
95 | Some((Fix::new("Wrap with ok", source_change), tail_expr_range)) | ||
96 | } | ||
97 | } | ||
98 | |||
99 | fn missing_struct_field_fix( | ||
100 | sema: &Semantics<RootDatabase>, | ||
101 | usage_file_id: FileId, | ||
102 | record_expr_field: &ast::RecordExprField, | ||
103 | ) -> Option<Fix> { | ||
104 | let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; | ||
105 | let def_id = sema.resolve_variant(record_lit)?; | ||
106 | let module; | ||
107 | let def_file_id; | ||
108 | let record_fields = match VariantDef::from(def_id) { | ||
109 | VariantDef::Struct(s) => { | ||
110 | module = s.module(sema.db); | ||
111 | let source = s.source(sema.db); | ||
112 | def_file_id = source.file_id; | ||
113 | let fields = source.value.field_list()?; | ||
114 | record_field_list(fields)? | ||
115 | } | ||
116 | VariantDef::Union(u) => { | ||
117 | module = u.module(sema.db); | ||
118 | let source = u.source(sema.db); | ||
119 | def_file_id = source.file_id; | ||
120 | source.value.record_field_list()? | ||
121 | } | ||
122 | VariantDef::EnumVariant(e) => { | ||
123 | module = e.module(sema.db); | ||
124 | let source = e.source(sema.db); | ||
125 | def_file_id = source.file_id; | ||
126 | let fields = source.value.field_list()?; | ||
127 | record_field_list(fields)? | ||
128 | } | ||
129 | }; | ||
130 | let def_file_id = def_file_id.original_file(sema.db); | ||
131 | |||
132 | let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?; | ||
133 | if new_field_type.is_unknown() { | ||
134 | return None; | ||
135 | } | ||
136 | let new_field = make::record_field( | ||
137 | record_expr_field.field_name()?, | ||
138 | make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
139 | ); | ||
140 | |||
141 | let last_field = record_fields.fields().last()?; | ||
142 | let last_field_syntax = last_field.syntax(); | ||
143 | let indent = IndentLevel::from_node(last_field_syntax); | ||
144 | |||
145 | let mut new_field = new_field.to_string(); | ||
146 | if usage_file_id != def_file_id { | ||
147 | new_field = format!("pub(crate) {}", new_field); | ||
148 | } | ||
149 | new_field = format!("\n{}{}", indent, new_field); | ||
150 | |||
151 | let needs_comma = !last_field_syntax.to_string().ends_with(','); | ||
152 | if needs_comma { | ||
153 | new_field = format!(",{}", new_field); | ||
154 | } | ||
155 | |||
156 | let source_change = SourceFileEdit { | ||
157 | file_id: def_file_id, | ||
158 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
159 | }; | ||
160 | let fix = Fix::new("Create field", source_change.into()); | ||
161 | return Some(fix); | ||
41 | 162 | ||
42 | fn fix_source(&self, db: &dyn AstDatabase) -> Option<Self::AST> { | 163 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { |
43 | let root = db.parse_or_expand(self.file)?; | 164 | match field_def_list { |
44 | Some(self.expr.to_node(&root)) | 165 | ast::FieldList::RecordFieldList(it) => Some(it), |
166 | ast::FieldList::TupleFieldList(_) => None, | ||
167 | } | ||
45 | } | 168 | } |
46 | } | 169 | } |