aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/diagnostics.rs348
-rw-r--r--crates/ra_ide/src/mock_analysis.rs20
2 files changed, 131 insertions, 237 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index c1cbfcc2c..00f6bb186 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -281,43 +281,11 @@ fn check_struct_shorthand_initialization(
281 281
282#[cfg(test)] 282#[cfg(test)]
283mod tests { 283mod tests {
284 use insta::assert_debug_snapshot;
285 use ra_syntax::SourceFile;
286 use stdx::trim_indent; 284 use stdx::trim_indent;
287 use test_utils::assert_eq_text; 285 use test_utils::assert_eq_text;
288 286
289 use crate::mock_analysis::{analysis_and_position, single_file}; 287 use crate::mock_analysis::{analysis_and_position, single_file, MockAnalysis};
290 288 use expect::{expect, Expect};
291 use super::*;
292
293 type DiagnosticChecker = fn(&mut Vec<Diagnostic>, FileId, &SyntaxNode) -> Option<()>;
294
295 fn check_not_applicable(code: &str, func: DiagnosticChecker) {
296 let parse = SourceFile::parse(code);
297 let mut diagnostics = Vec::new();
298 for node in parse.tree().syntax().descendants() {
299 func(&mut diagnostics, FileId(0), &node);
300 }
301 assert!(diagnostics.is_empty());
302 }
303
304 fn check_apply(before: &str, after: &str, func: DiagnosticChecker) {
305 let parse = SourceFile::parse(before);
306 let mut diagnostics = Vec::new();
307 for node in parse.tree().syntax().descendants() {
308 func(&mut diagnostics, FileId(0), &node);
309 }
310 let diagnostic =
311 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
312 let mut fix = diagnostic.fix.unwrap();
313 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
314 let actual = {
315 let mut actual = before.to_string();
316 edit.apply(&mut actual);
317 actual
318 };
319 assert_eq_text!(after, &actual);
320 }
321 289
322 /// Takes a multi-file input fixture with annotated cursor positions, 290 /// Takes a multi-file input fixture with annotated cursor positions,
323 /// and checks that: 291 /// and checks that:
@@ -350,16 +318,21 @@ mod tests {
350 318
351 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics 319 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
352 /// apply to the file containing the cursor. 320 /// apply to the file containing the cursor.
353 fn check_no_diagnostic_for_target_file(ra_fixture: &str) { 321 fn check_no_diagnostics(ra_fixture: &str) {
354 let (analysis, file_position) = analysis_and_position(ra_fixture); 322 let mock = MockAnalysis::with_files(ra_fixture);
355 let diagnostics = analysis.diagnostics(file_position.file_id).unwrap(); 323 let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>();
356 assert_eq!(diagnostics.len(), 0); 324 let analysis = mock.analysis();
357 } 325 let diagnostics = files
358 326 .into_iter()
359 fn check_no_diagnostic(ra_fixture: &str) { 327 .flat_map(|file_id| analysis.diagnostics(file_id).unwrap())
328 .collect::<Vec<_>>();
329 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
330 }
331
332 fn check_expect(ra_fixture: &str, expect: Expect) {
360 let (analysis, file_id) = single_file(ra_fixture); 333 let (analysis, file_id) = single_file(ra_fixture);
361 let diagnostics = analysis.diagnostics(file_id).unwrap(); 334 let diagnostics = analysis.diagnostics(file_id).unwrap();
362 assert_eq!(diagnostics.len(), 0, "expected no diagnostic, found one"); 335 expect.assert_debug_eq(&diagnostics)
363 } 336 }
364 337
365 #[test] 338 #[test]
@@ -397,7 +370,7 @@ fn div(x: i32, y: i32) -> Result<i32, ()> {
397 fn test_wrap_return_type_handles_generic_functions() { 370 fn test_wrap_return_type_handles_generic_functions() {
398 check_fix( 371 check_fix(
399 r#" 372 r#"
400 //- /main.rs 373//- /main.rs
401use core::result::Result::{self, Ok, Err}; 374use core::result::Result::{self, Ok, Err};
402 375
403fn div<T>(x: T) -> Result<T, i32> { 376fn div<T>(x: T) -> Result<T, i32> {
@@ -461,44 +434,37 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
461 434
462 #[test] 435 #[test]
463 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { 436 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
464 check_no_diagnostic_for_target_file( 437 check_no_diagnostics(
465 r" 438 r#"
466 //- /main.rs 439//- /main.rs
467 use core::result::Result::{self, Ok, Err}; 440use core::result::Result::{self, Ok, Err};
468 441
469 fn foo() -> Result<(), i32> { 442fn foo() -> Result<(), i32> { 0 }
470 0<|>
471 }
472 443
473 //- /core/lib.rs 444//- /core/lib.rs
474 pub mod result { 445pub mod result {
475 pub enum Result<T, E> { Ok(T), Err(E) } 446 pub enum Result<T, E> { Ok(T), Err(E) }
476 } 447}
477 ", 448"#,
478 ); 449 );
479 } 450 }
480 451
481 #[test] 452 #[test]
482 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { 453 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
483 check_no_diagnostic_for_target_file( 454 check_no_diagnostics(
484 r" 455 r#"
485 //- /main.rs 456//- /main.rs
486 use core::result::Result::{self, Ok, Err}; 457use core::result::Result::{self, Ok, Err};
487 458
488 enum SomeOtherEnum { 459enum SomeOtherEnum { Ok(i32), Err(String) }
489 Ok(i32),
490 Err(String),
491 }
492 460
493 fn foo() -> SomeOtherEnum { 461fn foo() -> SomeOtherEnum { 0 }
494 0<|>
495 }
496 462
497 //- /core/lib.rs 463//- /core/lib.rs
498 pub mod result { 464pub mod result {
499 pub enum Result<T, E> { Ok(T), Err(E) } 465 pub enum Result<T, E> { Ok(T), Err(E) }
500 } 466}
501 ", 467"#,
502 ); 468 );
503 } 469 }
504 470
@@ -592,7 +558,7 @@ fn test_fn() {
592 558
593 #[test] 559 #[test]
594 fn test_fill_struct_fields_no_diagnostic() { 560 fn test_fill_struct_fields_no_diagnostic() {
595 check_no_diagnostic( 561 check_no_diagnostics(
596 r" 562 r"
597 struct TestStruct { one: i32, two: i64 } 563 struct TestStruct { one: i32, two: i64 }
598 564
@@ -606,7 +572,7 @@ fn test_fn() {
606 572
607 #[test] 573 #[test]
608 fn test_fill_struct_fields_no_diagnostic_on_spread() { 574 fn test_fill_struct_fields_no_diagnostic_on_spread() {
609 check_no_diagnostic( 575 check_no_diagnostics(
610 r" 576 r"
611 struct TestStruct { one: i32, two: i64 } 577 struct TestStruct { one: i32, two: i64 }
612 578
@@ -620,202 +586,134 @@ fn test_fn() {
620 586
621 #[test] 587 #[test]
622 fn test_unresolved_module_diagnostic() { 588 fn test_unresolved_module_diagnostic() {
623 let (analysis, file_id) = single_file("mod foo;"); 589 check_expect(
624 let diagnostics = analysis.diagnostics(file_id).unwrap(); 590 r#"mod foo;"#,
625 assert_debug_snapshot!(diagnostics, @r###" 591 expect![[r#"
626 [ 592 [
627 Diagnostic { 593 Diagnostic {
628 message: "unresolved module", 594 message: "unresolved module",
629 range: 0..8, 595 range: 0..8,
630 severity: Error, 596 severity: Error,
631 fix: Some( 597 fix: Some(
632 Fix { 598 Fix {
633 label: "Create module", 599 label: "Create module",
634 source_change: SourceChange { 600 source_change: SourceChange {
635 source_file_edits: [], 601 source_file_edits: [],
636 file_system_edits: [ 602 file_system_edits: [
637 CreateFile { 603 CreateFile {
638 anchor: FileId( 604 anchor: FileId(
639 1, 605 1,
640 ), 606 ),
641 dst: "foo.rs", 607 dst: "foo.rs",
608 },
609 ],
610 is_snippet: false,
642 }, 611 },
643 ], 612 },
644 is_snippet: false, 613 ),
645 },
646 }, 614 },
647 ), 615 ]
648 }, 616 "#]],
649 ] 617 );
650 "###);
651 } 618 }
652 619
653 #[test] 620 #[test]
654 fn range_mapping_out_of_macros() { 621 fn range_mapping_out_of_macros() {
655 let (analysis, file_id) = single_file( 622 // FIXME: this is very wrong, but somewhat tricky to fix.
656 r" 623 check_fix(
657 fn some() {} 624 r#"
658 fn items() {} 625fn some() {}
659 fn here() {} 626fn items() {}
627fn here() {}
660 628
661 macro_rules! id { 629macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
662 ($($tt:tt)*) => { $($tt)*};
663 }
664 630
665 fn main() { 631fn main() {
666 let _x = id![Foo { a: 42 }]; 632 let _x = id![Foo { a: <|>42 }];
667 } 633}
668 634
669 pub struct Foo { 635pub struct Foo { pub a: i32, pub b: i32 }
670 pub a: i32, 636"#,
671 pub b: i32, 637 r#"
672 } 638fn {a:42, b: ()} {}
673 ", 639fn items() {}
640fn here() {}
641
642macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
643
644fn main() {
645 let _x = id![Foo { a: 42 }];
646}
647
648pub struct Foo { pub a: i32, pub b: i32 }
649"#,
674 ); 650 );
675 let diagnostics = analysis.diagnostics(file_id).unwrap();
676 assert_debug_snapshot!(diagnostics, @r###"
677 [
678 Diagnostic {
679 message: "Missing structure fields:\n- b\n",
680 range: 127..136,
681 severity: Error,
682 fix: Some(
683 Fix {
684 label: "Fill struct fields",
685 source_change: SourceChange {
686 source_file_edits: [
687 SourceFileEdit {
688 file_id: FileId(
689 1,
690 ),
691 edit: TextEdit {
692 indels: [
693 Indel {
694 insert: "{a:42, b: ()}",
695 delete: 3..9,
696 },
697 ],
698 },
699 },
700 ],
701 file_system_edits: [],
702 is_snippet: false,
703 },
704 },
705 ),
706 },
707 ]
708 "###);
709 } 651 }
710 652
711 #[test] 653 #[test]
712 fn test_check_unnecessary_braces_in_use_statement() { 654 fn test_check_unnecessary_braces_in_use_statement() {
713 check_not_applicable( 655 check_no_diagnostics(
714 " 656 r#"
715 use a; 657use a;
716 use a::{c, d::e}; 658use a::{c, d::e};
717 ", 659"#,
718 check_unnecessary_braces_in_use_statement,
719 );
720 check_apply("use {b};", "use b;", check_unnecessary_braces_in_use_statement);
721 check_apply("use a::{c};", "use a::c;", check_unnecessary_braces_in_use_statement);
722 check_apply("use a::{self};", "use a;", check_unnecessary_braces_in_use_statement);
723 check_apply(
724 "use a::{c, d::{e}};",
725 "use a::{c, d::e};",
726 check_unnecessary_braces_in_use_statement,
727 ); 660 );
661 check_fix(r#"use {<|>b};"#, r#"use b;"#);
662 check_fix(r#"use {b<|>};"#, r#"use b;"#);
663 check_fix(r#"use a::{c<|>};"#, r#"use a::c;"#);
664 check_fix(r#"use a::{self<|>};"#, r#"use a;"#);
665 check_fix(r#"use a::{c, d::{e<|>}};"#, r#"use a::{c, d::e};"#);
728 } 666 }
729 667
730 #[test] 668 #[test]
731 fn test_check_struct_shorthand_initialization() { 669 fn test_check_struct_shorthand_initialization() {
732 check_not_applicable( 670 check_no_diagnostics(
733 r#" 671 r#"
734 struct A { 672struct A { a: &'static str }
735 a: &'static str 673fn main() { A { a: "hello" } }
736 } 674"#,
737
738 fn main() {
739 A {
740 a: "hello"
741 }
742 }
743 "#,
744 check_struct_shorthand_initialization,
745 ); 675 );
746 check_not_applicable( 676 check_no_diagnostics(
747 r#" 677 r#"
748 struct A(usize); 678struct A(usize);
749 679fn main() { A { 0: 0 } }
750 fn main() { 680"#,
751 A {
752 0: 0
753 }
754 }
755 "#,
756 check_struct_shorthand_initialization,
757 ); 681 );
758 682
759 check_apply( 683 check_fix(
760 r#" 684 r#"
761struct A { 685struct A { a: &'static str }
762 a: &'static str
763}
764
765fn main() { 686fn main() {
766 let a = "haha"; 687 let a = "haha";
767 A { 688 A { a<|>: a }
768 a: a
769 }
770} 689}
771 "#, 690"#,
772 r#" 691 r#"
773struct A { 692struct A { a: &'static str }
774 a: &'static str
775}
776
777fn main() { 693fn main() {
778 let a = "haha"; 694 let a = "haha";
779 A { 695 A { a }
780 a
781 }
782} 696}
783 "#, 697"#,
784 check_struct_shorthand_initialization,
785 ); 698 );
786 699
787 check_apply( 700 check_fix(
788 r#" 701 r#"
789struct A { 702struct A { a: &'static str, b: &'static str }
790 a: &'static str,
791 b: &'static str
792}
793
794fn main() { 703fn main() {
795 let a = "haha"; 704 let a = "haha";
796 let b = "bb"; 705 let b = "bb";
797 A { 706 A { a<|>: a, b }
798 a: a,
799 b
800 }
801} 707}
802 "#, 708"#,
803 r#" 709 r#"
804struct A { 710struct A { a: &'static str, b: &'static str }
805 a: &'static str,
806 b: &'static str
807}
808
809fn main() { 711fn main() {
810 let a = "haha"; 712 let a = "haha";
811 let b = "bb"; 713 let b = "bb";
812 A { 714 A { a, b }
813 a,
814 b
815 }
816} 715}
817 "#, 716"#,
818 check_struct_shorthand_initialization,
819 ); 717 );
820 } 718 }
821 719
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index a393d3dba..b28054688 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -71,20 +71,13 @@ impl MockAnalysis {
71 } 71 }
72 72
73 pub fn id_of(&self, path: &str) -> FileId { 73 pub fn id_of(&self, path: &str) -> FileId {
74 let (idx, _) = self 74 let (file_id, _) =
75 .files 75 self.files().find(|(_, data)| path == data.path).expect("no file in this mock");
76 .iter() 76 file_id
77 .enumerate()
78 .find(|(_, data)| path == data.path)
79 .expect("no file in this mock");
80 FileId(idx as u32 + 1)
81 } 77 }
82 pub fn annotations(&self) -> Vec<(FileRange, String)> { 78 pub fn annotations(&self) -> Vec<(FileRange, String)> {
83 self.files 79 self.files()
84 .iter() 80 .flat_map(|(file_id, fixture)| {
85 .enumerate()
86 .flat_map(|(idx, fixture)| {
87 let file_id = FileId(idx as u32 + 1);
88 let annotations = extract_annotations(&fixture.text); 81 let annotations = extract_annotations(&fixture.text);
89 annotations 82 annotations
90 .into_iter() 83 .into_iter()
@@ -92,6 +85,9 @@ impl MockAnalysis {
92 }) 85 })
93 .collect() 86 .collect()
94 } 87 }
88 pub fn files(&self) -> impl Iterator<Item = (FileId, &Fixture)> + '_ {
89 self.files.iter().enumerate().map(|(idx, fixture)| (FileId(idx as u32 + 1), fixture))
90 }
95 pub fn annotation(&self) -> (FileRange, String) { 91 pub fn annotation(&self) -> (FileRange, String) {
96 let mut all = self.annotations(); 92 let mut all = self.annotations();
97 assert_eq!(all.len(), 1); 93 assert_eq!(all.len(), 1);