diff options
Diffstat (limited to 'crates/ra_ide/src/diagnostics.rs')
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 140 |
1 files changed, 111 insertions, 29 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index fd9abb55b..05fb799d6 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::SourceDatabase; | 14 | use ra_db::SourceDatabase; |
@@ -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}; |
@@ -119,7 +119,16 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
119 | severity: Severity::Error, | 119 | severity: Severity::Error, |
120 | fix: Some(fix), | 120 | fix: Some(fix), |
121 | }) | 121 | }) |
122 | }) | ||
123 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | ||
124 | res.borrow_mut().push(Diagnostic { | ||
125 | range: sema.diagnostics_range(d).range, | ||
126 | message: d.message(), | ||
127 | severity: Severity::Error, | ||
128 | fix: missing_struct_field_fix(&sema, file_id, d), | ||
129 | }) | ||
122 | }); | 130 | }); |
131 | |||
123 | if let Some(m) = sema.to_module_def(file_id) { | 132 | if let Some(m) = sema.to_module_def(file_id) { |
124 | m.diagnostics(db, &mut sink); | 133 | m.diagnostics(db, &mut sink); |
125 | }; | 134 | }; |
@@ -127,6 +136,71 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
127 | res.into_inner() | 136 | res.into_inner() |
128 | } | 137 | } |
129 | 138 | ||
139 | fn missing_struct_field_fix( | ||
140 | sema: &Semantics<RootDatabase>, | ||
141 | file_id: FileId, | ||
142 | d: &hir::diagnostics::NoSuchField, | ||
143 | ) -> Option<Fix> { | ||
144 | let record_expr = sema.ast(d); | ||
145 | |||
146 | let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?; | ||
147 | let def_id = sema.resolve_variant(record_lit)?; | ||
148 | let module; | ||
149 | let record_fields = match VariantDef::from(def_id) { | ||
150 | VariantDef::Struct(s) => { | ||
151 | module = s.module(sema.db); | ||
152 | let source = s.source(sema.db); | ||
153 | let fields = source.value.field_def_list()?; | ||
154 | record_field_def_list(fields)? | ||
155 | } | ||
156 | VariantDef::Union(u) => { | ||
157 | module = u.module(sema.db); | ||
158 | let source = u.source(sema.db); | ||
159 | source.value.record_field_def_list()? | ||
160 | } | ||
161 | VariantDef::EnumVariant(e) => { | ||
162 | module = e.module(sema.db); | ||
163 | let source = e.source(sema.db); | ||
164 | let fields = source.value.field_def_list()?; | ||
165 | record_field_def_list(fields)? | ||
166 | } | ||
167 | }; | ||
168 | |||
169 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; | ||
170 | if new_field_type.is_unknown() { | ||
171 | return None; | ||
172 | } | ||
173 | let new_field = make::record_field_def( | ||
174 | record_expr.field_name()?, | ||
175 | make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
176 | ); | ||
177 | |||
178 | let last_field = record_fields.fields().last()?; | ||
179 | let last_field_syntax = last_field.syntax(); | ||
180 | let indent = IndentLevel::from_node(last_field_syntax); | ||
181 | |||
182 | let mut new_field = format!("\n{}{}", indent, new_field); | ||
183 | |||
184 | let needs_comma = !last_field_syntax.to_string().ends_with(","); | ||
185 | if needs_comma { | ||
186 | new_field = format!(",{}", new_field); | ||
187 | } | ||
188 | |||
189 | let source_change = SourceFileEdit { | ||
190 | file_id, | ||
191 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
192 | }; | ||
193 | let fix = Fix::new("Create field", source_change.into()); | ||
194 | return Some(fix); | ||
195 | |||
196 | fn record_field_def_list(field_def_list: ast::FieldDefList) -> Option<ast::RecordFieldDefList> { | ||
197 | match field_def_list { | ||
198 | ast::FieldDefList::RecordFieldDefList(it) => Some(it), | ||
199 | ast::FieldDefList::TupleFieldDefList(_) => None, | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | |||
130 | fn check_unnecessary_braces_in_use_statement( | 204 | fn check_unnecessary_braces_in_use_statement( |
131 | acc: &mut Vec<Diagnostic>, | 205 | acc: &mut Vec<Diagnostic>, |
132 | file_id: FileId, | 206 | file_id: FileId, |
@@ -209,7 +283,7 @@ fn check_struct_shorthand_initialization( | |||
209 | mod tests { | 283 | mod tests { |
210 | use insta::assert_debug_snapshot; | 284 | use insta::assert_debug_snapshot; |
211 | use ra_syntax::SourceFile; | 285 | use ra_syntax::SourceFile; |
212 | use stdx::SepBy; | 286 | use stdx::trim_indent; |
213 | use test_utils::assert_eq_text; | 287 | use test_utils::assert_eq_text; |
214 | 288 | ||
215 | use crate::mock_analysis::{analysis_and_position, single_file}; | 289 | use crate::mock_analysis::{analysis_and_position, single_file}; |
@@ -251,6 +325,8 @@ mod tests { | |||
251 | /// * this diagnostic touches the input cursor position | 325 | /// * this diagnostic touches the input cursor position |
252 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 326 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
253 | fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) { | 327 | fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) { |
328 | let after = trim_indent(after); | ||
329 | |||
254 | let (analysis, file_position) = analysis_and_position(fixture); | 330 | let (analysis, file_position) = analysis_and_position(fixture); |
255 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); | 331 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); |
256 | let mut fix = diagnostic.fix.unwrap(); | 332 | let mut fix = diagnostic.fix.unwrap(); |
@@ -262,21 +338,6 @@ mod tests { | |||
262 | actual | 338 | actual |
263 | }; | 339 | }; |
264 | 340 | ||
265 | // Strip indent and empty lines from `after`, to match the behaviour of | ||
266 | // `parse_fixture` called from `analysis_and_position`. | ||
267 | let margin = fixture | ||
268 | .lines() | ||
269 | .filter(|it| it.trim_start().starts_with("//-")) | ||
270 | .map(|it| it.len() - it.trim_start().len()) | ||
271 | .next() | ||
272 | .expect("empty fixture"); | ||
273 | let after = after | ||
274 | .lines() | ||
275 | .filter_map(|line| if line.len() > margin { Some(&line[margin..]) } else { None }) | ||
276 | .sep_by("\n") | ||
277 | .suffix("\n") | ||
278 | .to_string(); | ||
279 | |||
280 | assert_eq_text!(&after, &actual); | 341 | assert_eq_text!(&after, &actual); |
281 | assert!( | 342 | assert!( |
282 | diagnostic.range.start() <= file_position.offset | 343 | diagnostic.range.start() <= file_position.offset |
@@ -287,8 +348,10 @@ mod tests { | |||
287 | ); | 348 | ); |
288 | } | 349 | } |
289 | 350 | ||
290 | fn check_apply_diagnostic_fix(before: &str, after: &str) { | 351 | fn check_apply_diagnostic_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
291 | let (analysis, file_id) = single_file(before); | 352 | let ra_fixture_after = &trim_indent(ra_fixture_after); |
353 | let (analysis, file_id) = single_file(ra_fixture_before); | ||
354 | let before = analysis.file_text(file_id).unwrap(); | ||
292 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); | 355 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); |
293 | let mut fix = diagnostic.fix.unwrap(); | 356 | let mut fix = diagnostic.fix.unwrap(); |
294 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; | 357 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
@@ -297,7 +360,7 @@ mod tests { | |||
297 | edit.apply(&mut actual); | 360 | edit.apply(&mut actual); |
298 | actual | 361 | actual |
299 | }; | 362 | }; |
300 | assert_eq_text!(after, &actual); | 363 | assert_eq_text!(ra_fixture_after, &actual); |
301 | } | 364 | } |
302 | 365 | ||
303 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | 366 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics |
@@ -326,7 +389,6 @@ mod tests { | |||
326 | } | 389 | } |
327 | x / y<|> | 390 | x / y<|> |
328 | } | 391 | } |
329 | |||
330 | //- /core/lib.rs | 392 | //- /core/lib.rs |
331 | pub mod result { | 393 | pub mod result { |
332 | pub enum Result<T, E> { Ok(T), Err(E) } | 394 | pub enum Result<T, E> { Ok(T), Err(E) } |
@@ -357,7 +419,6 @@ mod tests { | |||
357 | } | 419 | } |
358 | <|>x | 420 | <|>x |
359 | } | 421 | } |
360 | |||
361 | //- /core/lib.rs | 422 | //- /core/lib.rs |
362 | pub mod result { | 423 | pub mod result { |
363 | pub enum Result<T, E> { Ok(T), Err(E) } | 424 | pub enum Result<T, E> { Ok(T), Err(E) } |
@@ -390,7 +451,6 @@ mod tests { | |||
390 | } | 451 | } |
391 | x <|>/ y | 452 | x <|>/ y |
392 | } | 453 | } |
393 | |||
394 | //- /core/lib.rs | 454 | //- /core/lib.rs |
395 | pub mod result { | 455 | pub mod result { |
396 | pub enum Result<T, E> { Ok(T), Err(E) } | 456 | pub enum Result<T, E> { Ok(T), Err(E) } |
@@ -400,6 +460,7 @@ mod tests { | |||
400 | use core::result::Result::{self, Ok, Err}; | 460 | use core::result::Result::{self, Ok, Err}; |
401 | 461 | ||
402 | type MyResult<T> = Result<T, ()>; | 462 | type MyResult<T> = Result<T, ()>; |
463 | |||
403 | fn div(x: i32, y: i32) -> MyResult<i32> { | 464 | fn div(x: i32, y: i32) -> MyResult<i32> { |
404 | if y == 0 { | 465 | if y == 0 { |
405 | return Err(()); | 466 | return Err(()); |
@@ -512,10 +573,9 @@ mod tests { | |||
512 | 573 | ||
513 | impl Expr { | 574 | impl Expr { |
514 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | 575 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { |
515 | Expr::Bin { <|> } | 576 | Expr::Bin { } |
516 | } | 577 | } |
517 | } | 578 | } |
518 | |||
519 | "; | 579 | "; |
520 | let after = r" | 580 | let after = r" |
521 | enum Expr { | 581 | enum Expr { |
@@ -524,10 +584,9 @@ mod tests { | |||
524 | 584 | ||
525 | impl Expr { | 585 | impl Expr { |
526 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | 586 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { |
527 | Expr::Bin { lhs: (), rhs: () <|> } | 587 | Expr::Bin { lhs: (), rhs: () } |
528 | } | 588 | } |
529 | } | 589 | } |
530 | |||
531 | "; | 590 | "; |
532 | check_apply_diagnostic_fix(before, after); | 591 | check_apply_diagnostic_fix(before, after); |
533 | } | 592 | } |
@@ -650,7 +709,7 @@ mod tests { | |||
650 | [ | 709 | [ |
651 | Diagnostic { | 710 | Diagnostic { |
652 | message: "Missing structure fields:\n- b\n", | 711 | message: "Missing structure fields:\n- b\n", |
653 | range: 224..233, | 712 | range: 127..136, |
654 | severity: Error, | 713 | severity: Error, |
655 | fix: Some( | 714 | fix: Some( |
656 | Fix { | 715 | Fix { |
@@ -791,4 +850,27 @@ fn main() { | |||
791 | check_struct_shorthand_initialization, | 850 | check_struct_shorthand_initialization, |
792 | ); | 851 | ); |
793 | } | 852 | } |
853 | |||
854 | #[test] | ||
855 | fn test_add_field_from_usage() { | ||
856 | check_apply_diagnostic_fix( | ||
857 | r" | ||
858 | fn main() { | ||
859 | Foo { bar: 3, baz: false}; | ||
860 | } | ||
861 | struct Foo { | ||
862 | bar: i32 | ||
863 | } | ||
864 | ", | ||
865 | r" | ||
866 | fn main() { | ||
867 | Foo { bar: 3, baz: false}; | ||
868 | } | ||
869 | struct Foo { | ||
870 | bar: i32, | ||
871 | baz: bool | ||
872 | } | ||
873 | ", | ||
874 | ) | ||
875 | } | ||
794 | } | 876 | } |