]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide/src/diagnostics.rs
54c2bcc0942aab887b0cc8e13f98392e65ed0d9e
[rust.git] / crates / ra_ide / src / diagnostics.rs
1 //! Collects diagnostics & fixits  for a single file.
2 //!
3 //! The tricky bit here is that diagnostics are produced by hir in terms of
4 //! macro-expanded files, but we need to present them to the users in terms of
5 //! original files. So we need to map the ranges.
6
7 use std::cell::RefCell;
8
9 use hir::{
10     diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink},
11     Semantics,
12 };
13 use itertools::Itertools;
14 use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt};
15 use ra_ide_db::RootDatabase;
16 use ra_prof::profile;
17 use ra_syntax::{
18     algo,
19     ast::{self, make, AstNode},
20     SyntaxNode, TextRange, T,
21 };
22 use ra_text_edit::{TextEdit, TextEditBuilder};
23
24 use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit};
25
26 #[derive(Debug, Copy, Clone)]
27 pub enum Severity {
28     Error,
29     WeakWarning,
30 }
31
32 pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> {
33     let _p = profile("diagnostics");
34     let sema = Semantics::new(db);
35     let parse = db.parse(file_id);
36     let mut res = Vec::new();
37
38     res.extend(parse.errors().iter().map(|err| Diagnostic {
39         range: err.range(),
40         message: format!("Syntax Error: {}", err),
41         severity: Severity::Error,
42         fix: None,
43     }));
44
45     for node in parse.tree().syntax().descendants() {
46         check_unnecessary_braces_in_use_statement(&mut res, file_id, &node);
47         check_struct_shorthand_initialization(&mut res, file_id, &node);
48     }
49     let res = RefCell::new(res);
50     let mut sink = DiagnosticSink::new(|d| {
51         res.borrow_mut().push(Diagnostic {
52             message: d.message(),
53             range: sema.diagnostics_range(d).range,
54             severity: Severity::Error,
55             fix: None,
56         })
57     })
58     .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
59         let original_file = d.source().file_id.original_file(db);
60         let source_root = db.file_source_root(original_file);
61         let path = db
62             .file_relative_path(original_file)
63             .parent()
64             .unwrap_or_else(|| RelativePath::new(""))
65             .join(&d.candidate);
66         let create_file = FileSystemEdit::CreateFile { source_root, path };
67         let fix = SourceChange::file_system_edit("Create module", create_file);
68         res.borrow_mut().push(Diagnostic {
69             range: sema.diagnostics_range(d).range,
70             message: d.message(),
71             severity: Severity::Error,
72             fix: Some(fix),
73         })
74     })
75     .on::<hir::diagnostics::MissingFields, _>(|d| {
76         // Note that although we could add a diagnostics to
77         // fill the missing tuple field, e.g :
78         // `struct A(usize);`
79         // `let a = A { 0: () }`
80         // but it is uncommon usage and it should not be encouraged.
81         let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
82             None
83         } else {
84             let mut field_list = d.ast(db);
85             for f in d.missed_fields.iter() {
86                 let field =
87                     make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
88                 field_list = field_list.append_field(&field);
89             }
90
91             let mut builder = TextEditBuilder::default();
92             algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
93
94             Some(SourceChange::source_file_edit_from(
95                 "Fill struct fields",
96                 file_id,
97                 builder.finish(),
98             ))
99         };
100
101         res.borrow_mut().push(Diagnostic {
102             range: sema.diagnostics_range(d).range,
103             message: d.message(),
104             severity: Severity::Error,
105             fix,
106         })
107     })
108     .on::<hir::diagnostics::MissingMatchArms, _>(|d| {
109         res.borrow_mut().push(Diagnostic {
110             range: sema.diagnostics_range(d).range,
111             message: d.message(),
112             severity: Severity::Error,
113             fix: None,
114         })
115     })
116     .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
117         let node = d.ast(db);
118         let replacement = format!("Ok({})", node.syntax());
119         let edit = TextEdit::replace(node.syntax().text_range(), replacement);
120         let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit);
121         res.borrow_mut().push(Diagnostic {
122             range: sema.diagnostics_range(d).range,
123             message: d.message(),
124             severity: Severity::Error,
125             fix: Some(fix),
126         })
127     });
128     if let Some(m) = sema.to_module_def(file_id) {
129         m.diagnostics(db, &mut sink);
130     };
131     drop(sink);
132     res.into_inner()
133 }
134
135 fn check_unnecessary_braces_in_use_statement(
136     acc: &mut Vec<Diagnostic>,
137     file_id: FileId,
138     node: &SyntaxNode,
139 ) -> Option<()> {
140     let use_tree_list = ast::UseTreeList::cast(node.clone())?;
141     if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
142         let range = use_tree_list.syntax().text_range();
143         let edit =
144             text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
145                 .unwrap_or_else(|| {
146                     let to_replace = single_use_tree.syntax().text().to_string();
147                     let mut edit_builder = TextEditBuilder::default();
148                     edit_builder.delete(range);
149                     edit_builder.insert(range.start(), to_replace);
150                     edit_builder.finish()
151                 });
152
153         acc.push(Diagnostic {
154             range,
155             message: "Unnecessary braces in use statement".to_string(),
156             severity: Severity::WeakWarning,
157             fix: Some(SourceChange::source_file_edit(
158                 "Remove unnecessary braces",
159                 SourceFileEdit { file_id, edit },
160             )),
161         });
162     }
163
164     Some(())
165 }
166
167 fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
168     single_use_tree: &ast::UseTree,
169 ) -> Option<TextEdit> {
170     let use_tree_list_node = single_use_tree.syntax().parent()?;
171     if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] {
172         let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
173         let end = use_tree_list_node.text_range().end();
174         let range = TextRange::new(start, end);
175         return Some(TextEdit::delete(range));
176     }
177     None
178 }
179
180 fn check_struct_shorthand_initialization(
181     acc: &mut Vec<Diagnostic>,
182     file_id: FileId,
183     node: &SyntaxNode,
184 ) -> Option<()> {
185     let record_lit = ast::RecordLit::cast(node.clone())?;
186     let record_field_list = record_lit.record_field_list()?;
187     for record_field in record_field_list.fields() {
188         if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) {
189             let field_name = name_ref.syntax().text().to_string();
190             let field_expr = expr.syntax().text().to_string();
191             if field_name == field_expr {
192                 let mut edit_builder = TextEditBuilder::default();
193                 edit_builder.delete(record_field.syntax().text_range());
194                 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
195                 let edit = edit_builder.finish();
196
197                 acc.push(Diagnostic {
198                     range: record_field.syntax().text_range(),
199                     message: "Shorthand struct initialization".to_string(),
200                     severity: Severity::WeakWarning,
201                     fix: Some(SourceChange::source_file_edit(
202                         "Use struct shorthand initialization",
203                         SourceFileEdit { file_id, edit },
204                     )),
205                 });
206             }
207         }
208     }
209     Some(())
210 }
211
212 #[cfg(test)]
213 mod tests {
214     use insta::assert_debug_snapshot;
215     use ra_syntax::SourceFile;
216     use stdx::SepBy;
217     use test_utils::assert_eq_text;
218
219     use crate::mock_analysis::{analysis_and_position, single_file};
220
221     use super::*;
222
223     type DiagnosticChecker = fn(&mut Vec<Diagnostic>, FileId, &SyntaxNode) -> Option<()>;
224
225     fn check_not_applicable(code: &str, func: DiagnosticChecker) {
226         let parse = SourceFile::parse(code);
227         let mut diagnostics = Vec::new();
228         for node in parse.tree().syntax().descendants() {
229             func(&mut diagnostics, FileId(0), &node);
230         }
231         assert!(diagnostics.is_empty());
232     }
233
234     fn check_apply(before: &str, after: &str, func: DiagnosticChecker) {
235         let parse = SourceFile::parse(before);
236         let mut diagnostics = Vec::new();
237         for node in parse.tree().syntax().descendants() {
238             func(&mut diagnostics, FileId(0), &node);
239         }
240         let diagnostic =
241             diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
242         let mut fix = diagnostic.fix.unwrap();
243         let edit = fix.source_file_edits.pop().unwrap().edit;
244         let actual = {
245             let mut actual = before.to_string();
246             edit.apply(&mut actual);
247             actual
248         };
249         assert_eq_text!(after, &actual);
250     }
251
252     /// Takes a multi-file input fixture with annotated cursor positions,
253     /// and checks that:
254     ///  * a diagnostic is produced
255     ///  * this diagnostic touches the input cursor position
256     ///  * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
257     fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) {
258         let (analysis, file_position) = analysis_and_position(fixture);
259         let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
260         let mut fix = diagnostic.fix.unwrap();
261         let edit = fix.source_file_edits.pop().unwrap().edit;
262         let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
263         let actual = {
264             let mut actual = target_file_contents.to_string();
265             edit.apply(&mut actual);
266             actual
267         };
268
269         // Strip indent and empty lines from `after`, to match the behaviour of
270         // `parse_fixture` called from `analysis_and_position`.
271         let margin = fixture
272             .lines()
273             .filter(|it| it.trim_start().starts_with("//-"))
274             .map(|it| it.len() - it.trim_start().len())
275             .next()
276             .expect("empty fixture");
277         let after = after
278             .lines()
279             .filter_map(|line| if line.len() > margin { Some(&line[margin..]) } else { None })
280             .sep_by("\n")
281             .suffix("\n")
282             .to_string();
283
284         assert_eq_text!(&after, &actual);
285         assert!(
286             diagnostic.range.start() <= file_position.offset
287                 && diagnostic.range.end() >= file_position.offset,
288             "diagnostic range {:?} does not touch cursor position {:?}",
289             diagnostic.range,
290             file_position.offset
291         );
292     }
293
294     fn check_apply_diagnostic_fix(before: &str, after: &str) {
295         let (analysis, file_id) = single_file(before);
296         let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
297         let mut fix = diagnostic.fix.unwrap();
298         let edit = fix.source_file_edits.pop().unwrap().edit;
299         let actual = {
300             let mut actual = before.to_string();
301             edit.apply(&mut actual);
302             actual
303         };
304         assert_eq_text!(after, &actual);
305     }
306
307     /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
308     /// apply to the file containing the cursor.
309     fn check_no_diagnostic_for_target_file(fixture: &str) {
310         let (analysis, file_position) = analysis_and_position(fixture);
311         let diagnostics = analysis.diagnostics(file_position.file_id).unwrap();
312         assert_eq!(diagnostics.len(), 0);
313     }
314
315     fn check_no_diagnostic(content: &str) {
316         let (analysis, file_id) = single_file(content);
317         let diagnostics = analysis.diagnostics(file_id).unwrap();
318         assert_eq!(diagnostics.len(), 0, "expected no diagnostic, found one");
319     }
320
321     #[test]
322     fn test_wrap_return_type() {
323         let before = r#"
324             //- /main.rs
325             use std::{string::String, result::Result::{self, Ok, Err}};
326
327             fn div(x: i32, y: i32) -> Result<i32, String> {
328                 if y == 0 {
329                     return Err("div by zero".into());
330                 }
331                 x / y<|>
332             }
333
334             //- /std/lib.rs
335             pub mod string {
336                 pub struct String { }
337             }
338             pub mod result {
339                 pub enum Result<T, E> { Ok(T), Err(E) }
340             }
341         "#;
342         let after = r#"
343             use std::{string::String, result::Result::{self, Ok, Err}};
344
345             fn div(x: i32, y: i32) -> Result<i32, String> {
346                 if y == 0 {
347                     return Err("div by zero".into());
348                 }
349                 Ok(x / y)
350             }
351         "#;
352         check_apply_diagnostic_fix_from_position(before, after);
353     }
354
355     #[test]
356     fn test_wrap_return_type_handles_generic_functions() {
357         let before = r#"
358             //- /main.rs
359             use std::result::Result::{self, Ok, Err};
360
361             fn div<T>(x: T) -> Result<T, i32> {
362                 if x == 0 {
363                     return Err(7);
364                 }
365                 <|>x
366             }
367
368             //- /std/lib.rs
369             pub mod result {
370                 pub enum Result<T, E> { Ok(T), Err(E) }
371             }
372         "#;
373         let after = r#"
374             use std::result::Result::{self, Ok, Err};
375
376             fn div<T>(x: T) -> Result<T, i32> {
377                 if x == 0 {
378                     return Err(7);
379                 }
380                 Ok(x)
381             }
382         "#;
383         check_apply_diagnostic_fix_from_position(before, after);
384     }
385
386     #[test]
387     fn test_wrap_return_type_handles_type_aliases() {
388         let before = r#"
389             //- /main.rs
390             use std::{string::String, result::Result::{self, Ok, Err}};
391
392             type MyResult<T> = Result<T, String>;
393
394             fn div(x: i32, y: i32) -> MyResult<i32> {
395                 if y == 0 {
396                     return Err("div by zero".into());
397                 }
398                 x <|>/ y
399             }
400
401             //- /std/lib.rs
402             pub mod string {
403                 pub struct String { }
404             }
405             pub mod result {
406                 pub enum Result<T, E> { Ok(T), Err(E) }
407             }
408         "#;
409         let after = r#"
410             use std::{string::String, result::Result::{self, Ok, Err}};
411
412             type MyResult<T> = Result<T, String>;
413             fn div(x: i32, y: i32) -> MyResult<i32> {
414                 if y == 0 {
415                     return Err("div by zero".into());
416                 }
417                 Ok(x / y)
418             }
419         "#;
420         check_apply_diagnostic_fix_from_position(before, after);
421     }
422
423     #[test]
424     fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
425         let content = r#"
426             //- /main.rs
427             use std::{string::String, result::Result::{self, Ok, Err}};
428
429             fn foo() -> Result<String, i32> {
430                 0<|>
431             }
432
433             //- /std/lib.rs
434             pub mod string {
435                 pub struct String { }
436             }
437             pub mod result {
438                 pub enum Result<T, E> { Ok(T), Err(E) }
439             }
440         "#;
441         check_no_diagnostic_for_target_file(content);
442     }
443
444     #[test]
445     fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
446         let content = r#"
447             //- /main.rs
448             use std::{string::String, result::Result::{self, Ok, Err}};
449
450             enum SomeOtherEnum {
451                 Ok(i32),
452                 Err(String),
453             }
454
455             fn foo() -> SomeOtherEnum {
456                 0<|>
457             }
458
459             //- /std/lib.rs
460             pub mod string {
461                 pub struct String { }
462             }
463             pub mod result {
464                 pub enum Result<T, E> { Ok(T), Err(E) }
465             }
466         "#;
467         check_no_diagnostic_for_target_file(content);
468     }
469
470     #[test]
471     fn test_fill_struct_fields_empty() {
472         let before = r"
473             struct TestStruct {
474                 one: i32,
475                 two: i64,
476             }
477
478             fn test_fn() {
479                 let s = TestStruct{};
480             }
481         ";
482         let after = r"
483             struct TestStruct {
484                 one: i32,
485                 two: i64,
486             }
487
488             fn test_fn() {
489                 let s = TestStruct{ one: (), two: ()};
490             }
491         ";
492         check_apply_diagnostic_fix(before, after);
493     }
494
495     #[test]
496     fn test_fill_struct_fields_self() {
497         let before = r"
498             struct TestStruct {
499                 one: i32,
500             }
501
502             impl TestStruct {
503                 fn test_fn() {
504                     let s = Self {};
505                 }
506             }
507         ";
508         let after = r"
509             struct TestStruct {
510                 one: i32,
511             }
512
513             impl TestStruct {
514                 fn test_fn() {
515                     let s = Self { one: ()};
516                 }
517             }
518         ";
519         check_apply_diagnostic_fix(before, after);
520     }
521
522     #[test]
523     fn test_fill_struct_fields_enum() {
524         let before = r"
525             enum Expr {
526                 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
527             }
528
529             impl Expr {
530                 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
531                     Expr::Bin { <|> }
532                 }
533             }
534
535         ";
536         let after = r"
537             enum Expr {
538                 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
539             }
540
541             impl Expr {
542                 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
543                     Expr::Bin { lhs: (), rhs: () <|> }
544                 }
545             }
546
547         ";
548         check_apply_diagnostic_fix(before, after);
549     }
550
551     #[test]
552     fn test_fill_struct_fields_partial() {
553         let before = r"
554             struct TestStruct {
555                 one: i32,
556                 two: i64,
557             }
558
559             fn test_fn() {
560                 let s = TestStruct{ two: 2 };
561             }
562         ";
563         let after = r"
564             struct TestStruct {
565                 one: i32,
566                 two: i64,
567             }
568
569             fn test_fn() {
570                 let s = TestStruct{ two: 2, one: () };
571             }
572         ";
573         check_apply_diagnostic_fix(before, after);
574     }
575
576     #[test]
577     fn test_fill_struct_fields_no_diagnostic() {
578         let content = r"
579             struct TestStruct {
580                 one: i32,
581                 two: i64,
582             }
583
584             fn test_fn() {
585                 let one = 1;
586                 let s = TestStruct{ one, two: 2 };
587             }
588         ";
589
590         check_no_diagnostic(content);
591     }
592
593     #[test]
594     fn test_fill_struct_fields_no_diagnostic_on_spread() {
595         let content = r"
596             struct TestStruct {
597                 one: i32,
598                 two: i64,
599             }
600
601             fn test_fn() {
602                 let one = 1;
603                 let s = TestStruct{ ..a };
604             }
605         ";
606
607         check_no_diagnostic(content);
608     }
609
610     #[test]
611     fn test_unresolved_module_diagnostic() {
612         let (analysis, file_id) = single_file("mod foo;");
613         let diagnostics = analysis.diagnostics(file_id).unwrap();
614         assert_debug_snapshot!(diagnostics, @r###"
615         [
616             Diagnostic {
617                 message: "unresolved module",
618                 range: 0..8,
619                 fix: Some(
620                     SourceChange {
621                         label: "Create module",
622                         source_file_edits: [],
623                         file_system_edits: [
624                             CreateFile {
625                                 source_root: SourceRootId(
626                                     0,
627                                 ),
628                                 path: "foo.rs",
629                             },
630                         ],
631                         cursor_position: None,
632                         is_snippet: false,
633                     },
634                 ),
635                 severity: Error,
636             },
637         ]
638         "###);
639     }
640
641     #[test]
642     fn range_mapping_out_of_macros() {
643         let (analysis, file_id) = single_file(
644             r"
645             fn some() {}
646             fn items() {}
647             fn here() {}
648
649             macro_rules! id {
650                 ($($tt:tt)*) => { $($tt)*};
651             }
652
653             fn main() {
654                 let _x = id![Foo { a: 42 }];
655             }
656
657             pub struct Foo {
658                 pub a: i32,
659                 pub b: i32,
660             }
661         ",
662         );
663         let diagnostics = analysis.diagnostics(file_id).unwrap();
664         assert_debug_snapshot!(diagnostics, @r###"
665         [
666             Diagnostic {
667                 message: "Missing structure fields:\n- b",
668                 range: 224..233,
669                 fix: Some(
670                     SourceChange {
671                         label: "Fill struct fields",
672                         source_file_edits: [
673                             SourceFileEdit {
674                                 file_id: FileId(
675                                     1,
676                                 ),
677                                 edit: TextEdit {
678                                     indels: [
679                                         Indel {
680                                             insert: "{a:42, b: ()}",
681                                             delete: 3..9,
682                                         },
683                                     ],
684                                 },
685                             },
686                         ],
687                         file_system_edits: [],
688                         cursor_position: None,
689                         is_snippet: false,
690                     },
691                 ),
692                 severity: Error,
693             },
694         ]
695         "###);
696     }
697
698     #[test]
699     fn test_check_unnecessary_braces_in_use_statement() {
700         check_not_applicable(
701             "
702             use a;
703             use a::{c, d::e};
704         ",
705             check_unnecessary_braces_in_use_statement,
706         );
707         check_apply("use {b};", "use b;", check_unnecessary_braces_in_use_statement);
708         check_apply("use a::{c};", "use a::c;", check_unnecessary_braces_in_use_statement);
709         check_apply("use a::{self};", "use a;", check_unnecessary_braces_in_use_statement);
710         check_apply(
711             "use a::{c, d::{e}};",
712             "use a::{c, d::e};",
713             check_unnecessary_braces_in_use_statement,
714         );
715     }
716
717     #[test]
718     fn test_check_struct_shorthand_initialization() {
719         check_not_applicable(
720             r#"
721             struct A {
722                 a: &'static str
723             }
724
725             fn main() {
726                 A {
727                     a: "hello"
728                 }
729             }
730         "#,
731             check_struct_shorthand_initialization,
732         );
733
734         check_apply(
735             r#"
736 struct A {
737     a: &'static str
738 }
739
740 fn main() {
741     let a = "haha";
742     A {
743         a: a
744     }
745 }
746         "#,
747             r#"
748 struct A {
749     a: &'static str
750 }
751
752 fn main() {
753     let a = "haha";
754     A {
755         a
756     }
757 }
758         "#,
759             check_struct_shorthand_initialization,
760         );
761
762         check_apply(
763             r#"
764 struct A {
765     a: &'static str,
766     b: &'static str
767 }
768
769 fn main() {
770     let a = "haha";
771     let b = "bb";
772     A {
773         a: a,
774         b
775     }
776 }
777         "#,
778             r#"
779 struct A {
780     a: &'static str,
781     b: &'static str
782 }
783
784 fn main() {
785     let a = "haha";
786     let b = "bb";
787     A {
788         a,
789         b
790     }
791 }
792         "#,
793             check_struct_shorthand_initialization,
794         );
795     }
796 }