ast_grep_core/match_tree/
strictness.rs

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