diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_ide/src/runnables.rs | 164 | ||||
-rw-r--r-- | crates/ra_ssr/src/parsing.rs | 24 | ||||
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 39 |
3 files changed, 172 insertions, 55 deletions
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 95a35a28d..45e0a7d85 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -220,15 +220,7 @@ fn runnable_mod( | |||
220 | module: ast::Module, | 220 | module: ast::Module, |
221 | file_id: FileId, | 221 | file_id: FileId, |
222 | ) -> Option<Runnable> { | 222 | ) -> Option<Runnable> { |
223 | let has_test_function = module | 223 | if !has_test_function_or_multiple_test_submodules(&module) { |
224 | .item_list()? | ||
225 | .items() | ||
226 | .filter_map(|it| match it { | ||
227 | ast::ModuleItem::FnDef(it) => Some(it), | ||
228 | _ => None, | ||
229 | }) | ||
230 | .any(|f| has_test_related_attribute(&f)); | ||
231 | if !has_test_function { | ||
232 | return None; | 224 | return None; |
233 | } | 225 | } |
234 | let module_def = sema.to_def(&module)?; | 226 | let module_def = sema.to_def(&module)?; |
@@ -246,6 +238,34 @@ fn runnable_mod( | |||
246 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) | 238 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) |
247 | } | 239 | } |
248 | 240 | ||
241 | // We could create runnables for modules with number_of_test_submodules > 0, | ||
242 | // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already | ||
243 | fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { | ||
244 | if let Some(item_list) = module.item_list() { | ||
245 | let mut number_of_test_submodules = 0; | ||
246 | |||
247 | for item in item_list.items() { | ||
248 | match item { | ||
249 | ast::ModuleItem::FnDef(f) => { | ||
250 | if has_test_related_attribute(&f) { | ||
251 | return true; | ||
252 | } | ||
253 | } | ||
254 | ast::ModuleItem::Module(submodule) => { | ||
255 | if has_test_function_or_multiple_test_submodules(&submodule) { | ||
256 | number_of_test_submodules += 1; | ||
257 | } | ||
258 | } | ||
259 | _ => (), | ||
260 | } | ||
261 | } | ||
262 | |||
263 | number_of_test_submodules > 1 | ||
264 | } else { | ||
265 | false | ||
266 | } | ||
267 | } | ||
268 | |||
249 | #[cfg(test)] | 269 | #[cfg(test)] |
250 | mod tests { | 270 | mod tests { |
251 | use expect::{expect, Expect}; | 271 | use expect::{expect, Expect}; |
@@ -571,19 +591,33 @@ mod test_mod { | |||
571 | } | 591 | } |
572 | 592 | ||
573 | #[test] | 593 | #[test] |
574 | fn test_runnables_one_depth_layer_module() { | 594 | fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() { |
575 | check( | 595 | check( |
576 | r#" | 596 | r#" |
577 | //- /lib.rs | 597 | //- /lib.rs |
578 | <|> | 598 | <|> |
579 | mod foo { | 599 | mod root_tests { |
580 | mod test_mod { | 600 | mod nested_tests_0 { |
581 | #[test] | 601 | mod nested_tests_1 { |
582 | fn test_foo1() {} | 602 | #[test] |
603 | fn nested_test_11() {} | ||
604 | |||
605 | #[test] | ||
606 | fn nested_test_12() {} | ||
607 | } | ||
608 | |||
609 | mod nested_tests_2 { | ||
610 | #[test] | ||
611 | fn nested_test_2() {} | ||
612 | } | ||
613 | |||
614 | mod nested_tests_3 {} | ||
583 | } | 615 | } |
616 | |||
617 | mod nested_tests_4 {} | ||
584 | } | 618 | } |
585 | "#, | 619 | "#, |
586 | &[&TEST, &TEST], | 620 | &[&TEST, &TEST, &TEST, &TEST, &TEST, &TEST], |
587 | expect![[r#" | 621 | expect![[r#" |
588 | [ | 622 | [ |
589 | Runnable { | 623 | Runnable { |
@@ -591,18 +625,18 @@ mod foo { | |||
591 | file_id: FileId( | 625 | file_id: FileId( |
592 | 1, | 626 | 1, |
593 | ), | 627 | ), |
594 | full_range: 15..77, | 628 | full_range: 22..323, |
595 | focus_range: Some( | 629 | focus_range: Some( |
596 | 19..27, | 630 | 26..40, |
597 | ), | 631 | ), |
598 | name: "test_mod", | 632 | name: "nested_tests_0", |
599 | kind: MODULE, | 633 | kind: MODULE, |
600 | container_name: None, | 634 | container_name: None, |
601 | description: None, | 635 | description: None, |
602 | docs: None, | 636 | docs: None, |
603 | }, | 637 | }, |
604 | kind: TestMod { | 638 | kind: TestMod { |
605 | path: "foo::test_mod", | 639 | path: "root_tests::nested_tests_0", |
606 | }, | 640 | }, |
607 | cfg_exprs: [], | 641 | cfg_exprs: [], |
608 | }, | 642 | }, |
@@ -611,11 +645,31 @@ mod foo { | |||
611 | file_id: FileId( | 645 | file_id: FileId( |
612 | 1, | 646 | 1, |
613 | ), | 647 | ), |
614 | full_range: 38..71, | 648 | full_range: 51..192, |
615 | focus_range: Some( | 649 | focus_range: Some( |
616 | 57..66, | 650 | 55..69, |
617 | ), | 651 | ), |
618 | name: "test_foo1", | 652 | name: "nested_tests_1", |
653 | kind: MODULE, | ||
654 | container_name: None, | ||
655 | description: None, | ||
656 | docs: None, | ||
657 | }, | ||
658 | kind: TestMod { | ||
659 | path: "root_tests::nested_tests_0::nested_tests_1", | ||
660 | }, | ||
661 | cfg_exprs: [], | ||
662 | }, | ||
663 | Runnable { | ||
664 | nav: NavigationTarget { | ||
665 | file_id: FileId( | ||
666 | 1, | ||
667 | ), | ||
668 | full_range: 84..126, | ||
669 | focus_range: Some( | ||
670 | 107..121, | ||
671 | ), | ||
672 | name: "nested_test_11", | ||
619 | kind: FN_DEF, | 673 | kind: FN_DEF, |
620 | container_name: None, | 674 | container_name: None, |
621 | description: None, | 675 | description: None, |
@@ -623,7 +677,7 @@ mod foo { | |||
623 | }, | 677 | }, |
624 | kind: Test { | 678 | kind: Test { |
625 | test_id: Path( | 679 | test_id: Path( |
626 | "foo::test_mod::test_foo1", | 680 | "root_tests::nested_tests_0::nested_tests_1::nested_test_11", |
627 | ), | 681 | ), |
628 | attr: TestAttr { | 682 | attr: TestAttr { |
629 | ignore: false, | 683 | ignore: false, |
@@ -631,46 +685,48 @@ mod foo { | |||
631 | }, | 685 | }, |
632 | cfg_exprs: [], | 686 | cfg_exprs: [], |
633 | }, | 687 | }, |
634 | ] | ||
635 | "#]], | ||
636 | ); | ||
637 | } | ||
638 | |||
639 | #[test] | ||
640 | fn test_runnables_multiple_depth_module() { | ||
641 | check( | ||
642 | r#" | ||
643 | //- /lib.rs | ||
644 | <|> | ||
645 | mod foo { | ||
646 | mod bar { | ||
647 | mod test_mod { | ||
648 | #[test] | ||
649 | fn test_foo1() {} | ||
650 | } | ||
651 | } | ||
652 | } | ||
653 | "#, | ||
654 | &[&TEST, &TEST], | ||
655 | expect![[r#" | ||
656 | [ | ||
657 | Runnable { | 688 | Runnable { |
658 | nav: NavigationTarget { | 689 | nav: NavigationTarget { |
659 | file_id: FileId( | 690 | file_id: FileId( |
660 | 1, | 691 | 1, |
661 | ), | 692 | ), |
662 | full_range: 33..107, | 693 | full_range: 140..182, |
663 | focus_range: Some( | 694 | focus_range: Some( |
664 | 37..45, | 695 | 163..177, |
665 | ), | 696 | ), |
666 | name: "test_mod", | 697 | name: "nested_test_12", |
698 | kind: FN_DEF, | ||
699 | container_name: None, | ||
700 | description: None, | ||
701 | docs: None, | ||
702 | }, | ||
703 | kind: Test { | ||
704 | test_id: Path( | ||
705 | "root_tests::nested_tests_0::nested_tests_1::nested_test_12", | ||
706 | ), | ||
707 | attr: TestAttr { | ||
708 | ignore: false, | ||
709 | }, | ||
710 | }, | ||
711 | cfg_exprs: [], | ||
712 | }, | ||
713 | Runnable { | ||
714 | nav: NavigationTarget { | ||
715 | file_id: FileId( | ||
716 | 1, | ||
717 | ), | ||
718 | full_range: 202..286, | ||
719 | focus_range: Some( | ||
720 | 206..220, | ||
721 | ), | ||
722 | name: "nested_tests_2", | ||
667 | kind: MODULE, | 723 | kind: MODULE, |
668 | container_name: None, | 724 | container_name: None, |
669 | description: None, | 725 | description: None, |
670 | docs: None, | 726 | docs: None, |
671 | }, | 727 | }, |
672 | kind: TestMod { | 728 | kind: TestMod { |
673 | path: "foo::bar::test_mod", | 729 | path: "root_tests::nested_tests_0::nested_tests_2", |
674 | }, | 730 | }, |
675 | cfg_exprs: [], | 731 | cfg_exprs: [], |
676 | }, | 732 | }, |
@@ -679,11 +735,11 @@ mod foo { | |||
679 | file_id: FileId( | 735 | file_id: FileId( |
680 | 1, | 736 | 1, |
681 | ), | 737 | ), |
682 | full_range: 60..97, | 738 | full_range: 235..276, |
683 | focus_range: Some( | 739 | focus_range: Some( |
684 | 83..92, | 740 | 258..271, |
685 | ), | 741 | ), |
686 | name: "test_foo1", | 742 | name: "nested_test_2", |
687 | kind: FN_DEF, | 743 | kind: FN_DEF, |
688 | container_name: None, | 744 | container_name: None, |
689 | description: None, | 745 | description: None, |
@@ -691,7 +747,7 @@ mod foo { | |||
691 | }, | 747 | }, |
692 | kind: Test { | 748 | kind: Test { |
693 | test_id: Path( | 749 | test_id: Path( |
694 | "foo::bar::test_mod::test_foo1", | 750 | "root_tests::nested_tests_0::nested_tests_2::nested_test_2", |
695 | ), | 751 | ), |
696 | attr: TestAttr { | 752 | attr: TestAttr { |
697 | ignore: false, | 753 | ignore: false, |
diff --git a/crates/ra_ssr/src/parsing.rs b/crates/ra_ssr/src/parsing.rs index 2d6f4e514..769720bef 100644 --- a/crates/ra_ssr/src/parsing.rs +++ b/crates/ra_ssr/src/parsing.rs | |||
@@ -10,6 +10,7 @@ use crate::{SsrError, SsrPattern, SsrRule}; | |||
10 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T}; | 10 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T}; |
11 | use rustc_hash::{FxHashMap, FxHashSet}; | 11 | use rustc_hash::{FxHashMap, FxHashSet}; |
12 | use std::str::FromStr; | 12 | use std::str::FromStr; |
13 | use test_utils::mark; | ||
13 | 14 | ||
14 | #[derive(Debug)] | 15 | #[derive(Debug)] |
15 | pub(crate) struct ParsedRule { | 16 | pub(crate) struct ParsedRule { |
@@ -102,14 +103,35 @@ impl RuleBuilder { | |||
102 | } | 103 | } |
103 | } | 104 | } |
104 | 105 | ||
105 | fn build(self) -> Result<Vec<ParsedRule>, SsrError> { | 106 | fn build(mut self) -> Result<Vec<ParsedRule>, SsrError> { |
106 | if self.rules.is_empty() { | 107 | if self.rules.is_empty() { |
107 | bail!("Not a valid Rust expression, type, item, path or pattern"); | 108 | bail!("Not a valid Rust expression, type, item, path or pattern"); |
108 | } | 109 | } |
110 | // If any rules contain paths, then we reject any rules that don't contain paths. Allowing a | ||
111 | // mix leads to strange semantics, since the path-based rules only match things where the | ||
112 | // path refers to semantically the same thing, whereas the non-path-based rules could match | ||
113 | // anything. Specifically, if we have a rule like `foo ==>> bar` we only want to match the | ||
114 | // `foo` that is in the current scope, not any `foo`. However "foo" can be parsed as a | ||
115 | // pattern (BIND_PAT -> NAME -> IDENT). Allowing such a rule through would result in | ||
116 | // renaming everything called `foo` to `bar`. It'd also be slow, since without a path, we'd | ||
117 | // have to use the slow-scan search mechanism. | ||
118 | if self.rules.iter().any(|rule| contains_path(&rule.pattern)) { | ||
119 | let old_len = self.rules.len(); | ||
120 | self.rules.retain(|rule| contains_path(&rule.pattern)); | ||
121 | if self.rules.len() < old_len { | ||
122 | mark::hit!(pattern_is_a_single_segment_path); | ||
123 | } | ||
124 | } | ||
109 | Ok(self.rules) | 125 | Ok(self.rules) |
110 | } | 126 | } |
111 | } | 127 | } |
112 | 128 | ||
129 | /// Returns whether there are any paths in `node`. | ||
130 | fn contains_path(node: &SyntaxNode) -> bool { | ||
131 | node.kind() == SyntaxKind::PATH | ||
132 | || node.descendants().any(|node| node.kind() == SyntaxKind::PATH) | ||
133 | } | ||
134 | |||
113 | impl FromStr for SsrRule { | 135 | impl FromStr for SsrRule { |
114 | type Err = SsrError; | 136 | type Err = SsrError; |
115 | 137 | ||
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 6a44ef378..f5ffff7cc 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs | |||
@@ -900,6 +900,45 @@ fn ufcs_matches_method_call() { | |||
900 | } | 900 | } |
901 | 901 | ||
902 | #[test] | 902 | #[test] |
903 | fn pattern_is_a_single_segment_path() { | ||
904 | mark::check!(pattern_is_a_single_segment_path); | ||
905 | // The first function should not be altered because the `foo` in scope at the cursor position is | ||
906 | // a different `foo`. This case is special because "foo" can be parsed as a pattern (BIND_PAT -> | ||
907 | // NAME -> IDENT), which contains no path. If we're not careful we'll end up matching the `foo` | ||
908 | // in `let foo` from the first function. Whether we should match the `let foo` in the second | ||
909 | // function is less clear. At the moment, we don't. Doing so sounds like a rename operation, | ||
910 | // which isn't really what SSR is for, especially since the replacement `bar` must be able to be | ||
911 | // resolved, which means if we rename `foo` we'll get a name collision. | ||
912 | assert_ssr_transform( | ||
913 | "foo ==>> bar", | ||
914 | r#" | ||
915 | fn f1() -> i32 { | ||
916 | let foo = 1; | ||
917 | let bar = 2; | ||
918 | foo | ||
919 | } | ||
920 | fn f1() -> i32 { | ||
921 | let foo = 1; | ||
922 | let bar = 2; | ||
923 | foo<|> | ||
924 | } | ||
925 | "#, | ||
926 | expect![[r#" | ||
927 | fn f1() -> i32 { | ||
928 | let foo = 1; | ||
929 | let bar = 2; | ||
930 | foo | ||
931 | } | ||
932 | fn f1() -> i32 { | ||
933 | let foo = 1; | ||
934 | let bar = 2; | ||
935 | bar | ||
936 | } | ||
937 | "#]], | ||
938 | ); | ||
939 | } | ||
940 | |||
941 | #[test] | ||
903 | fn replace_local_variable_reference() { | 942 | fn replace_local_variable_reference() { |
904 | // The pattern references a local variable `foo` in the block containing the cursor. We should | 943 | // The pattern references a local variable `foo` in the block containing the cursor. We should |
905 | // only replace references to this variable `foo`, not other variables that just happen to have | 944 | // only replace references to this variable `foo`, not other variables that just happen to have |