ast_grep_config/
rule_core.rs

1use crate::check_var::{check_rule_with_hint, CheckHint};
2use crate::fixer::{Fixer, FixerError, SerializableFixer};
3use crate::rule::referent_rule::RuleRegistration;
4use crate::rule::Rule;
5use crate::rule::{RuleSerializeError, SerializableRule};
6use crate::transform::{Transform, TransformError, Transformation};
7use crate::DeserializeEnv;
8
9use ast_grep_core::language::Language;
10use ast_grep_core::meta_var::MetaVarEnv;
11use ast_grep_core::{Doc, Matcher, Node};
12use serde::{Deserialize, Serialize};
13use serde_yaml::Error as YamlError;
14
15use bit_set::BitSet;
16use schemars::JsonSchema;
17use thiserror::Error;
18
19use std::borrow::Cow;
20use std::collections::{HashMap, HashSet};
21use std::ops::Deref;
22
23#[derive(Debug, Error)]
24pub enum RuleCoreError {
25  #[error("Fail to parse yaml as RuleConfig")]
26  Yaml(#[from] YamlError),
27  #[error("`utils` is not configured correctly.")]
28  Utils(#[source] RuleSerializeError),
29  #[error("`rule` is not configured correctly.")]
30  Rule(#[from] RuleSerializeError),
31  #[error("`constraints` is not configured correctly.")]
32  Constraints(#[source] RuleSerializeError),
33  #[error("`transform` is not configured correctly.")]
34  Transform(#[from] TransformError),
35  #[error("`fix` pattern is invalid.")]
36  Fixer(#[from] FixerError),
37  #[error("Undefined meta var `{0}` used in `{1}`.")]
38  UndefinedMetaVar(String, &'static str),
39}
40
41type RResult<T> = std::result::Result<T, RuleCoreError>;
42
43/// Used for global rules, rewriters, and pyo3/napi
44#[derive(Serialize, Deserialize, Clone, JsonSchema)]
45pub struct SerializableRuleCore {
46  /// A rule object to find matching AST nodes
47  pub rule: SerializableRule,
48  /// Additional meta variables pattern to filter matching
49  pub constraints: Option<HashMap<String, SerializableRule>>,
50  /// Utility rules that can be used in `matches`
51  pub utils: Option<HashMap<String, SerializableRule>>,
52  /// A dictionary for metavariable manipulation. Dict key is the new variable name.
53  /// Dict value is a [transformation] that specifies how meta var is processed.
54  /// See [transformation doc](https://ast-grep.github.io/reference/yaml/transformation.html).
55  pub transform: Option<HashMap<String, Transformation>>,
56  /// A pattern string or a FixConfig object to auto fix the issue.
57  /// It can reference metavariables appeared in rule.
58  /// See details in fix [object reference](https://ast-grep.github.io/reference/yaml/fix.html#fixconfig).
59  pub fix: Option<SerializableFixer>,
60}
61
62impl SerializableRuleCore {
63  /// This function assumes env's local is empty.
64  fn get_deserialize_env<L: Language>(&self, env: DeserializeEnv<L>) -> RResult<DeserializeEnv<L>> {
65    if let Some(utils) = &self.utils {
66      let env = env.with_utils(utils).map_err(RuleCoreError::Utils)?;
67      Ok(env)
68    } else {
69      Ok(env)
70    }
71  }
72
73  fn get_constraints<L: Language>(
74    &self,
75    env: &DeserializeEnv<L>,
76  ) -> RResult<HashMap<String, Rule>> {
77    let mut constraints = HashMap::new();
78    let Some(serde_cons) = &self.constraints else {
79      return Ok(constraints);
80    };
81    for (key, ser) in serde_cons {
82      let constraint = env
83        .deserialize_rule(ser.clone())
84        .map_err(RuleCoreError::Constraints)?;
85      constraints.insert(key.to_string(), constraint);
86    }
87    Ok(constraints)
88  }
89
90  fn get_fixer<L: Language>(&self, env: &DeserializeEnv<L>) -> RResult<Vec<Fixer>> {
91    if let Some(fix) = &self.fix {
92      let parsed = Fixer::parse(fix, env, &self.transform)?;
93      Ok(parsed)
94    } else {
95      Ok(vec![])
96    }
97  }
98
99  fn get_matcher_from_env<L: Language>(&self, env: &DeserializeEnv<L>) -> RResult<RuleCore> {
100    let rule = env.deserialize_rule(self.rule.clone())?;
101    let constraints = self.get_constraints(env)?;
102    let transform = self
103      .transform
104      .as_ref()
105      .map(|t| Transform::deserialize(t, env))
106      .transpose()?;
107    let fixer = self.get_fixer(env)?;
108    Ok(
109      RuleCore::new(rule)
110        .with_matchers(constraints)
111        .with_registration(env.registration.clone())
112        .with_transform(transform)
113        .with_fixer(fixer),
114    )
115  }
116
117  pub fn get_matcher<L: Language>(&self, env: DeserializeEnv<L>) -> RResult<RuleCore> {
118    self.get_matcher_with_hint(env, CheckHint::Normal)
119  }
120
121  pub(crate) fn get_matcher_with_hint<L: Language>(
122    &self,
123    env: DeserializeEnv<L>,
124    hint: CheckHint,
125  ) -> RResult<RuleCore> {
126    let env = self.get_deserialize_env(env)?;
127    let ret = self.get_matcher_from_env(&env)?;
128    check_rule_with_hint(
129      &ret.rule,
130      &ret.registration,
131      &ret.constraints,
132      &ret.transform,
133      &ret.fixer,
134      hint,
135    )?;
136    Ok(ret)
137  }
138}
139
140pub struct RuleCore {
141  rule: Rule,
142  constraints: HashMap<String, Rule>,
143  kinds: Option<BitSet>,
144  pub(crate) transform: Option<Transform>,
145  pub fixer: Vec<Fixer>,
146  // this is required to hold util rule reference
147  registration: RuleRegistration,
148}
149
150impl RuleCore {
151  #[inline]
152  pub fn new(rule: Rule) -> Self {
153    let kinds = rule.potential_kinds();
154    Self {
155      rule,
156      kinds,
157      ..Default::default()
158    }
159  }
160
161  #[inline]
162  pub fn with_matchers(self, constraints: HashMap<String, Rule>) -> Self {
163    Self {
164      constraints,
165      ..self
166    }
167  }
168
169  #[inline]
170  pub fn with_registration(self, registration: RuleRegistration) -> Self {
171    Self {
172      registration,
173      ..self
174    }
175  }
176
177  #[inline]
178  pub fn with_transform(self, transform: Option<Transform>) -> Self {
179    Self { transform, ..self }
180  }
181
182  #[inline]
183  pub fn with_fixer(self, fixer: Vec<Fixer>) -> Self {
184    Self { fixer, ..self }
185  }
186
187  pub fn get_env<L: Language>(&self, lang: L) -> DeserializeEnv<L> {
188    DeserializeEnv {
189      lang,
190      registration: self.registration.clone(),
191    }
192  }
193  /// Get the meta variables that have real ast node matches
194  /// that is, meta vars defined in the rules and constraints
195  pub(crate) fn defined_node_vars(&self) -> HashSet<&str> {
196    let mut ret = self.rule.defined_vars();
197    for v in self.registration.get_local_util_vars() {
198      ret.insert(v);
199    }
200    for constraint in self.constraints.values() {
201      for var in constraint.defined_vars() {
202        ret.insert(var);
203      }
204    }
205    ret
206  }
207
208  pub fn defined_vars(&self) -> HashSet<&str> {
209    let mut ret = self.defined_node_vars();
210    if let Some(trans) = &self.transform {
211      for key in trans.keys() {
212        ret.insert(key);
213      }
214    }
215    ret
216  }
217
218  pub(crate) fn do_match<'tree, D: Doc>(
219    &self,
220    node: Node<'tree, D>,
221    env: &mut Cow<MetaVarEnv<'tree, D>>,
222    enclosing_env: Option<&MetaVarEnv<'tree, D>>,
223  ) -> Option<Node<'tree, D>> {
224    if let Some(kinds) = &self.kinds {
225      if !kinds.contains(node.kind_id().into()) {
226        return None;
227      }
228    }
229    let ret = self.rule.match_node_with_env(node, env)?;
230    if !env.to_mut().match_constraints(&self.constraints) {
231      return None;
232    }
233    if let Some(trans) = &self.transform {
234      let rewriters = self.registration.get_rewriters();
235      let env = env.to_mut();
236      if let Some(enclosing) = enclosing_env {
237        trans.apply_transform(env, rewriters, enclosing);
238      } else {
239        let enclosing = env.clone();
240        trans.apply_transform(env, rewriters, &enclosing);
241      };
242    }
243    Some(ret)
244  }
245}
246impl Deref for RuleCore {
247  type Target = Rule;
248  fn deref(&self) -> &Self::Target {
249    &self.rule
250  }
251}
252
253impl Default for RuleCore {
254  #[inline]
255  fn default() -> Self {
256    Self {
257      rule: Rule::default(),
258      constraints: HashMap::default(),
259      kinds: None,
260      transform: None,
261      fixer: vec![],
262      registration: RuleRegistration::default(),
263    }
264  }
265}
266
267impl Matcher for RuleCore {
268  fn match_node_with_env<'tree, D: Doc>(
269    &self,
270    node: Node<'tree, D>,
271    env: &mut Cow<MetaVarEnv<'tree, D>>,
272  ) -> Option<Node<'tree, D>> {
273    self.do_match(node, env, None)
274  }
275
276  fn potential_kinds(&self) -> Option<BitSet> {
277    self.rule.potential_kinds()
278  }
279}
280
281#[cfg(test)]
282mod test {
283  use super::*;
284  use crate::from_str;
285  use crate::rule::referent_rule::{ReferentRule, ReferentRuleError};
286  use crate::test::TypeScript;
287  use ast_grep_core::matcher::{Pattern, RegexMatcher};
288  use ast_grep_core::tree_sitter::LanguageExt;
289
290  fn get_matcher(src: &str) -> RResult<RuleCore> {
291    let env = DeserializeEnv::new(TypeScript::Tsx);
292    let rule: SerializableRuleCore = from_str(src).expect("should word");
293    rule.get_matcher(env)
294  }
295
296  #[test]
297  fn test_rule_error() {
298    let ret = get_matcher(r"rule: {kind: bbb}");
299    assert!(matches!(ret, Err(RuleCoreError::Rule(_))));
300  }
301
302  #[test]
303  fn test_utils_error() {
304    let ret = get_matcher(
305      r"
306rule: { kind: number }
307utils: { testa: {kind: bbb} }
308  ",
309    );
310    assert!(matches!(ret, Err(RuleCoreError::Utils(_))));
311  }
312
313  #[test]
314  fn test_undefined_utils_error() {
315    let ret = get_matcher(r"rule: { kind: number, matches: undefined-util }");
316    match ret {
317      Err(RuleCoreError::Rule(RuleSerializeError::MatchesReference(
318        ReferentRuleError::UndefinedUtil(name),
319      ))) => {
320        assert_eq!(name, "undefined-util");
321      }
322      _ => panic!("wrong error"),
323    }
324  }
325
326  #[test]
327  fn test_cyclic_transform_error() {
328    let ret = get_matcher(
329      r"
330rule: { kind: number }
331transform:
332  A: {substring: {source: $B}}
333  B: {substring: {source: $A}}",
334    );
335    assert!(matches!(
336      ret,
337      Err(RuleCoreError::Transform(TransformError::Cyclic(_)))
338    ));
339  }
340
341  #[test]
342  fn test_rule_reg_with_utils() {
343    let env = DeserializeEnv::new(TypeScript::Tsx);
344    let ser_rule: SerializableRuleCore =
345      from_str("{rule: {matches: test}, utils: {test: {kind: number}} }").expect("should deser");
346    let rule = ReferentRule::try_new("test".into(), &env.registration).expect("should work");
347    let not = ReferentRule::try_new("test2".into(), &env.registration).expect("should work");
348    let matcher = ser_rule.get_matcher(env).expect("should parse");
349    let grep = TypeScript::Tsx.ast_grep("a = 123");
350    assert!(grep.root().find(&matcher).is_some());
351    assert!(grep.root().find(&rule).is_some());
352    assert!(grep.root().find(&not).is_none());
353    let grep = TypeScript::Tsx.ast_grep("a = '123'");
354    assert!(grep.root().find(&matcher).is_none());
355    assert!(grep.root().find(&rule).is_none());
356    assert!(grep.root().find(&not).is_none());
357  }
358
359  #[test]
360  fn test_rule_with_constraints() {
361    let mut constraints = HashMap::new();
362    constraints.insert(
363      "A".to_string(),
364      Rule::Regex(RegexMatcher::try_new("a").unwrap()),
365    );
366    let rule =
367      RuleCore::new(Rule::Pattern(Pattern::new("$A", TypeScript::Tsx))).with_matchers(constraints);
368    let grep = TypeScript::Tsx.ast_grep("a");
369    assert!(grep.root().find(&rule).is_some());
370    let grep = TypeScript::Tsx.ast_grep("bbb");
371    assert!(grep.root().find(&rule).is_none());
372  }
373
374  #[test]
375  fn test_constraints_inheriting_env() {
376    let env = DeserializeEnv::new(TypeScript::Tsx);
377    let ser_rule: SerializableRuleCore =
378      from_str("{rule: {pattern: $A = $B}, constraints: {A: {pattern: $B}} }")
379        .expect("should deser");
380    let matcher = ser_rule.get_matcher(env).expect("should parse");
381    let grep = TypeScript::Tsx.ast_grep("a = a");
382    assert!(grep.root().find(&matcher).is_some());
383    let grep = TypeScript::Tsx.ast_grep("a = b");
384    assert!(grep.root().find(&matcher).is_none());
385  }
386
387  #[test]
388  fn test_constraints_writing_to_env() {
389    let env = DeserializeEnv::new(TypeScript::Tsx);
390    let ser_rule: SerializableRuleCore =
391      from_str("{rule: {pattern: $A = $B}, constraints: {B: {pattern: $C + $D}} }")
392        .expect("should deser");
393    let matcher = ser_rule.get_matcher(env).expect("should parse");
394    let grep = TypeScript::Tsx.ast_grep("a = a");
395    assert!(grep.root().find(&matcher).is_none());
396    let grep = TypeScript::Tsx.ast_grep("a = 1 + 2");
397    let nm = grep.root().find(&matcher).expect("should match");
398    let env = nm.get_env();
399    let matched = env.get_match("C").expect("should match C").text();
400    assert_eq!(matched, "1");
401    let matched = env.get_match("D").expect("should match D").text();
402    assert_eq!(matched, "2");
403  }
404
405  fn get_rewriters() -> (&'static str, RuleCore) {
406    // NOTE: initialize a DeserializeEnv here is not 100% correct
407    // it does not inherit global rules or local rules
408    let env = DeserializeEnv::new(TypeScript::Tsx);
409    let rewriter: SerializableRuleCore =
410      from_str("{rule: {kind: number, pattern: $REWRITE}, fix: yjsnp}").expect("should parse");
411    let rewriter = rewriter.get_matcher(env).expect("should work");
412    ("re", rewriter)
413  }
414
415  #[test]
416  fn test_rewriter_writing_to_env() {
417    let (id, rewriter) = get_rewriters();
418    let env = DeserializeEnv::new(TypeScript::Tsx);
419    env.registration.insert_rewriter(id, rewriter);
420    let ser_rule: SerializableRuleCore = from_str(
421      r"
422rule: {pattern: $A = $B}
423transform:
424  C:
425    rewrite:
426      source: $B
427      rewriters: [re]",
428    )
429    .expect("should deser");
430    let matcher = ser_rule.get_matcher(env).expect("should parse");
431    let grep = TypeScript::Tsx.ast_grep("a = 1 + 2");
432    let nm = grep.root().find(&matcher).expect("should match");
433    let env = nm.get_env();
434    let matched = env.get_match("B").expect("should match").text();
435    assert_eq!(matched, "1 + 2");
436    let matched = env.get_match("A").expect("should match").text();
437    assert_eq!(matched, "a");
438    let transformed = env.get_transformed("C").expect("should transform");
439    assert_eq!(String::from_utf8_lossy(transformed), "yjsnp + yjsnp");
440    assert!(env.get_match("REWRITE").is_none());
441
442    let grep = TypeScript::Tsx.ast_grep("a = a");
443    let nm = grep.root().find(&matcher).expect("should match");
444    let env = nm.get_env();
445    let matched = env.get_match("B").expect("should match").text();
446    assert_eq!(matched, "a");
447    let transformed = env.get_transformed("C").expect("should transform");
448    assert_eq!(String::from_utf8_lossy(transformed), "a");
449  }
450}