diff options
author | adamrk <[email protected]> | 2020-02-22 21:58:48 +0000 |
---|---|---|
committer | adamrk <[email protected]> | 2020-02-23 21:14:47 +0000 |
commit | 4f1d90e73bb47b4da3df62b05a84ebd4297ed78d (patch) | |
tree | 4dd4fc4fd03b098abdfc44d3d21a3f0e296e0ca3 /crates | |
parent | cd01e72a9eca909392caea7efa5744f0f5578a41 (diff) |
Handle trivia in strucural search and replace
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_ide/src/ssr.rs | 145 |
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 @@ | |||
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::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken}; |
7 | use ra_syntax::SyntaxElement; | ||
8 | use ra_syntax::SyntaxNode; | ||
9 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 7 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
10 | use rustc_hash::FxHashMap; | 8 | use rustc_hash::FxHashMap; |
11 | use std::collections::HashMap; | 9 | use std::collections::HashMap; |
@@ -72,6 +70,7 @@ type Binding = HashMap<Var, SyntaxNode>; | |||
72 | struct Match { | 70 | struct 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 { | |||
225 | fn replace(matches: &SsrMatches, template: &SsrTemplate) -> TextEdit { | 244 | fn 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 | ||
233 | fn render_replace(binding: &Binding, template: &SsrTemplate) -> String { | 255 | fn 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 | } |