Skip to main content

ast_grep_config/
rule_core.rs

1use crate::DeserializeEnv;
2use crate::check_var::{CheckHint, check_rule_with_hint};
3use crate::rule::Rule;
4use crate::rule::referent_rule::RuleRegistration;
5use crate::rule::{RuleSerializeError, SerializableRule};
6use crate::transform::{Transform, TransformError, Transformation};
7
8use ast_grep_core::language::Language;
9use ast_grep_core::meta_var::MetaVarEnv;
10use ast_grep_core::{Doc, Matcher, Node};
11use serde::{Deserialize, Serialize};
12use serde_yaml::Error as YamlError;
13
14use bit_set::BitSet;
15use schemars::JsonSchema;
16use thiserror::Error;
17
18use std::borrow::Cow;
19use std::collections::{HashMap, HashSet};
20use std::ops::Deref;
21
22#[derive(Debug, Error)]
23pub enum RuleCoreError {
24  #[error("Fail to parse yaml as RuleConfig")]
25  Yaml(#[from] YamlError),
26  #[error("`utils` is not configured correctly.")]
27  Utils(#[source] RuleSerializeError),
28  #[error("`rule` is not configured correctly.")]
29  Rule(#[from] RuleSerializeError),
30  #[error("`constraints` is not configured correctly.")]
31  Constraints(#[source] RuleSerializeError),
32  #[error("`transform` is not configured correctly.")]
33  Transform(#[from] TransformError),
34  #[error("Undefined meta var `{0}` used in `{1}`.")]
35  UndefinedMetaVar(String, &'static str),
36}
37
38type RResult<T> = std::result::Result<T, RuleCoreError>;
39
40/// Used for global rules, rewriters, and pyo3/napi
41#[derive(Serialize, Deserialize, Clone, JsonSchema)]
42pub struct SerializableRuleCore {
43  /// A rule object to find matching AST nodes
44  pub rule: SerializableRule,
45  /// Additional meta variables pattern to filter matching
46  pub constraints: Option<HashMap<String, SerializableRule>>,
47  /// Utility rules that can be used in `matches`
48  pub utils: Option<HashMap<String, SerializableRule>>,
49  /// A dictionary for metavariable manipulation. Dict key is the new variable name.
50  /// Dict value is a [transformation] that specifies how meta var is processed.
51  /// See [transformation doc](https://ast-grep.github.io/reference/yaml/transformation.html).
52  pub transform: Option<HashMap<String, Transformation>>,
53}
54
55impl SerializableRuleCore {
56  /// This function assumes env's local is empty.
57  fn get_deserialize_env<L: Language>(&self, env: DeserializeEnv<L>) -> RResult<DeserializeEnv<L>> {
58    if let Some(utils) = &self.utils {
59      let env = env.with_utils(utils).map_err(RuleCoreError::Utils)?;
60      Ok(env)
61    } else {
62      Ok(env)
63    }
64  }
65
66  fn get_constraints<L: Language>(
67    &self,
68    env: &DeserializeEnv<L>,
69  ) -> RResult<HashMap<String, Rule>> {
70    let mut constraints = HashMap::new();
71    let Some(serde_cons) = &self.constraints else {
72      return Ok(constraints);
73    };
74    for (key, ser) in serde_cons {
75      let constraint = env
76        .deserialize_rule(ser.clone())
77        .map_err(RuleCoreError::Constraints)?;
78      constraints.insert(key.to_string(), constraint);
79    }
80    Ok(constraints)
81  }
82
83  fn get_matcher_from_env<L: Language>(&self, env: &DeserializeEnv<L>) -> RResult<RuleCore> {
84    let rule = env.deserialize_rule(self.rule.clone())?;
85    let constraints = self.get_constraints(env)?;
86    let transform = self
87      .transform
88      .as_ref()
89      .map(|t| Transform::deserialize(t, env))
90      .transpose()?;
91    Ok(
92      RuleCore::new(rule)
93        .with_matchers(constraints)
94        .with_registration(env.registration.clone())
95        .with_transform(transform),
96    )
97  }
98
99  pub fn get_matcher<L: Language>(&self, env: DeserializeEnv<L>) -> RResult<RuleCore> {
100    self.get_matcher_with_hint(env, CheckHint::Normal)
101  }
102
103  pub(crate) fn get_matcher_with_hint<L: Language>(
104    &self,
105    env: DeserializeEnv<L>,
106    hint: CheckHint,
107  ) -> RResult<RuleCore> {
108    let env = self.get_deserialize_env(env)?;
109    let ret = self.get_matcher_from_env(&env)?;
110    check_rule_with_hint(&ret, hint)?;
111    Ok(ret)
112  }
113}
114
115pub struct RuleCore {
116  pub(crate) rule: Rule,
117  pub(crate) constraints: HashMap<String, Rule>,
118  kinds: Option<BitSet>,
119  pub(crate) transform: Option<Transform>,
120  // this is required to hold util rule reference
121  pub(crate) registration: RuleRegistration,
122}
123
124impl RuleCore {
125  #[inline]
126  pub fn new(rule: Rule) -> Self {
127    let kinds = rule.potential_kinds();
128    Self {
129      rule,
130      kinds,
131      ..Default::default()
132    }
133  }
134
135  #[inline]
136  pub fn with_matchers(self, constraints: HashMap<String, Rule>) -> Self {
137    Self {
138      constraints,
139      ..self
140    }
141  }
142
143  #[inline]
144  pub fn with_registration(self, registration: RuleRegistration) -> Self {
145    Self {
146      registration,
147      ..self
148    }
149  }
150
151  #[inline]
152  pub fn with_transform(self, transform: Option<Transform>) -> Self {
153    Self { transform, ..self }
154  }
155
156  pub fn get_env<L: Language>(&self, lang: L) -> DeserializeEnv<L> {
157    DeserializeEnv::from_registration(lang, self.registration.clone())
158  }
159
160  /// Get the meta variables that have real ast node matches
161  /// that is, meta vars defined in the rules and constraints
162  pub(crate) fn defined_node_vars(&self) -> HashSet<&str> {
163    let mut ret = self.rule.defined_vars();
164    for v in self.registration.get_local_util_vars() {
165      ret.insert(v);
166    }
167    for constraint in self.constraints.values() {
168      for var in constraint.defined_vars() {
169        ret.insert(var);
170      }
171    }
172    ret
173  }
174
175  pub fn defined_vars(&self) -> HashSet<&str> {
176    let mut ret = self.defined_node_vars();
177    if let Some(trans) = &self.transform {
178      for key in trans.keys() {
179        ret.insert(key);
180      }
181    }
182    ret
183  }
184
185  pub(crate) fn do_match<'tree, D: Doc>(
186    &self,
187    node: Node<'tree, D>,
188    env: &mut Cow<MetaVarEnv<'tree, D>>,
189    enclosing_env: Option<&MetaVarEnv<'tree, D>>,
190  ) -> Option<Node<'tree, D>> {
191    if let Some(kinds) = &self.kinds
192      && !kinds.contains(node.kind_id().into())
193    {
194      return None;
195    }
196    let ret = self.rule.match_node_with_env(node, env)?;
197    if !env.to_mut().match_constraints(&self.constraints) {
198      return None;
199    }
200    if let Some(trans) = &self.transform {
201      let rewriters = self.registration.get_rewriters();
202      let env = env.to_mut();
203      if let Some(enclosing) = enclosing_env {
204        trans.apply_transform(env, rewriters, enclosing);
205      } else {
206        let enclosing = env.clone();
207        trans.apply_transform(env, rewriters, &enclosing);
208      };
209    }
210    Some(ret)
211  }
212}
213impl Deref for RuleCore {
214  type Target = Rule;
215  fn deref(&self) -> &Self::Target {
216    &self.rule
217  }
218}
219
220impl Default for RuleCore {
221  #[inline]
222  fn default() -> Self {
223    Self {
224      rule: Rule::default(),
225      constraints: HashMap::default(),
226      kinds: None,
227      transform: None,
228      registration: RuleRegistration::default(),
229    }
230  }
231}
232
233impl Matcher for RuleCore {
234  fn match_node_with_env<'tree, D: Doc>(
235    &self,
236    node: Node<'tree, D>,
237    env: &mut Cow<MetaVarEnv<'tree, D>>,
238  ) -> Option<Node<'tree, D>> {
239    self.do_match(node, env, None)
240  }
241
242  fn potential_kinds(&self) -> Option<BitSet> {
243    self.kinds.clone()
244  }
245}
246
247#[cfg(test)]
248mod test {
249  use super::*;
250  use crate::SerializableGlobalRule;
251  use crate::from_str;
252  use crate::rewriter::{Rewriter, SerializableRewriter};
253  use crate::rule::referent_rule::{ReferentRule, ReferentRuleError};
254  use crate::test::TypeScript;
255  use ast_grep_core::matcher::{Pattern, RegexMatcher};
256  use ast_grep_core::tree_sitter::LanguageExt;
257
258  fn get_matcher(src: &str) -> RResult<RuleCore> {
259    let env = DeserializeEnv::new(TypeScript::Tsx);
260    let rule: SerializableRuleCore = from_str(src).expect("should word");
261    rule.get_matcher(env)
262  }
263
264  fn get_matcher_with_globals(rule_src: &str, globals_src: &str) -> RResult<RuleCore> {
265    let globals: Vec<SerializableGlobalRule<TypeScript>> =
266      from_str(globals_src).expect("should parse globals");
267    let globals = DeserializeEnv::parse_global_utils(globals).expect("should parse global rules");
268    let env = DeserializeEnv::new(TypeScript::Tsx).with_globals(&globals);
269    let rule: SerializableRuleCore = from_str(rule_src).expect("should parse rule");
270    rule.get_matcher(env)
271  }
272
273  // A `not` sub-rule must not leak a metavariable binding into the match env,
274  // even when reached through a relational rule. Regression for the env-leak
275  // class: the `return $A` sibling makes the negated inner match (binding A),
276  // but `not` succeeds on a different sibling, so $A must remain unbound.
277  #[test]
278  fn test_not_does_not_leak_env_via_yaml() {
279    use ast_grep_core::tree_sitter::LanguageExt;
280    let matcher = get_matcher(
281      r"
282rule:
283  kind: expression_statement
284  regex: '^target'
285  follows:
286    not:
287      pattern: return $A
288    stopBy: end
289",
290    )
291    .expect("rule should compile");
292    let grep = TypeScript::Tsx.ast_grep("function f() { bar(); return foo; target; }");
293    let nm = grep.root().find(&matcher).expect("should match target;");
294    assert!(
295      nm.get_env().get_match("A").is_none(),
296      "`not` must export no binding for $A"
297    );
298  }
299
300  #[test]
301  fn test_rule_error() {
302    let ret = get_matcher(r"rule: {kind: bbb}");
303    assert!(matches!(ret, Err(RuleCoreError::Rule(_))));
304  }
305
306  #[test]
307  fn test_utils_error() {
308    let ret = get_matcher(
309      r"
310rule: { kind: number }
311utils: { testa: {kind: bbb} }
312  ",
313    );
314    assert!(matches!(ret, Err(RuleCoreError::Utils(_))));
315  }
316
317  #[test]
318  fn test_undefined_utils_error() {
319    let ret = get_matcher(r"rule: { kind: number, matches: undefined-util }");
320    match ret {
321      Err(RuleCoreError::Rule(RuleSerializeError::MatchesReference(
322        ReferentRuleError::UndefinedUtil(name),
323      ))) => {
324        assert_eq!(name, "undefined-util");
325      }
326      _ => panic!("wrong error"),
327    }
328  }
329
330  #[test]
331  fn test_cyclic_transform_error() {
332    let ret = get_matcher(
333      r"
334rule: { kind: number }
335transform:
336  A: {substring: {source: $B}}
337  B: {substring: {source: $A}}",
338    );
339    assert!(matches!(
340      ret,
341      Err(RuleCoreError::Transform(TransformError::Cyclic(_)))
342    ));
343  }
344
345  #[test]
346  fn test_rule_reg_with_utils() {
347    let env = DeserializeEnv::new(TypeScript::Tsx);
348    let ser_rule: SerializableRuleCore =
349      from_str("{rule: {matches: test}, utils: {test: {kind: number}} }").expect("should deser");
350    let rule = ReferentRule::try_new("test".into(), &env.registration).expect("should work");
351    let not = ReferentRule::try_new("test2".into(), &env.registration).expect("should work");
352    let matcher = ser_rule.get_matcher(env).expect("should parse");
353    let grep = TypeScript::Tsx.ast_grep("a = 123");
354    assert!(grep.root().find(&matcher).is_some());
355    assert!(grep.root().find(&rule).is_some());
356    assert!(grep.root().find(&not).is_none());
357    let grep = TypeScript::Tsx.ast_grep("a = '123'");
358    assert!(grep.root().find(&matcher).is_none());
359    assert!(grep.root().find(&rule).is_none());
360    assert!(grep.root().find(&not).is_none());
361  }
362
363  #[test]
364  fn test_augmented_rule() {
365    let matcher = get_matcher(
366      r"
367rule:
368  pattern: console.log($A)
369  inside:
370    stopBy: end
371    pattern: function test() { $$$ }
372",
373    )
374    .expect("should parse");
375    let grep = TypeScript::Tsx.ast_grep("console.log(1)");
376    assert!(grep.root().find(&matcher).is_none());
377    let grep = TypeScript::Tsx.ast_grep("function test() { console.log(1) }");
378    assert!(grep.root().find(&matcher).is_some());
379  }
380
381  #[test]
382  fn test_multiple_augment_rule() {
383    let matcher = get_matcher(
384      r"
385rule:
386  pattern: console.log($A)
387  inside:
388    stopBy: end
389    pattern: function test() { $$$ }
390  has:
391    stopBy: end
392    pattern: '123'
393",
394    )
395    .expect("should parse");
396    let grep = TypeScript::Tsx.ast_grep("function test() { console.log(1) }");
397    assert!(grep.root().find(&matcher).is_none());
398    let grep = TypeScript::Tsx.ast_grep("function test() { console.log(123) }");
399    assert!(grep.root().find(&matcher).is_some());
400  }
401
402  #[test]
403  fn test_rule_env() {
404    let matcher = get_matcher(
405      r"
406rule:
407  all:
408    - pattern: console.log($A)
409    - inside:
410        stopBy: end
411        pattern: function $B() {$$$}
412",
413    )
414    .expect("should parse");
415    let grep = TypeScript::Tsx.ast_grep("function test() { console.log(1) }");
416    let node_match = grep.root().find(&matcher).expect("should found");
417    let env = node_match.get_env();
418    let a = env.get_match("A").expect("should exist").text();
419    assert_eq!(a, "1");
420    let b = env.get_match("B").expect("should exist").text();
421    assert_eq!(b, "test");
422  }
423
424  #[test]
425  fn test_utils_rule() {
426    let matcher = get_matcher(
427      r"
428rule:
429  matches: test-rule
430utils:
431  test-rule:
432    pattern: some($A)
433",
434    )
435    .expect("should parse");
436    let grep = TypeScript::Tsx.ast_grep("some(123)");
437    assert!(grep.root().find(&matcher).is_some());
438    let grep = TypeScript::Tsx.ast_grep("some()");
439    assert!(grep.root().find(&matcher).is_none());
440  }
441
442  #[test]
443  fn test_local_util_metavar_does_affect_yaml_rule_matching() {
444    let matcher = get_matcher(
445      r"
446rule:
447  all:
448    - pattern: $A
449    - matches: local-rule
450utils:
451  local-rule:
452    pattern: Some($A)
453",
454    )
455    .expect("should parse");
456    let grep = TypeScript::Tsx.ast_grep("Some(123)");
457    assert!(grep.root().find(&matcher).is_none());
458  }
459
460  #[test]
461  fn test_transform() {
462    let matcher = get_matcher(
463      r"
464rule:
465  pattern: console.log($A)
466transform:
467  B:
468    substring:
469      source: $A
470      startChar: 1
471      endChar: -1
472",
473    )
474    .expect("should parse");
475    let grep = TypeScript::Tsx.ast_grep("function test() { console.log(123) }");
476    let node_match = grep.root().find(&matcher).expect("should found");
477    let env = node_match.get_env();
478    let a = env.get_match("A").expect("should exist").text();
479    assert_eq!(a, "123");
480    let b = env.get_transformed("B").expect("should exist");
481    assert_eq!(b, b"2");
482  }
483
484  #[test]
485  fn test_parameterized_global_rule_requires_all_args() {
486    let ret = get_matcher_with_globals(
487      r"
488rule:
489  matches:
490    wrap: {}
491",
492      r"
493- id: wrap
494  arguments: [BODY]
495  language: Tsx
496  rule:
497    matches: BODY
498",
499    );
500    assert!(matches!(
501      ret,
502      Err(RuleCoreError::Rule(RuleSerializeError::InvalidUtils(
503        crate::rule::ParameterizedUtilError::MissingUtilityArgument { callee, arg }
504      ))) if callee == "wrap" && arg == "BODY"
505    ));
506  }
507
508  #[test]
509  fn test_parameterized_global_rule_rejects_unknown_args() {
510    let ret = get_matcher_with_globals(
511      r"
512rule:
513  matches:
514    wrap:
515      OTHER:
516        kind: number
517      BODY:
518        kind: number
519",
520      r"
521- id: wrap
522  arguments: [BODY]
523  language: Tsx
524  rule:
525    matches: BODY
526",
527    );
528    assert!(matches!(
529      ret,
530      Err(RuleCoreError::Rule(RuleSerializeError::InvalidUtils(
531        crate::rule::ParameterizedUtilError::UnknownUtilityArgument { callee, arg }
532      ))) if callee == "wrap" && arg == "OTHER"
533    ));
534  }
535
536  #[test]
537  fn test_bare_parameterized_global_rule_without_args_is_rejected() {
538    let ret = get_matcher_with_globals(
539      r"
540rule:
541  matches: wrap
542",
543      r"
544- id: wrap
545  arguments: [BODY]
546  language: Tsx
547  rule:
548    matches: BODY
549",
550    );
551    assert!(matches!(
552      ret,
553      Err(RuleCoreError::Rule(RuleSerializeError::InvalidUtils(
554        crate::rule::ParameterizedUtilError::MissingUtilityArguments(name)
555      ))) if name == "wrap"
556    ));
557  }
558
559  #[test]
560  fn test_parameterized_global_call_cycle_in_argument_rule() {
561    let ret = get_matcher_with_globals(
562      r"
563rule:
564  matches:
565    RECUR:
566      x:
567        matches:
568          RECUR:
569            x:
570              kind: number
571",
572      r"
573- id: RECUR
574  arguments: [x]
575  language: Tsx
576  rule:
577    matches: x
578",
579    );
580    assert!(matches!(
581      ret,
582      Err(RuleCoreError::Rule(RuleSerializeError::MatchesReference(
583        ReferentRuleError::CyclicRule(rule)
584      ))) if rule == "RECUR"
585    ));
586  }
587
588  #[test]
589  fn test_parameterized_global_call_cycle_in_argument_rule_with_param_reference() {
590    let ret = get_matcher_with_globals(
591      r"
592rule:
593  matches:
594    RECUR:
595      x:
596        matches:
597          RECUR:
598            x:
599              matches: x
600",
601      r"
602- id: RECUR
603  arguments: [x]
604  language: Tsx
605  rule:
606    matches: x
607",
608    );
609    assert!(matches!(
610      ret,
611      Err(RuleCoreError::Rule(RuleSerializeError::MatchesReference(
612        ReferentRuleError::CyclicRule(rule)
613      ))) if rule == "RECUR"
614    ));
615  }
616
617  #[test]
618  fn test_local_utils_in_parameterized_global_rule_can_match_param_rule() {
619    let matcher = get_matcher_with_globals(
620      r"
621rule:
622  matches:
623    wrap:
624      BODY:
625        pattern: Some($INNER)
626",
627      r"
628- id: wrap
629  arguments: [BODY]
630  language: Tsx
631  rule:
632    matches: helper
633  utils:
634    helper:
635      matches: BODY
636",
637    )
638    .expect("should parse");
639    let grep = TypeScript::Tsx.ast_grep("Some(123)");
640    assert!(grep.root().find(&matcher).is_some());
641    let grep = TypeScript::Tsx.ast_grep("None");
642    assert!(grep.root().find(&matcher).is_none());
643  }
644
645  #[test]
646  fn test_nested_parameterized_global_rule_can_use_outer_param_in_nested_call() {
647    let matcher = get_matcher_with_globals(
648      r"
649rule:
650  matches:
651    outer:
652      X:
653        pattern: Some($INNER)
654",
655      r"
656- id: outer
657  arguments: [X]
658  language: Tsx
659  rule:
660    matches:
661      inner:
662        Y:
663          matches: X
664- id: inner
665  arguments: [Y]
666  language: Tsx
667  rule:
668    matches: Y
669",
670    )
671    .expect("should parse");
672    let grep = TypeScript::Tsx.ast_grep("Some(123)");
673    assert!(grep.root().find(&matcher).is_some());
674    let grep = TypeScript::Tsx.ast_grep("None");
675    assert!(grep.root().find(&matcher).is_none());
676  }
677
678  #[test]
679  fn test_rule_with_constraints() {
680    let mut constraints = HashMap::new();
681    constraints.insert(
682      "A".to_string(),
683      Rule::Regex(RegexMatcher::try_new("a").unwrap()),
684    );
685    let rule =
686      RuleCore::new(Rule::Pattern(Pattern::new("$A", TypeScript::Tsx))).with_matchers(constraints);
687    let grep = TypeScript::Tsx.ast_grep("a");
688    assert!(grep.root().find(&rule).is_some());
689    let grep = TypeScript::Tsx.ast_grep("bbb");
690    assert!(grep.root().find(&rule).is_none());
691  }
692
693  #[test]
694  fn test_constraints_inheriting_env() {
695    let env = DeserializeEnv::new(TypeScript::Tsx);
696    let ser_rule: SerializableRuleCore =
697      from_str("{rule: {pattern: $A = $B}, constraints: {A: {pattern: $B}} }")
698        .expect("should deser");
699    let matcher = ser_rule.get_matcher(env).expect("should parse");
700    let grep = TypeScript::Tsx.ast_grep("a = a");
701    assert!(grep.root().find(&matcher).is_some());
702    let grep = TypeScript::Tsx.ast_grep("a = b");
703    assert!(grep.root().find(&matcher).is_none());
704  }
705
706  #[test]
707  fn test_constraints_writing_to_env() {
708    let env = DeserializeEnv::new(TypeScript::Tsx);
709    let ser_rule: SerializableRuleCore =
710      from_str("{rule: {pattern: $A = $B}, constraints: {B: {pattern: $C + $D}} }")
711        .expect("should deser");
712    let matcher = ser_rule.get_matcher(env).expect("should parse");
713    let grep = TypeScript::Tsx.ast_grep("a = a");
714    assert!(grep.root().find(&matcher).is_none());
715    let grep = TypeScript::Tsx.ast_grep("a = 1 + 2");
716    let nm = grep.root().find(&matcher).expect("should match");
717    let env = nm.get_env();
718    let matched = env.get_match("C").expect("should match C").text();
719    assert_eq!(matched, "1");
720    let matched = env.get_match("D").expect("should match D").text();
721    assert_eq!(matched, "2");
722  }
723
724  fn get_rewriters() -> (&'static str, Rewriter) {
725    // NOTE: initialize a DeserializeEnv here is not 100% correct
726    // it does not inherit global rules or local rules
727    let env = DeserializeEnv::new(TypeScript::Tsx);
728    let rewriter: SerializableRewriter =
729      from_str("{id: xx, rule: {kind: number, pattern: $REWRITE}, fix: yjsnp}")
730        .expect("should parse");
731    let rewriter = rewriter
732      .try_parse_rewriter(&Default::default(), &env)
733      .expect("should work");
734    ("re", rewriter)
735  }
736
737  #[test]
738  fn test_rewriter_writing_to_env() {
739    let (id, rewriter) = get_rewriters();
740    let env = DeserializeEnv::new(TypeScript::Tsx);
741    env.registration.insert_rewriter(id, rewriter);
742    let ser_rule: SerializableRuleCore = from_str(
743      r"
744rule: {pattern: $A = $B}
745transform:
746  C:
747    rewrite:
748      source: $B
749      rewriters: [re]",
750    )
751    .expect("should deser");
752    let matcher = ser_rule.get_matcher(env).expect("should parse");
753    let grep = TypeScript::Tsx.ast_grep("a = 1 + 2");
754    let nm = grep.root().find(&matcher).expect("should match");
755    let env = nm.get_env();
756    let matched = env.get_match("B").expect("should match").text();
757    assert_eq!(matched, "1 + 2");
758    let matched = env.get_match("A").expect("should match").text();
759    assert_eq!(matched, "a");
760    let transformed = env.get_transformed("C").expect("should transform");
761    assert_eq!(String::from_utf8_lossy(transformed), "yjsnp + yjsnp");
762    assert!(env.get_match("REWRITE").is_none());
763
764    let grep = TypeScript::Tsx.ast_grep("a = a");
765    let nm = grep.root().find(&matcher).expect("should match");
766    let env = nm.get_env();
767    let matched = env.get_match("B").expect("should match").text();
768    assert_eq!(matched, "a");
769    let transformed = env.get_transformed("C").expect("should transform");
770    assert_eq!(String::from_utf8_lossy(transformed), "a");
771  }
772}