1use crate::{
5 SsrMatches,
6 parsing::{Constraint, NodeKind, Placeholder, Var},
7 resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo},
8};
9use hir::{FileRange, ImportPathConfig, Semantics};
10use ide_db::{FxHashMap, base_db::RootQueryDb};
11use std::{cell::Cell, iter::Peekable};
12use syntax::{
13 SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken,
14 ast::{self, AstNode, AstToken, HasGenericArgs},
15};
16
17macro_rules! match_error {
20 ($e:expr) => {{
21 MatchFailed {
22 reason: if recording_match_fail_reasons() {
23 Some(format!("{}", $e))
24 } else {
25 None
26 }
27 }
28 }};
29 ($fmt:expr, $($arg:tt)+) => {{
30 MatchFailed {
31 reason: if recording_match_fail_reasons() {
32 Some(format!($fmt, $($arg)+))
33 } else {
34 None
35 }
36 }
37 }};
38}
39
40macro_rules! fail_match {
42 ($($args:tt)*) => {return Err(match_error!($($args)*))};
43}
44
45#[derive(Debug)]
47pub struct Match {
48 pub(crate) range: FileRange,
49 pub(crate) matched_node: SyntaxNode,
50 pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>,
51 pub(crate) ignored_comments: Vec<ast::Comment>,
52 pub(crate) rule_index: usize,
53 pub(crate) depth: usize,
55 pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>,
57}
58
59#[derive(Debug)]
61pub(crate) struct PlaceholderMatch {
62 pub(crate) range: FileRange,
63 pub(crate) inner_matches: SsrMatches,
65 pub(crate) autoderef_count: usize,
68 pub(crate) autoref_kind: ast::SelfParamKind,
69}
70
71#[derive(Debug)]
72pub(crate) struct MatchFailureReason {
73 pub(crate) reason: String,
74}
75
76#[derive(Clone)]
78pub(crate) struct MatchFailed {
79 pub(crate) reason: Option<String>,
82}
83
84pub(crate) fn get_match<'db>(
88 debug_active: bool,
89 rule: &ResolvedRule<'db>,
90 code: &SyntaxNode,
91 restrict_range: &Option<FileRange>,
92 sema: &Semantics<'db, ide_db::RootDatabase>,
93) -> Result<Match, MatchFailed> {
94 record_match_fails_reasons_scope(debug_active, || {
95 Matcher::try_match(rule, code, restrict_range, sema)
96 })
97}
98
99struct Matcher<'db, 'sema> {
101 sema: &'sema Semantics<'db, ide_db::RootDatabase>,
102 restrict_range: Option<FileRange>,
105 rule: &'sema ResolvedRule<'db>,
106}
107
108enum Phase<'a> {
111 First,
113 Second(&'a mut Match),
116}
117
118impl<'db, 'sema> Matcher<'db, 'sema> {
119 fn try_match(
120 rule: &ResolvedRule<'db>,
121 code: &SyntaxNode,
122 restrict_range: &Option<FileRange>,
123 sema: &'sema Semantics<'db, ide_db::RootDatabase>,
124 ) -> Result<Match, MatchFailed> {
125 let match_state = Matcher { sema, restrict_range: *restrict_range, rule };
126 match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?;
128 let file_range = sema
129 .original_range_opt(code)
130 .ok_or(MatchFailed { reason: Some("def site definition".to_owned()) })?;
131 match_state.validate_range(&file_range)?;
132 let mut the_match = Match {
133 range: file_range,
134 matched_node: code.clone(),
135 placeholder_values: FxHashMap::default(),
136 ignored_comments: Vec::new(),
137 rule_index: rule.index,
138 depth: 0,
139 rendered_template_paths: FxHashMap::default(),
140 };
141 match_state.attempt_match_node(
144 &mut Phase::Second(&mut the_match),
145 &rule.pattern.node,
146 code,
147 )?;
148 the_match.depth = sema.ancestors_with_macros(the_match.matched_node.clone()).count();
149 if let Some(template) = &rule.template {
150 the_match.render_template_paths(template, sema)?;
151 }
152 Ok(the_match)
153 }
154
155 fn validate_range(&self, range: &FileRange) -> Result<(), MatchFailed> {
159 if let Some(restrict_range) = &self.restrict_range
160 && (restrict_range.file_id != range.file_id
161 || !restrict_range.range.contains_range(range.range))
162 {
163 fail_match!("Node originated from a macro");
164 }
165 Ok(())
166 }
167
168 fn attempt_match_node(
169 &self,
170 phase: &mut Phase<'_>,
171 pattern: &SyntaxNode,
172 code: &SyntaxNode,
173 ) -> Result<(), MatchFailed> {
174 if let Some(placeholder) = self.get_placeholder_for_node(pattern) {
176 for constraint in &placeholder.constraints {
177 self.check_constraint(constraint, code)?;
178 }
179 if let Phase::Second(matches_out) = phase {
180 let original_range = self
181 .sema
182 .original_range_opt(code)
183 .ok_or(MatchFailed { reason: Some("def site definition".to_owned()) })?;
184 self.validate_range(&original_range)?;
187 matches_out.placeholder_values.insert(
188 placeholder.ident.clone(),
189 PlaceholderMatch::from_range(original_range),
190 );
191 }
192 return Ok(());
193 }
194 if let Some(pattern_ufcs) = self.rule.pattern.ufcs_function_calls.get(pattern) {
196 if let Some(code) = ast::MethodCallExpr::cast(code.clone()) {
197 return self.attempt_match_ufcs_to_method_call(phase, pattern_ufcs, &code);
198 }
199 if let Some(code) = ast::CallExpr::cast(code.clone()) {
200 return self.attempt_match_ufcs_to_ufcs(phase, pattern_ufcs, &code);
201 }
202 }
203 if pattern.kind() != code.kind() {
204 fail_match!(
205 "Pattern had `{}` ({:?}), code had `{}` ({:?})",
206 pattern.text(),
207 pattern.kind(),
208 code.text(),
209 code.kind()
210 );
211 }
212 match code.kind() {
215 SyntaxKind::RECORD_EXPR_FIELD_LIST => {
216 self.attempt_match_record_field_list(phase, pattern, code)
217 }
218 SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code),
219 SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code),
220 _ => self.attempt_match_node_children(phase, pattern, code),
221 }
222 }
223
224 fn attempt_match_node_children(
225 &self,
226 phase: &mut Phase<'_>,
227 pattern: &SyntaxNode,
228 code: &SyntaxNode,
229 ) -> Result<(), MatchFailed> {
230 self.attempt_match_sequences(
231 phase,
232 PatternIterator::new(pattern),
233 code.children_with_tokens(),
234 )
235 }
236
237 fn attempt_match_sequences(
238 &self,
239 phase: &mut Phase<'_>,
240 pattern_it: PatternIterator,
241 mut code_it: SyntaxElementChildren,
242 ) -> Result<(), MatchFailed> {
243 let mut pattern_it = pattern_it.peekable();
244 loop {
245 match phase.next_non_trivial(&mut code_it) {
246 None => {
247 if let Some(p) = pattern_it.next() {
248 fail_match!("Part of the pattern was unmatched: {:?}", p);
249 }
250 return Ok(());
251 }
252 Some(SyntaxElement::Token(c)) => {
253 self.attempt_match_token(phase, &mut pattern_it, &c)?;
254 }
255 Some(SyntaxElement::Node(c)) => match pattern_it.next() {
256 Some(SyntaxElement::Node(p)) => {
257 self.attempt_match_node(phase, &p, &c)?;
258 }
259 Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()),
260 None => fail_match!("Pattern reached end, code has {}", c.text()),
261 },
262 }
263 }
264 }
265
266 fn attempt_match_token(
267 &self,
268 phase: &mut Phase<'_>,
269 pattern: &mut Peekable<PatternIterator>,
270 code: &syntax::SyntaxToken,
271 ) -> Result<(), MatchFailed> {
272 phase.record_ignored_comments(code);
273 if code.kind().is_trivia() {
275 return Ok(());
276 }
277 if let Some(SyntaxElement::Token(p)) = pattern.peek() {
278 if code.kind() == SyntaxKind::COMMA && is_closing_token(p.kind()) {
281 return Ok(());
282 }
283 if p.kind() == SyntaxKind::COMMA && is_closing_token(code.kind()) {
286 pattern.next();
287 }
288 }
289 match pattern.next() {
291 Some(SyntaxElement::Token(p)) => {
292 if p.kind() != code.kind() || p.text() != code.text() {
293 fail_match!(
294 "Pattern wanted token '{}' ({:?}), but code had token '{}' ({:?})",
295 p.text(),
296 p.kind(),
297 code.text(),
298 code.kind()
299 )
300 }
301 }
302 Some(SyntaxElement::Node(p)) => {
303 fail_match!(
305 "Pattern wanted {:?}, but code had token '{}' ({:?})",
306 p,
307 code.text(),
308 code.kind()
309 );
310 }
311 None => {
312 fail_match!("Pattern exhausted, while code remains: `{}`", code.text());
313 }
314 }
315 Ok(())
316 }
317
318 #[allow(clippy::only_used_in_recursion)]
319 fn check_constraint(
320 &self,
321 constraint: &Constraint,
322 code: &SyntaxNode,
323 ) -> Result<(), MatchFailed> {
324 match constraint {
325 Constraint::Kind(kind) => {
326 kind.matches(code)?;
327 }
328 Constraint::Not(sub) => {
329 if self.check_constraint(sub, code).is_ok() {
330 fail_match!("Constraint {:?} failed for '{}'", constraint, code.text());
331 }
332 }
333 }
334 Ok(())
335 }
336
337 fn attempt_match_path(
340 &self,
341 phase: &mut Phase<'_>,
342 pattern: &SyntaxNode,
343 code: &SyntaxNode,
344 ) -> Result<(), MatchFailed> {
345 if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) {
346 let pattern_path = ast::Path::cast(pattern.clone()).unwrap();
347 let code_path = ast::Path::cast(code.clone()).unwrap();
348 if let (Some(pattern_segment), Some(code_segment)) =
349 (pattern_path.segment(), code_path.segment())
350 {
351 self.attempt_match_opt(
354 phase,
355 pattern_segment.generic_arg_list(),
356 code_segment.generic_arg_list(),
357 )?;
358 self.attempt_match_opt(
359 phase,
360 pattern_segment.parenthesized_arg_list(),
361 code_segment.parenthesized_arg_list(),
362 )?;
363 }
364 if matches!(phase, Phase::Second(_)) {
365 let resolution = self
366 .sema
367 .resolve_path(&code_path)
368 .ok_or_else(|| match_error!("Failed to resolve path `{}`", code.text()))?;
369 if pattern_resolved.resolution != resolution {
370 fail_match!("Pattern had path `{}` code had `{}`", pattern.text(), code.text());
371 }
372 }
373 } else {
374 return self.attempt_match_node_children(phase, pattern, code);
375 }
376 Ok(())
377 }
378
379 fn attempt_match_opt<T: AstNode>(
380 &self,
381 phase: &mut Phase<'_>,
382 pattern: Option<T>,
383 code: Option<T>,
384 ) -> Result<(), MatchFailed> {
385 match (pattern, code) {
386 (Some(p), Some(c)) => self.attempt_match_node(phase, p.syntax(), c.syntax()),
387 (None, None) => Ok(()),
388 (Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()),
389 (None, Some(c)) => {
390 fail_match!("Nothing in pattern to match code `{}`", c.syntax().text())
391 }
392 }
393 }
394
395 fn attempt_match_record_field_list(
398 &self,
399 phase: &mut Phase<'_>,
400 pattern: &SyntaxNode,
401 code: &SyntaxNode,
402 ) -> Result<(), MatchFailed> {
403 let mut fields_by_name: FxHashMap<SmolStr, SyntaxNode> = FxHashMap::default();
405 for child in code.children() {
406 if let Some(record) = ast::RecordExprField::cast(child.clone())
407 && let Some(name) = record.field_name()
408 {
409 fields_by_name.insert(name.text().into(), child.clone());
410 }
411 }
412 for p in pattern.children_with_tokens() {
413 if let SyntaxElement::Node(p) = p
414 && let Some(name_element) = p.first_child_or_token()
415 {
416 if self.get_placeholder(&name_element).is_some() {
417 return self.attempt_match_node_children(phase, pattern, code);
421 }
422 if let Some(ident) = only_ident(name_element) {
423 let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| {
424 match_error!("Placeholder has record field '{}', but code doesn't", ident)
425 })?;
426 self.attempt_match_node(phase, &p, &code_record)?;
427 }
428 }
429 }
430 if let Some(unmatched_fields) = fields_by_name.keys().next() {
431 fail_match!(
432 "{} field(s) of a record literal failed to match, starting with {}",
433 fields_by_name.len(),
434 unmatched_fields
435 );
436 }
437 Ok(())
438 }
439
440 fn attempt_match_token_tree(
445 &self,
446 phase: &mut Phase<'_>,
447 pattern: &SyntaxNode,
448 code: &syntax::SyntaxNode,
449 ) -> Result<(), MatchFailed> {
450 let mut pattern = PatternIterator::new(pattern).peekable();
451 let mut children = code.children_with_tokens();
452 while let Some(child) = children.next() {
453 if let Some(placeholder) = pattern.peek().and_then(|p| self.get_placeholder(p)) {
454 pattern.next();
455 let next_pattern_token = pattern
456 .peek()
457 .and_then(|p| match p {
458 SyntaxElement::Token(t) => Some(t.clone()),
459 SyntaxElement::Node(n) => n.first_token(),
460 })
461 .map(|p| p.text().to_owned());
462 let first_matched_token = child.clone();
463 let mut last_matched_token = child;
464 for next in &mut children {
467 match &next {
468 SyntaxElement::Token(t) => {
469 if Some(t.to_string()) == next_pattern_token {
470 pattern.next();
471 break;
472 }
473 }
474 SyntaxElement::Node(n) => {
475 if let Some(first_token) = n.first_token()
476 && Some(first_token.text()) == next_pattern_token.as_deref()
477 && let Some(SyntaxElement::Node(p)) = pattern.next()
478 {
479 self.attempt_match_token_tree(phase, &p, n)?;
481 break;
482 }
483 }
484 };
485 last_matched_token = next;
486 }
487 if let Phase::Second(match_out) = phase {
488 match_out.placeholder_values.insert(
489 placeholder.ident.clone(),
490 PlaceholderMatch::from_range(FileRange {
491 file_id: self
492 .sema
493 .original_range_opt(code)
494 .ok_or(MatchFailed {
495 reason: Some("def site definition".to_owned()),
496 })?
497 .file_id,
498 range: first_matched_token
499 .text_range()
500 .cover(last_matched_token.text_range()),
501 }),
502 );
503 }
504 continue;
505 }
506 match child {
508 SyntaxElement::Token(token) => {
509 self.attempt_match_token(phase, &mut pattern, &token)?;
510 }
511 SyntaxElement::Node(node) => match pattern.next() {
512 Some(SyntaxElement::Node(p)) => {
513 self.attempt_match_token_tree(phase, &p, &node)?;
514 }
515 Some(SyntaxElement::Token(p)) => fail_match!(
516 "Pattern has token '{}', code has subtree '{}'",
517 p.text(),
518 node.text()
519 ),
520 None => fail_match!("Pattern has nothing, code has '{}'", node.text()),
521 },
522 }
523 }
524 if let Some(p) = pattern.next() {
525 fail_match!("Reached end of token tree in code, but pattern still has {:?}", p);
526 }
527 Ok(())
528 }
529
530 fn attempt_match_ufcs_to_method_call(
531 &self,
532 phase: &mut Phase<'_>,
533 pattern_ufcs: &UfcsCallInfo<'db>,
534 code: &ast::MethodCallExpr,
535 ) -> Result<(), MatchFailed> {
536 use ast::HasArgList;
537 let code_resolved_function = self
538 .sema
539 .resolve_method_call(code)
540 .ok_or_else(|| match_error!("Failed to resolve method call"))?;
541 if pattern_ufcs.function != code_resolved_function {
542 fail_match!("Method call resolved to a different function");
543 }
544 let mut pattern_args = pattern_ufcs
546 .call_expr
547 .arg_list()
548 .ok_or_else(|| match_error!("Pattern function call has no args"))?
549 .args();
550 if code_resolved_function.self_param(self.sema.db).is_some() {
554 if let (Some(pattern_type), Some(expr)) =
555 (&pattern_ufcs.qualifier_type, &code.receiver())
556 {
557 let deref_count = self.check_expr_type(pattern_type, expr)?;
558 let pattern_receiver = pattern_args.next();
559 self.attempt_match_opt(phase, pattern_receiver.clone(), code.receiver())?;
560 if let Phase::Second(match_out) = phase
561 && let Some(placeholder_value) = pattern_receiver
562 .and_then(|n| self.get_placeholder_for_node(n.syntax()))
563 .and_then(|placeholder| {
564 match_out.placeholder_values.get_mut(&placeholder.ident)
565 })
566 {
567 placeholder_value.autoderef_count = deref_count;
568 placeholder_value.autoref_kind = self
569 .sema
570 .resolve_method_call_as_callable(code)
571 .and_then(|callable| {
572 let (self_param, _) = callable.receiver_param(self.sema.db)?;
573 Some(self.sema.source(self_param)?.value.kind())
574 })
575 .unwrap_or(ast::SelfParamKind::Owned);
576 }
577 }
578 } else {
579 self.attempt_match_opt(phase, pattern_args.next(), code.receiver())?;
580 }
581 let mut code_args =
582 code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
583 loop {
584 match (pattern_args.next(), code_args.next()) {
585 (None, None) => return Ok(()),
586 (p, c) => self.attempt_match_opt(phase, p, c)?,
587 }
588 }
589 }
590
591 fn attempt_match_ufcs_to_ufcs(
592 &self,
593 phase: &mut Phase<'_>,
594 pattern_ufcs: &UfcsCallInfo<'db>,
595 code: &ast::CallExpr,
596 ) -> Result<(), MatchFailed> {
597 use ast::HasArgList;
598 if let (Some(pattern_type), Some(expr)) = (
600 &pattern_ufcs.qualifier_type,
601 &code.arg_list().and_then(|code_args| code_args.args().next()),
602 ) {
603 self.check_expr_type(pattern_type, expr)?;
604 }
605 self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax())
606 }
607
608 fn check_expr_type(
611 &self,
612 pattern_type: &hir::Type<'db>,
613 expr: &ast::Expr,
614 ) -> Result<usize, MatchFailed> {
615 use hir::HirDisplay;
616 let code_type = self
617 .sema
618 .type_of_expr(expr)
619 .ok_or_else(|| {
620 match_error!("Failed to get receiver type for `{}`", expr.syntax().text())
621 })?
622 .original;
623 let krate = self.sema.scope(expr.syntax()).map(|it| it.krate()).unwrap_or_else(|| {
624 hir::Crate::from(*self.sema.db.all_crates().last().expect("no crate graph present"))
625 });
626
627 code_type
628 .autoderef(self.sema.db)
629 .enumerate()
630 .find(|(_, deref_code_type)| pattern_type == deref_code_type)
631 .map(|(count, _)| count)
632 .ok_or_else(|| {
633 let display_target = krate.to_display_target(self.sema.db);
634 match_error!(
636 "Pattern type `{}` didn't match code type `{}`",
637 pattern_type.display(self.sema.db, display_target),
638 code_type.display(self.sema.db, display_target)
639 )
640 })
641 }
642
643 fn get_placeholder_for_node(&self, node: &SyntaxNode) -> Option<&Placeholder> {
644 self.get_placeholder(&SyntaxElement::Node(node.clone()))
645 }
646
647 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
648 only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident))
649 }
650}
651
652impl Match {
653 fn render_template_paths<'db>(
654 &mut self,
655 template: &ResolvedPattern<'db>,
656 sema: &Semantics<'db, ide_db::RootDatabase>,
657 ) -> Result<(), MatchFailed> {
658 let module = sema
659 .scope(&self.matched_node)
660 .ok_or_else(|| match_error!("Matched node isn't in a module"))?
661 .module();
662 for (path, resolved_path) in &template.resolved_paths {
663 if let hir::PathResolution::Def(module_def) = resolved_path.resolution {
664 let cfg = ImportPathConfig {
665 prefer_no_std: false,
666 prefer_prelude: true,
667 prefer_absolute: false,
668 allow_unstable: true,
669 };
670 let mod_path = module.find_path(sema.db, module_def, cfg).ok_or_else(|| {
671 match_error!("Failed to render template path `{}` at match location")
672 })?;
673 self.rendered_template_paths.insert(path.clone(), mod_path);
674 }
675 }
676 Ok(())
677 }
678}
679
680impl Phase<'_> {
681 fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
682 loop {
683 let c = code_it.next();
684 if let Some(SyntaxElement::Token(t)) = &c {
685 self.record_ignored_comments(t);
686 if t.kind().is_trivia() {
687 continue;
688 }
689 }
690 return c;
691 }
692 }
693
694 fn record_ignored_comments(&mut self, token: &SyntaxToken) {
695 if token.kind() == SyntaxKind::COMMENT
696 && let Phase::Second(match_out) = self
697 && let Some(comment) = ast::Comment::cast(token.clone())
698 {
699 match_out.ignored_comments.push(comment);
700 }
701 }
702}
703
704fn is_closing_token(kind: SyntaxKind) -> bool {
705 kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK
706}
707
708pub(crate) fn record_match_fails_reasons_scope<F, T>(debug_active: bool, f: F) -> T
709where
710 F: Fn() -> T,
711{
712 RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(debug_active));
713 let res = f();
714 RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(false));
715 res
716}
717
718thread_local! {
723 pub static RECORDING_MATCH_FAIL_REASONS: Cell<bool> = const { Cell::new(false) };
724}
725
726fn recording_match_fail_reasons() -> bool {
727 RECORDING_MATCH_FAIL_REASONS.with(|c| c.get())
728}
729
730impl PlaceholderMatch {
731 fn from_range(range: FileRange) -> Self {
732 Self {
733 range,
734 inner_matches: SsrMatches::default(),
735 autoderef_count: 0,
736 autoref_kind: ast::SelfParamKind::Owned,
737 }
738 }
739}
740
741impl NodeKind {
742 fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> {
743 let ok = match self {
744 Self::Literal => {
745 cov_mark::hit!(literal_constraint);
746 ast::Literal::can_cast(node.kind())
747 }
748 };
749 if !ok {
750 fail_match!("Code '{}' isn't of kind {:?}", node.text(), self);
751 }
752 Ok(())
753 }
754}
755
756fn only_ident(element: SyntaxElement) -> Option<SyntaxToken> {
758 match element {
759 SyntaxElement::Token(t) => {
760 if t.kind() == SyntaxKind::IDENT {
761 return Some(t);
762 }
763 }
764 SyntaxElement::Node(n) => {
765 let mut children = n.children_with_tokens();
766 if let (Some(only_child), None) = (children.next(), children.next()) {
767 return only_ident(only_child);
768 }
769 }
770 }
771 None
772}
773
774struct PatternIterator {
775 iter: SyntaxElementChildren,
776}
777
778impl Iterator for PatternIterator {
779 type Item = SyntaxElement;
780
781 fn next(&mut self) -> Option<SyntaxElement> {
782 self.iter.find(|element| !element.kind().is_trivia())
783 }
784}
785
786impl PatternIterator {
787 fn new(parent: &SyntaxNode) -> Self {
788 Self { iter: parent.children_with_tokens() }
789 }
790}
791
792#[cfg(test)]
793mod tests {
794 use crate::{MatchFinder, SsrRule};
795
796 #[test]
797 fn parse_match_replace() {
798 let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
799 let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
800
801 let (db, position, selections) = crate::tests::single_file(input);
802 let position = ide_db::FilePosition {
803 file_id: position.file_id.file_id(&db),
804 offset: position.offset,
805 };
806 let mut match_finder = MatchFinder::in_context(
807 &db,
808 position,
809 selections
810 .into_iter()
811 .map(|frange| ide_db::FileRange {
812 file_id: frange.file_id.file_id(&db),
813 range: frange.range,
814 })
815 .collect(),
816 )
817 .unwrap();
818 match_finder.add_rule(rule).unwrap();
819 let matches = match_finder.matches();
820 assert_eq!(matches.matches.len(), 1);
821 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
822 assert_eq!(matches.matches[0].placeholder_values.len(), 1);
823
824 let edits = match_finder.edits();
825 assert_eq!(edits.len(), 1);
826 let edit = &edits[&position.file_id];
827 let mut after = input.to_owned();
828 edit.apply(&mut after);
829 assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");
830 }
831}