Skip to main content

ast_grep_core/matcher/
pattern.rs

1use crate::language::Language;
2use crate::match_tree::{MatchStrictness, match_end_non_recursive, match_node_non_recursive};
3use crate::matcher::{KindMatcher, KindMatcherError, Matcher, kind_utils};
4use crate::meta_var::{MetaVarEnv, MetaVariable};
5use crate::source::SgNode;
6use crate::{Doc, Node, Root};
7
8use bit_set::BitSet;
9use thiserror::Error;
10
11use std::borrow::Cow;
12use std::collections::HashSet;
13
14#[derive(Clone)]
15pub struct Pattern {
16  pub node: PatternNode,
17  root_kind: Option<u16>,
18  pub strictness: MatchStrictness,
19}
20
21pub struct PatternBuilder<'a> {
22  selector: Option<&'a str>,
23  src: Cow<'a, str>,
24}
25
26impl PatternBuilder<'_> {
27  pub fn build<D, F>(&self, parse: F) -> Result<Pattern, PatternError>
28  where
29    F: FnOnce(&str) -> Result<D, String>,
30    D: Doc,
31  {
32    let doc = parse(&self.src).map_err(PatternError::Parse)?;
33    let root = Root::doc(doc);
34    let pattern = if let Some(selector) = self.selector {
35      self.contextual(&root, selector)?
36    } else {
37      self.single(&root)?
38    };
39    // A multi meta variable matches a list of nodes, but a pattern matches a
40    // single node, so `$$$MULTI` as the root of a pattern is invalid input.
41    if let PatternNode::MetaVar {
42      meta_var: MetaVariable::MultiCapture(_) | MetaVariable::Multiple,
43    } = &pattern.node
44    {
45      return Err(PatternError::RootMultiMetaVar(self.src.to_string()));
46    }
47    Ok(pattern)
48  }
49  fn single<D: Doc>(&self, root: &Root<D>) -> Result<Pattern, PatternError> {
50    let goal = root.root();
51    if goal.children().len() == 0 {
52      return Err(PatternError::NoContent(self.src.to_string()));
53    }
54    if !is_single_node(&goal.inner) {
55      return Err(PatternError::MultipleNode(self.src.to_string()));
56    }
57    let node = Pattern::single_matcher(root);
58    Ok(Pattern::from(node))
59  }
60
61  fn contextual<D: Doc>(&self, root: &Root<D>, selector: &str) -> Result<Pattern, PatternError> {
62    let goal = root.root();
63    let kind_matcher = KindMatcher::try_new(selector, root.lang().clone())?;
64    let Some(node) = goal.find(&kind_matcher) else {
65      return Err(PatternError::NoSelectorInContext {
66        context: self.src.to_string(),
67        selector: selector.into(),
68      });
69    };
70    Ok(Pattern {
71      root_kind: Some(node.kind_id()),
72      node: convert_node_to_pattern(node.get_node().clone()),
73      strictness: MatchStrictness::Smart,
74    })
75  }
76}
77
78pub struct DumpPattern<'p> {
79  pub is_meta_var: bool,
80  pub kind: Option<Cow<'static, str>>,
81  pub text: Cow<'p, str>,
82  pub children: Vec<DumpPattern<'p>>,
83}
84
85fn dump_pattern_impl<'p>(
86  pattern: &'p PatternNode,
87  strictness: &MatchStrictness,
88  to_kind_str: &impl Fn(u16) -> Option<Cow<'static, str>>,
89) -> Option<DumpPattern<'p>> {
90  match pattern {
91    PatternNode::MetaVar { meta_var } => {
92      let meta_var = match meta_var {
93        MetaVariable::Capture(name, _) => format!("${name}"),
94        MetaVariable::MultiCapture(name) => format!("$$${name}"),
95        MetaVariable::Multiple => "$$$".to_string(),
96        MetaVariable::Dropped(_) => "$_".to_string(),
97      };
98      Some(DumpPattern {
99        is_meta_var: true,
100        kind: Some("MetaVar".into()),
101        text: meta_var.into(),
102        children: vec![],
103      })
104    }
105    PatternNode::Terminal {
106      text,
107      kind_id,
108      is_named,
109    } => {
110      if !*is_named {
111        if matches!(
112          strictness,
113          MatchStrictness::Cst | MatchStrictness::Smart | MatchStrictness::Template
114        ) {
115          return Some(DumpPattern {
116            is_meta_var: false,
117            kind: None,
118            text: text.into(),
119            children: vec![],
120          });
121        }
122        return None;
123      }
124      let kind = if matches!(strictness, MatchStrictness::Template) {
125        None
126      } else {
127        Some(to_kind_str(*kind_id).unwrap_or("UNKNOWN".into()))
128      };
129      let text = if matches!(strictness, MatchStrictness::Signature) {
130        ""
131      } else {
132        text
133      };
134      Some(DumpPattern {
135        is_meta_var: false,
136        kind,
137        text: text.into(),
138        children: vec![],
139      })
140    }
141    PatternNode::Internal { kind_id, children } => {
142      let kind = if matches!(strictness, MatchStrictness::Template) {
143        Cow::Borrowed("(node)")
144      } else {
145        to_kind_str(*kind_id).unwrap_or_else(|| "UNKNOWN".into())
146      };
147      let children = children
148        .iter()
149        .filter_map(|n| dump_pattern_impl(n, strictness, to_kind_str))
150        .collect();
151      Some(DumpPattern {
152        is_meta_var: false,
153        kind: Some(kind),
154        text: Cow::Borrowed(""),
155        children,
156      })
157    }
158  }
159}
160
161#[derive(Clone)]
162pub enum PatternNode {
163  MetaVar {
164    meta_var: MetaVariable,
165  },
166  /// Node without children.
167  Terminal {
168    text: String,
169    is_named: bool,
170    kind_id: u16,
171  },
172  /// Non-Terminal Syntax Nodes are called Internal
173  Internal {
174    kind_id: u16,
175    children: Vec<PatternNode>,
176  },
177}
178
179impl PatternNode {
180  // for skipping trivial nodes in goal after ellipsis
181  pub fn is_trivial(&self) -> bool {
182    match self {
183      PatternNode::Terminal { is_named, .. } => !*is_named,
184      _ => false,
185    }
186  }
187
188  pub fn fixed_string(&self) -> Cow<'_, str> {
189    match &self {
190      PatternNode::Terminal { text, .. } => Cow::Borrowed(text),
191      PatternNode::MetaVar { .. } => Cow::Borrowed(""),
192      PatternNode::Internal { children, .. } => {
193        children
194          .iter()
195          .map(|n| n.fixed_string())
196          .fold(Cow::Borrowed(""), |longest, curr| {
197            if longest.len() >= curr.len() {
198              longest
199            } else {
200              curr
201            }
202          })
203      }
204    }
205  }
206}
207impl<'r, D: Doc> From<Node<'r, D>> for PatternNode {
208  fn from(node: Node<'r, D>) -> Self {
209    convert_node_to_pattern(node)
210  }
211}
212
213impl<'r, D: Doc> From<Node<'r, D>> for Pattern {
214  fn from(node: Node<'r, D>) -> Self {
215    Self {
216      node: convert_node_to_pattern(node),
217      root_kind: None,
218      strictness: MatchStrictness::Smart,
219    }
220  }
221}
222
223fn convert_node_to_pattern<D: Doc>(node: Node<'_, D>) -> PatternNode {
224  if let Some(meta_var) = extract_var_from_node(&node) {
225    PatternNode::MetaVar { meta_var }
226  } else if node.is_leaf() {
227    PatternNode::Terminal {
228      text: node.text().to_string(),
229      is_named: node.is_named(),
230      kind_id: node.kind_id(),
231    }
232  } else {
233    let children = node.children().filter_map(|n| {
234      if n.is_missing() {
235        None
236      } else {
237        Some(PatternNode::from(n))
238      }
239    });
240    PatternNode::Internal {
241      kind_id: node.kind_id(),
242      children: children.collect(),
243    }
244  }
245}
246
247fn extract_var_from_node<D: Doc>(goal: &Node<'_, D>) -> Option<MetaVariable> {
248  let key = goal.text();
249  goal.lang().extract_meta_var(&key)
250}
251
252#[derive(Debug, Error)]
253pub enum PatternError {
254  #[error("Fails to parse the pattern query: `{0}`")]
255  Parse(String),
256  #[error("No AST root is detected. Please check the pattern source `{0}`.")]
257  NoContent(String),
258  #[error("Multiple AST nodes are detected. Please check the pattern source `{0}`.")]
259  MultipleNode(String),
260  #[error(
261    "Standalone multi meta variable `{0}` is invalid. Use `$VAR` or wrap `$$$VAR` in a larger pattern."
262  )]
263  RootMultiMetaVar(String),
264  #[error(transparent)]
265  InvalidKind(#[from] KindMatcherError),
266  #[error(
267    "Fails to create Contextual pattern: selector `{selector}` matches no node in the context `{context}`."
268  )]
269  NoSelectorInContext { context: String, selector: String },
270}
271
272#[inline]
273fn is_single_node<'r, N: SgNode<'r>>(n: &N) -> bool {
274  match n.children().len() {
275    1 => true,
276    2 => {
277      let c = n.child(1).expect("second child must exist");
278      // some language will have weird empty syntax node at the end
279      // see golang's `$A = 0` pattern test case
280      c.is_missing() || c.kind().is_empty()
281    }
282    _ => false,
283  }
284}
285impl Pattern {
286  pub fn dump(
287    &self,
288    kind_id_to_name: &impl Fn(u16) -> Option<Cow<'static, str>>,
289  ) -> Option<DumpPattern<'_>> {
290    dump_pattern_impl(&self.node, &self.strictness, kind_id_to_name)
291  }
292  pub fn has_error(&self) -> bool {
293    let kind = match &self.node {
294      PatternNode::Terminal { kind_id, .. } => *kind_id,
295      PatternNode::Internal { kind_id, .. } => *kind_id,
296      PatternNode::MetaVar { .. } => match self.root_kind {
297        Some(k) => k,
298        None => return false,
299      },
300    };
301    kind_utils::is_error_kind(kind)
302  }
303
304  pub fn fixed_string(&self) -> Cow<'_, str> {
305    self.node.fixed_string()
306  }
307
308  /// Get all defined variables in the pattern.
309  /// Used for validating rules and report undefined variables.
310  pub fn defined_vars(&self) -> HashSet<&str> {
311    let mut vars = HashSet::new();
312    collect_vars(&self.node, &mut vars);
313    vars
314  }
315}
316
317fn meta_var_name(meta_var: &MetaVariable) -> Option<&str> {
318  use MetaVariable as MV;
319  match meta_var {
320    MV::Capture(name, _) => Some(name),
321    MV::MultiCapture(name) => Some(name),
322    MV::Dropped(_) => None,
323    MV::Multiple => None,
324  }
325}
326
327fn collect_vars<'p>(p: &'p PatternNode, vars: &mut HashSet<&'p str>) {
328  match p {
329    PatternNode::MetaVar { meta_var, .. } => {
330      if let Some(name) = meta_var_name(meta_var) {
331        vars.insert(name);
332      }
333    }
334    PatternNode::Terminal { .. } => {
335      // collect nothing for terminal nodes!
336    }
337    PatternNode::Internal { children, .. } => {
338      for c in children {
339        collect_vars(c, vars);
340      }
341    }
342  }
343}
344
345impl Pattern {
346  pub fn try_new<L: Language>(src: &str, lang: L) -> Result<Self, PatternError> {
347    let processed = lang.pre_process_pattern(src);
348    let builder = PatternBuilder {
349      selector: None,
350      src: processed,
351    };
352    lang.build_pattern(&builder)
353  }
354
355  pub fn new<L: Language>(src: &str, lang: L) -> Self {
356    Self::try_new(src, lang).unwrap()
357  }
358
359  pub fn with_strictness(mut self, strictness: MatchStrictness) -> Self {
360    self.strictness = strictness;
361    self
362  }
363
364  pub fn contextual<L: Language>(
365    context: &str,
366    selector: &str,
367    lang: L,
368  ) -> Result<Self, PatternError> {
369    let processed = lang.pre_process_pattern(context);
370    let builder = PatternBuilder {
371      selector: Some(selector),
372      src: processed,
373    };
374    lang.build_pattern(&builder)
375  }
376  fn single_matcher<D: Doc>(root: &Root<D>) -> Node<'_, D> {
377    // debug_assert!(matches!(self.style, PatternStyle::Single));
378    let node = root.root();
379    let mut inner = node.inner;
380    while is_single_node(&inner) {
381      inner = inner.child(0).unwrap();
382    }
383    Node { inner, root }
384  }
385}
386
387impl Matcher for Pattern {
388  fn match_node_with_env<'tree, D: Doc>(
389    &self,
390    node: Node<'tree, D>,
391    env: &mut Cow<MetaVarEnv<'tree, D>>,
392  ) -> Option<Node<'tree, D>> {
393    if let Some(k) = self.root_kind
394      && node.kind_id() != k
395    {
396      return None;
397    }
398    // do not pollute the env if pattern does not match
399    let mut may_write = Cow::Borrowed(env.as_ref());
400    let node = match_node_non_recursive(self, node, &mut may_write)?;
401    if let Cow::Owned(map) = may_write {
402      // only change env when pattern matches
403      *env = Cow::Owned(map);
404    }
405    Some(node)
406  }
407
408  fn potential_kinds(&self) -> Option<bit_set::BitSet> {
409    // if strictness is Template, we can match any kind
410    if matches!(self.strictness, MatchStrictness::Template) {
411      return None;
412    }
413    let kind = match self.node {
414      PatternNode::Terminal { kind_id, .. } => kind_id,
415      PatternNode::MetaVar { .. } => self.root_kind?,
416      PatternNode::Internal { kind_id, .. } => {
417        if kind_utils::is_error_kind(kind_id) {
418          // error can match any kind
419          return None;
420        }
421        kind_id
422      }
423    };
424
425    let mut kinds = BitSet::new();
426    kinds.insert(kind.into());
427    Some(kinds)
428  }
429
430  fn get_match_len<D: Doc>(&self, node: Node<'_, D>) -> Option<usize> {
431    let start = node.range().start;
432    let end = match_end_non_recursive(self, node)?;
433    Some(end - start)
434  }
435}
436impl std::fmt::Debug for PatternNode {
437  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438    match self {
439      Self::MetaVar { meta_var, .. } => write!(f, "{meta_var:?}"),
440      Self::Terminal { text, .. } => write!(f, "{text}"),
441      Self::Internal { children, .. } => write!(f, "{children:?}"),
442    }
443  }
444}
445
446impl std::fmt::Debug for Pattern {
447  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448    write!(f, "{:?}", self.node)
449  }
450}
451
452#[cfg(test)]
453mod test {
454  use super::*;
455  use crate::language::Tsx;
456  use crate::matcher::MatcherExt;
457  use crate::meta_var::MetaVarEnv;
458  use crate::tree_sitter::StrDoc;
459  use std::collections::HashMap;
460
461  fn pattern_node(s: &str) -> Root<StrDoc<Tsx>> {
462    Root::str(s, Tsx)
463  }
464
465  fn test_match(s1: &str, s2: &str) {
466    let pattern = Pattern::new(s1, Tsx);
467    let cand = pattern_node(s2);
468    let cand = cand.root();
469    assert!(
470      pattern.find_node(cand.clone()).is_some(),
471      "goal: {:?}, candidate: {}",
472      pattern,
473      cand.get_inner_node().to_sexp(),
474    );
475  }
476  fn test_non_match(s1: &str, s2: &str) {
477    let pattern = Pattern::new(s1, Tsx);
478    let cand = pattern_node(s2);
479    let cand = cand.root();
480    assert!(
481      pattern.find_node(cand.clone()).is_none(),
482      "goal: {:?}, candidate: {}",
483      pattern,
484      cand.get_inner_node().to_sexp(),
485    );
486  }
487
488  #[test]
489  fn test_meta_variable() {
490    test_match("const a = $VALUE", "const a = 123");
491    test_match("const $VARIABLE = $VALUE", "const a = 123");
492    test_match("const $VARIABLE = $VALUE", "const a = 123");
493  }
494
495  #[test]
496  fn test_whitespace() {
497    test_match("function t() { }", "function t() {}");
498    test_match("function t() {}", "function t() {  }");
499  }
500
501  fn match_env(goal_str: &str, cand: &str) -> HashMap<String, String> {
502    let pattern = Pattern::new(goal_str, Tsx);
503    let cand = pattern_node(cand);
504    let cand = cand.root();
505    let nm = pattern.find_node(cand).unwrap();
506    HashMap::from(nm.get_env().clone())
507  }
508
509  #[test]
510  fn test_meta_variable_env() {
511    let env = match_env("const a = $VALUE", "const a = 123");
512    assert_eq!(env["VALUE"], "123");
513  }
514
515  #[test]
516  fn test_pattern_should_not_pollute_env() {
517    // gh issue #1164
518    let pattern = Pattern::new("const $A = 114", Tsx);
519    let cand = pattern_node("const a = 514");
520    let cand = cand.root().child(0).unwrap();
521    let map = MetaVarEnv::new();
522    let mut env = Cow::Borrowed(&map);
523    let nm = pattern.match_node_with_env(cand, &mut env);
524    assert!(nm.is_none());
525    assert!(env.get_match("A").is_none());
526    assert!(map.get_match("A").is_none());
527  }
528
529  #[test]
530  fn test_match_non_atomic() {
531    let env = match_env("const a = $VALUE", "const a = 5 + 3");
532    assert_eq!(env["VALUE"], "5 + 3");
533  }
534
535  #[test]
536  fn test_class_assignment() {
537    test_match("class $C { $MEMBER = $VAL}", "class A {a = 123}");
538    test_non_match("class $C { $MEMBER = $VAL; b = 123; }", "class A {a = 123}");
539    // test_match("a = 123", "class A {a = 123}");
540    test_non_match("a = 123", "class B {b = 123}");
541  }
542
543  #[test]
544  fn test_return() {
545    test_match("$A($B)", "return test(123)");
546  }
547
548  #[test]
549  fn test_contextual_pattern() {
550    let pattern =
551      Pattern::contextual("class A { $F = $I }", "public_field_definition", Tsx).expect("test");
552    let cand = pattern_node("class B { b = 123 }");
553    assert!(pattern.find_node(cand.root()).is_some());
554    let cand = pattern_node("let b = 123");
555    assert!(pattern.find_node(cand.root()).is_none());
556  }
557
558  #[test]
559  fn test_contextual_match_with_env() {
560    let pattern =
561      Pattern::contextual("class A { $F = $I }", "public_field_definition", Tsx).expect("test");
562    let cand = pattern_node("class B { b = 123 }");
563    let nm = pattern.find_node(cand.root()).expect("test");
564    let env = nm.get_env();
565    let env = HashMap::from(env.clone());
566    assert_eq!(env["F"], "b");
567    assert_eq!(env["I"], "123");
568  }
569
570  #[test]
571  fn test_contextual_unmatch_with_env() {
572    let pattern =
573      Pattern::contextual("class A { $F = $I }", "public_field_definition", Tsx).expect("test");
574    let cand = pattern_node("let b = 123");
575    let nm = pattern.find_node(cand.root());
576    assert!(nm.is_none());
577  }
578
579  fn get_kind(kind_str: &str) -> usize {
580    Tsx.kind_to_id(kind_str).into()
581  }
582
583  #[test]
584  fn test_pattern_potential_kinds() {
585    let pattern = Pattern::new("const a = 1", Tsx);
586    let kind = get_kind("lexical_declaration");
587    let kinds = pattern.potential_kinds().expect("should have kinds");
588    assert_eq!(kinds.count(), 1);
589    assert!(kinds.contains(kind));
590  }
591
592  #[test]
593  fn test_pattern_with_non_root_meta_var() {
594    let pattern = Pattern::new("const $A = $B", Tsx);
595    let kind = get_kind("lexical_declaration");
596    let kinds = pattern.potential_kinds().expect("should have kinds");
597    assert_eq!(kinds.count(), 1);
598    assert!(kinds.contains(kind));
599  }
600
601  #[test]
602  fn test_bare_wildcard() {
603    let pattern = Pattern::new("$A", Tsx);
604    // wildcard should match anything, so kinds should be None
605    assert!(pattern.potential_kinds().is_none());
606  }
607
608  #[test]
609  fn test_contextual_potential_kinds() {
610    let pattern =
611      Pattern::contextual("class A { $F = $I }", "public_field_definition", Tsx).expect("test");
612    let kind = get_kind("public_field_definition");
613    let kinds = pattern.potential_kinds().expect("should have kinds");
614    assert_eq!(kinds.count(), 1);
615    assert!(kinds.contains(kind));
616  }
617
618  #[test]
619  fn test_contextual_wildcard() {
620    let pattern = Pattern::contextual("class A { $F }", "property_identifier", Tsx).expect("test");
621    let kind = get_kind("property_identifier");
622    let kinds = pattern.potential_kinds().expect("should have kinds");
623    assert_eq!(kinds.count(), 1);
624    assert!(kinds.contains(kind));
625  }
626
627  #[test]
628  #[ignore]
629  fn test_multi_node_pattern() {
630    let pattern = Pattern::new("a;b;c;", Tsx);
631    let kinds = pattern.potential_kinds().expect("should have kinds");
632    assert_eq!(kinds.count(), 1);
633    test_match("a;b;c", "a;b;c;");
634  }
635
636  #[test]
637  #[ignore]
638  fn test_multi_node_meta_var() {
639    let env = match_env("a;$B;c", "a;b;c");
640    assert_eq!(env["B"], "b");
641    let env = match_env("a;$B;c", "a;1+2+3;c");
642    assert_eq!(env["B"], "1+2+3");
643  }
644
645  #[test]
646  #[ignore]
647  fn test_pattern_size() {
648    assert_eq!(std::mem::size_of::<Pattern>(), 40);
649  }
650
651  #[test]
652  fn test_error_kind() {
653    let ret = Pattern::contextual("a", "property_identifier", Tsx);
654    assert!(ret.is_err());
655    let ret = Pattern::new("123+", Tsx);
656    assert!(ret.has_error());
657  }
658
659  #[test]
660  fn test_bare_wildcard_in_context() {
661    let pattern = Pattern::contextual("class A { $F }", "property_identifier", Tsx).expect("test");
662    let cand = pattern_node("let b = 123");
663    // it should not match
664    assert!(pattern.find_node(cand.root()).is_none());
665  }
666
667  #[test]
668  fn test_pattern_fixed_string() {
669    let pattern = Pattern::new("class A { $F }", Tsx);
670    assert_eq!(pattern.fixed_string(), "class");
671    let pattern = Pattern::contextual("class A { $F }", "property_identifier", Tsx).expect("test");
672    assert!(pattern.fixed_string().is_empty());
673  }
674
675  #[test]
676  fn test_pattern_error() {
677    let pattern = Pattern::try_new("", Tsx);
678    assert!(matches!(pattern, Err(PatternError::NoContent(_))));
679    let pattern = Pattern::try_new("12  3344", Tsx);
680    assert!(matches!(pattern, Err(PatternError::MultipleNode(_))));
681  }
682
683  #[test]
684  fn test_root_multi_meta_var_is_rejected() {
685    // gh #2697: a standalone multi meta variable is invalid input. A pattern
686    // matches a single node, so it must be rejected when the pattern is
687    // created, not silently stored as a single meta var.
688    for src in ["$$$A", "$$$PARAMS"] {
689      let pattern = Pattern::try_new(src, Tsx);
690      assert!(
691        matches!(pattern, Err(PatternError::RootMultiMetaVar(_))),
692        "expected RootMultiMetaVar for `{src}`, got {pattern:?}"
693      );
694    }
695  }
696
697  #[test]
698  fn test_single_root_meta_var_is_accepted() {
699    // a single $META as the whole pattern is still valid
700    assert!(Pattern::try_new("$A", Tsx).is_ok());
701    assert!(Pattern::try_new("$_", Tsx).is_ok());
702  }
703
704  #[test]
705  fn test_in_list_multi_meta_var_is_unchanged() {
706    // in-list $$$ stays valid: the root node is the call expression, not the
707    // multi meta var, so existing matching behavior is preserved.
708    assert!(Pattern::try_new("foo($$$A, c)", Tsx).is_ok());
709    test_match("foo($$$A, c)", "foo(a, b, c)");
710  }
711
712  #[test]
713  fn test_debug_pattern() {
714    let pattern = Pattern::new("var $A = 1", Tsx);
715    assert_eq!(
716      format!("{pattern:?}"),
717      "[var, [Capture(\"A\", true), =, 1]]"
718    );
719  }
720
721  fn defined_vars(s: &str) -> Vec<String> {
722    let pattern = Pattern::new(s, Tsx);
723    let mut vars: Vec<_> = pattern
724      .defined_vars()
725      .into_iter()
726      .map(String::from)
727      .collect();
728    vars.sort();
729    vars
730  }
731
732  #[test]
733  fn test_extract_meta_var_from_pattern() {
734    let vars = defined_vars("var $A = 1");
735    assert_eq!(vars, ["A"]);
736  }
737
738  #[test]
739  fn test_extract_complex_meta_var() {
740    let vars = defined_vars("function $FUNC($$$ARGS): $RET { $$$BODY }");
741    assert_eq!(vars, ["ARGS", "BODY", "FUNC", "RET"]);
742  }
743
744  #[test]
745  fn test_extract_duplicate_meta_var() {
746    let vars = defined_vars("var $A = $A");
747    assert_eq!(vars, ["A"]);
748  }
749
750  #[test]
751  fn test_contextual_pattern_vars() {
752    let pattern = Pattern::contextual("<div ref={$A}/>", "jsx_attribute", Tsx).expect("correct");
753    assert_eq!(pattern.defined_vars(), ["A"].into_iter().collect());
754  }
755
756  #[test]
757  fn test_gh_1087() {
758    test_match("($P) => $F($P)", "(x) => bar(x)");
759  }
760
761  #[test]
762  fn test_template_pattern_have_no_kinds() {
763    let pattern = Pattern::new("$A = $B", Tsx).with_strictness(MatchStrictness::Template);
764    assert!(pattern.potential_kinds().is_none());
765    let pattern = Pattern::contextual("{a: b}", "pair", Tsx)
766      .expect("should create template pattern")
767      .with_strictness(MatchStrictness::Template);
768    assert!(pattern.potential_kinds().is_none());
769  }
770}