ast_grep_core/matcher/
kind.rs

1use super::Matcher;
2
3use crate::language::Language;
4use crate::meta_var::MetaVarEnv;
5use crate::node::KindId;
6use crate::{Doc, Node};
7
8use std::borrow::Cow;
9
10use bit_set::BitSet;
11use thiserror::Error;
12
13// 0 is symbol_end for not found, 65535 is builtin symbol ERROR
14// see https://tree-sitter.docsforge.com/master/api/#TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION
15// and https://tree-sitter.docsforge.com/master/api/ts_language_symbol_for_name/
16const TS_BUILTIN_SYM_END: KindId = 0;
17const TS_BUILTIN_SYM_ERROR: KindId = 65535;
18
19#[derive(Debug, Error)]
20pub enum KindMatcherError {
21  #[error("Kind `{0}` is invalid.")]
22  InvalidKindName(String),
23}
24
25#[derive(Clone)]
26pub struct KindMatcher {
27  kind: KindId,
28}
29
30impl KindMatcher {
31  pub fn new<L: Language>(node_kind: &str, lang: L) -> Self {
32    Self {
33      kind: lang.kind_to_id(node_kind),
34    }
35  }
36
37  pub fn try_new<L: Language>(node_kind: &str, lang: L) -> Result<Self, KindMatcherError> {
38    let s = Self::new(node_kind, lang);
39    if s.is_invalid() {
40      Err(KindMatcherError::InvalidKindName(node_kind.into()))
41    } else {
42      Ok(s)
43    }
44  }
45
46  pub fn from_id(kind: KindId) -> Self {
47    Self { kind }
48  }
49
50  /// Whether the kind matcher contains undefined tree-sitter kind.
51  pub fn is_invalid(&self) -> bool {
52    self.kind == TS_BUILTIN_SYM_END
53  }
54
55  /// Construct a matcher that only matches ERROR
56  pub fn error_matcher() -> Self {
57    Self::from_id(TS_BUILTIN_SYM_ERROR)
58  }
59}
60
61pub mod kind_utils {
62  use super::*;
63
64  /// Whether the kind will match parsing error occurred in the source code.
65  /// for example, we can use `kind: ERROR` in YAML to find invalid syntax in source.
66  /// the name `is_error` implies the matcher itself is error.
67  /// But here the matcher itself is valid and it is what it matches is error.
68  pub fn is_error_kind(kind: KindId) -> bool {
69    kind == TS_BUILTIN_SYM_ERROR
70  }
71
72  pub fn are_kinds_matching(goal: KindId, candidate: KindId) -> bool {
73    goal == candidate || is_error_kind(goal)
74  }
75}
76
77impl Matcher for KindMatcher {
78  fn match_node_with_env<'tree, D: Doc>(
79    &self,
80    node: Node<'tree, D>,
81    _env: &mut Cow<MetaVarEnv<'tree, D>>,
82  ) -> Option<Node<'tree, D>> {
83    if node.kind_id() == self.kind {
84      Some(node)
85    } else {
86      None
87    }
88  }
89
90  fn potential_kinds(&self) -> Option<BitSet> {
91    let mut set = BitSet::new();
92    set.insert(self.kind.into());
93    Some(set)
94  }
95}
96
97#[cfg(test)]
98mod test {
99  use super::*;
100  use crate::language::Tsx;
101  use crate::matcher::MatcherExt;
102  use crate::{tree_sitter::StrDoc, Root};
103
104  fn pattern_node(s: &str) -> Root<StrDoc<Tsx>> {
105    Root::str(s, Tsx)
106  }
107  #[test]
108  fn test_kind_match() {
109    let kind = "public_field_definition";
110    let cand = pattern_node("class A { a = 123 }");
111    let cand = cand.root();
112    let pattern = KindMatcher::new(kind, Tsx);
113    assert!(
114      pattern.find_node(cand.clone()).is_some(),
115      "goal: {}, candidate: {}",
116      kind,
117      cand.get_inner_node().to_sexp(),
118    );
119  }
120
121  #[test]
122  fn test_kind_non_match() {
123    let kind = "field_definition";
124    let cand = pattern_node("const a = 123");
125    let cand = cand.root();
126    let pattern = KindMatcher::new(kind, Tsx);
127    assert!(
128      pattern.find_node(cand.clone()).is_none(),
129      "goal: {}, candidate: {}",
130      kind,
131      cand.get_inner_node().to_sexp(),
132    );
133  }
134
135  #[test]
136  fn test_kind_potential_kinds() {
137    let kind = "field_definition";
138    let matcher = KindMatcher::new(kind, Tsx);
139    let potential_kinds = matcher
140      .potential_kinds()
141      .expect("should have potential kinds");
142    // should has exactly one potential kind
143    assert_eq!(potential_kinds.len(), 1);
144  }
145}