aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/ssr.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-02-27 08:54:18 +0000
committerGitHub <[email protected]>2020-02-27 08:54:18 +0000
commit50956f89fce93245e52b693ae63283274bc92142 (patch)
tree3393c2c20b6adb02faf8156308f18ff842f6968d /crates/ra_ide/src/ssr.rs
parent7f7d96cffed6ed4836432a16cfc1dbd080bbab17 (diff)
parentb1ee6d17a4af6a2fcbaeb0b98965cd610c7b1b72 (diff)
Merge #3285
3285: Handle trivia in Structural Search and Replace r=matklad a=adamrk Addresses the second point of https://github.com/rust-analyzer/rust-analyzer/issues/3186. Structural search and replace will now match code that has varies from the pattern in whitespace or comments. One issue is that it's not clear where comments in the matched code should go in the replacement. With this change they're just tacked on at the end, which can cause some unexpected moving of comments (see the last test example). Co-authored-by: adamrk <[email protected]>
Diffstat (limited to 'crates/ra_ide/src/ssr.rs')
-rw-r--r--crates/ra_ide/src/ssr.rs141
1 files changed, 115 insertions, 26 deletions
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 902c29fc6..c011a2e74 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -3,9 +3,8 @@
3use crate::source_change::SourceFileEdit; 3use crate::source_change::SourceFileEdit;
4use ra_ide_db::RootDatabase; 4use ra_ide_db::RootDatabase;
5use ra_syntax::ast::make::expr_from_text; 5use ra_syntax::ast::make::expr_from_text;
6use ra_syntax::AstNode; 6use ra_syntax::ast::{AstToken, Comment};
7use ra_syntax::SyntaxElement; 7use ra_syntax::{AstNode, SyntaxElement, SyntaxNode};
8use ra_syntax::SyntaxNode;
9use ra_text_edit::{TextEdit, TextEditBuilder}; 8use ra_text_edit::{TextEdit, TextEditBuilder};
10use rustc_hash::FxHashMap; 9use rustc_hash::FxHashMap;
11use std::collections::HashMap; 10use std::collections::HashMap;
@@ -72,6 +71,7 @@ type Binding = HashMap<Var, SyntaxNode>;
72struct Match { 71struct Match {
73 place: SyntaxNode, 72 place: SyntaxNode,
74 binding: Binding, 73 binding: Binding,
74 ignored_comments: Vec<Comment>,
75} 75}
76 76
77#[derive(Debug)] 77#[derive(Debug)]
@@ -179,44 +179,61 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
179 pattern: &SyntaxElement, 179 pattern: &SyntaxElement,
180 code: &SyntaxElement, 180 code: &SyntaxElement,
181 placeholders: &[Var], 181 placeholders: &[Var],
182 match_: &mut Match, 182 mut match_: Match,
183 ) -> bool { 183 ) -> Option<Match> {
184 match (pattern, code) { 184 match (pattern, code) {
185 (SyntaxElement::Token(ref pattern), SyntaxElement::Token(ref code)) => { 185 (SyntaxElement::Token(ref pattern), SyntaxElement::Token(ref code)) => {
186 pattern.text() == code.text() 186 if pattern.text() == code.text() {
187 Some(match_)
188 } else {
189 None
190 }
187 } 191 }
188 (SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => { 192 (SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => {
189 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) { 193 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) {
190 match_.binding.insert(Var(pattern.text().to_string()), code.clone()); 194 match_.binding.insert(Var(pattern.text().to_string()), code.clone());
191 true 195 Some(match_)
192 } else { 196 } else {
193 pattern.green().children().count() == code.green().children().count() 197 let mut pattern_children = pattern
194 && pattern 198 .children_with_tokens()
195 .children_with_tokens() 199 .filter(|element| !element.kind().is_trivia());
196 .zip(code.children_with_tokens()) 200 let mut code_children =
197 .all(|(a, b)| check(&a, &b, placeholders, match_)) 201 code.children_with_tokens().filter(|element| !element.kind().is_trivia());
202 let new_ignored_comments = code.children_with_tokens().filter_map(|element| {
203 element.as_token().and_then(|token| Comment::cast(token.clone()))
204 });
205 match_.ignored_comments.extend(new_ignored_comments);
206 let match_from_children = pattern_children
207 .by_ref()
208 .zip(code_children.by_ref())
209 .fold(Some(match_), |accum, (a, b)| {
210 accum.and_then(|match_| check(&a, &b, placeholders, match_))
211 });
212 match_from_children.and_then(|match_| {
213 if pattern_children.count() == 0 && code_children.count() == 0 {
214 Some(match_)
215 } else {
216 None
217 }
218 })
198 } 219 }
199 } 220 }
200 _ => false, 221 _ => None,
201 } 222 }
202 } 223 }
203 let kind = pattern.pattern.kind(); 224 let kind = pattern.pattern.kind();
204 let matches = code 225 let matches = code
205 .descendants_with_tokens() 226 .descendants()
206 .filter(|n| n.kind() == kind) 227 .filter(|n| n.kind() == kind)
207 .filter_map(|code| { 228 .filter_map(|code| {
208 let mut match_ = 229 let match_ =
209 Match { place: code.as_node().unwrap().clone(), binding: HashMap::new() }; 230 Match { place: code.clone(), binding: HashMap::new(), ignored_comments: vec![] };
210 if check( 231 check(
211 &SyntaxElement::from(pattern.pattern.clone()), 232 &SyntaxElement::from(pattern.pattern.clone()),
212 &code, 233 &SyntaxElement::from(code),
213 &pattern.vars, 234 &pattern.vars,
214 &mut match_, 235 match_,
215 ) { 236 )
216 Some(match_)
217 } else {
218 None
219 }
220 }) 237 })
221 .collect(); 238 .collect();
222 SsrMatches { matches } 239 SsrMatches { matches }
@@ -225,18 +242,28 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
225fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit { 242fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit {
226 let mut builder = TextEditBuilder::default(); 243 let mut builder = TextEditBuilder::default();
227 for match_ in &matches.matches { 244 for match_ in &matches.matches {
228 builder.replace(match_.place.text_range(), render_replace(&match_.binding, template)); 245 builder.replace(
246 match_.place.text_range(),
247 render_replace(&match_.binding, &match_.ignored_comments, template),
248 );
229 } 249 }
230 builder.finish() 250 builder.finish()
231} 251}
232 252
233fn render_replace(binding: &Binding, template: &SsrTemplate) -> String { 253fn render_replace(
254 binding: &Binding,
255 ignored_comments: &Vec<Comment>,
256 template: &SsrTemplate,
257) -> String {
234 let mut builder = TextEditBuilder::default(); 258 let mut builder = TextEditBuilder::default();
235 for element in template.template.descendants() { 259 for element in template.template.descendants() {
236 if let Some(var) = template.placeholders.get(&element) { 260 if let Some(var) = template.placeholders.get(&element) {
237 builder.replace(element.text_range(), binding[var].to_string()) 261 builder.replace(element.text_range(), binding[var].to_string())
238 } 262 }
239 } 263 }
264 for comment in ignored_comments {
265 builder.insert(template.template.text_range().end(), comment.syntax().to_string())
266 }
240 builder.finish().apply(&template.template.text().to_string()) 267 builder.finish().apply(&template.template.text().to_string())
241} 268}
242 269
@@ -325,4 +352,66 @@ mod tests {
325 let edit = replace(&matches, &query.template); 352 let edit = replace(&matches, &query.template);
326 assert_eq!(edit.apply(input), "fn main() { bar(1+2); }"); 353 assert_eq!(edit.apply(input), "fn main() { bar(1+2); }");
327 } 354 }
355
356 fn assert_ssr_transform(query: &str, input: &str, result: &str) {
357 let query: SsrQuery = query.parse().unwrap();
358 let code = SourceFile::parse(input).tree();
359 let matches = find(&query.pattern, code.syntax());
360 let edit = replace(&matches, &query.template);
361 assert_eq!(edit.apply(input), result);
362 }
363
364 #[test]
365 fn ssr_function_to_method() {
366 assert_ssr_transform(
367 "my_function($a:expr, $b:expr) ==>> ($a).my_method($b)",
368 "loop { my_function( other_func(x, y), z + w) }",
369 "loop { (other_func(x, y)).my_method(z + w) }",
370 )
371 }
372
373 #[test]
374 fn ssr_nested_function() {
375 assert_ssr_transform(
376 "foo($a:expr, $b:expr, $c:expr) ==>> bar($c, baz($a, $b))",
377 "fn main { foo (x + value.method(b), x+y-z, true && false) }",
378 "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }",
379 )
380 }
381
382 #[test]
383 fn ssr_expected_spacing() {
384 assert_ssr_transform(
385 "foo($x:expr) + bar() ==>> bar($x)",
386 "fn main() { foo(5) + bar() }",
387 "fn main() { bar(5) }",
388 );
389 }
390
391 #[test]
392 fn ssr_with_extra_space() {
393 assert_ssr_transform(
394 "foo($x:expr ) + bar() ==>> bar($x)",
395 "fn main() { foo( 5 ) +bar( ) }",
396 "fn main() { bar(5) }",
397 );
398 }
399
400 #[test]
401 fn ssr_keeps_nested_comment() {
402 assert_ssr_transform(
403 "foo($x:expr) ==>> bar($x)",
404 "fn main() { foo(other(5 /* using 5 */)) }",
405 "fn main() { bar(other(5 /* using 5 */)) }",
406 )
407 }
408
409 #[test]
410 fn ssr_keeps_comment() {
411 assert_ssr_transform(
412 "foo($x:expr) ==>> bar($x)",
413 "fn main() { foo(5 /* using 5 */) }",
414 "fn main() { bar(5)/* using 5 */ }",
415 )
416 }
328} 417}