diff options
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 348 | ||||
-rw-r--r-- | crates/ra_ide/src/mock_analysis.rs | 20 |
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)] |
283 | mod tests { | 283 | mod 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 |
401 | use core::result::Result::{self, Ok, Err}; | 374 | use core::result::Result::{self, Ok, Err}; |
402 | 375 | ||
403 | fn div<T>(x: T) -> Result<T, i32> { | 376 | fn 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}; | 440 | use core::result::Result::{self, Ok, Err}; |
468 | 441 | ||
469 | fn foo() -> Result<(), i32> { | 442 | fn foo() -> Result<(), i32> { 0 } |
470 | 0<|> | ||
471 | } | ||
472 | 443 | ||
473 | //- /core/lib.rs | 444 | //- /core/lib.rs |
474 | pub mod result { | 445 | pub 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}; | 457 | use core::result::Result::{self, Ok, Err}; |
487 | 458 | ||
488 | enum SomeOtherEnum { | 459 | enum SomeOtherEnum { Ok(i32), Err(String) } |
489 | Ok(i32), | ||
490 | Err(String), | ||
491 | } | ||
492 | 460 | ||
493 | fn foo() -> SomeOtherEnum { | 461 | fn foo() -> SomeOtherEnum { 0 } |
494 | 0<|> | ||
495 | } | ||
496 | 462 | ||
497 | //- /core/lib.rs | 463 | //- /core/lib.rs |
498 | pub mod result { | 464 | pub 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() {} | 625 | fn some() {} |
659 | fn here() {} | 626 | fn items() {} |
627 | fn here() {} | ||
660 | 628 | ||
661 | macro_rules! id { | 629 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } |
662 | ($($tt:tt)*) => { $($tt)*}; | ||
663 | } | ||
664 | 630 | ||
665 | fn main() { | 631 | fn main() { |
666 | let _x = id![Foo { a: 42 }]; | 632 | let _x = id![Foo { a: <|>42 }]; |
667 | } | 633 | } |
668 | 634 | ||
669 | pub struct Foo { | 635 | pub struct Foo { pub a: i32, pub b: i32 } |
670 | pub a: i32, | 636 | "#, |
671 | pub b: i32, | 637 | r#" |
672 | } | 638 | fn {a:42, b: ()} {} |
673 | ", | 639 | fn items() {} |
640 | fn here() {} | ||
641 | |||
642 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
643 | |||
644 | fn main() { | ||
645 | let _x = id![Foo { a: 42 }]; | ||
646 | } | ||
647 | |||
648 | pub 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; | 657 | use a; |
716 | use a::{c, d::e}; | 658 | use 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 { | 672 | struct A { a: &'static str } |
735 | a: &'static str | 673 | fn 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); | 678 | struct A(usize); |
749 | 679 | fn 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#" |
761 | struct A { | 685 | struct A { a: &'static str } |
762 | a: &'static str | ||
763 | } | ||
764 | |||
765 | fn main() { | 686 | fn 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#" |
773 | struct A { | 692 | struct A { a: &'static str } |
774 | a: &'static str | ||
775 | } | ||
776 | |||
777 | fn main() { | 693 | fn 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#" |
789 | struct A { | 702 | struct A { a: &'static str, b: &'static str } |
790 | a: &'static str, | ||
791 | b: &'static str | ||
792 | } | ||
793 | |||
794 | fn main() { | 703 | fn 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#" |
804 | struct A { | 710 | struct A { a: &'static str, b: &'static str } |
805 | a: &'static str, | ||
806 | b: &'static str | ||
807 | } | ||
808 | |||
809 | fn main() { | 711 | fn 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); |