1use crate::language::Language;
2use crate::match_tree::{MatchStrictness, match_end_non_recursive, match_node_non_recursive};
3use crate::matcher::{KindMatcher, KindMatcherError, Matcher, kind_utils};
4use crate::meta_var::{MetaVarEnv, MetaVariable};
5use crate::source::SgNode;
6use crate::{Doc, Node, Root};
7
8use bit_set::BitSet;
9use thiserror::Error;
10
11use std::borrow::Cow;
12use std::collections::HashSet;
13
14#[derive(Clone)]
15pub struct Pattern {
16 pub node: PatternNode,
17 root_kind: Option<u16>,
18 pub strictness: MatchStrictness,
19}
20
21pub struct PatternBuilder<'a> {
22 selector: Option<&'a str>,
23 src: Cow<'a, str>,
24}
25
26impl PatternBuilder<'_> {
27 pub fn build<D, F>(&self, parse: F) -> Result<Pattern, PatternError>
28 where
29 F: FnOnce(&str) -> Result<D, String>,
30 D: Doc,
31 {
32 let doc = parse(&self.src).map_err(PatternError::Parse)?;
33 let root = Root::doc(doc);
34 let pattern = if let Some(selector) = self.selector {
35 self.contextual(&root, selector)?
36 } else {
37 self.single(&root)?
38 };
39 if let PatternNode::MetaVar {
42 meta_var: MetaVariable::MultiCapture(_) | MetaVariable::Multiple,
43 } = &pattern.node
44 {
45 return Err(PatternError::RootMultiMetaVar(self.src.to_string()));
46 }
47 Ok(pattern)
48 }
49 fn single<D: Doc>(&self, root: &Root<D>) -> Result<Pattern, PatternError> {
50 let goal = root.root();
51 if goal.children().len() == 0 {
52 return Err(PatternError::NoContent(self.src.to_string()));
53 }
54 if !is_single_node(&goal.inner) {
55 return Err(PatternError::MultipleNode(self.src.to_string()));
56 }
57 let node = Pattern::single_matcher(root);
58 Ok(Pattern::from(node))
59 }
60
61 fn contextual<D: Doc>(&self, root: &Root<D>, selector: &str) -> Result<Pattern, PatternError> {
62 let goal = root.root();
63 let kind_matcher = KindMatcher::try_new(selector, root.lang().clone())?;
64 let Some(node) = goal.find(&kind_matcher) else {
65 return Err(PatternError::NoSelectorInContext {
66 context: self.src.to_string(),
67 selector: selector.into(),
68 });
69 };
70 Ok(Pattern {
71 root_kind: Some(node.kind_id()),
72 node: convert_node_to_pattern(node.get_node().clone()),
73 strictness: MatchStrictness::Smart,
74 })
75 }
76}
77
78pub struct DumpPattern<'p> {
79 pub is_meta_var: bool,
80 pub kind: Option<Cow<'static, str>>,
81 pub text: Cow<'p, str>,
82 pub children: Vec<DumpPattern<'p>>,
83}
84
85fn dump_pattern_impl<'p>(
86 pattern: &'p PatternNode,
87 strictness: &MatchStrictness,
88 to_kind_str: &impl Fn(u16) -> Option<Cow<'static, str>>,
89) -> Option<DumpPattern<'p>> {
90 match pattern {
91 PatternNode::MetaVar { meta_var } => {
92 let meta_var = match meta_var {
93 MetaVariable::Capture(name, _) => format!("${name}"),
94 MetaVariable::MultiCapture(name) => format!("$$${name}"),
95 MetaVariable::Multiple => "$$$".to_string(),
96 MetaVariable::Dropped(_) => "$_".to_string(),
97 };
98 Some(DumpPattern {
99 is_meta_var: true,
100 kind: Some("MetaVar".into()),
101 text: meta_var.into(),
102 children: vec![],
103 })
104 }
105 PatternNode::Terminal {
106 text,
107 kind_id,
108 is_named,
109 } => {
110 if !*is_named {
111 if matches!(
112 strictness,
113 MatchStrictness::Cst | MatchStrictness::Smart | MatchStrictness::Template
114 ) {
115 return Some(DumpPattern {
116 is_meta_var: false,
117 kind: None,
118 text: text.into(),
119 children: vec![],
120 });
121 }
122 return None;
123 }
124 let kind = if matches!(strictness, MatchStrictness::Template) {
125 None
126 } else {
127 Some(to_kind_str(*kind_id).unwrap_or("UNKNOWN".into()))
128 };
129 let text = if matches!(strictness, MatchStrictness::Signature) {
130 ""
131 } else {
132 text
133 };
134 Some(DumpPattern {
135 is_meta_var: false,
136 kind,
137 text: text.into(),
138 children: vec![],
139 })
140 }
141 PatternNode::Internal { kind_id, children } => {
142 let kind = if matches!(strictness, MatchStrictness::Template) {
143 Cow::Borrowed("(node)")
144 } else {
145 to_kind_str(*kind_id).unwrap_or_else(|| "UNKNOWN".into())
146 };
147 let children = children
148 .iter()
149 .filter_map(|n| dump_pattern_impl(n, strictness, to_kind_str))
150 .collect();
151 Some(DumpPattern {
152 is_meta_var: false,
153 kind: Some(kind),
154 text: Cow::Borrowed(""),
155 children,
156 })
157 }
158 }
159}
160
161#[derive(Clone)]
162pub enum PatternNode {
163 MetaVar {
164 meta_var: MetaVariable,
165 },
166 Terminal {
168 text: String,
169 is_named: bool,
170 kind_id: u16,
171 },
172 Internal {
174 kind_id: u16,
175 children: Vec<PatternNode>,
176 },
177}
178
179impl PatternNode {
180 pub fn is_trivial(&self) -> bool {
182 match self {
183 PatternNode::Terminal { is_named, .. } => !*is_named,
184 _ => false,
185 }
186 }
187
188 pub fn fixed_string(&self) -> Cow<'_, str> {
189 match &self {
190 PatternNode::Terminal { text, .. } => Cow::Borrowed(text),
191 PatternNode::MetaVar { .. } => Cow::Borrowed(""),
192 PatternNode::Internal { children, .. } => {
193 children
194 .iter()
195 .map(|n| n.fixed_string())
196 .fold(Cow::Borrowed(""), |longest, curr| {
197 if longest.len() >= curr.len() {
198 longest
199 } else {
200 curr
201 }
202 })
203 }
204 }
205 }
206}
207impl<'r, D: Doc> From<Node<'r, D>> for PatternNode {
208 fn from(node: Node<'r, D>) -> Self {
209 convert_node_to_pattern(node)
210 }
211}
212
213impl<'r, D: Doc> From<Node<'r, D>> for Pattern {
214 fn from(node: Node<'r, D>) -> Self {
215 Self {
216 node: convert_node_to_pattern(node),
217 root_kind: None,
218 strictness: MatchStrictness::Smart,
219 }
220 }
221}
222
223fn convert_node_to_pattern<D: Doc>(node: Node<'_, D>) -> PatternNode {
224 if let Some(meta_var) = extract_var_from_node(&node) {
225 PatternNode::MetaVar { meta_var }
226 } else if node.is_leaf() {
227 PatternNode::Terminal {
228 text: node.text().to_string(),
229 is_named: node.is_named(),
230 kind_id: node.kind_id(),
231 }
232 } else {
233 let children = node.children().filter_map(|n| {
234 if n.is_missing() {
235 None
236 } else {
237 Some(PatternNode::from(n))
238 }
239 });
240 PatternNode::Internal {
241 kind_id: node.kind_id(),
242 children: children.collect(),
243 }
244 }
245}
246
247fn extract_var_from_node<D: Doc>(goal: &Node<'_, D>) -> Option<MetaVariable> {
248 let key = goal.text();
249 goal.lang().extract_meta_var(&key)
250}
251
252#[derive(Debug, Error)]
253pub enum PatternError {
254 #[error("Fails to parse the pattern query: `{0}`")]
255 Parse(String),
256 #[error("No AST root is detected. Please check the pattern source `{0}`.")]
257 NoContent(String),
258 #[error("Multiple AST nodes are detected. Please check the pattern source `{0}`.")]
259 MultipleNode(String),
260 #[error(
261 "Standalone multi meta variable `{0}` is invalid. Use `$VAR` or wrap `$$$VAR` in a larger pattern."
262 )]
263 RootMultiMetaVar(String),
264 #[error(transparent)]
265 InvalidKind(#[from] KindMatcherError),
266 #[error(
267 "Fails to create Contextual pattern: selector `{selector}` matches no node in the context `{context}`."
268 )]
269 NoSelectorInContext { context: String, selector: String },
270}
271
272#[inline]
273fn is_single_node<'r, N: SgNode<'r>>(n: &N) -> bool {
274 match n.children().len() {
275 1 => true,
276 2 => {
277 let c = n.child(1).expect("second child must exist");
278 c.is_missing() || c.kind().is_empty()
281 }
282 _ => false,
283 }
284}
285impl Pattern {
286 pub fn dump(
287 &self,
288 kind_id_to_name: &impl Fn(u16) -> Option<Cow<'static, str>>,
289 ) -> Option<DumpPattern<'_>> {
290 dump_pattern_impl(&self.node, &self.strictness, kind_id_to_name)
291 }
292 pub fn has_error(&self) -> bool {
293 let kind = match &self.node {
294 PatternNode::Terminal { kind_id, .. } => *kind_id,
295 PatternNode::Internal { kind_id, .. } => *kind_id,
296 PatternNode::MetaVar { .. } => match self.root_kind {
297 Some(k) => k,
298 None => return false,
299 },
300 };
301 kind_utils::is_error_kind(kind)
302 }
303
304 pub fn fixed_string(&self) -> Cow<'_, str> {
305 self.node.fixed_string()
306 }
307
308 pub fn defined_vars(&self) -> HashSet<&str> {
311 let mut vars = HashSet::new();
312 collect_vars(&self.node, &mut vars);
313 vars
314 }
315}
316
317fn meta_var_name(meta_var: &MetaVariable) -> Option<&str> {
318 use MetaVariable as MV;
319 match meta_var {
320 MV::Capture(name, _) => Some(name),
321 MV::MultiCapture(name) => Some(name),
322 MV::Dropped(_) => None,
323 MV::Multiple => None,
324 }
325}
326
327fn collect_vars<'p>(p: &'p PatternNode, vars: &mut HashSet<&'p str>) {
328 match p {
329 PatternNode::MetaVar { meta_var, .. } => {
330 if let Some(name) = meta_var_name(meta_var) {
331 vars.insert(name);
332 }
333 }
334 PatternNode::Terminal { .. } => {
335 }
337 PatternNode::Internal { children, .. } => {
338 for c in children {
339 collect_vars(c, vars);
340 }
341 }
342 }
343}
344
345impl Pattern {
346 pub fn try_new<L: Language>(src: &str, lang: L) -> Result<Self, PatternError> {
347 let processed = lang.pre_process_pattern(src);
348 let builder = PatternBuilder {
349 selector: None,
350 src: processed,
351 };
352 lang.build_pattern(&builder)
353 }
354
355 pub fn new<L: Language>(src: &str, lang: L) -> Self {
356 Self::try_new(src, lang).unwrap()
357 }
358
359 pub fn with_strictness(mut self, strictness: MatchStrictness) -> Self {
360 self.strictness = strictness;
361 self
362 }
363
364 pub fn contextual<L: Language>(
365 context: &str,
366 selector: &str,
367 lang: L,
368 ) -> Result<Self, PatternError> {
369 let processed = lang.pre_process_pattern(context);
370 let builder = PatternBuilder {
371 selector: Some(selector),
372 src: processed,
373 };
374 lang.build_pattern(&builder)
375 }
376 fn single_matcher<D: Doc>(root: &Root<D>) -> Node<'_, D> {
377 let node = root.root();
379 let mut inner = node.inner;
380 while is_single_node(&inner) {
381 inner = inner.child(0).unwrap();
382 }
383 Node { inner, root }
384 }
385}
386
387impl Matcher for Pattern {
388 fn match_node_with_env<'tree, D: Doc>(
389 &self,
390 node: Node<'tree, D>,
391 env: &mut Cow<MetaVarEnv<'tree, D>>,
392 ) -> Option<Node<'tree, D>> {
393 if let Some(k) = self.root_kind
394 && node.kind_id() != k
395 {
396 return None;
397 }
398 let mut may_write = Cow::Borrowed(env.as_ref());
400 let node = match_node_non_recursive(self, node, &mut may_write)?;
401 if let Cow::Owned(map) = may_write {
402 *env = Cow::Owned(map);
404 }
405 Some(node)
406 }
407
408 fn potential_kinds(&self) -> Option<bit_set::BitSet> {
409 if matches!(self.strictness, MatchStrictness::Template) {
411 return None;
412 }
413 let kind = match self.node {
414 PatternNode::Terminal { kind_id, .. } => kind_id,
415 PatternNode::MetaVar { .. } => self.root_kind?,
416 PatternNode::Internal { kind_id, .. } => {
417 if kind_utils::is_error_kind(kind_id) {
418 return None;
420 }
421 kind_id
422 }
423 };
424
425 let mut kinds = BitSet::new();
426 kinds.insert(kind.into());
427 Some(kinds)
428 }
429
430 fn get_match_len<D: Doc>(&self, node: Node<'_, D>) -> Option<usize> {
431 let start = node.range().start;
432 let end = match_end_non_recursive(self, node)?;
433 Some(end - start)
434 }
435}
436impl std::fmt::Debug for PatternNode {
437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438 match self {
439 Self::MetaVar { meta_var, .. } => write!(f, "{meta_var:?}"),
440 Self::Terminal { text, .. } => write!(f, "{text}"),
441 Self::Internal { children, .. } => write!(f, "{children:?}"),
442 }
443 }
444}
445
446impl std::fmt::Debug for Pattern {
447 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448 write!(f, "{:?}", self.node)
449 }
450}
451
452#[cfg(test)]
453mod test {
454 use super::*;
455 use crate::language::Tsx;
456 use crate::matcher::MatcherExt;
457 use crate::meta_var::MetaVarEnv;
458 use crate::tree_sitter::StrDoc;
459 use std::collections::HashMap;
460
461 fn pattern_node(s: &str) -> Root<StrDoc<Tsx>> {
462 Root::str(s, Tsx)
463 }
464
465 fn test_match(s1: &str, s2: &str) {
466 let pattern = Pattern::new(s1, Tsx);
467 let cand = pattern_node(s2);
468 let cand = cand.root();
469 assert!(
470 pattern.find_node(cand.clone()).is_some(),
471 "goal: {:?}, candidate: {}",
472 pattern,
473 cand.get_inner_node().to_sexp(),
474 );
475 }
476 fn test_non_match(s1: &str, s2: &str) {
477 let pattern = Pattern::new(s1, Tsx);
478 let cand = pattern_node(s2);
479 let cand = cand.root();
480 assert!(
481 pattern.find_node(cand.clone()).is_none(),
482 "goal: {:?}, candidate: {}",
483 pattern,
484 cand.get_inner_node().to_sexp(),
485 );
486 }
487
488 #[test]
489 fn test_meta_variable() {
490 test_match("const a = $VALUE", "const a = 123");
491 test_match("const $VARIABLE = $VALUE", "const a = 123");
492 test_match("const $VARIABLE = $VALUE", "const a = 123");
493 }
494
495 #[test]
496 fn test_whitespace() {
497 test_match("function t() { }", "function t() {}");
498 test_match("function t() {}", "function t() { }");
499 }
500
501 fn match_env(goal_str: &str, cand: &str) -> HashMap<String, String> {
502 let pattern = Pattern::new(goal_str, Tsx);
503 let cand = pattern_node(cand);
504 let cand = cand.root();
505 let nm = pattern.find_node(cand).unwrap();
506 HashMap::from(nm.get_env().clone())
507 }
508
509 #[test]
510 fn test_meta_variable_env() {
511 let env = match_env("const a = $VALUE", "const a = 123");
512 assert_eq!(env["VALUE"], "123");
513 }
514
515 #[test]
516 fn test_pattern_should_not_pollute_env() {
517 let pattern = Pattern::new("const $A = 114", Tsx);
519 let cand = pattern_node("const a = 514");
520 let cand = cand.root().child(0).unwrap();
521 let map = MetaVarEnv::new();
522 let mut env = Cow::Borrowed(&map);
523 let nm = pattern.match_node_with_env(cand, &mut env);
524 assert!(nm.is_none());
525 assert!(env.get_match("A").is_none());
526 assert!(map.get_match("A").is_none());
527 }
528
529 #[test]
530 fn test_match_non_atomic() {
531 let env = match_env("const a = $VALUE", "const a = 5 + 3");
532 assert_eq!(env["VALUE"], "5 + 3");
533 }
534
535 #[test]
536 fn test_class_assignment() {
537 test_match("class $C { $MEMBER = $VAL}", "class A {a = 123}");
538 test_non_match("class $C { $MEMBER = $VAL; b = 123; }", "class A {a = 123}");
539 test_non_match("a = 123", "class B {b = 123}");
541 }
542
543 #[test]
544 fn test_return() {
545 test_match("$A($B)", "return test(123)");
546 }
547
548 #[test]
549 fn test_contextual_pattern() {
550 let pattern =
551 Pattern::contextual("class A { $F = $I }", "public_field_definition", Tsx).expect("test");
552 let cand = pattern_node("class B { b = 123 }");
553 assert!(pattern.find_node(cand.root()).is_some());
554 let cand = pattern_node("let b = 123");
555 assert!(pattern.find_node(cand.root()).is_none());
556 }
557
558 #[test]
559 fn test_contextual_match_with_env() {
560 let pattern =
561 Pattern::contextual("class A { $F = $I }", "public_field_definition", Tsx).expect("test");
562 let cand = pattern_node("class B { b = 123 }");
563 let nm = pattern.find_node(cand.root()).expect("test");
564 let env = nm.get_env();
565 let env = HashMap::from(env.clone());
566 assert_eq!(env["F"], "b");
567 assert_eq!(env["I"], "123");
568 }
569
570 #[test]
571 fn test_contextual_unmatch_with_env() {
572 let pattern =
573 Pattern::contextual("class A { $F = $I }", "public_field_definition", Tsx).expect("test");
574 let cand = pattern_node("let b = 123");
575 let nm = pattern.find_node(cand.root());
576 assert!(nm.is_none());
577 }
578
579 fn get_kind(kind_str: &str) -> usize {
580 Tsx.kind_to_id(kind_str).into()
581 }
582
583 #[test]
584 fn test_pattern_potential_kinds() {
585 let pattern = Pattern::new("const a = 1", Tsx);
586 let kind = get_kind("lexical_declaration");
587 let kinds = pattern.potential_kinds().expect("should have kinds");
588 assert_eq!(kinds.count(), 1);
589 assert!(kinds.contains(kind));
590 }
591
592 #[test]
593 fn test_pattern_with_non_root_meta_var() {
594 let pattern = Pattern::new("const $A = $B", Tsx);
595 let kind = get_kind("lexical_declaration");
596 let kinds = pattern.potential_kinds().expect("should have kinds");
597 assert_eq!(kinds.count(), 1);
598 assert!(kinds.contains(kind));
599 }
600
601 #[test]
602 fn test_bare_wildcard() {
603 let pattern = Pattern::new("$A", Tsx);
604 assert!(pattern.potential_kinds().is_none());
606 }
607
608 #[test]
609 fn test_contextual_potential_kinds() {
610 let pattern =
611 Pattern::contextual("class A { $F = $I }", "public_field_definition", Tsx).expect("test");
612 let kind = get_kind("public_field_definition");
613 let kinds = pattern.potential_kinds().expect("should have kinds");
614 assert_eq!(kinds.count(), 1);
615 assert!(kinds.contains(kind));
616 }
617
618 #[test]
619 fn test_contextual_wildcard() {
620 let pattern = Pattern::contextual("class A { $F }", "property_identifier", Tsx).expect("test");
621 let kind = get_kind("property_identifier");
622 let kinds = pattern.potential_kinds().expect("should have kinds");
623 assert_eq!(kinds.count(), 1);
624 assert!(kinds.contains(kind));
625 }
626
627 #[test]
628 #[ignore]
629 fn test_multi_node_pattern() {
630 let pattern = Pattern::new("a;b;c;", Tsx);
631 let kinds = pattern.potential_kinds().expect("should have kinds");
632 assert_eq!(kinds.count(), 1);
633 test_match("a;b;c", "a;b;c;");
634 }
635
636 #[test]
637 #[ignore]
638 fn test_multi_node_meta_var() {
639 let env = match_env("a;$B;c", "a;b;c");
640 assert_eq!(env["B"], "b");
641 let env = match_env("a;$B;c", "a;1+2+3;c");
642 assert_eq!(env["B"], "1+2+3");
643 }
644
645 #[test]
646 #[ignore]
647 fn test_pattern_size() {
648 assert_eq!(std::mem::size_of::<Pattern>(), 40);
649 }
650
651 #[test]
652 fn test_error_kind() {
653 let ret = Pattern::contextual("a", "property_identifier", Tsx);
654 assert!(ret.is_err());
655 let ret = Pattern::new("123+", Tsx);
656 assert!(ret.has_error());
657 }
658
659 #[test]
660 fn test_bare_wildcard_in_context() {
661 let pattern = Pattern::contextual("class A { $F }", "property_identifier", Tsx).expect("test");
662 let cand = pattern_node("let b = 123");
663 assert!(pattern.find_node(cand.root()).is_none());
665 }
666
667 #[test]
668 fn test_pattern_fixed_string() {
669 let pattern = Pattern::new("class A { $F }", Tsx);
670 assert_eq!(pattern.fixed_string(), "class");
671 let pattern = Pattern::contextual("class A { $F }", "property_identifier", Tsx).expect("test");
672 assert!(pattern.fixed_string().is_empty());
673 }
674
675 #[test]
676 fn test_pattern_error() {
677 let pattern = Pattern::try_new("", Tsx);
678 assert!(matches!(pattern, Err(PatternError::NoContent(_))));
679 let pattern = Pattern::try_new("12 3344", Tsx);
680 assert!(matches!(pattern, Err(PatternError::MultipleNode(_))));
681 }
682
683 #[test]
684 fn test_root_multi_meta_var_is_rejected() {
685 for src in ["$$$A", "$$$PARAMS"] {
689 let pattern = Pattern::try_new(src, Tsx);
690 assert!(
691 matches!(pattern, Err(PatternError::RootMultiMetaVar(_))),
692 "expected RootMultiMetaVar for `{src}`, got {pattern:?}"
693 );
694 }
695 }
696
697 #[test]
698 fn test_single_root_meta_var_is_accepted() {
699 assert!(Pattern::try_new("$A", Tsx).is_ok());
701 assert!(Pattern::try_new("$_", Tsx).is_ok());
702 }
703
704 #[test]
705 fn test_in_list_multi_meta_var_is_unchanged() {
706 assert!(Pattern::try_new("foo($$$A, c)", Tsx).is_ok());
709 test_match("foo($$$A, c)", "foo(a, b, c)");
710 }
711
712 #[test]
713 fn test_debug_pattern() {
714 let pattern = Pattern::new("var $A = 1", Tsx);
715 assert_eq!(
716 format!("{pattern:?}"),
717 "[var, [Capture(\"A\", true), =, 1]]"
718 );
719 }
720
721 fn defined_vars(s: &str) -> Vec<String> {
722 let pattern = Pattern::new(s, Tsx);
723 let mut vars: Vec<_> = pattern
724 .defined_vars()
725 .into_iter()
726 .map(String::from)
727 .collect();
728 vars.sort();
729 vars
730 }
731
732 #[test]
733 fn test_extract_meta_var_from_pattern() {
734 let vars = defined_vars("var $A = 1");
735 assert_eq!(vars, ["A"]);
736 }
737
738 #[test]
739 fn test_extract_complex_meta_var() {
740 let vars = defined_vars("function $FUNC($$$ARGS): $RET { $$$BODY }");
741 assert_eq!(vars, ["ARGS", "BODY", "FUNC", "RET"]);
742 }
743
744 #[test]
745 fn test_extract_duplicate_meta_var() {
746 let vars = defined_vars("var $A = $A");
747 assert_eq!(vars, ["A"]);
748 }
749
750 #[test]
751 fn test_contextual_pattern_vars() {
752 let pattern = Pattern::contextual("<div ref={$A}/>", "jsx_attribute", Tsx).expect("correct");
753 assert_eq!(pattern.defined_vars(), ["A"].into_iter().collect());
754 }
755
756 #[test]
757 fn test_gh_1087() {
758 test_match("($P) => $F($P)", "(x) => bar(x)");
759 }
760
761 #[test]
762 fn test_template_pattern_have_no_kinds() {
763 let pattern = Pattern::new("$A = $B", Tsx).with_strictness(MatchStrictness::Template);
764 assert!(pattern.potential_kinds().is_none());
765 let pattern = Pattern::contextual("{a: b}", "pair", Tsx)
766 .expect("should create template pattern")
767 .with_strictness(MatchStrictness::Template);
768 assert!(pattern.potential_kinds().is_none());
769 }
770}