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::from_registration(lang, self.registration.clone())
189 }
190
191 pub(crate) fn defined_node_vars(&self) -> HashSet<&str> {
194 let mut ret = self.rule.defined_vars();
195 for v in self.registration.get_local_util_vars() {
196 ret.insert(v);
197 }
198 for constraint in self.constraints.values() {
199 for var in constraint.defined_vars() {
200 ret.insert(var);
201 }
202 }
203 ret
204 }
205
206 pub fn defined_vars(&self) -> HashSet<&str> {
207 let mut ret = self.defined_node_vars();
208 if let Some(trans) = &self.transform {
209 for key in trans.keys() {
210 ret.insert(key);
211 }
212 }
213 ret
214 }
215
216 pub(crate) fn do_match<'tree, D: Doc>(
217 &self,
218 node: Node<'tree, D>,
219 env: &mut Cow<MetaVarEnv<'tree, D>>,
220 enclosing_env: Option<&MetaVarEnv<'tree, D>>,
221 ) -> Option<Node<'tree, D>> {
222 if let Some(kinds) = &self.kinds {
223 if !kinds.contains(node.kind_id().into()) {
224 return None;
225 }
226 }
227 let ret = self.rule.match_node_with_env(node, env)?;
228 if !env.to_mut().match_constraints(&self.constraints) {
229 return None;
230 }
231 if let Some(trans) = &self.transform {
232 let rewriters = self.registration.get_rewriters();
233 let env = env.to_mut();
234 if let Some(enclosing) = enclosing_env {
235 trans.apply_transform(env, rewriters, enclosing);
236 } else {
237 let enclosing = env.clone();
238 trans.apply_transform(env, rewriters, &enclosing);
239 };
240 }
241 Some(ret)
242 }
243}
244impl Deref for RuleCore {
245 type Target = Rule;
246 fn deref(&self) -> &Self::Target {
247 &self.rule
248 }
249}
250
251impl Default for RuleCore {
252 #[inline]
253 fn default() -> Self {
254 Self {
255 rule: Rule::default(),
256 constraints: HashMap::default(),
257 kinds: None,
258 transform: None,
259 fixer: vec![],
260 registration: RuleRegistration::default(),
261 }
262 }
263}
264
265impl Matcher for RuleCore {
266 fn match_node_with_env<'tree, D: Doc>(
267 &self,
268 node: Node<'tree, D>,
269 env: &mut Cow<MetaVarEnv<'tree, D>>,
270 ) -> Option<Node<'tree, D>> {
271 self.do_match(node, env, None)
272 }
273
274 fn potential_kinds(&self) -> Option<BitSet> {
275 self.kinds.clone()
276 }
277}
278
279#[cfg(test)]
280mod test {
281 use super::*;
282 use crate::from_str;
283 use crate::rule::referent_rule::{ReferentRule, ReferentRuleError};
284 use crate::test::TypeScript;
285 use crate::SerializableGlobalRule;
286 use ast_grep_core::matcher::{Pattern, RegexMatcher};
287 use ast_grep_core::tree_sitter::LanguageExt;
288
289 fn get_matcher(src: &str) -> RResult<RuleCore> {
290 let env = DeserializeEnv::new(TypeScript::Tsx);
291 let rule: SerializableRuleCore = from_str(src).expect("should word");
292 rule.get_matcher(env)
293 }
294
295 fn get_matcher_with_globals(rule_src: &str, globals_src: &str) -> RResult<RuleCore> {
296 let globals: Vec<SerializableGlobalRule<TypeScript>> =
297 from_str(globals_src).expect("should parse globals");
298 let globals = DeserializeEnv::parse_global_utils(globals).expect("should parse global rules");
299 let env = DeserializeEnv::new(TypeScript::Tsx).with_globals(&globals);
300 let rule: SerializableRuleCore = from_str(rule_src).expect("should parse rule");
301 rule.get_matcher(env)
302 }
303
304 #[test]
305 fn test_rule_error() {
306 let ret = get_matcher(r"rule: {kind: bbb}");
307 assert!(matches!(ret, Err(RuleCoreError::Rule(_))));
308 }
309
310 #[test]
311 fn test_utils_error() {
312 let ret = get_matcher(
313 r"
314rule: { kind: number }
315utils: { testa: {kind: bbb} }
316 ",
317 );
318 assert!(matches!(ret, Err(RuleCoreError::Utils(_))));
319 }
320
321 #[test]
322 fn test_undefined_utils_error() {
323 let ret = get_matcher(r"rule: { kind: number, matches: undefined-util }");
324 match ret {
325 Err(RuleCoreError::Rule(RuleSerializeError::MatchesReference(
326 ReferentRuleError::UndefinedUtil(name),
327 ))) => {
328 assert_eq!(name, "undefined-util");
329 }
330 _ => panic!("wrong error"),
331 }
332 }
333
334 #[test]
335 fn test_cyclic_transform_error() {
336 let ret = get_matcher(
337 r"
338rule: { kind: number }
339transform:
340 A: {substring: {source: $B}}
341 B: {substring: {source: $A}}",
342 );
343 assert!(matches!(
344 ret,
345 Err(RuleCoreError::Transform(TransformError::Cyclic(_)))
346 ));
347 }
348
349 #[test]
350 fn test_rule_reg_with_utils() {
351 let env = DeserializeEnv::new(TypeScript::Tsx);
352 let ser_rule: SerializableRuleCore =
353 from_str("{rule: {matches: test}, utils: {test: {kind: number}} }").expect("should deser");
354 let rule = ReferentRule::try_new("test".into(), &env.registration).expect("should work");
355 let not = ReferentRule::try_new("test2".into(), &env.registration).expect("should work");
356 let matcher = ser_rule.get_matcher(env).expect("should parse");
357 let grep = TypeScript::Tsx.ast_grep("a = 123");
358 assert!(grep.root().find(&matcher).is_some());
359 assert!(grep.root().find(&rule).is_some());
360 assert!(grep.root().find(¬).is_none());
361 let grep = TypeScript::Tsx.ast_grep("a = '123'");
362 assert!(grep.root().find(&matcher).is_none());
363 assert!(grep.root().find(&rule).is_none());
364 assert!(grep.root().find(¬).is_none());
365 }
366
367 #[test]
368 fn test_parameterized_global_rule_requires_all_args() {
369 let ret = get_matcher_with_globals(
370 r"
371rule:
372 matches:
373 wrap: {}
374",
375 r"
376- id: wrap
377 arguments: [BODY]
378 language: Tsx
379 rule:
380 matches: BODY
381",
382 );
383 assert!(matches!(
384 ret,
385 Err(RuleCoreError::Rule(RuleSerializeError::InvalidUtils(
386 crate::rule::ParameterizedUtilError::MissingUtilityArgument { callee, arg }
387 ))) if callee == "wrap" && arg == "BODY"
388 ));
389 }
390
391 #[test]
392 fn test_parameterized_global_rule_rejects_unknown_args() {
393 let ret = get_matcher_with_globals(
394 r"
395rule:
396 matches:
397 wrap:
398 OTHER:
399 kind: number
400 BODY:
401 kind: number
402",
403 r"
404- id: wrap
405 arguments: [BODY]
406 language: Tsx
407 rule:
408 matches: BODY
409",
410 );
411 assert!(matches!(
412 ret,
413 Err(RuleCoreError::Rule(RuleSerializeError::InvalidUtils(
414 crate::rule::ParameterizedUtilError::UnknownUtilityArgument { callee, arg }
415 ))) if callee == "wrap" && arg == "OTHER"
416 ));
417 }
418
419 #[test]
420 fn test_bare_parameterized_global_rule_without_args_is_rejected() {
421 let ret = get_matcher_with_globals(
422 r"
423rule:
424 matches: wrap
425",
426 r"
427- id: wrap
428 arguments: [BODY]
429 language: Tsx
430 rule:
431 matches: BODY
432",
433 );
434 assert!(matches!(
435 ret,
436 Err(RuleCoreError::Rule(RuleSerializeError::InvalidUtils(
437 crate::rule::ParameterizedUtilError::MissingUtilityArguments(name)
438 ))) if name == "wrap"
439 ));
440 }
441
442 #[test]
443 fn test_parameterized_global_call_cycle_in_argument_rule() {
444 let ret = get_matcher_with_globals(
445 r"
446rule:
447 matches:
448 RECUR:
449 x:
450 matches:
451 RECUR:
452 x:
453 kind: number
454",
455 r"
456- id: RECUR
457 arguments: [x]
458 language: Tsx
459 rule:
460 matches: x
461",
462 );
463 assert!(matches!(
464 ret,
465 Err(RuleCoreError::Rule(RuleSerializeError::MatchesReference(
466 ReferentRuleError::CyclicRule(rule)
467 ))) if rule == "RECUR"
468 ));
469 }
470
471 #[test]
472 fn test_parameterized_global_call_cycle_in_argument_rule_with_param_reference() {
473 let ret = get_matcher_with_globals(
474 r"
475rule:
476 matches:
477 RECUR:
478 x:
479 matches:
480 RECUR:
481 x:
482 matches: x
483",
484 r"
485- id: RECUR
486 arguments: [x]
487 language: Tsx
488 rule:
489 matches: x
490",
491 );
492 assert!(matches!(
493 ret,
494 Err(RuleCoreError::Rule(RuleSerializeError::MatchesReference(
495 ReferentRuleError::CyclicRule(rule)
496 ))) if rule == "RECUR"
497 ));
498 }
499
500 #[test]
501 fn test_local_utils_in_parameterized_global_rule_can_match_param_rule() {
502 let matcher = get_matcher_with_globals(
503 r"
504rule:
505 matches:
506 wrap:
507 BODY:
508 pattern: Some($INNER)
509",
510 r"
511- id: wrap
512 arguments: [BODY]
513 language: Tsx
514 rule:
515 matches: helper
516 utils:
517 helper:
518 matches: BODY
519",
520 )
521 .expect("should parse");
522 let grep = TypeScript::Tsx.ast_grep("Some(123)");
523 assert!(grep.root().find(&matcher).is_some());
524 let grep = TypeScript::Tsx.ast_grep("None");
525 assert!(grep.root().find(&matcher).is_none());
526 }
527
528 #[test]
529 fn test_nested_parameterized_global_rule_can_use_outer_param_in_nested_call() {
530 let matcher = get_matcher_with_globals(
531 r"
532rule:
533 matches:
534 outer:
535 X:
536 pattern: Some($INNER)
537",
538 r"
539- id: outer
540 arguments: [X]
541 language: Tsx
542 rule:
543 matches:
544 inner:
545 Y:
546 matches: X
547- id: inner
548 arguments: [Y]
549 language: Tsx
550 rule:
551 matches: Y
552",
553 )
554 .expect("should parse");
555 let grep = TypeScript::Tsx.ast_grep("Some(123)");
556 assert!(grep.root().find(&matcher).is_some());
557 let grep = TypeScript::Tsx.ast_grep("None");
558 assert!(grep.root().find(&matcher).is_none());
559 }
560
561 #[test]
562 fn test_rule_with_constraints() {
563 let mut constraints = HashMap::new();
564 constraints.insert(
565 "A".to_string(),
566 Rule::Regex(RegexMatcher::try_new("a").unwrap()),
567 );
568 let rule =
569 RuleCore::new(Rule::Pattern(Pattern::new("$A", TypeScript::Tsx))).with_matchers(constraints);
570 let grep = TypeScript::Tsx.ast_grep("a");
571 assert!(grep.root().find(&rule).is_some());
572 let grep = TypeScript::Tsx.ast_grep("bbb");
573 assert!(grep.root().find(&rule).is_none());
574 }
575
576 #[test]
577 fn test_constraints_inheriting_env() {
578 let env = DeserializeEnv::new(TypeScript::Tsx);
579 let ser_rule: SerializableRuleCore =
580 from_str("{rule: {pattern: $A = $B}, constraints: {A: {pattern: $B}} }")
581 .expect("should deser");
582 let matcher = ser_rule.get_matcher(env).expect("should parse");
583 let grep = TypeScript::Tsx.ast_grep("a = a");
584 assert!(grep.root().find(&matcher).is_some());
585 let grep = TypeScript::Tsx.ast_grep("a = b");
586 assert!(grep.root().find(&matcher).is_none());
587 }
588
589 #[test]
590 fn test_constraints_writing_to_env() {
591 let env = DeserializeEnv::new(TypeScript::Tsx);
592 let ser_rule: SerializableRuleCore =
593 from_str("{rule: {pattern: $A = $B}, constraints: {B: {pattern: $C + $D}} }")
594 .expect("should deser");
595 let matcher = ser_rule.get_matcher(env).expect("should parse");
596 let grep = TypeScript::Tsx.ast_grep("a = a");
597 assert!(grep.root().find(&matcher).is_none());
598 let grep = TypeScript::Tsx.ast_grep("a = 1 + 2");
599 let nm = grep.root().find(&matcher).expect("should match");
600 let env = nm.get_env();
601 let matched = env.get_match("C").expect("should match C").text();
602 assert_eq!(matched, "1");
603 let matched = env.get_match("D").expect("should match D").text();
604 assert_eq!(matched, "2");
605 }
606
607 fn get_rewriters() -> (&'static str, RuleCore) {
608 let env = DeserializeEnv::new(TypeScript::Tsx);
611 let rewriter: SerializableRuleCore =
612 from_str("{rule: {kind: number, pattern: $REWRITE}, fix: yjsnp}").expect("should parse");
613 let rewriter = rewriter.get_matcher(env).expect("should work");
614 ("re", rewriter)
615 }
616
617 #[test]
618 fn test_rewriter_writing_to_env() {
619 let (id, rewriter) = get_rewriters();
620 let env = DeserializeEnv::new(TypeScript::Tsx);
621 env.registration.insert_rewriter(id, rewriter);
622 let ser_rule: SerializableRuleCore = from_str(
623 r"
624rule: {pattern: $A = $B}
625transform:
626 C:
627 rewrite:
628 source: $B
629 rewriters: [re]",
630 )
631 .expect("should deser");
632 let matcher = ser_rule.get_matcher(env).expect("should parse");
633 let grep = TypeScript::Tsx.ast_grep("a = 1 + 2");
634 let nm = grep.root().find(&matcher).expect("should match");
635 let env = nm.get_env();
636 let matched = env.get_match("B").expect("should match").text();
637 assert_eq!(matched, "1 + 2");
638 let matched = env.get_match("A").expect("should match").text();
639 assert_eq!(matched, "a");
640 let transformed = env.get_transformed("C").expect("should transform");
641 assert_eq!(String::from_utf8_lossy(transformed), "yjsnp + yjsnp");
642 assert!(env.get_match("REWRITE").is_none());
643
644 let grep = TypeScript::Tsx.ast_grep("a = a");
645 let nm = grep.root().find(&matcher).expect("should match");
646 let env = nm.get_env();
647 let matched = env.get_match("B").expect("should match").text();
648 assert_eq!(matched, "a");
649 let transformed = env.get_transformed("C").expect("should transform");
650 assert_eq!(String::from_utf8_lossy(transformed), "a");
651 }
652}