diff options
Diffstat (limited to 'crates/ra_ssr')
-rw-r--r-- | crates/ra_ssr/src/parsing.rs | 24 | ||||
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 39 |
2 files changed, 62 insertions, 1 deletions
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 |