ast_grep_core/match_tree/
strictness.rs1use crate::Doc;
2use crate::matcher::{PatternNode, kind_utils};
3use crate::meta_var::MetaVariable;
4use crate::node::Node;
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.is_extra()
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::Ast => false,
54 M::Smart | 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 self.should_skip_comment() && skip_comment(candidate)
106 }
107
108 pub(crate) fn should_skip_trailing<D: Doc>(&self, candidate: &Node<D>) -> bool {
110 use MatchStrictness as M;
111 match self {
112 M::Cst => false,
113 M::Smart => true,
114 M::Ast => !candidate.is_named(),
115 M::Relaxed => skip_comment_or_unnamed(candidate),
116 M::Signature => skip_comment_or_unnamed(candidate),
117 M::Template => skip_comment(candidate),
118 }
119 }
120
121 pub(crate) fn should_skip_goal<'p>(
122 &self,
123 goal_children: &mut Peekable<impl Iterator<Item = &'p PatternNode>>,
124 ) -> bool {
125 use MatchStrictness as M;
126 while let Some(pattern) = goal_children.peek() {
127 let skipped = match self {
128 M::Cst => false,
129 M::Smart | M::Template => match pattern {
130 PatternNode::MetaVar { meta_var } => match meta_var {
131 MetaVariable::Multiple => true,
132 MetaVariable::MultiCapture(_) => true,
133 MetaVariable::Dropped(_) => false,
134 MetaVariable::Capture(..) => false,
135 },
136 PatternNode::Terminal { .. } => false,
137 PatternNode::Internal { .. } => false,
138 },
139 M::Ast | M::Relaxed | M::Signature => match pattern {
140 PatternNode::MetaVar { meta_var } => match meta_var {
141 MetaVariable::Multiple => true,
142 MetaVariable::MultiCapture(_) => true,
143 MetaVariable::Dropped(named) => !named,
144 MetaVariable::Capture(_, named) => !named,
145 },
146 PatternNode::Terminal { is_named, .. } => !is_named,
147 PatternNode::Internal { .. } => false,
148 },
149 };
150 if !skipped {
151 return false;
152 }
153 goal_children.next();
154 }
155 true
156 }
157}
158
159impl FromStr for MatchStrictness {
160 type Err = &'static str;
161 fn from_str(s: &str) -> Result<Self, Self::Err> {
162 match s {
163 "cst" => Ok(MatchStrictness::Cst),
164 "smart" => Ok(MatchStrictness::Smart),
165 "ast" => Ok(MatchStrictness::Ast),
166 "relaxed" => Ok(MatchStrictness::Relaxed),
167 "signature" => Ok(MatchStrictness::Signature),
168 "template" => Ok(MatchStrictness::Template),
169 _ => Err("invalid strictness, valid options are: cst, smart, ast, relaxed, signature"),
170 }
171 }
172}
173
174#[cfg(test)]
175mod test {
176 use super::*;
177 use crate::language::Tsx;
178 use crate::{Pattern, Root};
179
180 fn test_match(p: &str, n: &str, strictness: MatchStrictness) -> bool {
181 let mut pattern = Pattern::new(p, Tsx);
182 pattern.strictness = strictness;
183 let root = Root::str(n, Tsx);
184 let node = root.root();
185 node.find(pattern).is_some()
186 }
187
188 fn template_pattern(p: &str, n: &str) -> bool {
189 test_match(p, n, MatchStrictness::Template)
190 }
191
192 #[test]
193 fn test_template_pattern() {
194 assert!(template_pattern("$A = $B", "a = b"));
195 assert!(template_pattern("$A = $B", "var a = b"));
196 assert!(template_pattern("$A = $B", "let a = b"));
197 assert!(template_pattern("$A = $B", "const a = b"));
198 assert!(template_pattern("$A = $B", "class A { a = b }"));
199 }
200
201 fn relaxed_pattern(p: &str, n: &str) -> bool {
202 test_match(p, n, MatchStrictness::Relaxed)
203 }
204
205 #[test]
206 fn test_ignore_comment() {
207 assert!(relaxed_pattern("$A($B)", "foo(bar /* .. */)"));
208 assert!(relaxed_pattern(
209 "$A($B)",
210 "
211 foo(
212 bar, // ..
213 )"
214 ));
215 assert!(relaxed_pattern("$A($B)", "foo(/* .. */ bar)"));
216 assert!(relaxed_pattern(
217 "$A($B)",
218 "
219 foo( // ..
220 bar
221 )"
222 ));
223 }
224
225 #[test]
226 fn test_ast_trailing_comma() {
227 assert!(test_match("foo(bar)", "foo(bar,)", MatchStrictness::Ast));
228 }
229}