1mod deserialize_env;
2mod nth_child;
3mod parameterized_util;
4mod range;
5pub mod referent_rule;
6mod relational_rule;
7mod selector;
8mod stop_by;
9
10pub use deserialize_env::{DeserializeEnv, SerializableGlobalRule};
11pub use parameterized_util::ParameterizedUtilError;
12use parameterized_util::{deserialize_utility_call_matches, SerializableUtilityCall};
13pub use relational_rule::Relation;
14pub use selector::{parse_selector, SelectorError};
15pub use stop_by::StopBy;
16
17use crate::maybe::Maybe;
18use nth_child::{NthChild, NthChildError, SerializableNthChild};
19use range::{RangeMatcher, RangeMatcherError, SerializableRange};
20use referent_rule::{ReferentRule, ReferentRuleError};
21use relational_rule::{Follows, Has, Inside, Precedes};
22
23use ast_grep_core::language::Language;
24use ast_grep_core::matcher::{KindMatcher, RegexMatcher, RegexMatcherError};
25use ast_grep_core::meta_var::MetaVarEnv;
26use ast_grep_core::{ops as o, Doc, Node};
27use ast_grep_core::{MatchStrictness, Matcher, Pattern, PatternError};
28
29use bit_set::BitSet;
30use schemars::JsonSchema;
31use serde::{Deserialize, Serialize};
32use std::borrow::Cow;
33use std::collections::HashSet;
34use thiserror::Error;
35
36#[derive(Serialize, Deserialize, Clone, Default, JsonSchema)]
46#[serde(deny_unknown_fields)]
47pub struct SerializableRule {
48 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
53 pub pattern: Maybe<PatternStyle>,
54 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
56 pub kind: Maybe<String>,
57 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
59 pub regex: Maybe<String>,
60 #[serde(default, skip_serializing_if = "Maybe::is_absent", rename = "nthChild")]
63 pub nth_child: Maybe<SerializableNthChild>,
64 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
67 pub range: Maybe<SerializableRange>,
68
69 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
73 pub inside: Maybe<Box<Relation>>,
74 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
77 pub has: Maybe<Box<Relation>>,
78 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
81 pub precedes: Maybe<Box<Relation>>,
82 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
85 pub follows: Maybe<Box<Relation>>,
86 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
90 pub all: Maybe<Vec<SerializableRule>>,
91 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
94 pub any: Maybe<Vec<SerializableRule>>,
95 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
96 pub not: Maybe<Box<SerializableRule>>,
98 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
101 pub matches: Maybe<SerializableMatches>,
102}
103
104#[derive(Serialize, Deserialize, Clone, JsonSchema)]
105#[serde(untagged)]
106pub enum SerializableMatches {
107 Id(String),
108 Call(SerializableUtilityCall),
109}
110
111struct Categorized {
112 pub atomic: AtomicRule,
113 pub relational: RelationalRule,
114 pub composite: CompositeRule,
115}
116
117impl SerializableRule {
118 fn categorized(self) -> Categorized {
119 Categorized {
120 atomic: AtomicRule {
121 pattern: self.pattern.into(),
122 kind: self.kind.into(),
123 regex: self.regex.into(),
124 nth_child: self.nth_child.into(),
125 range: self.range.into(),
126 },
127 relational: RelationalRule {
128 inside: self.inside.into(),
129 has: self.has.into(),
130 precedes: self.precedes.into(),
131 follows: self.follows.into(),
132 },
133 composite: CompositeRule {
134 all: self.all.into(),
135 any: self.any.into(),
136 not: self.not.into(),
137 matches: self.matches.into(),
138 },
139 }
140 }
141}
142
143pub struct AtomicRule {
144 pub pattern: Option<PatternStyle>,
145 pub kind: Option<String>,
146 pub regex: Option<String>,
147 pub nth_child: Option<SerializableNthChild>,
148 pub range: Option<SerializableRange>,
149}
150#[derive(Serialize, Deserialize, Clone, JsonSchema)]
151#[serde(rename_all = "camelCase")]
152pub enum Strictness {
153 Cst,
155 Smart,
157 Ast,
159 Relaxed,
161 Signature,
163 Template,
165}
166
167impl From<MatchStrictness> for Strictness {
168 fn from(value: MatchStrictness) -> Self {
169 use MatchStrictness as M;
170 use Strictness as S;
171 match value {
172 M::Cst => S::Cst,
173 M::Smart => S::Smart,
174 M::Ast => S::Ast,
175 M::Relaxed => S::Relaxed,
176 M::Signature => S::Signature,
177 M::Template => S::Template,
178 }
179 }
180}
181
182impl From<Strictness> for MatchStrictness {
183 fn from(value: Strictness) -> Self {
184 use MatchStrictness as M;
185 use Strictness as S;
186 match value {
187 S::Cst => M::Cst,
188 S::Smart => M::Smart,
189 S::Ast => M::Ast,
190 S::Relaxed => M::Relaxed,
191 S::Signature => M::Signature,
192 S::Template => M::Template,
193 }
194 }
195}
196
197#[derive(Serialize, Deserialize, Clone, JsonSchema)]
200#[serde(untagged, deny_unknown_fields)]
201pub enum PatternStyle {
202 Str(String),
203 Contextual {
204 context: String,
206 selector: Option<String>,
208 strictness: Option<Strictness>,
210 },
211}
212
213pub struct RelationalRule {
214 pub inside: Option<Box<Relation>>,
215 pub has: Option<Box<Relation>>,
216 pub precedes: Option<Box<Relation>>,
217 pub follows: Option<Box<Relation>>,
218}
219
220pub struct CompositeRule {
221 pub all: Option<Vec<SerializableRule>>,
222 pub any: Option<Vec<SerializableRule>>,
223 pub not: Option<Box<SerializableRule>>,
224 pub matches: Option<SerializableMatches>,
225}
226
227pub enum Rule {
228 Pattern(Pattern),
230 Kind(KindMatcher),
231 Regex(RegexMatcher),
232 NthChild(NthChild),
233 Range(RangeMatcher),
234 Inside(Box<Inside>),
236 Has(Box<Has>),
237 Precedes(Box<Precedes>),
238 Follows(Box<Follows>),
239 All(o::All<Rule>),
241 Any(o::Any<Rule>),
242 Not(Box<o::Not<Rule>>),
243 Matches(ReferentRule),
244}
245
246impl Rule {
247 pub(crate) fn check_cyclic(&self, id: &str) -> bool {
249 match self {
250 Rule::All(all) => all.inner().iter().any(|r| r.check_cyclic(id)),
251 Rule::Any(any) => any.inner().iter().any(|r| r.check_cyclic(id)),
252 Rule::Not(not) => not.inner().check_cyclic(id),
253 Rule::Matches(rule) => rule.check_cyclic(id),
254 _ => false,
255 }
256 }
257
258 pub fn defined_vars(&self) -> HashSet<&str> {
259 match self {
260 Rule::Pattern(p) => p.defined_vars(),
261 Rule::Kind(_) => HashSet::new(),
262 Rule::Regex(_) => HashSet::new(),
263 Rule::NthChild(n) => n.defined_vars(),
264 Rule::Range(_) => HashSet::new(),
265 Rule::Has(c) => c.defined_vars(),
266 Rule::Inside(p) => p.defined_vars(),
267 Rule::Precedes(f) => f.defined_vars(),
268 Rule::Follows(f) => f.defined_vars(),
269 Rule::All(sub) => sub.inner().iter().flat_map(|r| r.defined_vars()).collect(),
270 Rule::Any(sub) => sub.inner().iter().flat_map(|r| r.defined_vars()).collect(),
271 Rule::Not(sub) => sub.inner().defined_vars(),
272 Rule::Matches(rule) => rule.defined_vars(),
273 }
274 }
275
276 pub fn verify_util(&self) -> Result<(), RuleSerializeError> {
278 match self {
279 Rule::Pattern(_) => Ok(()),
280 Rule::Kind(_) => Ok(()),
281 Rule::Regex(_) => Ok(()),
282 Rule::NthChild(n) => n.verify_util(),
283 Rule::Range(_) => Ok(()),
284 Rule::Has(c) => c.verify_util(),
285 Rule::Inside(p) => p.verify_util(),
286 Rule::Precedes(f) => f.verify_util(),
287 Rule::Follows(f) => f.verify_util(),
288 Rule::All(sub) => sub.inner().iter().try_for_each(|r| r.verify_util()),
289 Rule::Any(sub) => sub.inner().iter().try_for_each(|r| r.verify_util()),
290 Rule::Not(sub) => sub.inner().verify_util(),
291 Rule::Matches(rule) => rule.verify_util(),
292 }
293 }
294}
295
296impl Matcher for Rule {
297 fn match_node_with_env<'tree, D: Doc>(
298 &self,
299 node: Node<'tree, D>,
300 env: &mut Cow<MetaVarEnv<'tree, D>>,
301 ) -> Option<Node<'tree, D>> {
302 use Rule::*;
303 match self {
304 Pattern(pattern) => pattern.match_node_with_env(node, env),
306 Kind(kind) => kind.match_node_with_env(node, env),
307 Regex(regex) => regex.match_node_with_env(node, env),
308 NthChild(nth_child) => nth_child.match_node_with_env(node, env),
309 Range(range) => range.match_node_with_env(node, env),
310 Inside(parent) => match_and_add_label(&**parent, node, env),
312 Has(child) => match_and_add_label(&**child, node, env),
313 Precedes(latter) => match_and_add_label(&**latter, node, env),
314 Follows(former) => match_and_add_label(&**former, node, env),
315 All(all) => all.match_node_with_env(node, env),
317 Any(any) => any.match_node_with_env(node, env),
318 Not(not) => not.match_node_with_env(node, env),
319 Matches(rule) => rule.match_node_with_env(node, env),
320 }
321 }
322
323 fn potential_kinds(&self) -> Option<BitSet> {
324 use Rule::*;
325 match self {
326 Pattern(pattern) => pattern.potential_kinds(),
328 Kind(kind) => kind.potential_kinds(),
329 Regex(regex) => regex.potential_kinds(),
330 NthChild(nth_child) => nth_child.potential_kinds(),
331 Range(range) => range.potential_kinds(),
332 Inside(parent) => parent.potential_kinds(),
334 Has(child) => child.potential_kinds(),
335 Precedes(latter) => latter.potential_kinds(),
336 Follows(former) => former.potential_kinds(),
337 All(all) => all.potential_kinds(),
339 Any(any) => any.potential_kinds(),
340 Not(not) => not.potential_kinds(),
341 Matches(rule) => rule.potential_kinds(),
342 }
343 }
344}
345
346impl Default for Rule {
349 fn default() -> Self {
350 Self::Any(o::Any::new(std::iter::empty()))
351 }
352}
353
354fn match_and_add_label<'tree, D: Doc, M: Matcher>(
355 inner: &M,
356 node: Node<'tree, D>,
357 env: &mut Cow<MetaVarEnv<'tree, D>>,
358) -> Option<Node<'tree, D>> {
359 let matched = inner.match_node_with_env(node, env)?;
360 env.to_mut().add_label("secondary", matched.clone());
361 Some(matched)
362}
363
364#[derive(Debug, Error)]
365pub enum RuleSerializeError {
366 #[error("Rule must have one positive matcher.")]
367 MissPositiveMatcher,
368 #[error("Rule contains invalid kind matcher.")]
369 InvalidKind(#[from] SelectorError),
370 #[error("Rule contains invalid pattern matcher.")]
371 InvalidPattern(#[from] PatternError),
372 #[error("Rule contains invalid nthChild.")]
373 NthChild(#[from] NthChildError),
374 #[error("Rule contains invalid regex matcher.")]
375 WrongRegex(#[from] RegexMatcherError),
376 #[error("Rule contains invalid matches reference.")]
377 MatchesReference(#[from] ReferentRuleError),
378 #[error("Rule contains invalid utils.")]
379 InvalidUtils(#[from] ParameterizedUtilError),
380 #[error("Rule contains invalid range matcher.")]
381 InvalidRange(#[from] RangeMatcherError),
382 #[error("field is only supported in has/inside.")]
383 FieldNotSupported,
384 #[error("Relational rule contains invalid field {0}.")]
385 InvalidField(String),
386}
387
388pub fn deserialize_rule<L: Language>(
390 serialized: SerializableRule,
391 env: &DeserializeEnv<L>,
392) -> Result<Rule, RuleSerializeError> {
393 let mut rules = Vec::with_capacity(1);
394 use Rule as R;
395 let categorized = serialized.categorized();
396 deserialze_atomic_rule(categorized.atomic, &mut rules, env)?;
399 deserialze_composite_rule(categorized.composite, &mut rules, env)?;
400 deserialize_relational_rule(categorized.relational, &mut rules, env)?;
401
402 if rules.is_empty() {
403 Err(RuleSerializeError::MissPositiveMatcher)
404 } else if rules.len() == 1 {
405 Ok(rules.pop().expect("should not be empty"))
406 } else {
407 Ok(R::All(o::All::new(rules)))
408 }
409}
410
411fn deserialze_composite_rule<L: Language>(
412 composite: CompositeRule,
413 rules: &mut Vec<Rule>,
414 env: &DeserializeEnv<L>,
415) -> Result<(), RuleSerializeError> {
416 use Rule as R;
417 let convert_rules = |rules: Vec<SerializableRule>| -> Result<_, RuleSerializeError> {
418 let mut inner = Vec::with_capacity(rules.len());
419 for rule in rules {
420 inner.push(deserialize_rule(rule, env)?);
421 }
422 Ok(inner)
423 };
424 if let Some(all) = composite.all {
425 rules.push(R::All(o::All::new(convert_rules(all)?)));
426 }
427 if let Some(any) = composite.any {
428 rules.push(R::Any(o::Any::new(convert_rules(any)?)));
429 }
430 if let Some(not) = composite.not {
431 let not = o::Not::new(deserialize_rule(*not, env)?);
432 rules.push(R::Not(Box::new(not)));
433 }
434 if let Some(matches) = composite.matches {
435 rules.push(deserialize_matches_rule(matches, env)?);
436 }
437 Ok(())
438}
439
440fn deserialize_matches_rule<L: Language>(
441 matches: SerializableMatches,
442 env: &DeserializeEnv<L>,
443) -> Result<Rule, RuleSerializeError> {
444 use Rule as R;
445 match matches {
446 SerializableMatches::Id(id) => {
447 let matches = if env.registration.has_current_param(&id) {
448 ReferentRule::try_new_param(id, &env.registration)?
449 } else {
450 ReferentRule::try_new(id, &env.registration)?
451 };
452 Ok(R::Matches(matches))
453 }
454 SerializableMatches::Call(call) => deserialize_utility_call_matches(call, env),
455 }
456}
457
458fn deserialize_relational_rule<L: Language>(
459 relational: RelationalRule,
460 rules: &mut Vec<Rule>,
461 env: &DeserializeEnv<L>,
462) -> Result<(), RuleSerializeError> {
463 use Rule as R;
464 if let Some(inside) = relational.inside {
466 rules.push(R::Inside(Box::new(Inside::try_new(*inside, env)?)));
467 }
468 if let Some(has) = relational.has {
469 rules.push(R::Has(Box::new(Has::try_new(*has, env)?)));
470 }
471 if let Some(precedes) = relational.precedes {
472 rules.push(R::Precedes(Box::new(Precedes::try_new(*precedes, env)?)));
473 }
474 if let Some(follows) = relational.follows {
475 rules.push(R::Follows(Box::new(Follows::try_new(*follows, env)?)));
476 }
477 Ok(())
478}
479
480fn deserialze_atomic_rule<L: Language>(
481 atomic: AtomicRule,
482 rules: &mut Vec<Rule>,
483 env: &DeserializeEnv<L>,
484) -> Result<(), RuleSerializeError> {
485 use Rule as R;
486 if let Some(pattern) = atomic.pattern {
487 rules.push(match pattern {
488 PatternStyle::Str(pat) => R::Pattern(Pattern::try_new(&pat, env.lang.clone())?),
489 PatternStyle::Contextual {
490 context,
491 selector,
492 strictness,
493 } => {
494 let pattern = if let Some(selector) = selector {
495 Pattern::contextual(&context, &selector, env.lang.clone())?
496 } else {
497 Pattern::try_new(&context, env.lang.clone())?
498 };
499 let pattern = if let Some(strictness) = strictness {
500 pattern.with_strictness(strictness.into())
501 } else {
502 pattern
503 };
504 R::Pattern(pattern)
505 }
506 });
507 }
508 if let Some(kind) = atomic.kind {
509 let rule = parse_selector(&kind, env.lang.clone())?;
510 rules.push(rule);
511 }
512 if let Some(regex) = atomic.regex {
513 rules.push(R::Regex(RegexMatcher::try_new(®ex)?));
514 }
515 if let Some(nth_child) = atomic.nth_child {
516 rules.push(R::NthChild(NthChild::try_new(nth_child, env)?));
517 }
518 if let Some(range) = atomic.range {
519 rules.push(R::Range(RangeMatcher::try_new(range.start, range.end)?));
520 }
521 Ok(())
522}
523
524#[cfg(test)]
525mod test {
526 use super::*;
527 use crate::from_str;
528 use crate::test::TypeScript;
529 use ast_grep_core::tree_sitter::LanguageExt;
530 use PatternStyle::*;
531
532 #[test]
533 fn test_pattern() {
534 let src = r"
535pattern: Test
536";
537 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
538 assert!(rule.pattern.is_present());
539 let src = r"
540pattern:
541 context: class $C { set $B() {} }
542 selector: method_definition
543";
544 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
545 assert!(matches!(rule.pattern, Maybe::Present(Contextual { .. }),));
546
547 let src = r"
549pattern:
550 context: class $C { set $B() {} }
551 selector: method_definition
552";
553 let rule: Result<PatternStyle, _> = from_str(src);
554 assert!(rule.is_err());
555 }
556
557 #[test]
558 fn test_augmentation() {
559 let src = r"
560pattern: class A {}
561inside:
562 pattern: function() {}
563";
564 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
565 assert!(rule.inside.is_present());
566 assert!(rule.pattern.is_present());
567 }
568
569 #[test]
570 fn test_multi_augmentation() {
571 let src = r"
572pattern: class A {}
573inside:
574 pattern: function() {}
575has:
576 pattern: Some()
577";
578 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
579 assert!(rule.inside.is_present());
580 assert!(rule.has.is_present());
581 assert!(rule.follows.is_absent());
582 assert!(rule.precedes.is_absent());
583 assert!(rule.pattern.is_present());
584 }
585
586 #[test]
587 fn test_maybe_not() {
588 let src = "not: 123";
589 let ret: Result<SerializableRule, _> = from_str(src);
590 assert!(ret.is_err());
591 let src = "not:";
592 let ret: Result<SerializableRule, _> = from_str(src);
593 assert!(ret.is_err());
594 }
595
596 #[test]
597 fn test_nested_augmentation() {
598 let src = r"
599pattern: class A {}
600inside:
601 pattern: function() {}
602 inside:
603 pattern:
604 context: Some()
605 selector: ss
606";
607 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
608 assert!(rule.inside.is_present());
609 let inside = rule.inside.unwrap();
610 assert!(inside.rule.pattern.is_present());
611 assert!(inside.rule.inside.unwrap().rule.pattern.is_present());
612 }
613
614 #[test]
615 fn test_precedes_follows() {
616 let src = r"
617pattern: class A {}
618precedes:
619 pattern: function() {}
620follows:
621 pattern:
622 context: Some()
623 selector: ss
624";
625 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
626 assert!(rule.precedes.is_present());
627 assert!(rule.follows.is_present());
628 let follows = rule.follows.unwrap();
629 assert!(follows.rule.pattern.is_present());
630 assert!(follows.rule.pattern.is_present());
631 }
632
633 #[test]
634 fn test_parameterized_matches_syntax() {
635 let src = r"
636matches:
637 maybe_parenthesized:
638 BODY:
639 pattern: foo($A)
640";
641 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
642 assert!(matches!(
643 rule.matches,
644 Maybe::Present(SerializableMatches::Call(_))
645 ));
646 }
647
648 #[test]
649 fn test_multiple_parameterized_matches_syntax() {
650 let src = r"
651matches:
652 first:
653 ARG:
654 pattern: foo($A)
655 second:
656 ARG:
657 kind: number
658";
659 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
660 match rule.matches {
661 Maybe::Present(SerializableMatches::Call(call)) => assert_eq!(call.0.len(), 2),
662 _ => panic!("expected matches call"),
663 }
664 }
665
666 #[test]
667 fn test_deserialize_rule() {
668 let src = r"
669pattern: class A {}
670kind: class_declaration
671";
672 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
673 let env = DeserializeEnv::new(TypeScript::Tsx);
674 let rule = deserialize_rule(rule, &env).expect("should deserialize");
675 let root = TypeScript::Tsx.ast_grep("class A {}");
676 assert!(root.root().find(rule).is_some());
677 }
678
679 #[test]
680 fn test_deserialize_order() {
681 let src = r"
682pattern: class A {}
683inside:
684 kind: class
685";
686 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
687 let env = DeserializeEnv::new(TypeScript::Tsx);
688 let rule = deserialize_rule(rule, &env).expect("should deserialize");
689 assert!(matches!(rule, Rule::All(_)));
690 }
691
692 #[test]
693 fn test_defined_vars() {
694 let src = r"
695pattern: var $A = 123
696inside:
697 pattern: var $B = 456
698";
699 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
700 let env = DeserializeEnv::new(TypeScript::Tsx);
701 let rule = deserialize_rule(rule, &env).expect("should deserialize");
702 assert_eq!(rule.defined_vars(), ["A", "B"].into_iter().collect());
703 }
704
705 #[test]
706 fn test_issue_1164() {
707 let src = r"
708 kind: statement_block
709 has:
710 pattern: this.$A = promise()
711 stopBy: end";
712 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
713 let env = DeserializeEnv::new(TypeScript::Tsx);
714 let rule = deserialize_rule(rule, &env).expect("should deserialize");
715 let root = TypeScript::Tsx.ast_grep(
716 "if (a) {
717 this.a = b;
718 this.d = promise()
719 }",
720 );
721 assert!(root.root().find(rule).is_some());
722 }
723
724 #[test]
725 fn test_issue_1225() {
726 let src = r"
727 kind: statement_block
728 has:
729 pattern: $A
730 regex: const";
731 let rule: SerializableRule = from_str(src).expect("cannot parse rule");
732 let env = DeserializeEnv::new(TypeScript::Tsx);
733 let rule = deserialize_rule(rule, &env).expect("should deserialize");
734 let root = TypeScript::Tsx.ast_grep(
735 "{
736 let x = 1;
737 const z = 9;
738 }",
739 );
740 assert!(root.root().find(rule).is_some());
741 }
742}