diff options
Diffstat (limited to 'crates/ra_ide')
-rw-r--r-- | crates/ra_ide/src/ssr.rs | 141 |
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 @@ | |||
3 | use crate::source_change::SourceFileEdit; | 3 | use crate::source_change::SourceFileEdit; |
4 | use ra_ide_db::RootDatabase; | 4 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::ast::make::expr_from_text; | 5 | use ra_syntax::ast::make::expr_from_text; |
6 | use ra_syntax::AstNode; | 6 | use ra_syntax::ast::{AstToken, Comment}; |
7 | use ra_syntax::SyntaxElement; | 7 | use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; |
8 | use ra_syntax::SyntaxNode; | ||
9 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 8 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
10 | use rustc_hash::FxHashMap; | 9 | use rustc_hash::FxHashMap; |
11 | use std::collections::HashMap; | 10 | use std::collections::HashMap; |
@@ -72,6 +71,7 @@ type Binding = HashMap<Var, SyntaxNode>; | |||
72 | struct Match { | 71 | struct 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 { | |||
225 | fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit { | 242 | fn 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 | ||
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 { | ||
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 | } |