diff options
Diffstat (limited to 'crates/ra_ssr/src/tests.rs')
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs new file mode 100644 index 000000000..3ee1e74e9 --- /dev/null +++ b/crates/ra_ssr/src/tests.rs | |||
@@ -0,0 +1,549 @@ | |||
1 | use crate::matching::MatchFailureReason; | ||
2 | use crate::{matching, Match, MatchFinder, SsrMatches, SsrPattern, SsrRule}; | ||
3 | use matching::record_match_fails_reasons_scope; | ||
4 | use ra_db::{FileId, FileRange, SourceDatabaseExt}; | ||
5 | use ra_syntax::ast::AstNode; | ||
6 | use ra_syntax::{ast, SyntaxKind, SyntaxNode, TextRange}; | ||
7 | |||
8 | struct MatchDebugInfo { | ||
9 | node: SyntaxNode, | ||
10 | /// Our search pattern parsed as the same kind of syntax node as `node`. e.g. expression, item, | ||
11 | /// etc. Will be absent if the pattern can't be parsed as that kind. | ||
12 | pattern: Result<SyntaxNode, MatchFailureReason>, | ||
13 | matched: Result<Match, MatchFailureReason>, | ||
14 | } | ||
15 | |||
16 | impl SsrPattern { | ||
17 | pub(crate) fn tree_for_kind_with_reason( | ||
18 | &self, | ||
19 | kind: SyntaxKind, | ||
20 | ) -> Result<&SyntaxNode, MatchFailureReason> { | ||
21 | record_match_fails_reasons_scope(true, || self.tree_for_kind(kind)) | ||
22 | .map_err(|e| MatchFailureReason { reason: e.reason.unwrap() }) | ||
23 | } | ||
24 | } | ||
25 | |||
26 | impl std::fmt::Debug for MatchDebugInfo { | ||
27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
28 | write!(f, "========= PATTERN ==========\n")?; | ||
29 | match &self.pattern { | ||
30 | Ok(pattern) => { | ||
31 | write!(f, "{:#?}", pattern)?; | ||
32 | } | ||
33 | Err(err) => { | ||
34 | write!(f, "{}", err.reason)?; | ||
35 | } | ||
36 | } | ||
37 | write!( | ||
38 | f, | ||
39 | "\n============ AST ===========\n\ | ||
40 | {:#?}\n============================", | ||
41 | self.node | ||
42 | )?; | ||
43 | match &self.matched { | ||
44 | Ok(_) => write!(f, "Node matched")?, | ||
45 | Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?, | ||
46 | } | ||
47 | Ok(()) | ||
48 | } | ||
49 | } | ||
50 | |||
51 | impl SsrMatches { | ||
52 | /// Returns `self` with any nested matches removed and made into top-level matches. | ||
53 | pub(crate) fn flattened(self) -> SsrMatches { | ||
54 | let mut out = SsrMatches::default(); | ||
55 | self.flatten_into(&mut out); | ||
56 | out | ||
57 | } | ||
58 | |||
59 | fn flatten_into(self, out: &mut SsrMatches) { | ||
60 | for mut m in self.matches { | ||
61 | for p in m.placeholder_values.values_mut() { | ||
62 | std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out); | ||
63 | } | ||
64 | out.matches.push(m); | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | |||
69 | impl Match { | ||
70 | pub(crate) fn matched_text(&self) -> String { | ||
71 | self.matched_node.text().to_string() | ||
72 | } | ||
73 | } | ||
74 | |||
75 | impl<'db> MatchFinder<'db> { | ||
76 | /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you | ||
77 | /// intend to do replacement, use `add_rule` instead. | ||
78 | fn add_search_pattern(&mut self, pattern: SsrPattern) { | ||
79 | self.add_rule(SsrRule { pattern, template: "()".parse().unwrap() }) | ||
80 | } | ||
81 | |||
82 | /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match | ||
83 | /// them, while recording reasons why they don't match. This API is useful for command | ||
84 | /// line-based debugging where providing a range is difficult. | ||
85 | fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> { | ||
86 | let file = self.sema.parse(file_id); | ||
87 | let mut res = Vec::new(); | ||
88 | let file_text = self.sema.db.file_text(file_id); | ||
89 | let mut remaining_text = file_text.as_str(); | ||
90 | let mut base = 0; | ||
91 | let len = snippet.len() as u32; | ||
92 | while let Some(offset) = remaining_text.find(snippet) { | ||
93 | let start = base + offset as u32; | ||
94 | let end = start + len; | ||
95 | self.output_debug_for_nodes_at_range( | ||
96 | file.syntax(), | ||
97 | TextRange::new(start.into(), end.into()), | ||
98 | &None, | ||
99 | &mut res, | ||
100 | ); | ||
101 | remaining_text = &remaining_text[offset + snippet.len()..]; | ||
102 | base = end; | ||
103 | } | ||
104 | res | ||
105 | } | ||
106 | |||
107 | fn output_debug_for_nodes_at_range( | ||
108 | &self, | ||
109 | node: &SyntaxNode, | ||
110 | range: TextRange, | ||
111 | restrict_range: &Option<FileRange>, | ||
112 | out: &mut Vec<MatchDebugInfo>, | ||
113 | ) { | ||
114 | for node in node.children() { | ||
115 | if !node.text_range().contains_range(range) { | ||
116 | continue; | ||
117 | } | ||
118 | if node.text_range() == range { | ||
119 | for rule in &self.rules { | ||
120 | let pattern = | ||
121 | rule.pattern.tree_for_kind_with_reason(node.kind()).map(|p| p.clone()); | ||
122 | out.push(MatchDebugInfo { | ||
123 | matched: matching::get_match(true, rule, &node, restrict_range, &self.sema) | ||
124 | .map_err(|e| MatchFailureReason { | ||
125 | reason: e.reason.unwrap_or_else(|| { | ||
126 | "Match failed, but no reason was given".to_owned() | ||
127 | }), | ||
128 | }), | ||
129 | pattern, | ||
130 | node: node.clone(), | ||
131 | }); | ||
132 | } | ||
133 | } else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) { | ||
134 | if let Some(expanded) = self.sema.expand(¯o_call) { | ||
135 | if let Some(tt) = macro_call.token_tree() { | ||
136 | self.output_debug_for_nodes_at_range( | ||
137 | &expanded, | ||
138 | range, | ||
139 | &Some(self.sema.original_range(tt.syntax())), | ||
140 | out, | ||
141 | ); | ||
142 | } | ||
143 | } | ||
144 | } | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | |||
149 | fn parse_error_text(query: &str) -> String { | ||
150 | format!("{}", query.parse::<SsrRule>().unwrap_err()) | ||
151 | } | ||
152 | |||
153 | #[test] | ||
154 | fn parser_empty_query() { | ||
155 | assert_eq!(parse_error_text(""), "Parse error: Cannot find delemiter `==>>`"); | ||
156 | } | ||
157 | |||
158 | #[test] | ||
159 | fn parser_no_delimiter() { | ||
160 | assert_eq!(parse_error_text("foo()"), "Parse error: Cannot find delemiter `==>>`"); | ||
161 | } | ||
162 | |||
163 | #[test] | ||
164 | fn parser_two_delimiters() { | ||
165 | assert_eq!( | ||
166 | parse_error_text("foo() ==>> a ==>> b "), | ||
167 | "Parse error: More than one delimiter found" | ||
168 | ); | ||
169 | } | ||
170 | |||
171 | #[test] | ||
172 | fn parser_repeated_name() { | ||
173 | assert_eq!( | ||
174 | parse_error_text("foo($a, $a) ==>>"), | ||
175 | "Parse error: Name `a` repeats more than once" | ||
176 | ); | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn parser_invalid_pattern() { | ||
181 | assert_eq!( | ||
182 | parse_error_text(" ==>> ()"), | ||
183 | "Parse error: Pattern is not a valid Rust expression, type, item, path or pattern" | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn parser_invalid_template() { | ||
189 | assert_eq!( | ||
190 | parse_error_text("() ==>> )"), | ||
191 | "Parse error: Replacement is not a valid Rust expression, type, item, path or pattern" | ||
192 | ); | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn parser_undefined_placeholder_in_replacement() { | ||
197 | assert_eq!( | ||
198 | parse_error_text("42 ==>> $a"), | ||
199 | "Parse error: Replacement contains undefined placeholders: $a" | ||
200 | ); | ||
201 | } | ||
202 | |||
203 | fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { | ||
204 | use ra_db::fixture::WithFixture; | ||
205 | ra_ide_db::RootDatabase::with_single_file(code) | ||
206 | } | ||
207 | |||
208 | fn assert_ssr_transform(rule: &str, input: &str, result: &str) { | ||
209 | assert_ssr_transforms(&[rule], input, result); | ||
210 | } | ||
211 | |||
212 | fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { | ||
213 | let (db, file_id) = single_file(input); | ||
214 | let mut match_finder = MatchFinder::new(&db); | ||
215 | for rule in rules { | ||
216 | let rule: SsrRule = rule.parse().unwrap(); | ||
217 | match_finder.add_rule(rule); | ||
218 | } | ||
219 | if let Some(edits) = match_finder.edits_for_file(file_id) { | ||
220 | let mut after = input.to_string(); | ||
221 | edits.apply(&mut after); | ||
222 | assert_eq!(after, result); | ||
223 | } else { | ||
224 | panic!("No edits were made"); | ||
225 | } | ||
226 | } | ||
227 | |||
228 | fn assert_matches(pattern: &str, code: &str, expected: &[&str]) { | ||
229 | let (db, file_id) = single_file(code); | ||
230 | let mut match_finder = MatchFinder::new(&db); | ||
231 | match_finder.add_search_pattern(pattern.parse().unwrap()); | ||
232 | let matched_strings: Vec<String> = match_finder | ||
233 | .find_matches_in_file(file_id) | ||
234 | .flattened() | ||
235 | .matches | ||
236 | .iter() | ||
237 | .map(|m| m.matched_text()) | ||
238 | .collect(); | ||
239 | if matched_strings != expected && !expected.is_empty() { | ||
240 | let debug_info = match_finder.debug_where_text_equal(file_id, &expected[0]); | ||
241 | eprintln!("Test is about to fail. Some possibly useful info: {} nodes had text exactly equal to '{}'", debug_info.len(), &expected[0]); | ||
242 | for d in debug_info { | ||
243 | eprintln!("{:#?}", d); | ||
244 | } | ||
245 | } | ||
246 | assert_eq!(matched_strings, expected); | ||
247 | } | ||
248 | |||
249 | fn assert_no_match(pattern: &str, code: &str) { | ||
250 | assert_matches(pattern, code, &[]); | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn ssr_function_to_method() { | ||
255 | assert_ssr_transform( | ||
256 | "my_function($a, $b) ==>> ($a).my_method($b)", | ||
257 | "loop { my_function( other_func(x, y), z + w) }", | ||
258 | "loop { (other_func(x, y)).my_method(z + w) }", | ||
259 | ) | ||
260 | } | ||
261 | |||
262 | #[test] | ||
263 | fn ssr_nested_function() { | ||
264 | assert_ssr_transform( | ||
265 | "foo($a, $b, $c) ==>> bar($c, baz($a, $b))", | ||
266 | "fn main { foo (x + value.method(b), x+y-z, true && false) }", | ||
267 | "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", | ||
268 | ) | ||
269 | } | ||
270 | |||
271 | #[test] | ||
272 | fn ssr_expected_spacing() { | ||
273 | assert_ssr_transform( | ||
274 | "foo($x) + bar() ==>> bar($x)", | ||
275 | "fn main() { foo(5) + bar() }", | ||
276 | "fn main() { bar(5) }", | ||
277 | ); | ||
278 | } | ||
279 | |||
280 | #[test] | ||
281 | fn ssr_with_extra_space() { | ||
282 | assert_ssr_transform( | ||
283 | "foo($x ) + bar() ==>> bar($x)", | ||
284 | "fn main() { foo( 5 ) +bar( ) }", | ||
285 | "fn main() { bar(5) }", | ||
286 | ); | ||
287 | } | ||
288 | |||
289 | #[test] | ||
290 | fn ssr_keeps_nested_comment() { | ||
291 | assert_ssr_transform( | ||
292 | "foo($x) ==>> bar($x)", | ||
293 | "fn main() { foo(other(5 /* using 5 */)) }", | ||
294 | "fn main() { bar(other(5 /* using 5 */)) }", | ||
295 | ) | ||
296 | } | ||
297 | |||
298 | #[test] | ||
299 | fn ssr_keeps_comment() { | ||
300 | assert_ssr_transform( | ||
301 | "foo($x) ==>> bar($x)", | ||
302 | "fn main() { foo(5 /* using 5 */) }", | ||
303 | "fn main() { bar(5)/* using 5 */ }", | ||
304 | ) | ||
305 | } | ||
306 | |||
307 | #[test] | ||
308 | fn ssr_struct_lit() { | ||
309 | assert_ssr_transform( | ||
310 | "foo{a: $a, b: $b} ==>> foo::new($a, $b)", | ||
311 | "fn main() { foo{b:2, a:1} }", | ||
312 | "fn main() { foo::new(1, 2) }", | ||
313 | ) | ||
314 | } | ||
315 | |||
316 | #[test] | ||
317 | fn ignores_whitespace() { | ||
318 | assert_matches("1+2", "fn f() -> i32 {1 + 2}", &["1 + 2"]); | ||
319 | assert_matches("1 + 2", "fn f() -> i32 {1+2}", &["1+2"]); | ||
320 | } | ||
321 | |||
322 | #[test] | ||
323 | fn no_match() { | ||
324 | assert_no_match("1 + 3", "fn f() -> i32 {1 + 2}"); | ||
325 | } | ||
326 | |||
327 | #[test] | ||
328 | fn match_fn_definition() { | ||
329 | assert_matches("fn $a($b: $t) {$c}", "fn f(a: i32) {bar()}", &["fn f(a: i32) {bar()}"]); | ||
330 | } | ||
331 | |||
332 | #[test] | ||
333 | fn match_struct_definition() { | ||
334 | assert_matches( | ||
335 | "struct $n {$f: Option<String>}", | ||
336 | "struct Bar {} struct Foo {name: Option<String>}", | ||
337 | &["struct Foo {name: Option<String>}"], | ||
338 | ); | ||
339 | } | ||
340 | |||
341 | #[test] | ||
342 | fn match_expr() { | ||
343 | let code = "fn f() -> i32 {foo(40 + 2, 42)}"; | ||
344 | assert_matches("foo($a, $b)", code, &["foo(40 + 2, 42)"]); | ||
345 | assert_no_match("foo($a, $b, $c)", code); | ||
346 | assert_no_match("foo($a)", code); | ||
347 | } | ||
348 | |||
349 | #[test] | ||
350 | fn match_nested_method_calls() { | ||
351 | assert_matches( | ||
352 | "$a.z().z().z()", | ||
353 | "fn f() {h().i().j().z().z().z().d().e()}", | ||
354 | &["h().i().j().z().z().z()"], | ||
355 | ); | ||
356 | } | ||
357 | |||
358 | #[test] | ||
359 | fn match_complex_expr() { | ||
360 | let code = "fn f() -> i32 {foo(bar(40, 2), 42)}"; | ||
361 | assert_matches("foo($a, $b)", code, &["foo(bar(40, 2), 42)"]); | ||
362 | assert_no_match("foo($a, $b, $c)", code); | ||
363 | assert_no_match("foo($a)", code); | ||
364 | assert_matches("bar($a, $b)", code, &["bar(40, 2)"]); | ||
365 | } | ||
366 | |||
367 | // Trailing commas in the code should be ignored. | ||
368 | #[test] | ||
369 | fn match_with_trailing_commas() { | ||
370 | // Code has comma, pattern doesn't. | ||
371 | assert_matches("foo($a, $b)", "fn f() {foo(1, 2,);}", &["foo(1, 2,)"]); | ||
372 | assert_matches("Foo{$a, $b}", "fn f() {Foo{1, 2,};}", &["Foo{1, 2,}"]); | ||
373 | |||
374 | // Pattern has comma, code doesn't. | ||
375 | assert_matches("foo($a, $b,)", "fn f() {foo(1, 2);}", &["foo(1, 2)"]); | ||
376 | assert_matches("Foo{$a, $b,}", "fn f() {Foo{1, 2};}", &["Foo{1, 2}"]); | ||
377 | } | ||
378 | |||
379 | #[test] | ||
380 | fn match_type() { | ||
381 | assert_matches("i32", "fn f() -> i32 {1 + 2}", &["i32"]); | ||
382 | assert_matches("Option<$a>", "fn f() -> Option<i32> {42}", &["Option<i32>"]); | ||
383 | assert_no_match("Option<$a>", "fn f() -> Result<i32, ()> {42}"); | ||
384 | } | ||
385 | |||
386 | #[test] | ||
387 | fn match_struct_instantiation() { | ||
388 | assert_matches( | ||
389 | "Foo {bar: 1, baz: 2}", | ||
390 | "fn f() {Foo {bar: 1, baz: 2}}", | ||
391 | &["Foo {bar: 1, baz: 2}"], | ||
392 | ); | ||
393 | // Now with placeholders for all parts of the struct. | ||
394 | assert_matches( | ||
395 | "Foo {$a: $b, $c: $d}", | ||
396 | "fn f() {Foo {bar: 1, baz: 2}}", | ||
397 | &["Foo {bar: 1, baz: 2}"], | ||
398 | ); | ||
399 | assert_matches("Foo {}", "fn f() {Foo {}}", &["Foo {}"]); | ||
400 | } | ||
401 | |||
402 | #[test] | ||
403 | fn match_path() { | ||
404 | assert_matches("foo::bar", "fn f() {foo::bar(42)}", &["foo::bar"]); | ||
405 | assert_matches("$a::bar", "fn f() {foo::bar(42)}", &["foo::bar"]); | ||
406 | assert_matches("foo::$b", "fn f() {foo::bar(42)}", &["foo::bar"]); | ||
407 | } | ||
408 | |||
409 | #[test] | ||
410 | fn match_pattern() { | ||
411 | assert_matches("Some($a)", "fn f() {if let Some(x) = foo() {}}", &["Some(x)"]); | ||
412 | } | ||
413 | |||
414 | #[test] | ||
415 | fn match_reordered_struct_instantiation() { | ||
416 | assert_matches( | ||
417 | "Foo {aa: 1, b: 2, ccc: 3}", | ||
418 | "fn f() {Foo {b: 2, ccc: 3, aa: 1}}", | ||
419 | &["Foo {b: 2, ccc: 3, aa: 1}"], | ||
420 | ); | ||
421 | assert_no_match("Foo {a: 1}", "fn f() {Foo {b: 1}}"); | ||
422 | assert_no_match("Foo {a: 1}", "fn f() {Foo {a: 2}}"); | ||
423 | assert_no_match("Foo {a: 1, b: 2}", "fn f() {Foo {a: 1}}"); | ||
424 | assert_no_match("Foo {a: 1, b: 2}", "fn f() {Foo {b: 2}}"); | ||
425 | assert_no_match("Foo {a: 1, }", "fn f() {Foo {a: 1, b: 2}}"); | ||
426 | assert_no_match("Foo {a: 1, z: 9}", "fn f() {Foo {a: 1}}"); | ||
427 | } | ||
428 | |||
429 | #[test] | ||
430 | fn match_macro_invocation() { | ||
431 | assert_matches("foo!($a)", "fn() {foo(foo!(foo()))}", &["foo!(foo())"]); | ||
432 | assert_matches("foo!(41, $a, 43)", "fn() {foo!(41, 42, 43)}", &["foo!(41, 42, 43)"]); | ||
433 | assert_no_match("foo!(50, $a, 43)", "fn() {foo!(41, 42, 43}"); | ||
434 | assert_no_match("foo!(41, $a, 50)", "fn() {foo!(41, 42, 43}"); | ||
435 | assert_matches("foo!($a())", "fn() {foo!(bar())}", &["foo!(bar())"]); | ||
436 | } | ||
437 | |||
438 | // When matching within a macro expansion, we only allow matches of nodes that originated from | ||
439 | // the macro call, not from the macro definition. | ||
440 | #[test] | ||
441 | fn no_match_expression_from_macro() { | ||
442 | assert_no_match( | ||
443 | "$a.clone()", | ||
444 | r#" | ||
445 | macro_rules! m1 { | ||
446 | () => {42.clone()} | ||
447 | } | ||
448 | fn f1() {m1!()} | ||
449 | "#, | ||
450 | ); | ||
451 | } | ||
452 | |||
453 | // We definitely don't want to allow matching of an expression that part originates from the | ||
454 | // macro call `42` and part from the macro definition `.clone()`. | ||
455 | #[test] | ||
456 | fn no_match_split_expression() { | ||
457 | assert_no_match( | ||
458 | "$a.clone()", | ||
459 | r#" | ||
460 | macro_rules! m1 { | ||
461 | ($x:expr) => {$x.clone()} | ||
462 | } | ||
463 | fn f1() {m1!(42)} | ||
464 | "#, | ||
465 | ); | ||
466 | } | ||
467 | |||
468 | #[test] | ||
469 | fn replace_function_call() { | ||
470 | assert_ssr_transform("foo() ==>> bar()", "fn f1() {foo(); foo();}", "fn f1() {bar(); bar();}"); | ||
471 | } | ||
472 | |||
473 | #[test] | ||
474 | fn replace_function_call_with_placeholders() { | ||
475 | assert_ssr_transform( | ||
476 | "foo($a, $b) ==>> bar($b, $a)", | ||
477 | "fn f1() {foo(5, 42)}", | ||
478 | "fn f1() {bar(42, 5)}", | ||
479 | ); | ||
480 | } | ||
481 | |||
482 | #[test] | ||
483 | fn replace_nested_function_calls() { | ||
484 | assert_ssr_transform( | ||
485 | "foo($a) ==>> bar($a)", | ||
486 | "fn f1() {foo(foo(42))}", | ||
487 | "fn f1() {bar(bar(42))}", | ||
488 | ); | ||
489 | } | ||
490 | |||
491 | #[test] | ||
492 | fn replace_type() { | ||
493 | assert_ssr_transform( | ||
494 | "Result<(), $a> ==>> Option<$a>", | ||
495 | "fn f1() -> Result<(), Vec<Error>> {foo()}", | ||
496 | "fn f1() -> Option<Vec<Error>> {foo()}", | ||
497 | ); | ||
498 | } | ||
499 | |||
500 | #[test] | ||
501 | fn replace_struct_init() { | ||
502 | assert_ssr_transform( | ||
503 | "Foo {a: $a, b: $b} ==>> Foo::new($a, $b)", | ||
504 | "fn f1() {Foo{b: 1, a: 2}}", | ||
505 | "fn f1() {Foo::new(2, 1)}", | ||
506 | ); | ||
507 | } | ||
508 | |||
509 | #[test] | ||
510 | fn replace_macro_invocations() { | ||
511 | assert_ssr_transform( | ||
512 | "try!($a) ==>> $a?", | ||
513 | "fn f1() -> Result<(), E> {bar(try!(foo()));}", | ||
514 | "fn f1() -> Result<(), E> {bar(foo()?);}", | ||
515 | ); | ||
516 | assert_ssr_transform( | ||
517 | "foo!($a($b)) ==>> foo($b, $a)", | ||
518 | "fn f1() {foo!(abc(def() + 2));}", | ||
519 | "fn f1() {foo(def() + 2, abc);}", | ||
520 | ); | ||
521 | } | ||
522 | |||
523 | #[test] | ||
524 | fn replace_binary_op() { | ||
525 | assert_ssr_transform( | ||
526 | "$a + $b ==>> $b + $a", | ||
527 | "fn f() {2 * 3 + 4 * 5}", | ||
528 | "fn f() {4 * 5 + 2 * 3}", | ||
529 | ); | ||
530 | assert_ssr_transform( | ||
531 | "$a + $b ==>> $b + $a", | ||
532 | "fn f() {1 + 2 + 3 + 4}", | ||
533 | "fn f() {4 + 3 + 2 + 1}", | ||
534 | ); | ||
535 | } | ||
536 | |||
537 | #[test] | ||
538 | fn match_binary_op() { | ||
539 | assert_matches("$a + $b", "fn f() {1 + 2 + 3 + 4}", &["1 + 2", "1 + 2 + 3", "1 + 2 + 3 + 4"]); | ||
540 | } | ||
541 | |||
542 | #[test] | ||
543 | fn multiple_rules() { | ||
544 | assert_ssr_transforms( | ||
545 | &["$a + 1 ==>> add_one($a)", "$a + $b ==>> add($a, $b)"], | ||
546 | "fn f() -> i32 {3 + 2 + 1}", | ||
547 | "fn f() -> i32 {add_one(add(3, 2))}", | ||
548 | ) | ||
549 | } | ||