diff options
Diffstat (limited to 'crates/ssr')
-rw-r--r-- | crates/ssr/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/ssr/src/matching.rs | 4 | ||||
-rw-r--r-- | crates/ssr/src/parsing.rs | 15 | ||||
-rw-r--r-- | crates/ssr/src/search.rs | 20 | ||||
-rw-r--r-- | crates/ssr/src/tests.rs | 113 |
5 files changed, 130 insertions, 26 deletions
diff --git a/crates/ssr/Cargo.toml b/crates/ssr/Cargo.toml index 98ed25fb6..cc8136d22 100644 --- a/crates/ssr/Cargo.toml +++ b/crates/ssr/Cargo.toml | |||
@@ -12,7 +12,7 @@ doctest = false | |||
12 | 12 | ||
13 | [dependencies] | 13 | [dependencies] |
14 | rustc-hash = "1.1.0" | 14 | rustc-hash = "1.1.0" |
15 | itertools = "0.9.0" | 15 | itertools = "0.10.0" |
16 | 16 | ||
17 | text_edit = { path = "../text_edit", version = "0.0.0" } | 17 | text_edit = { path = "../text_edit", version = "0.0.0" } |
18 | syntax = { path = "../syntax", version = "0.0.0" } | 18 | syntax = { path = "../syntax", version = "0.0.0" } |
@@ -21,4 +21,4 @@ hir = { path = "../hir", version = "0.0.0" } | |||
21 | test_utils = { path = "../test_utils", version = "0.0.0" } | 21 | test_utils = { path = "../test_utils", version = "0.0.0" } |
22 | 22 | ||
23 | [dev-dependencies] | 23 | [dev-dependencies] |
24 | expect-test = "1.0" | 24 | expect-test = "1.1" |
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs index 99b187311..6cf831431 100644 --- a/crates/ssr/src/matching.rs +++ b/crates/ssr/src/matching.rs | |||
@@ -473,7 +473,9 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
473 | } | 473 | } |
474 | SyntaxElement::Node(n) => { | 474 | SyntaxElement::Node(n) => { |
475 | if let Some(first_token) = n.first_token() { | 475 | if let Some(first_token) = n.first_token() { |
476 | if Some(first_token.to_string()) == next_pattern_token { | 476 | if Some(first_token.text().as_str()) |
477 | == next_pattern_token.as_deref() | ||
478 | { | ||
477 | if let Some(SyntaxElement::Node(p)) = pattern.next() { | 479 | if let Some(SyntaxElement::Node(p)) = pattern.next() { |
478 | // We have a subtree that starts with the next token in our pattern. | 480 | // We have a subtree that starts with the next token in our pattern. |
479 | self.attempt_match_token_tree(phase, &p, &n)?; | 481 | self.attempt_match_token_tree(phase, &p, &n)?; |
diff --git a/crates/ssr/src/parsing.rs b/crates/ssr/src/parsing.rs index f3b084baf..3d5e4feb7 100644 --- a/crates/ssr/src/parsing.rs +++ b/crates/ssr/src/parsing.rs | |||
@@ -73,11 +73,18 @@ impl ParsedRule { | |||
73 | placeholders_by_stand_in: pattern.placeholders_by_stand_in(), | 73 | placeholders_by_stand_in: pattern.placeholders_by_stand_in(), |
74 | rules: Vec::new(), | 74 | rules: Vec::new(), |
75 | }; | 75 | }; |
76 | builder.try_add(ast::Expr::parse(&raw_pattern), raw_template.map(ast::Expr::parse)); | 76 | |
77 | let raw_template_stmt = raw_template.map(ast::Stmt::parse); | ||
78 | if let raw_template_expr @ Some(Ok(_)) = raw_template.map(ast::Expr::parse) { | ||
79 | builder.try_add(ast::Expr::parse(&raw_pattern), raw_template_expr); | ||
80 | } else { | ||
81 | builder.try_add(ast::Expr::parse(&raw_pattern), raw_template_stmt.clone()); | ||
82 | } | ||
77 | builder.try_add(ast::Type::parse(&raw_pattern), raw_template.map(ast::Type::parse)); | 83 | builder.try_add(ast::Type::parse(&raw_pattern), raw_template.map(ast::Type::parse)); |
78 | builder.try_add(ast::Item::parse(&raw_pattern), raw_template.map(ast::Item::parse)); | 84 | builder.try_add(ast::Item::parse(&raw_pattern), raw_template.map(ast::Item::parse)); |
79 | builder.try_add(ast::Path::parse(&raw_pattern), raw_template.map(ast::Path::parse)); | 85 | builder.try_add(ast::Path::parse(&raw_pattern), raw_template.map(ast::Path::parse)); |
80 | builder.try_add(ast::Pat::parse(&raw_pattern), raw_template.map(ast::Pat::parse)); | 86 | builder.try_add(ast::Pat::parse(&raw_pattern), raw_template.map(ast::Pat::parse)); |
87 | builder.try_add(ast::Stmt::parse(&raw_pattern), raw_template_stmt); | ||
81 | builder.build() | 88 | builder.build() |
82 | } | 89 | } |
83 | } | 90 | } |
@@ -88,7 +95,11 @@ struct RuleBuilder { | |||
88 | } | 95 | } |
89 | 96 | ||
90 | impl RuleBuilder { | 97 | impl RuleBuilder { |
91 | fn try_add<T: AstNode>(&mut self, pattern: Result<T, ()>, template: Option<Result<T, ()>>) { | 98 | fn try_add<T: AstNode, T2: AstNode>( |
99 | &mut self, | ||
100 | pattern: Result<T, ()>, | ||
101 | template: Option<Result<T2, ()>>, | ||
102 | ) { | ||
92 | match (pattern, template) { | 103 | match (pattern, template) { |
93 | (Ok(pattern), Some(Ok(template))) => self.rules.push(ParsedRule { | 104 | (Ok(pattern), Some(Ok(template))) => self.rules.push(ParsedRule { |
94 | placeholders_by_stand_in: self.placeholders_by_stand_in.clone(), | 105 | placeholders_by_stand_in: self.placeholders_by_stand_in.clone(), |
diff --git a/crates/ssr/src/search.rs b/crates/ssr/src/search.rs index 44b5db029..836eb94b2 100644 --- a/crates/ssr/src/search.rs +++ b/crates/ssr/src/search.rs | |||
@@ -5,10 +5,10 @@ use crate::{ | |||
5 | resolving::{ResolvedPath, ResolvedPattern, ResolvedRule}, | 5 | resolving::{ResolvedPath, ResolvedPattern, ResolvedRule}, |
6 | Match, MatchFinder, | 6 | Match, MatchFinder, |
7 | }; | 7 | }; |
8 | use ide_db::base_db::{FileId, FileRange}; | ||
9 | use ide_db::{ | 8 | use ide_db::{ |
9 | base_db::{FileId, FileRange}, | ||
10 | defs::Definition, | 10 | defs::Definition, |
11 | search::{Reference, SearchScope}, | 11 | search::{SearchScope, UsageSearchResult}, |
12 | }; | 12 | }; |
13 | use rustc_hash::FxHashSet; | 13 | use rustc_hash::FxHashSet; |
14 | use syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; | 14 | use syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; |
@@ -20,7 +20,7 @@ use test_utils::mark; | |||
20 | /// them more than once. | 20 | /// them more than once. |
21 | #[derive(Default)] | 21 | #[derive(Default)] |
22 | pub(crate) struct UsageCache { | 22 | pub(crate) struct UsageCache { |
23 | usages: Vec<(Definition, Vec<Reference>)>, | 23 | usages: Vec<(Definition, UsageSearchResult)>, |
24 | } | 24 | } |
25 | 25 | ||
26 | impl<'db> MatchFinder<'db> { | 26 | impl<'db> MatchFinder<'db> { |
@@ -58,8 +58,8 @@ impl<'db> MatchFinder<'db> { | |||
58 | ) { | 58 | ) { |
59 | if let Some(resolved_path) = pick_path_for_usages(pattern) { | 59 | if let Some(resolved_path) = pick_path_for_usages(pattern) { |
60 | let definition: Definition = resolved_path.resolution.clone().into(); | 60 | let definition: Definition = resolved_path.resolution.clone().into(); |
61 | for reference in self.find_usages(usage_cache, definition) { | 61 | for file_range in self.find_usages(usage_cache, definition).file_ranges() { |
62 | if let Some(node_to_match) = self.find_node_to_match(resolved_path, reference) { | 62 | if let Some(node_to_match) = self.find_node_to_match(resolved_path, file_range) { |
63 | if !is_search_permitted_ancestors(&node_to_match) { | 63 | if !is_search_permitted_ancestors(&node_to_match) { |
64 | mark::hit!(use_declaration_with_braces); | 64 | mark::hit!(use_declaration_with_braces); |
65 | continue; | 65 | continue; |
@@ -73,11 +73,11 @@ impl<'db> MatchFinder<'db> { | |||
73 | fn find_node_to_match( | 73 | fn find_node_to_match( |
74 | &self, | 74 | &self, |
75 | resolved_path: &ResolvedPath, | 75 | resolved_path: &ResolvedPath, |
76 | reference: &Reference, | 76 | file_range: FileRange, |
77 | ) -> Option<SyntaxNode> { | 77 | ) -> Option<SyntaxNode> { |
78 | let file = self.sema.parse(reference.file_range.file_id); | 78 | let file = self.sema.parse(file_range.file_id); |
79 | let depth = resolved_path.depth as usize; | 79 | let depth = resolved_path.depth as usize; |
80 | let offset = reference.file_range.range.start(); | 80 | let offset = file_range.range.start(); |
81 | if let Some(path) = | 81 | if let Some(path) = |
82 | self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset) | 82 | self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset) |
83 | { | 83 | { |
@@ -108,7 +108,7 @@ impl<'db> MatchFinder<'db> { | |||
108 | &self, | 108 | &self, |
109 | usage_cache: &'a mut UsageCache, | 109 | usage_cache: &'a mut UsageCache, |
110 | definition: Definition, | 110 | definition: Definition, |
111 | ) -> &'a [Reference] { | 111 | ) -> &'a UsageSearchResult { |
112 | // Logically if a lookup succeeds we should just return it. Unfortunately returning it would | 112 | // Logically if a lookup succeeds we should just return it. Unfortunately returning it would |
113 | // extend the lifetime of the borrow, then we wouldn't be able to do the insertion on a | 113 | // extend the lifetime of the borrow, then we wouldn't be able to do the insertion on a |
114 | // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two | 114 | // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two |
@@ -250,7 +250,7 @@ fn is_search_permitted(node: &SyntaxNode) -> bool { | |||
250 | } | 250 | } |
251 | 251 | ||
252 | impl UsageCache { | 252 | impl UsageCache { |
253 | fn find(&mut self, definition: &Definition) -> Option<&[Reference]> { | 253 | fn find(&mut self, definition: &Definition) -> Option<&UsageSearchResult> { |
254 | // We expect a very small number of cache entries (generally 1), so a linear scan should be | 254 | // We expect a very small number of cache entries (generally 1), so a linear scan should be |
255 | // fast enough and avoids the need to implement Hash for Definition. | 255 | // fast enough and avoids the need to implement Hash for Definition. |
256 | for (d, refs) in &self.usages { | 256 | for (d, refs) in &self.usages { |
diff --git a/crates/ssr/src/tests.rs b/crates/ssr/src/tests.rs index 63131f6ca..d6918c22d 100644 --- a/crates/ssr/src/tests.rs +++ b/crates/ssr/src/tests.rs | |||
@@ -59,7 +59,7 @@ fn parser_undefined_placeholder_in_replacement() { | |||
59 | ); | 59 | ); |
60 | } | 60 | } |
61 | 61 | ||
62 | /// `code` may optionally contain a cursor marker `<|>`. If it doesn't, then the position will be | 62 | /// `code` may optionally contain a cursor marker `$0`. If it doesn't, then the position will be |
63 | /// the start of the file. If there's a second cursor marker, then we'll return a single range. | 63 | /// the start of the file. If there's a second cursor marker, then we'll return a single range. |
64 | pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Vec<FileRange>) { | 64 | pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Vec<FileRange>) { |
65 | use ide_db::base_db::fixture::WithFixture; | 65 | use ide_db::base_db::fixture::WithFixture; |
@@ -160,6 +160,97 @@ fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expecte | |||
160 | } | 160 | } |
161 | 161 | ||
162 | #[test] | 162 | #[test] |
163 | fn ssr_let_stmt_in_macro_match() { | ||
164 | assert_matches( | ||
165 | "let a = 0", | ||
166 | r#" | ||
167 | macro_rules! m1 { ($a:stmt) => {$a}; } | ||
168 | fn f() {m1!{ let a = 0 };}"#, | ||
169 | // FIXME: Whitespace is not part of the matched block | ||
170 | &["leta=0"], | ||
171 | ); | ||
172 | } | ||
173 | |||
174 | #[test] | ||
175 | fn ssr_let_stmt_in_fn_match() { | ||
176 | assert_matches("let $a = 10;", "fn main() { let x = 10; x }", &["let x = 10;"]); | ||
177 | assert_matches("let $a = $b;", "fn main() { let x = 10; x }", &["let x = 10;"]); | ||
178 | } | ||
179 | |||
180 | #[test] | ||
181 | fn ssr_block_expr_match() { | ||
182 | assert_matches("{ let $a = $b; }", "fn main() { let x = 10; }", &["{ let x = 10; }"]); | ||
183 | assert_matches("{ let $a = $b; $c }", "fn main() { let x = 10; x }", &["{ let x = 10; x }"]); | ||
184 | } | ||
185 | |||
186 | #[test] | ||
187 | fn ssr_let_stmt_replace() { | ||
188 | // Pattern and template with trailing semicolon | ||
189 | assert_ssr_transform( | ||
190 | "let $a = $b; ==>> let $a = 11;", | ||
191 | "fn main() { let x = 10; x }", | ||
192 | expect![["fn main() { let x = 11; x }"]], | ||
193 | ); | ||
194 | } | ||
195 | |||
196 | #[test] | ||
197 | fn ssr_let_stmt_replace_expr() { | ||
198 | // Trailing semicolon should be dropped from the new expression | ||
199 | assert_ssr_transform( | ||
200 | "let $a = $b; ==>> $b", | ||
201 | "fn main() { let x = 10; }", | ||
202 | expect![["fn main() { 10 }"]], | ||
203 | ); | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn ssr_blockexpr_replace_stmt_with_stmt() { | ||
208 | assert_ssr_transform( | ||
209 | "if $a() {$b;} ==>> $b;", | ||
210 | "{ | ||
211 | if foo() { | ||
212 | bar(); | ||
213 | } | ||
214 | Ok(()) | ||
215 | }", | ||
216 | expect![[r#"{ | ||
217 | bar(); | ||
218 | Ok(()) | ||
219 | }"#]], | ||
220 | ); | ||
221 | } | ||
222 | |||
223 | #[test] | ||
224 | fn ssr_blockexpr_match_trailing_expr() { | ||
225 | assert_matches( | ||
226 | "if $a() {$b;}", | ||
227 | "{ | ||
228 | if foo() { | ||
229 | bar(); | ||
230 | } | ||
231 | }", | ||
232 | &["if foo() { | ||
233 | bar(); | ||
234 | }"], | ||
235 | ); | ||
236 | } | ||
237 | |||
238 | #[test] | ||
239 | fn ssr_blockexpr_replace_trailing_expr_with_stmt() { | ||
240 | assert_ssr_transform( | ||
241 | "if $a() {$b;} ==>> $b;", | ||
242 | "{ | ||
243 | if foo() { | ||
244 | bar(); | ||
245 | } | ||
246 | }", | ||
247 | expect![["{ | ||
248 | bar(); | ||
249 | }"]], | ||
250 | ); | ||
251 | } | ||
252 | |||
253 | #[test] | ||
163 | fn ssr_function_to_method() { | 254 | fn ssr_function_to_method() { |
164 | assert_ssr_transform( | 255 | assert_ssr_transform( |
165 | "my_function($a, $b) ==>> ($a).my_method($b)", | 256 | "my_function($a, $b) ==>> ($a).my_method($b)", |
@@ -505,7 +596,7 @@ fn replace_function_call() { | |||
505 | // This test also makes sure that we ignore empty-ranges. | 596 | // This test also makes sure that we ignore empty-ranges. |
506 | assert_ssr_transform( | 597 | assert_ssr_transform( |
507 | "foo() ==>> bar()", | 598 | "foo() ==>> bar()", |
508 | "fn foo() {<|><|>} fn bar() {} fn f1() {foo(); foo();}", | 599 | "fn foo() {$0$0} fn bar() {} fn f1() {foo(); foo();}", |
509 | expect![["fn foo() {} fn bar() {} fn f1() {bar(); bar();}"]], | 600 | expect![["fn foo() {} fn bar() {} fn f1() {bar(); bar();}"]], |
510 | ); | 601 | ); |
511 | } | 602 | } |
@@ -615,7 +706,7 @@ fn replace_associated_trait_constant() { | |||
615 | 706 | ||
616 | #[test] | 707 | #[test] |
617 | fn replace_path_in_different_contexts() { | 708 | fn replace_path_in_different_contexts() { |
618 | // Note the <|> inside module a::b which marks the point where the rule is interpreted. We | 709 | // Note the $0 inside module a::b which marks the point where the rule is interpreted. We |
619 | // replace foo with bar, but both need different path qualifiers in different contexts. In f4, | 710 | // replace foo with bar, but both need different path qualifiers in different contexts. In f4, |
620 | // foo is unqualified because of a use statement, however the replacement needs to be fully | 711 | // foo is unqualified because of a use statement, however the replacement needs to be fully |
621 | // qualified. | 712 | // qualified. |
@@ -623,7 +714,7 @@ fn replace_path_in_different_contexts() { | |||
623 | "c::foo() ==>> c::bar()", | 714 | "c::foo() ==>> c::bar()", |
624 | r#" | 715 | r#" |
625 | mod a { | 716 | mod a { |
626 | pub mod b {<|> | 717 | pub mod b {$0 |
627 | pub mod c { | 718 | pub mod c { |
628 | pub fn foo() {} | 719 | pub fn foo() {} |
629 | pub fn bar() {} | 720 | pub fn bar() {} |
@@ -1005,7 +1096,7 @@ fn pattern_is_a_single_segment_path() { | |||
1005 | fn f1() -> i32 { | 1096 | fn f1() -> i32 { |
1006 | let foo = 1; | 1097 | let foo = 1; |
1007 | let bar = 2; | 1098 | let bar = 2; |
1008 | foo<|> | 1099 | foo$0 |
1009 | } | 1100 | } |
1010 | "#, | 1101 | "#, |
1011 | expect![[r#" | 1102 | expect![[r#" |
@@ -1037,7 +1128,7 @@ fn replace_local_variable_reference() { | |||
1037 | let foo = 5; | 1128 | let foo = 5; |
1038 | res += foo + 1; | 1129 | res += foo + 1; |
1039 | let foo = 10; | 1130 | let foo = 10; |
1040 | res += foo + 2;<|> | 1131 | res += foo + 2;$0 |
1041 | res += foo + 3; | 1132 | res += foo + 3; |
1042 | let foo = 15; | 1133 | let foo = 15; |
1043 | res += foo + 4; | 1134 | res += foo + 4; |
@@ -1069,9 +1160,9 @@ fn replace_path_within_selection() { | |||
1069 | let foo = 41; | 1160 | let foo = 41; |
1070 | let bar = 42; | 1161 | let bar = 42; |
1071 | do_stuff(foo); | 1162 | do_stuff(foo); |
1072 | do_stuff(foo);<|> | 1163 | do_stuff(foo);$0 |
1073 | do_stuff(foo); | 1164 | do_stuff(foo); |
1074 | do_stuff(foo);<|> | 1165 | do_stuff(foo);$0 |
1075 | do_stuff(foo); | 1166 | do_stuff(foo); |
1076 | }"#, | 1167 | }"#, |
1077 | expect![[r#" | 1168 | expect![[r#" |
@@ -1094,9 +1185,9 @@ fn replace_nonpath_within_selection() { | |||
1094 | "$a + $b ==>> $b * $a", | 1185 | "$a + $b ==>> $b * $a", |
1095 | r#" | 1186 | r#" |
1096 | fn main() { | 1187 | fn main() { |
1097 | let v = 1 + 2;<|> | 1188 | let v = 1 + 2;$0 |
1098 | let v2 = 3 + 3; | 1189 | let v2 = 3 + 3; |
1099 | let v3 = 4 + 5;<|> | 1190 | let v3 = 4 + 5;$0 |
1100 | let v4 = 6 + 7; | 1191 | let v4 = 6 + 7; |
1101 | }"#, | 1192 | }"#, |
1102 | expect![[r#" | 1193 | expect![[r#" |
@@ -1121,7 +1212,7 @@ fn replace_self() { | |||
1121 | fn bar(_: &S1) {} | 1212 | fn bar(_: &S1) {} |
1122 | impl S1 { | 1213 | impl S1 { |
1123 | fn f1(&self) { | 1214 | fn f1(&self) { |
1124 | foo(self)<|> | 1215 | foo(self)$0 |
1125 | } | 1216 | } |
1126 | fn f2(&self) { | 1217 | fn f2(&self) { |
1127 | foo(self) | 1218 | foo(self) |