Skip to main content

ast_grep_config/rule/
mod.rs

1mod deserialize_env;
2mod nth_child;
3mod parameterized_util;
4mod range;
5pub mod referent_rule;
6mod relational_rule;
7mod selector;
8mod stop_by;
9
10pub use deserialize_env::{DeserializeEnv, SerializableGlobalRule};
11pub use parameterized_util::ParameterizedUtilError;
12use parameterized_util::{deserialize_utility_call_matches, SerializableUtilityCall};
13pub use relational_rule::Relation;
14pub use selector::{parse_selector, SelectorError};
15pub use stop_by::StopBy;
16
17use crate::maybe::Maybe;
18use nth_child::{NthChild, NthChildError, SerializableNthChild};
19use range::{RangeMatcher, RangeMatcherError, SerializableRange};
20use referent_rule::{ReferentRule, ReferentRuleError};
21use relational_rule::{Follows, Has, Inside, Precedes};
22
23use ast_grep_core::language::Language;
24use ast_grep_core::matcher::{KindMatcher, RegexMatcher, RegexMatcherError};
25use ast_grep_core::meta_var::MetaVarEnv;
26use ast_grep_core::{ops as o, Doc, Node};
27use ast_grep_core::{MatchStrictness, Matcher, Pattern, PatternError};
28
29use bit_set::BitSet;
30use schemars::JsonSchema;
31use serde::{Deserialize, Serialize};
32use std::borrow::Cow;
33use std::collections::HashSet;
34use thiserror::Error;
35
36/// A rule object to find matching AST nodes. We have three categories of rules in ast-grep.
37///
38/// * Atomic: the most basic rule to match AST. We have two variants: Pattern and Kind.
39///
40/// * Relational: filter matched target according to their position relative to other nodes.
41///
42/// * Composite: use logic operation all/any/not to compose the above rules to larger rules.
43///
44/// Every rule has it's unique name so we can combine several rules in one object.
45#[derive(Serialize, Deserialize, Clone, Default, JsonSchema)]
46#[serde(deny_unknown_fields)]
47pub struct SerializableRule {
48  // avoid embedding AtomicRule/RelationalRule/CompositeRule with flatten here for better error message
49
50  // atomic
51  /// A pattern string or a pattern object.
52  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
53  pub pattern: Maybe<PatternStyle>,
54  /// The kind name of the node to match. You can look up code's kind names in playground.
55  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
56  pub kind: Maybe<String>,
57  /// A Rust regular expression to match the node's text. https://docs.rs/regex/latest/regex/#syntax
58  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
59  pub regex: Maybe<String>,
60  /// `nth_child` accepts number, string or object.
61  /// It specifies the position in nodes' sibling list.
62  #[serde(default, skip_serializing_if = "Maybe::is_absent", rename = "nthChild")]
63  pub nth_child: Maybe<SerializableNthChild>,
64  /// `range` accepts a range object.
65  /// the target node must exactly appear in the range.
66  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
67  pub range: Maybe<SerializableRange>,
68
69  // relational
70  /// `inside` accepts a relational rule object.
71  /// the target node must appear inside of another node matching the `inside` sub-rule.
72  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
73  pub inside: Maybe<Box<Relation>>,
74  /// `has` accepts a relational rule object.
75  /// the target node must has a descendant node matching the `has` sub-rule.
76  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
77  pub has: Maybe<Box<Relation>>,
78  /// `precedes` accepts a relational rule object.
79  /// the target node must appear before another node matching the `precedes` sub-rule.
80  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
81  pub precedes: Maybe<Box<Relation>>,
82  /// `follows` accepts a relational rule object.
83  /// the target node must appear after another node matching the `follows` sub-rule.
84  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
85  pub follows: Maybe<Box<Relation>>,
86  // composite
87  /// A list of sub rules and matches a node if all of sub rules match.
88  /// The meta variables of the matched node contain all variables from the sub-rules.
89  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
90  pub all: Maybe<Vec<SerializableRule>>,
91  /// A list of sub rules and matches a node if any of sub rules match.
92  /// The meta variables of the matched node only contain those of the matched sub-rule.
93  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
94  pub any: Maybe<Vec<SerializableRule>>,
95  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
96  /// A single sub-rule and matches a node if the sub rule does not match.
97  pub not: Maybe<Box<SerializableRule>>,
98  /// A utility rule id or parameterized utility call object.
99  /// When multiple utility calls are present, they are combined with logical `all`.
100  #[serde(default, skip_serializing_if = "Maybe::is_absent")]
101  pub matches: Maybe<SerializableMatches>,
102}
103
104#[derive(Serialize, Deserialize, Clone, JsonSchema)]
105#[serde(untagged)]
106pub enum SerializableMatches {
107  Id(String),
108  Call(SerializableUtilityCall),
109}
110
111struct Categorized {
112  pub atomic: AtomicRule,
113  pub relational: RelationalRule,
114  pub composite: CompositeRule,
115}
116
117impl SerializableRule {
118  fn categorized(self) -> Categorized {
119    Categorized {
120      atomic: AtomicRule {
121        pattern: self.pattern.into(),
122        kind: self.kind.into(),
123        regex: self.regex.into(),
124        nth_child: self.nth_child.into(),
125        range: self.range.into(),
126      },
127      relational: RelationalRule {
128        inside: self.inside.into(),
129        has: self.has.into(),
130        precedes: self.precedes.into(),
131        follows: self.follows.into(),
132      },
133      composite: CompositeRule {
134        all: self.all.into(),
135        any: self.any.into(),
136        not: self.not.into(),
137        matches: self.matches.into(),
138      },
139    }
140  }
141}
142
143pub struct AtomicRule {
144  pub pattern: Option<PatternStyle>,
145  pub kind: Option<String>,
146  pub regex: Option<String>,
147  pub nth_child: Option<SerializableNthChild>,
148  pub range: Option<SerializableRange>,
149}
150#[derive(Serialize, Deserialize, Clone, JsonSchema)]
151#[serde(rename_all = "camelCase")]
152pub enum Strictness {
153  /// all nodes are matched
154  Cst,
155  /// all nodes except source trivial nodes are matched.
156  Smart,
157  /// only ast nodes are matched
158  Ast,
159  /// ast-nodes excluding comments are matched
160  Relaxed,
161  /// ast-nodes excluding comments, without text
162  Signature,
163  /// similar to smart, but node kinds are ignored, only text is matched.
164  Template,
165}
166
167impl From<MatchStrictness> for Strictness {
168  fn from(value: MatchStrictness) -> Self {
169    use MatchStrictness as M;
170    use Strictness as S;
171    match value {
172      M::Cst => S::Cst,
173      M::Smart => S::Smart,
174      M::Ast => S::Ast,
175      M::Relaxed => S::Relaxed,
176      M::Signature => S::Signature,
177      M::Template => S::Template,
178    }
179  }
180}
181
182impl From<Strictness> for MatchStrictness {
183  fn from(value: Strictness) -> Self {
184    use MatchStrictness as M;
185    use Strictness as S;
186    match value {
187      S::Cst => M::Cst,
188      S::Smart => M::Smart,
189      S::Ast => M::Ast,
190      S::Relaxed => M::Relaxed,
191      S::Signature => M::Signature,
192      S::Template => M::Template,
193    }
194  }
195}
196
197/// A String pattern will match one single AST node according to pattern syntax.
198/// Or an object with field `context`, `selector` and optionally `strictness`.
199#[derive(Serialize, Deserialize, Clone, JsonSchema)]
200#[serde(untagged, deny_unknown_fields)]
201pub enum PatternStyle {
202  Str(String),
203  Contextual {
204    /// The surrounding code that helps to resolve any ambiguity in the syntax.
205    context: String,
206    /// The sub-syntax node kind that is the actual matcher of the pattern.
207    selector: Option<String>,
208    /// Strictness of the pattern. More strict pattern matches fewer nodes.
209    strictness: Option<Strictness>,
210  },
211}
212
213pub struct RelationalRule {
214  pub inside: Option<Box<Relation>>,
215  pub has: Option<Box<Relation>>,
216  pub precedes: Option<Box<Relation>>,
217  pub follows: Option<Box<Relation>>,
218}
219
220pub struct CompositeRule {
221  pub all: Option<Vec<SerializableRule>>,
222  pub any: Option<Vec<SerializableRule>>,
223  pub not: Option<Box<SerializableRule>>,
224  pub matches: Option<SerializableMatches>,
225}
226
227pub enum Rule {
228  // atomic
229  Pattern(Pattern),
230  Kind(KindMatcher),
231  Regex(RegexMatcher),
232  NthChild(NthChild),
233  Range(RangeMatcher),
234  // relational
235  Inside(Box<Inside>),
236  Has(Box<Has>),
237  Precedes(Box<Precedes>),
238  Follows(Box<Follows>),
239  // composite
240  All(o::All<Rule>),
241  Any(o::Any<Rule>),
242  Not(Box<o::Not<Rule>>),
243  Matches(ReferentRule),
244}
245
246impl Rule {
247  /// Check if it has a cyclic referent rule with the id.
248  pub(crate) fn check_cyclic(&self, id: &str) -> bool {
249    match self {
250      Rule::All(all) => all.inner().iter().any(|r| r.check_cyclic(id)),
251      Rule::Any(any) => any.inner().iter().any(|r| r.check_cyclic(id)),
252      Rule::Not(not) => not.inner().check_cyclic(id),
253      Rule::Matches(rule) => rule.check_cyclic(id),
254      _ => false,
255    }
256  }
257
258  pub fn defined_vars(&self) -> HashSet<&str> {
259    match self {
260      Rule::Pattern(p) => p.defined_vars(),
261      Rule::Kind(_) => HashSet::new(),
262      Rule::Regex(_) => HashSet::new(),
263      Rule::NthChild(n) => n.defined_vars(),
264      Rule::Range(_) => HashSet::new(),
265      Rule::Has(c) => c.defined_vars(),
266      Rule::Inside(p) => p.defined_vars(),
267      Rule::Precedes(f) => f.defined_vars(),
268      Rule::Follows(f) => f.defined_vars(),
269      Rule::All(sub) => sub.inner().iter().flat_map(|r| r.defined_vars()).collect(),
270      Rule::Any(sub) => sub.inner().iter().flat_map(|r| r.defined_vars()).collect(),
271      Rule::Not(sub) => sub.inner().defined_vars(),
272      Rule::Matches(rule) => rule.defined_vars(),
273    }
274  }
275
276  /// check if util rules used are defined
277  pub fn verify_util(&self) -> Result<(), RuleSerializeError> {
278    match self {
279      Rule::Pattern(_) => Ok(()),
280      Rule::Kind(_) => Ok(()),
281      Rule::Regex(_) => Ok(()),
282      Rule::NthChild(n) => n.verify_util(),
283      Rule::Range(_) => Ok(()),
284      Rule::Has(c) => c.verify_util(),
285      Rule::Inside(p) => p.verify_util(),
286      Rule::Precedes(f) => f.verify_util(),
287      Rule::Follows(f) => f.verify_util(),
288      Rule::All(sub) => sub.inner().iter().try_for_each(|r| r.verify_util()),
289      Rule::Any(sub) => sub.inner().iter().try_for_each(|r| r.verify_util()),
290      Rule::Not(sub) => sub.inner().verify_util(),
291      Rule::Matches(rule) => rule.verify_util(),
292    }
293  }
294}
295
296impl Matcher for Rule {
297  fn match_node_with_env<'tree, D: Doc>(
298    &self,
299    node: Node<'tree, D>,
300    env: &mut Cow<MetaVarEnv<'tree, D>>,
301  ) -> Option<Node<'tree, D>> {
302    use Rule::*;
303    match self {
304      // atomic
305      Pattern(pattern) => pattern.match_node_with_env(node, env),
306      Kind(kind) => kind.match_node_with_env(node, env),
307      Regex(regex) => regex.match_node_with_env(node, env),
308      NthChild(nth_child) => nth_child.match_node_with_env(node, env),
309      Range(range) => range.match_node_with_env(node, env),
310      // relational
311      Inside(parent) => match_and_add_label(&**parent, node, env),
312      Has(child) => match_and_add_label(&**child, node, env),
313      Precedes(latter) => match_and_add_label(&**latter, node, env),
314      Follows(former) => match_and_add_label(&**former, node, env),
315      // composite
316      All(all) => all.match_node_with_env(node, env),
317      Any(any) => any.match_node_with_env(node, env),
318      Not(not) => not.match_node_with_env(node, env),
319      Matches(rule) => rule.match_node_with_env(node, env),
320    }
321  }
322
323  fn potential_kinds(&self) -> Option<BitSet> {
324    use Rule::*;
325    match self {
326      // atomic
327      Pattern(pattern) => pattern.potential_kinds(),
328      Kind(kind) => kind.potential_kinds(),
329      Regex(regex) => regex.potential_kinds(),
330      NthChild(nth_child) => nth_child.potential_kinds(),
331      Range(range) => range.potential_kinds(),
332      // relational
333      Inside(parent) => parent.potential_kinds(),
334      Has(child) => child.potential_kinds(),
335      Precedes(latter) => latter.potential_kinds(),
336      Follows(former) => former.potential_kinds(),
337      // composite
338      All(all) => all.potential_kinds(),
339      Any(any) => any.potential_kinds(),
340      Not(not) => not.potential_kinds(),
341      Matches(rule) => rule.potential_kinds(),
342    }
343  }
344}
345
346/// Rule matches nothing by default.
347/// In Math jargon, Rule is vacuously false.
348impl Default for Rule {
349  fn default() -> Self {
350    Self::Any(o::Any::new(std::iter::empty()))
351  }
352}
353
354fn match_and_add_label<'tree, D: Doc, M: Matcher>(
355  inner: &M,
356  node: Node<'tree, D>,
357  env: &mut Cow<MetaVarEnv<'tree, D>>,
358) -> Option<Node<'tree, D>> {
359  let matched = inner.match_node_with_env(node, env)?;
360  env.to_mut().add_label("secondary", matched.clone());
361  Some(matched)
362}
363
364#[derive(Debug, Error)]
365pub enum RuleSerializeError {
366  #[error("Rule must have one positive matcher.")]
367  MissPositiveMatcher,
368  #[error("Rule contains invalid kind matcher.")]
369  InvalidKind(#[from] SelectorError),
370  #[error("Rule contains invalid pattern matcher.")]
371  InvalidPattern(#[from] PatternError),
372  #[error("Rule contains invalid nthChild.")]
373  NthChild(#[from] NthChildError),
374  #[error("Rule contains invalid regex matcher.")]
375  WrongRegex(#[from] RegexMatcherError),
376  #[error("Rule contains invalid matches reference.")]
377  MatchesReference(#[from] ReferentRuleError),
378  #[error("Rule contains invalid utils.")]
379  InvalidUtils(#[from] ParameterizedUtilError),
380  #[error("Rule contains invalid range matcher.")]
381  InvalidRange(#[from] RangeMatcherError),
382  #[error("field is only supported in has/inside.")]
383  FieldNotSupported,
384  #[error("Relational rule contains invalid field {0}.")]
385  InvalidField(String),
386}
387
388// TODO: implement positive/non positive
389pub fn deserialize_rule<L: Language>(
390  serialized: SerializableRule,
391  env: &DeserializeEnv<L>,
392) -> Result<Rule, RuleSerializeError> {
393  let mut rules = Vec::with_capacity(1);
394  use Rule as R;
395  let categorized = serialized.categorized();
396  // ATTENTION, relational_rule should always come at last
397  // after target node is decided by atomic/composite rule
398  deserialze_atomic_rule(categorized.atomic, &mut rules, env)?;
399  deserialze_composite_rule(categorized.composite, &mut rules, env)?;
400  deserialize_relational_rule(categorized.relational, &mut rules, env)?;
401
402  if rules.is_empty() {
403    Err(RuleSerializeError::MissPositiveMatcher)
404  } else if rules.len() == 1 {
405    Ok(rules.pop().expect("should not be empty"))
406  } else {
407    Ok(R::All(o::All::new(rules)))
408  }
409}
410
411fn deserialze_composite_rule<L: Language>(
412  composite: CompositeRule,
413  rules: &mut Vec<Rule>,
414  env: &DeserializeEnv<L>,
415) -> Result<(), RuleSerializeError> {
416  use Rule as R;
417  let convert_rules = |rules: Vec<SerializableRule>| -> Result<_, RuleSerializeError> {
418    let mut inner = Vec::with_capacity(rules.len());
419    for rule in rules {
420      inner.push(deserialize_rule(rule, env)?);
421    }
422    Ok(inner)
423  };
424  if let Some(all) = composite.all {
425    rules.push(R::All(o::All::new(convert_rules(all)?)));
426  }
427  if let Some(any) = composite.any {
428    rules.push(R::Any(o::Any::new(convert_rules(any)?)));
429  }
430  if let Some(not) = composite.not {
431    let not = o::Not::new(deserialize_rule(*not, env)?);
432    rules.push(R::Not(Box::new(not)));
433  }
434  if let Some(matches) = composite.matches {
435    rules.push(deserialize_matches_rule(matches, env)?);
436  }
437  Ok(())
438}
439
440fn deserialize_matches_rule<L: Language>(
441  matches: SerializableMatches,
442  env: &DeserializeEnv<L>,
443) -> Result<Rule, RuleSerializeError> {
444  use Rule as R;
445  match matches {
446    SerializableMatches::Id(id) => {
447      let matches = if env.registration.has_current_param(&id) {
448        ReferentRule::try_new_param(id, &env.registration)?
449      } else {
450        ReferentRule::try_new(id, &env.registration)?
451      };
452      Ok(R::Matches(matches))
453    }
454    SerializableMatches::Call(call) => deserialize_utility_call_matches(call, env),
455  }
456}
457
458fn deserialize_relational_rule<L: Language>(
459  relational: RelationalRule,
460  rules: &mut Vec<Rule>,
461  env: &DeserializeEnv<L>,
462) -> Result<(), RuleSerializeError> {
463  use Rule as R;
464  // relational
465  if let Some(inside) = relational.inside {
466    rules.push(R::Inside(Box::new(Inside::try_new(*inside, env)?)));
467  }
468  if let Some(has) = relational.has {
469    rules.push(R::Has(Box::new(Has::try_new(*has, env)?)));
470  }
471  if let Some(precedes) = relational.precedes {
472    rules.push(R::Precedes(Box::new(Precedes::try_new(*precedes, env)?)));
473  }
474  if let Some(follows) = relational.follows {
475    rules.push(R::Follows(Box::new(Follows::try_new(*follows, env)?)));
476  }
477  Ok(())
478}
479
480fn deserialze_atomic_rule<L: Language>(
481  atomic: AtomicRule,
482  rules: &mut Vec<Rule>,
483  env: &DeserializeEnv<L>,
484) -> Result<(), RuleSerializeError> {
485  use Rule as R;
486  if let Some(pattern) = atomic.pattern {
487    rules.push(match pattern {
488      PatternStyle::Str(pat) => R::Pattern(Pattern::try_new(&pat, env.lang.clone())?),
489      PatternStyle::Contextual {
490        context,
491        selector,
492        strictness,
493      } => {
494        let pattern = if let Some(selector) = selector {
495          Pattern::contextual(&context, &selector, env.lang.clone())?
496        } else {
497          Pattern::try_new(&context, env.lang.clone())?
498        };
499        let pattern = if let Some(strictness) = strictness {
500          pattern.with_strictness(strictness.into())
501        } else {
502          pattern
503        };
504        R::Pattern(pattern)
505      }
506    });
507  }
508  if let Some(kind) = atomic.kind {
509    let rule = parse_selector(&kind, env.lang.clone())?;
510    rules.push(rule);
511  }
512  if let Some(regex) = atomic.regex {
513    rules.push(R::Regex(RegexMatcher::try_new(&regex)?));
514  }
515  if let Some(nth_child) = atomic.nth_child {
516    rules.push(R::NthChild(NthChild::try_new(nth_child, env)?));
517  }
518  if let Some(range) = atomic.range {
519    rules.push(R::Range(RangeMatcher::try_new(range.start, range.end)?));
520  }
521  Ok(())
522}
523
524#[cfg(test)]
525mod test {
526  use super::*;
527  use crate::from_str;
528  use crate::test::TypeScript;
529  use ast_grep_core::tree_sitter::LanguageExt;
530  use PatternStyle::*;
531
532  #[test]
533  fn test_pattern() {
534    let src = r"
535pattern: Test
536";
537    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
538    assert!(rule.pattern.is_present());
539    let src = r"
540pattern:
541  context: class $C { set $B() {} }
542  selector: method_definition
543";
544    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
545    assert!(matches!(rule.pattern, Maybe::Present(Contextual { .. }),));
546
547    // test #2390
548    let src = r"
549pattern:
550  context: class $C { set $B() {} }
551  selector: method_definition
552";
553    let rule: Result<PatternStyle, _> = from_str(src);
554    assert!(rule.is_err());
555  }
556
557  #[test]
558  fn test_augmentation() {
559    let src = r"
560pattern: class A {}
561inside:
562  pattern: function() {}
563";
564    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
565    assert!(rule.inside.is_present());
566    assert!(rule.pattern.is_present());
567  }
568
569  #[test]
570  fn test_multi_augmentation() {
571    let src = r"
572pattern: class A {}
573inside:
574  pattern: function() {}
575has:
576  pattern: Some()
577";
578    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
579    assert!(rule.inside.is_present());
580    assert!(rule.has.is_present());
581    assert!(rule.follows.is_absent());
582    assert!(rule.precedes.is_absent());
583    assert!(rule.pattern.is_present());
584  }
585
586  #[test]
587  fn test_maybe_not() {
588    let src = "not: 123";
589    let ret: Result<SerializableRule, _> = from_str(src);
590    assert!(ret.is_err());
591    let src = "not:";
592    let ret: Result<SerializableRule, _> = from_str(src);
593    assert!(ret.is_err());
594  }
595
596  #[test]
597  fn test_nested_augmentation() {
598    let src = r"
599pattern: class A {}
600inside:
601  pattern: function() {}
602  inside:
603    pattern:
604      context: Some()
605      selector: ss
606";
607    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
608    assert!(rule.inside.is_present());
609    let inside = rule.inside.unwrap();
610    assert!(inside.rule.pattern.is_present());
611    assert!(inside.rule.inside.unwrap().rule.pattern.is_present());
612  }
613
614  #[test]
615  fn test_precedes_follows() {
616    let src = r"
617pattern: class A {}
618precedes:
619  pattern: function() {}
620follows:
621  pattern:
622    context: Some()
623    selector: ss
624";
625    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
626    assert!(rule.precedes.is_present());
627    assert!(rule.follows.is_present());
628    let follows = rule.follows.unwrap();
629    assert!(follows.rule.pattern.is_present());
630    assert!(follows.rule.pattern.is_present());
631  }
632
633  #[test]
634  fn test_parameterized_matches_syntax() {
635    let src = r"
636matches:
637  maybe_parenthesized:
638    BODY:
639      pattern: foo($A)
640";
641    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
642    assert!(matches!(
643      rule.matches,
644      Maybe::Present(SerializableMatches::Call(_))
645    ));
646  }
647
648  #[test]
649  fn test_multiple_parameterized_matches_syntax() {
650    let src = r"
651matches:
652  first:
653    ARG:
654      pattern: foo($A)
655  second:
656    ARG:
657      kind: number
658";
659    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
660    match rule.matches {
661      Maybe::Present(SerializableMatches::Call(call)) => assert_eq!(call.0.len(), 2),
662      _ => panic!("expected matches call"),
663    }
664  }
665
666  #[test]
667  fn test_deserialize_rule() {
668    let src = r"
669pattern: class A {}
670kind: class_declaration
671";
672    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
673    let env = DeserializeEnv::new(TypeScript::Tsx);
674    let rule = deserialize_rule(rule, &env).expect("should deserialize");
675    let root = TypeScript::Tsx.ast_grep("class A {}");
676    assert!(root.root().find(rule).is_some());
677  }
678
679  #[test]
680  fn test_deserialize_order() {
681    let src = r"
682pattern: class A {}
683inside:
684  kind: class
685";
686    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
687    let env = DeserializeEnv::new(TypeScript::Tsx);
688    let rule = deserialize_rule(rule, &env).expect("should deserialize");
689    assert!(matches!(rule, Rule::All(_)));
690  }
691
692  #[test]
693  fn test_defined_vars() {
694    let src = r"
695pattern: var $A = 123
696inside:
697  pattern: var $B = 456
698";
699    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
700    let env = DeserializeEnv::new(TypeScript::Tsx);
701    let rule = deserialize_rule(rule, &env).expect("should deserialize");
702    assert_eq!(rule.defined_vars(), ["A", "B"].into_iter().collect());
703  }
704
705  #[test]
706  fn test_issue_1164() {
707    let src = r"
708    kind: statement_block
709    has:
710      pattern: this.$A = promise()
711      stopBy: end";
712    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
713    let env = DeserializeEnv::new(TypeScript::Tsx);
714    let rule = deserialize_rule(rule, &env).expect("should deserialize");
715    let root = TypeScript::Tsx.ast_grep(
716      "if (a) {
717      this.a = b;
718      this.d = promise()
719    }",
720    );
721    assert!(root.root().find(rule).is_some());
722  }
723
724  #[test]
725  fn test_issue_1225() {
726    let src = r"
727    kind: statement_block
728    has:
729      pattern: $A
730      regex: const";
731    let rule: SerializableRule = from_str(src).expect("cannot parse rule");
732    let env = DeserializeEnv::new(TypeScript::Tsx);
733    let rule = deserialize_rule(rule, &env).expect("should deserialize");
734    let root = TypeScript::Tsx.ast_grep(
735      "{
736        let x = 1;
737        const z = 9;
738      }",
739    );
740    assert!(root.root().find(rule).is_some());
741  }
742}