ast_grep_core/match_tree/
strictness.rs

1use crate::matcher::{kind_utils, PatternNode};
2use crate::meta_var::MetaVariable;
3use crate::node::Node;
4use crate::Doc;
5use std::iter::Peekable;
6use std::str::FromStr;
7
8#[derive(Clone)]
9pub enum MatchStrictness {
10  Cst,       // all nodes are matched
11  Smart,     // all nodes except source trivial nodes are matched.
12  Ast,       // only ast nodes are matched
13  Relaxed,   // ast-nodes excluding comments are matched
14  Signature, // ast-nodes excluding comments, without text
15}
16
17pub(crate) enum MatchOneNode {
18  MatchedBoth,
19  SkipBoth,
20  SkipGoal,
21  SkipCandidate,
22  NoMatch,
23}
24
25fn skip_comment_or_unnamed(n: &Node<impl Doc>) -> bool {
26  if !n.is_named() {
27    return true;
28  }
29  let kind = n.kind();
30  kind.contains("comment")
31}
32
33impl MatchStrictness {
34  pub(crate) fn match_terminal(
35    &self,
36    is_named: bool,
37    text: &str,
38    goal_kind: u16,
39    candidate: &Node<impl Doc>,
40  ) -> MatchOneNode {
41    use MatchStrictness as M;
42    let cand_kind = candidate.kind_id();
43    let is_kind_matched = kind_utils::are_kinds_matching(goal_kind, cand_kind);
44    // work around ast-grep/ast-grep#1419 and tree-sitter/tree-sitter-typescript#306
45    // tree-sitter-typescript has wrong span of unnamed node so text would not match
46    // just compare kind for unnamed node
47    if is_kind_matched && (!is_named || text == candidate.text()) {
48      return MatchOneNode::MatchedBoth;
49    }
50    let (skip_goal, skip_candidate) = match self {
51      M::Cst => (false, false),
52      M::Smart => (false, !candidate.is_named()),
53      M::Ast => (!is_named, !candidate.is_named()),
54      M::Relaxed => (!is_named, skip_comment_or_unnamed(candidate)),
55      M::Signature => {
56        if is_kind_matched {
57          return MatchOneNode::MatchedBoth;
58        }
59        (!is_named, skip_comment_or_unnamed(candidate))
60      }
61    };
62    match (skip_goal, skip_candidate) {
63      (true, true) => MatchOneNode::SkipBoth,
64      (true, false) => MatchOneNode::SkipGoal,
65      (false, true) => MatchOneNode::SkipCandidate,
66      (false, false) => MatchOneNode::NoMatch,
67    }
68  }
69
70  // TODO: this is a method for working around trailing nodes after pattern is matched
71  pub(crate) fn should_skip_trailing<D: Doc>(&self, candidate: &Node<D>) -> bool {
72    use MatchStrictness as M;
73    match self {
74      M::Cst => false,
75      M::Smart => true,
76      M::Ast => false,
77      M::Relaxed => skip_comment_or_unnamed(candidate),
78      M::Signature => skip_comment_or_unnamed(candidate),
79    }
80  }
81
82  pub(crate) fn should_skip_goal<'p>(
83    &self,
84    goal_children: &mut Peekable<impl Iterator<Item = &'p PatternNode>>,
85  ) -> bool {
86    use MatchStrictness as M;
87    while let Some(pattern) = goal_children.peek() {
88      let skipped = match self {
89        M::Cst => false,
90        M::Smart => match pattern {
91          PatternNode::MetaVar { meta_var } => match meta_var {
92            MetaVariable::Multiple => true,
93            MetaVariable::MultiCapture(_) => true,
94            MetaVariable::Dropped(_) => false,
95            MetaVariable::Capture(..) => false,
96          },
97          PatternNode::Terminal { .. } => false,
98          PatternNode::Internal { .. } => false,
99        },
100        M::Ast | M::Relaxed | M::Signature => match pattern {
101          PatternNode::MetaVar { meta_var } => match meta_var {
102            MetaVariable::Multiple => true,
103            MetaVariable::MultiCapture(_) => true,
104            MetaVariable::Dropped(named) => !named,
105            MetaVariable::Capture(_, named) => !named,
106          },
107          PatternNode::Terminal { is_named, .. } => !is_named,
108          PatternNode::Internal { .. } => false,
109        },
110      };
111      if !skipped {
112        return false;
113      }
114      goal_children.next();
115    }
116    true
117  }
118}
119
120impl FromStr for MatchStrictness {
121  type Err = &'static str;
122  fn from_str(s: &str) -> Result<Self, Self::Err> {
123    match s {
124      "cst" => Ok(MatchStrictness::Cst),
125      "smart" => Ok(MatchStrictness::Smart),
126      "ast" => Ok(MatchStrictness::Ast),
127      "relaxed" => Ok(MatchStrictness::Relaxed),
128      "signature" => Ok(MatchStrictness::Signature),
129      _ => Err("invalid strictness, valid options are: cst, smart, ast, relaxed, signature"),
130    }
131  }
132}