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#[derive(Serialize, Deserialize, Clone, JsonSchema)]
45pub struct SerializableRuleCore {
46 pub rule: SerializableRule,
48 pub constraints: Option<HashMap<String, SerializableRule>>,
50 pub utils: Option<HashMap<String, SerializableRule>>,
52 pub transform: Option<HashMap<String, Transformation>>,
56 pub fix: Option<SerializableFixer>,
60}
61
62impl SerializableRuleCore {
63 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 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 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(¬).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(¬).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 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}