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