aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/diagnostics.rs188
-rw-r--r--crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs163
2 files changed, 160 insertions, 191 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index ca1a7c1aa..165ff5249 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -7,22 +7,20 @@
7use std::cell::RefCell; 7use std::cell::RefCell;
8 8
9use hir::{ 9use hir::{
10 db::AstDatabase, 10 diagnostics::{Diagnostic as HirDiagnostics, DiagnosticSinkBuilder},
11 diagnostics::{Diagnostic as _, DiagnosticSinkBuilder}, 11 Semantics,
12 HasSource, HirDisplay, Semantics, VariantDef,
13}; 12};
14use itertools::Itertools; 13use itertools::Itertools;
15use ra_db::{SourceDatabase, Upcast}; 14use ra_db::SourceDatabase;
16use ra_ide_db::RootDatabase; 15use ra_ide_db::RootDatabase;
17use ra_prof::profile; 16use ra_prof::profile;
18use ra_syntax::{ 17use ra_syntax::{
19 algo, 18 ast::{self, AstNode},
20 ast::{self, edit::IndentLevel, make, AstNode},
21 SyntaxNode, TextRange, T, 19 SyntaxNode, TextRange, T,
22}; 20};
23use ra_text_edit::{TextEdit, TextEditBuilder}; 21use ra_text_edit::{TextEdit, TextEditBuilder};
24 22
25use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit}; 23use crate::{Diagnostic, FileId, Fix, SourceFileEdit};
26 24
27mod diagnostics_with_fix; 25mod diagnostics_with_fix;
28use diagnostics_with_fix::DiagnosticWithFix; 26use diagnostics_with_fix::DiagnosticWithFix;
@@ -58,96 +56,16 @@ pub(crate) fn diagnostics(
58 let res = RefCell::new(res); 56 let res = RefCell::new(res);
59 let mut sink = DiagnosticSinkBuilder::new() 57 let mut sink = DiagnosticSinkBuilder::new()
60 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 58 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
61 let fix = Fix::new( 59 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
62 "Create module",
63 FileSystemEdit::CreateFile {
64 anchor: d.file.original_file(db),
65 dst: d.candidate.clone(),
66 }
67 .into(),
68 );
69 let fix = diagnostic_fix_source(&sema, d)
70 .map(|unresolved_module| unresolved_module.syntax().text_range())
71 .map(|fix_range| (fix, fix_range));
72
73 res.borrow_mut().push(Diagnostic {
74 range: sema.diagnostics_presentation_range(d).range,
75 message: d.message(),
76 severity: Severity::Error,
77 fix,
78 });
79 Some(())
80 }) 60 })
81 .on::<hir::diagnostics::MissingFields, _>(|d| { 61 .on::<hir::diagnostics::MissingFields, _>(|d| {
82 // Note that although we could add a diagnostics to 62 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
83 // fill the missing tuple field, e.g :
84 // `struct A(usize);`
85 // `let a = A { 0: () }`
86 // but it is uncommon usage and it should not be encouraged.
87 let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
88 None
89 } else {
90 diagnostic_fix_source(&sema, d)
91 .and_then(|record_expr| record_expr.record_expr_field_list())
92 .map(|old_field_list| {
93 let mut new_field_list = old_field_list.clone();
94 for f in d.missed_fields.iter() {
95 let field = make::record_expr_field(
96 make::name_ref(&f.to_string()),
97 Some(make::expr_unit()),
98 );
99 new_field_list = new_field_list.append_field(&field);
100 }
101
102 let edit = {
103 let mut builder = TextEditBuilder::default();
104 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
105 .into_text_edit(&mut builder);
106 builder.finish()
107 };
108 (
109 Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()),
110 sema.original_range(&old_field_list.syntax()).range,
111 // old_field_list.syntax().text_range(),
112 )
113 })
114 };
115
116 res.borrow_mut().push(Diagnostic {
117 range: sema.diagnostics_presentation_range(d).range,
118 message: d.message(),
119 severity: Severity::Error,
120 fix,
121 });
122 Some(())
123 }) 63 })
124 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { 64 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
125 let fix = diagnostic_fix_source(&sema, d).map(|tail_expr| { 65 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
126 let tail_expr_range = tail_expr.syntax().text_range();
127 let edit =
128 TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax()));
129 let source_change = SourceFileEdit { file_id, edit }.into();
130 (Fix::new("Wrap with ok", source_change), tail_expr_range)
131 });
132
133 res.borrow_mut().push(Diagnostic {
134 range: sema.diagnostics_presentation_range(d).range,
135 message: d.message(),
136 severity: Severity::Error,
137 fix,
138 });
139 Some(())
140 }) 66 })
141 .on::<hir::diagnostics::NoSuchField, _>(|d| { 67 .on::<hir::diagnostics::NoSuchField, _>(|d| {
142 res.borrow_mut().push(Diagnostic { 68 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
143 range: sema.diagnostics_presentation_range(d).range,
144 message: d.message(),
145 severity: Severity::Error,
146 fix: missing_struct_field_fix(&sema, file_id, d).and_then(|fix| {
147 Some((fix, diagnostic_fix_source(&sema, d)?.syntax().text_range()))
148 }),
149 });
150 Some(())
151 }) 69 })
152 // Only collect experimental diagnostics when they're enabled. 70 // Only collect experimental diagnostics when they're enabled.
153 .filter(|diag| !diag.is_experimental() || enable_experimental) 71 .filter(|diag| !diag.is_experimental() || enable_experimental)
@@ -168,87 +86,15 @@ pub(crate) fn diagnostics(
168 res.into_inner() 86 res.into_inner()
169} 87}
170 88
171fn diagnostic_fix_source<T: DiagnosticWithFix + hir::diagnostics::Diagnostic>( 89fn diagnostic_with_fix<D: HirDiagnostics + DiagnosticWithFix>(
90 d: &D,
172 sema: &Semantics<RootDatabase>, 91 sema: &Semantics<RootDatabase>,
173 d: &T, 92) -> Diagnostic {
174) -> Option<<T as DiagnosticWithFix>::AST> { 93 Diagnostic {
175 let file_id = d.presentation().file_id; 94 range: sema.diagnostics_presentation_range(d).range,
176 let root = sema.db.parse_or_expand(file_id)?; 95 message: d.message(),
177 sema.cache(root, file_id); 96 severity: Severity::Error,
178 d.fix_source(sema.db.upcast()) 97 fix: d.fix(&sema),
179}
180
181fn missing_struct_field_fix(
182 sema: &Semantics<RootDatabase>,
183 usage_file_id: FileId,
184 d: &hir::diagnostics::NoSuchField,
185) -> Option<Fix> {
186 let record_expr_field = diagnostic_fix_source(&sema, d)?;
187
188 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
189 let def_id = sema.resolve_variant(record_lit)?;
190 let module;
191 let def_file_id;
192 let record_fields = match VariantDef::from(def_id) {
193 VariantDef::Struct(s) => {
194 module = s.module(sema.db);
195 let source = s.source(sema.db);
196 def_file_id = source.file_id;
197 let fields = source.value.field_list()?;
198 record_field_list(fields)?
199 }
200 VariantDef::Union(u) => {
201 module = u.module(sema.db);
202 let source = u.source(sema.db);
203 def_file_id = source.file_id;
204 source.value.record_field_list()?
205 }
206 VariantDef::EnumVariant(e) => {
207 module = e.module(sema.db);
208 let source = e.source(sema.db);
209 def_file_id = source.file_id;
210 let fields = source.value.field_list()?;
211 record_field_list(fields)?
212 }
213 };
214 let def_file_id = def_file_id.original_file(sema.db);
215
216 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
217 if new_field_type.is_unknown() {
218 return None;
219 }
220 let new_field = make::record_field(
221 record_expr_field.field_name()?,
222 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
223 );
224
225 let last_field = record_fields.fields().last()?;
226 let last_field_syntax = last_field.syntax();
227 let indent = IndentLevel::from_node(last_field_syntax);
228
229 let mut new_field = new_field.to_string();
230 if usage_file_id != def_file_id {
231 new_field = format!("pub(crate) {}", new_field);
232 }
233 new_field = format!("\n{}{}", indent, new_field);
234
235 let needs_comma = !last_field_syntax.to_string().ends_with(',');
236 if needs_comma {
237 new_field = format!(",{}", new_field);
238 }
239
240 let source_change = SourceFileEdit {
241 file_id: def_file_id,
242 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
243 };
244 let fix = Fix::new("Create field", source_change.into());
245 return Some(fix);
246
247 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
248 match field_def_list {
249 ast::FieldList::RecordFieldList(it) => Some(it),
250 ast::FieldList::TupleFieldList(_) => None,
251 }
252 } 98 }
253} 99}
254 100
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 @@
1use crate::Fix;
2use ast::{edit::IndentLevel, make};
1use hir::{ 3use 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};
5use ra_syntax::ast; 8use ra_db::FileId;
9use ra_ide_db::{
10 source_change::{FileSystemEdit, SourceFileEdit},
11 RootDatabase,
12};
13use ra_syntax::{algo, ast, AstNode, TextRange};
14use ra_text_edit::{TextEdit, TextEditBuilder};
6 15
7// TODO kb 16// TODO kb
8pub trait DiagnosticWithFix { 17pub 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
13impl DiagnosticWithFix for UnresolvedModule { 21impl 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
21impl DiagnosticWithFix for NoSuchField { 38impl 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
30impl DiagnosticWithFix for MissingFields { 48impl 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
39impl DiagnosticWithFix for MissingOkInTailExpr { 87impl 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
99fn 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}