aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/diagnostics.rs')
-rw-r--r--crates/ra_ide/src/diagnostics.rs140
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
9use hir::{ 9use hir::{
10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, 10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink},
11 Semantics, 11 HasSource, HirDisplay, Semantics, VariantDef,
12}; 12};
13use itertools::Itertools; 13use itertools::Itertools;
14use ra_db::SourceDatabase; 14use ra_db::SourceDatabase;
@@ -16,7 +16,7 @@ use ra_ide_db::RootDatabase;
16use ra_prof::profile; 16use ra_prof::profile;
17use ra_syntax::{ 17use 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};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use 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
139fn 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
130fn check_unnecessary_braces_in_use_statement( 204fn 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(
209mod tests { 283mod 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"
858fn main() {
859 Foo { bar: 3, baz: false};
860}
861struct Foo {
862 bar: i32
863}
864",
865 r"
866fn main() {
867 Foo { bar: 3, baz: false};
868}
869struct Foo {
870 bar: i32,
871 baz: bool
872}
873",
874 )
875 }
794} 876}