diff options
Diffstat (limited to 'crates/ra_ide/src/diagnostics.rs')
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 98 |
1 files changed, 96 insertions, 2 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index e1bfd72f9..365d52168 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -8,7 +8,7 @@ use std::cell::RefCell; | |||
8 | 8 | ||
9 | use hir::{ | 9 | use hir::{ |
10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, | 10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, |
11 | Semantics, | 11 | HasSource, HirDisplay, Semantics, VariantDef, |
12 | }; | 12 | }; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
14 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; | 14 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; |
@@ -16,7 +16,7 @@ use ra_ide_db::RootDatabase; | |||
16 | use ra_prof::profile; | 16 | use ra_prof::profile; |
17 | use ra_syntax::{ | 17 | use ra_syntax::{ |
18 | algo, | 18 | algo, |
19 | ast::{self, make, AstNode}, | 19 | ast::{self, edit::IndentLevel, make, AstNode}, |
20 | SyntaxNode, TextRange, T, | 20 | SyntaxNode, TextRange, T, |
21 | }; | 21 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
@@ -123,7 +123,16 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
123 | severity: Severity::Error, | 123 | severity: Severity::Error, |
124 | fix: Some(fix), | 124 | fix: Some(fix), |
125 | }) | 125 | }) |
126 | }) | ||
127 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | ||
128 | res.borrow_mut().push(Diagnostic { | ||
129 | range: sema.diagnostics_range(d).range, | ||
130 | message: d.message(), | ||
131 | severity: Severity::Error, | ||
132 | fix: missing_struct_field_fix(&sema, file_id, d), | ||
133 | }) | ||
126 | }); | 134 | }); |
135 | |||
127 | if let Some(m) = sema.to_module_def(file_id) { | 136 | if let Some(m) = sema.to_module_def(file_id) { |
128 | m.diagnostics(db, &mut sink); | 137 | m.diagnostics(db, &mut sink); |
129 | }; | 138 | }; |
@@ -131,6 +140,68 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
131 | res.into_inner() | 140 | res.into_inner() |
132 | } | 141 | } |
133 | 142 | ||
143 | fn missing_struct_field_fix( | ||
144 | sema: &Semantics<RootDatabase>, | ||
145 | file_id: FileId, | ||
146 | d: &hir::diagnostics::NoSuchField, | ||
147 | ) -> Option<Fix> { | ||
148 | let record_expr = sema.ast(d); | ||
149 | |||
150 | let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?; | ||
151 | let def_id = sema.resolve_variant(record_lit)?; | ||
152 | let module; | ||
153 | let record_fields = match VariantDef::from(def_id) { | ||
154 | VariantDef::Struct(s) => { | ||
155 | module = s.module(sema.db); | ||
156 | let source = s.source(sema.db); | ||
157 | let fields = source.value.field_def_list()?; | ||
158 | record_field_def_list(fields)? | ||
159 | } | ||
160 | VariantDef::Union(u) => { | ||
161 | module = u.module(sema.db); | ||
162 | let source = u.source(sema.db); | ||
163 | source.value.record_field_def_list()? | ||
164 | } | ||
165 | VariantDef::EnumVariant(e) => { | ||
166 | module = e.module(sema.db); | ||
167 | let source = e.source(sema.db); | ||
168 | let fields = source.value.field_def_list()?; | ||
169 | record_field_def_list(fields)? | ||
170 | } | ||
171 | }; | ||
172 | |||
173 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; | ||
174 | let new_field = make::record_field_def( | ||
175 | record_expr.field_name()?, | ||
176 | make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
177 | ); | ||
178 | |||
179 | let last_field = record_fields.fields().last()?; | ||
180 | let last_field_syntax = last_field.syntax(); | ||
181 | let indent = IndentLevel::from_node(last_field_syntax); | ||
182 | |||
183 | let mut new_field = format!("\n{}{}", indent, new_field); | ||
184 | |||
185 | let needs_comma = !last_field_syntax.to_string().ends_with(","); | ||
186 | if needs_comma { | ||
187 | new_field = format!(",{}", new_field); | ||
188 | } | ||
189 | |||
190 | let source_change = SourceFileEdit { | ||
191 | file_id, | ||
192 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
193 | }; | ||
194 | let fix = Fix::new("Create field", source_change.into()); | ||
195 | return Some(fix); | ||
196 | |||
197 | fn record_field_def_list(field_def_list: ast::FieldDefList) -> Option<ast::RecordFieldDefList> { | ||
198 | match field_def_list { | ||
199 | ast::FieldDefList::RecordFieldDefList(it) => Some(it), | ||
200 | ast::FieldDefList::TupleFieldDefList(_) => None, | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | |||
134 | fn check_unnecessary_braces_in_use_statement( | 205 | fn check_unnecessary_braces_in_use_statement( |
135 | acc: &mut Vec<Diagnostic>, | 206 | acc: &mut Vec<Diagnostic>, |
136 | file_id: FileId, | 207 | file_id: FileId, |
@@ -795,4 +866,27 @@ fn main() { | |||
795 | check_struct_shorthand_initialization, | 866 | check_struct_shorthand_initialization, |
796 | ); | 867 | ); |
797 | } | 868 | } |
869 | |||
870 | #[test] | ||
871 | fn test_add_field_from_usage() { | ||
872 | check_apply_diagnostic_fix( | ||
873 | r" | ||
874 | fn main() { | ||
875 | Foo { bar: 3, baz: false}; | ||
876 | } | ||
877 | struct Foo { | ||
878 | bar: i32 | ||
879 | } | ||
880 | ", | ||
881 | r" | ||
882 | fn main() { | ||
883 | Foo { bar: 3, baz: false}; | ||
884 | } | ||
885 | struct Foo { | ||
886 | bar: i32, | ||
887 | baz: bool | ||
888 | } | ||
889 | ", | ||
890 | ) | ||
891 | } | ||
798 | } | 892 | } |