aboutsummaryrefslogtreecommitdiff
path: root/crates/ssr
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ssr')
-rw-r--r--crates/ssr/Cargo.toml4
-rw-r--r--crates/ssr/src/matching.rs4
-rw-r--r--crates/ssr/src/parsing.rs15
-rw-r--r--crates/ssr/src/search.rs20
-rw-r--r--crates/ssr/src/tests.rs113
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]
14rustc-hash = "1.1.0" 14rustc-hash = "1.1.0"
15itertools = "0.9.0" 15itertools = "0.10.0"
16 16
17text_edit = { path = "../text_edit", version = "0.0.0" } 17text_edit = { path = "../text_edit", version = "0.0.0" }
18syntax = { path = "../syntax", version = "0.0.0" } 18syntax = { path = "../syntax", version = "0.0.0" }
@@ -21,4 +21,4 @@ hir = { path = "../hir", version = "0.0.0" }
21test_utils = { path = "../test_utils", version = "0.0.0" } 21test_utils = { path = "../test_utils", version = "0.0.0" }
22 22
23[dev-dependencies] 23[dev-dependencies]
24expect-test = "1.0" 24expect-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
90impl RuleBuilder { 97impl 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};
8use ide_db::base_db::{FileId, FileRange};
9use ide_db::{ 8use ide_db::{
9 base_db::{FileId, FileRange},
10 defs::Definition, 10 defs::Definition,
11 search::{Reference, SearchScope}, 11 search::{SearchScope, UsageSearchResult},
12}; 12};
13use rustc_hash::FxHashSet; 13use rustc_hash::FxHashSet;
14use syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; 14use 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)]
22pub(crate) struct UsageCache { 22pub(crate) struct UsageCache {
23 usages: Vec<(Definition, Vec<Reference>)>, 23 usages: Vec<(Definition, UsageSearchResult)>,
24} 24}
25 25
26impl<'db> MatchFinder<'db> { 26impl<'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
252impl UsageCache { 252impl 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.
64pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Vec<FileRange>) { 64pub(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]
163fn 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]
175fn 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]
181fn 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]
187fn 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]
197fn 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]
207fn 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]
224fn 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]
239fn 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]
163fn ssr_function_to_method() { 254fn 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]
617fn replace_path_in_different_contexts() { 708fn 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)