aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/ssr.rs145
1 files changed, 118 insertions, 27 deletions
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 902c29fc6..83c212494 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -3,9 +3,7 @@
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::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken};
7use ra_syntax::SyntaxElement;
8use ra_syntax::SyntaxNode;
9use ra_text_edit::{TextEdit, TextEditBuilder}; 7use ra_text_edit::{TextEdit, TextEditBuilder};
10use rustc_hash::FxHashMap; 8use rustc_hash::FxHashMap;
11use std::collections::HashMap; 9use std::collections::HashMap;
@@ -72,6 +70,7 @@ type Binding = HashMap<Var, SyntaxNode>;
72struct Match { 70struct Match {
73 place: SyntaxNode, 71 place: SyntaxNode,
74 binding: Binding, 72 binding: Binding,
73 ignored_comments: Vec<SyntaxToken>,
75} 74}
76 75
77#[derive(Debug)] 76#[derive(Debug)]
@@ -179,25 +178,51 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
179 pattern: &SyntaxElement, 178 pattern: &SyntaxElement,
180 code: &SyntaxElement, 179 code: &SyntaxElement,
181 placeholders: &[Var], 180 placeholders: &[Var],
182 match_: &mut Match, 181 mut match_: Match,
183 ) -> bool { 182 ) -> Option<Match> {
184 match (pattern, code) { 183 match (pattern, code) {
185 (SyntaxElement::Token(ref pattern), SyntaxElement::Token(ref code)) => { 184 (SyntaxElement::Token(ref pattern), SyntaxElement::Token(ref code)) => {
186 pattern.text() == code.text() 185 if pattern.text() == code.text() {
186 Some(match_)
187 } else {
188 None
189 }
187 } 190 }
188 (SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => { 191 (SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => {
189 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) { 192 if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) {
190 match_.binding.insert(Var(pattern.text().to_string()), code.clone()); 193 match_.binding.insert(Var(pattern.text().to_string()), code.clone());
191 true 194 Some(match_)
192 } else { 195 } else {
193 pattern.green().children().count() == code.green().children().count() 196 let mut pattern_children = pattern
194 && pattern 197 .children_with_tokens()
195 .children_with_tokens() 198 .filter(|element| !element.kind().is_trivia());
196 .zip(code.children_with_tokens()) 199 let mut code_children =
197 .all(|(a, b)| check(&a, &b, placeholders, match_)) 200 code.children_with_tokens().filter(|element| !element.kind().is_trivia());
201 let new_ignored_comments = code.children_with_tokens().filter_map(|element| {
202 if let SyntaxElement::Token(token) = element {
203 if token.kind() == SyntaxKind::COMMENT {
204 return Some(token.clone());
205 }
206 }
207 None
208 });
209 match_.ignored_comments.extend(new_ignored_comments);
210 let match_from_children = pattern_children
211 .by_ref()
212 .zip(code_children.by_ref())
213 .fold(Some(match_), |accum, (a, b)| {
214 accum.and_then(|match_| check(&a, &b, placeholders, match_))
215 });
216 match_from_children.and_then(|match_| {
217 if pattern_children.count() == 0 && code_children.count() == 0 {
218 Some(match_)
219 } else {
220 None
221 }
222 })
198 } 223 }
199 } 224 }
200 _ => false, 225 _ => None,
201 } 226 }
202 } 227 }
203 let kind = pattern.pattern.kind(); 228 let kind = pattern.pattern.kind();
@@ -205,18 +230,12 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
205 .descendants_with_tokens() 230 .descendants_with_tokens()
206 .filter(|n| n.kind() == kind) 231 .filter(|n| n.kind() == kind)
207 .filter_map(|code| { 232 .filter_map(|code| {
208 let mut match_ = 233 let match_ = Match {
209 Match { place: code.as_node().unwrap().clone(), binding: HashMap::new() }; 234 place: code.as_node().unwrap().clone(),
210 if check( 235 binding: HashMap::new(),
211 &SyntaxElement::from(pattern.pattern.clone()), 236 ignored_comments: vec![],
212 &code, 237 };
213 &pattern.vars, 238 check(&SyntaxElement::from(pattern.pattern.clone()), &code, &pattern.vars, match_)
214 &mut match_,
215 ) {
216 Some(match_)
217 } else {
218 None
219 }
220 }) 239 })
221 .collect(); 240 .collect();
222 SsrMatches { matches } 241 SsrMatches { matches }
@@ -225,18 +244,28 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
225fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit { 244fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit {
226 let mut builder = TextEditBuilder::default(); 245 let mut builder = TextEditBuilder::default();
227 for match_ in &matches.matches { 246 for match_ in &matches.matches {
228 builder.replace(match_.place.text_range(), render_replace(&match_.binding, template)); 247 builder.replace(
248 match_.place.text_range(),
249 render_replace(&match_.binding, &match_.ignored_comments, template),
250 );
229 } 251 }
230 builder.finish() 252 builder.finish()
231} 253}
232 254
233fn render_replace(binding: &Binding, template: &SsrTemplate) -> String { 255fn render_replace(
256 binding: &Binding,
257 ignored_comments: &Vec<SyntaxToken>,
258 template: &SsrTemplate,
259) -> String {
234 let mut builder = TextEditBuilder::default(); 260 let mut builder = TextEditBuilder::default();
235 for element in template.template.descendants() { 261 for element in template.template.descendants() {
236 if let Some(var) = template.placeholders.get(&element) { 262 if let Some(var) = template.placeholders.get(&element) {
237 builder.replace(element.text_range(), binding[var].to_string()) 263 builder.replace(element.text_range(), binding[var].to_string())
238 } 264 }
239 } 265 }
266 for comment in ignored_comments {
267 builder.insert(template.template.text_range().end(), comment.to_string())
268 }
240 builder.finish().apply(&template.template.text().to_string()) 269 builder.finish().apply(&template.template.text().to_string())
241} 270}
242 271
@@ -325,4 +354,66 @@ mod tests {
325 let edit = replace(&matches, &query.template); 354 let edit = replace(&matches, &query.template);
326 assert_eq!(edit.apply(input), "fn main() { bar(1+2); }"); 355 assert_eq!(edit.apply(input), "fn main() { bar(1+2); }");
327 } 356 }
357
358 fn assert_ssr_transform(query: &str, input: &str, result: &str) {
359 let query: SsrQuery = query.parse().unwrap();
360 let code = SourceFile::parse(input).tree();
361 let matches = find(&query.pattern, code.syntax());
362 let edit = replace(&matches, &query.template);
363 assert_eq!(edit.apply(input), result);
364 }
365
366 #[test]
367 fn ssr_function_to_method() {
368 assert_ssr_transform(
369 "my_function($a:expr, $b:expr) ==>> ($a).my_method($b)",
370 "loop { my_function( other_func(x, y), z + w) }",
371 "loop { (other_func(x, y)).my_method(z + w) }",
372 )
373 }
374
375 #[test]
376 fn ssr_nested_function() {
377 assert_ssr_transform(
378 "foo($a:expr, $b:expr, $c:expr) ==>> bar($c, baz($a, $b))",
379 "fn main { foo (x + value.method(b), x+y-z, true && false) }",
380 "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }",
381 )
382 }
383
384 #[test]
385 fn ssr_expected_spacing() {
386 assert_ssr_transform(
387 "foo($x:expr) + bar() ==>> bar($x)",
388 "fn main() { foo(5) + bar() }",
389 "fn main() { bar(5) }",
390 );
391 }
392
393 #[test]
394 fn ssr_with_extra_space() {
395 assert_ssr_transform(
396 "foo($x:expr ) + bar() ==>> bar($x)",
397 "fn main() { foo( 5 ) +bar( ) }",
398 "fn main() { bar(5) }",
399 );
400 }
401
402 #[test]
403 fn ssr_keeps_nested_comment() {
404 assert_ssr_transform(
405 "foo($x:expr) ==>> bar($x)",
406 "fn main() { foo(other(5 /* using 5 */)) }",
407 "fn main() { bar(other(5 /* using 5 */)) }",
408 )
409 }
410
411 #[test]
412 fn ssr_keeps_comment() {
413 assert_ssr_transform(
414 "foo($x:expr) ==>> bar($x)",
415 "fn main() { foo(5 /* using 5 */) }",
416 "fn main() { bar(5)/* using 5 */ }",
417 )
418 }
328} 419}