ast_grep_core/match_tree/
strictness.rs1use 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, Smart, Ast, Relaxed, Signature, Template, }
17
18pub(crate) enum MatchOneNode {
19 MatchedBoth,
20 SkipBoth,
21 SkipGoal,
22 SkipCandidate,
23 NoMatch,
24}
25
26fn skip_comment(n: &Node<impl Doc>) -> bool {
27 n.kind().contains("comment")
28}
29
30fn skip_comment_or_unnamed(n: &Node<impl Doc>) -> bool {
31 if !n.is_named() {
32 return true;
33 }
34 skip_comment(n)
35}
36
37impl MatchStrictness {
38 pub(crate) fn should_skip_kind(&self) -> bool {
39 use MatchStrictness as M;
40 match self {
41 M::Template => true,
42 M::Cst => false,
43 M::Smart => false,
44 M::Ast => false,
45 M::Relaxed => false,
46 M::Signature => false,
47 }
48 }
49
50 fn should_skip_comment(&self) -> bool {
51 use MatchStrictness as M;
52 match self {
53 M::Cst | M::Smart | M::Ast => false,
54 M::Relaxed | M::Signature | M::Template => true,
55 }
56 }
57
58 pub(crate) fn match_terminal(
59 &self,
60 is_named: bool,
61 text: &str,
62 goal_kind: u16,
63 candidate: &Node<impl Doc>,
64 ) -> MatchOneNode {
65 use MatchStrictness as M;
66 let cand_kind = candidate.kind_id();
67 let is_kind_matched = kind_utils::are_kinds_matching(goal_kind, cand_kind);
68 if is_kind_matched && (!is_named || text == candidate.text()) {
72 return MatchOneNode::MatchedBoth;
73 }
74 if self.should_skip_comment() && skip_comment(candidate) {
75 return MatchOneNode::SkipCandidate;
76 }
77 let (skip_goal, skip_candidate) = match self {
78 M::Cst => (false, false),
79 M::Smart => (false, !candidate.is_named()),
80 M::Ast => (!is_named, !candidate.is_named()),
81 M::Relaxed => (!is_named, !candidate.is_named()),
82 M::Signature => {
83 if is_kind_matched {
84 return MatchOneNode::MatchedBoth;
85 }
86 (!is_named, !candidate.is_named())
87 }
88 M::Template => {
89 if text == candidate.text() {
90 return MatchOneNode::MatchedBoth;
91 } else {
92 (false, !candidate.is_named())
93 }
94 }
95 };
96 match (skip_goal, skip_candidate) {
97 (true, true) => MatchOneNode::SkipBoth,
98 (true, false) => MatchOneNode::SkipGoal,
99 (false, true) => MatchOneNode::SkipCandidate,
100 (false, false) => MatchOneNode::NoMatch,
101 }
102 }
103
104 pub(crate) fn should_skip_cand_for_metavar<D: Doc>(&self, candidate: &Node<D>) -> bool {
105 use MatchStrictness as M;
106 match self {
107 M::Cst | M::Ast | M::Smart => false,
108 M::Relaxed | M::Signature | M::Template => skip_comment(candidate),
109 }
110 }
111
112 pub(crate) fn should_skip_trailing<D: Doc>(&self, candidate: &Node<D>) -> bool {
114 use MatchStrictness as M;
115 match self {
116 M::Cst => false,
117 M::Smart => true,
118 M::Ast => false,
119 M::Relaxed => skip_comment_or_unnamed(candidate),
120 M::Signature => skip_comment_or_unnamed(candidate),
121 M::Template => skip_comment(candidate),
122 }
123 }
124
125 pub(crate) fn should_skip_goal<'p>(
126 &self,
127 goal_children: &mut Peekable<impl Iterator<Item = &'p PatternNode>>,
128 ) -> bool {
129 use MatchStrictness as M;
130 while let Some(pattern) = goal_children.peek() {
131 let skipped = match self {
132 M::Cst => false,
133 M::Smart | M::Template => match pattern {
134 PatternNode::MetaVar { meta_var } => match meta_var {
135 MetaVariable::Multiple => true,
136 MetaVariable::MultiCapture(_) => true,
137 MetaVariable::Dropped(_) => false,
138 MetaVariable::Capture(..) => false,
139 },
140 PatternNode::Terminal { .. } => false,
141 PatternNode::Internal { .. } => false,
142 },
143 M::Ast | M::Relaxed | M::Signature => match pattern {
144 PatternNode::MetaVar { meta_var } => match meta_var {
145 MetaVariable::Multiple => true,
146 MetaVariable::MultiCapture(_) => true,
147 MetaVariable::Dropped(named) => !named,
148 MetaVariable::Capture(_, named) => !named,
149 },
150 PatternNode::Terminal { is_named, .. } => !is_named,
151 PatternNode::Internal { .. } => false,
152 },
153 };
154 if !skipped {
155 return false;
156 }
157 goal_children.next();
158 }
159 true
160 }
161}
162
163impl FromStr for MatchStrictness {
164 type Err = &'static str;
165 fn from_str(s: &str) -> Result<Self, Self::Err> {
166 match s {
167 "cst" => Ok(MatchStrictness::Cst),
168 "smart" => Ok(MatchStrictness::Smart),
169 "ast" => Ok(MatchStrictness::Ast),
170 "relaxed" => Ok(MatchStrictness::Relaxed),
171 "signature" => Ok(MatchStrictness::Signature),
172 "template" => Ok(MatchStrictness::Template),
173 _ => Err("invalid strictness, valid options are: cst, smart, ast, relaxed, signature"),
174 }
175 }
176}
177
178#[cfg(test)]
179mod test {
180 use super::*;
181 use crate::language::Tsx;
182 use crate::{Pattern, Root};
183
184 fn test_match(p: &str, n: &str, strictness: MatchStrictness) -> bool {
185 let mut pattern = Pattern::new(p, Tsx);
186 pattern.strictness = strictness;
187 let root = Root::str(n, Tsx);
188 let node = root.root();
189 node.find(pattern).is_some()
190 }
191
192 fn template_pattern(p: &str, n: &str) -> bool {
193 test_match(p, n, MatchStrictness::Template)
194 }
195
196 #[test]
197 fn test_template_pattern() {
198 assert!(template_pattern("$A = $B", "a = b"));
199 assert!(template_pattern("$A = $B", "var a = b"));
200 assert!(template_pattern("$A = $B", "let a = b"));
201 assert!(template_pattern("$A = $B", "const a = b"));
202 assert!(template_pattern("$A = $B", "class A { a = b }"));
203 }
204
205 fn relaxed_pattern(p: &str, n: &str) -> bool {
206 test_match(p, n, MatchStrictness::Relaxed)
207 }
208
209 #[test]
210 fn test_ignore_comment() {
211 assert!(relaxed_pattern("$A($B)", "foo(bar /* .. */)"));
212 assert!(relaxed_pattern(
213 "$A($B)",
214 "
215 foo(
216 bar, // ..
217 )"
218 ));
219 assert!(relaxed_pattern("$A($B)", "foo(/* .. */ bar)"));
220 assert!(relaxed_pattern(
221 "$A($B)",
222 "
223 foo( // ..
224 bar
225 )"
226 ));
227 }
228}