aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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}