aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide/src/runnables.rs164
-rw-r--r--crates/ra_ssr/src/parsing.rs24
-rw-r--r--crates/ra_ssr/src/tests.rs39
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
243fn 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)]
250mod tests { 270mod 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<|>
579mod foo { 599mod 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<|>
645mod 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};
10use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T}; 10use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T};
11use rustc_hash::{FxHashMap, FxHashSet}; 11use rustc_hash::{FxHashMap, FxHashSet};
12use std::str::FromStr; 12use std::str::FromStr;
13use test_utils::mark;
13 14
14#[derive(Debug)] 15#[derive(Debug)]
15pub(crate) struct ParsedRule { 16pub(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`.
130fn contains_path(node: &SyntaxNode) -> bool {
131 node.kind() == SyntaxKind::PATH
132 || node.descendants().any(|node| node.kind() == SyntaxKind::PATH)
133}
134
113impl FromStr for SsrRule { 135impl 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]
903fn 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]
903fn replace_local_variable_reference() { 942fn 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