Skip to content

Commit 50956f8

Browse files
bors[bot]adamrk
andauthored
Merge #3285
3285: Handle trivia in Structural Search and Replace r=matklad a=adamrk Addresses the second point of #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]>
2 parents 7f7d96c + b1ee6d1 commit 50956f8

File tree

1 file changed

+115
-26
lines changed

1 file changed

+115
-26
lines changed

crates/ra_ide/src/ssr.rs

Lines changed: 115 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
use crate::source_change::SourceFileEdit;
44
use ra_ide_db::RootDatabase;
55
use ra_syntax::ast::make::expr_from_text;
6-
use ra_syntax::AstNode;
7-
use ra_syntax::SyntaxElement;
8-
use ra_syntax::SyntaxNode;
6+
use ra_syntax::ast::{AstToken, Comment};
7+
use ra_syntax::{AstNode, SyntaxElement, SyntaxNode};
98
use ra_text_edit::{TextEdit, TextEditBuilder};
109
use rustc_hash::FxHashMap;
1110
use std::collections::HashMap;
@@ -72,6 +71,7 @@ type Binding = HashMap<Var, SyntaxNode>;
7271
struct Match {
7372
place: SyntaxNode,
7473
binding: Binding,
74+
ignored_comments: Vec<Comment>,
7575
}
7676

7777
#[derive(Debug)]
@@ -179,44 +179,61 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
179179
pattern: &SyntaxElement,
180180
code: &SyntaxElement,
181181
placeholders: &[Var],
182-
match_: &mut Match,
183-
) -> bool {
182+
mut match_: Match,
183+
) -> Option<Match> {
184184
match (pattern, code) {
185185
(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+
}
187191
}
188192
(SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => {
189193
if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) {
190194
match_.binding.insert(Var(pattern.text().to_string()), code.clone());
191-
true
195+
Some(match_)
192196
} else {
193-
pattern.green().children().count() == code.green().children().count()
194-
&& pattern
195-
.children_with_tokens()
196-
.zip(code.children_with_tokens())
197-
.all(|(a, b)| check(&a, &b, placeholders, match_))
197+
let mut pattern_children = pattern
198+
.children_with_tokens()
199+
.filter(|element| !element.kind().is_trivia());
200+
let mut code_children =
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+
})
198219
}
199220
}
200-
_ => false,
221+
_ => None,
201222
}
202223
}
203224
let kind = pattern.pattern.kind();
204225
let matches = code
205-
.descendants_with_tokens()
226+
.descendants()
206227
.filter(|n| n.kind() == kind)
207228
.filter_map(|code| {
208-
let mut match_ =
209-
Match { place: code.as_node().unwrap().clone(), binding: HashMap::new() };
210-
if check(
229+
let match_ =
230+
Match { place: code.clone(), binding: HashMap::new(), ignored_comments: vec![] };
231+
check(
211232
&SyntaxElement::from(pattern.pattern.clone()),
212-
&code,
233+
&SyntaxElement::from(code),
213234
&pattern.vars,
214-
&mut match_,
215-
) {
216-
Some(match_)
217-
} else {
218-
None
219-
}
235+
match_,
236+
)
220237
})
221238
.collect();
222239
SsrMatches { matches }
@@ -225,18 +242,28 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
225242
fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit {
226243
let mut builder = TextEditBuilder::default();
227244
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+
);
229249
}
230250
builder.finish()
231251
}
232252

233-
fn render_replace(binding: &Binding, template: &SsrTemplate) -> String {
253+
fn render_replace(
254+
binding: &Binding,
255+
ignored_comments: &Vec<Comment>,
256+
template: &SsrTemplate,
257+
) -> String {
234258
let mut builder = TextEditBuilder::default();
235259
for element in template.template.descendants() {
236260
if let Some(var) = template.placeholders.get(&element) {
237261
builder.replace(element.text_range(), binding[var].to_string())
238262
}
239263
}
264+
for comment in ignored_comments {
265+
builder.insert(template.template.text_range().end(), comment.syntax().to_string())
266+
}
240267
builder.finish().apply(&template.template.text().to_string())
241268
}
242269

@@ -325,4 +352,66 @@ mod tests {
325352
let edit = replace(&matches, &query.template);
326353
assert_eq!(edit.apply(input), "fn main() { bar(1+2); }");
327354
}
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+
}
328417
}

0 commit comments

Comments
 (0)