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#[derive(Serialize, Deserialize, Clone, JsonSchema)]
42pub struct SerializableRuleCore {
43 pub rule: SerializableRule,
45 pub constraints: Option<HashMap<String, SerializableRule>>,
47 pub utils: Option<HashMap<String, SerializableRule>>,
49 pub transform: Option<HashMap<String, Transformation>>,
53}
54
55impl SerializableRuleCore {
56 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 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 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 #[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(¬).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(¬).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 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}