ast_grep_config/
lib.rs

1mod check_var;
2mod combined;
3mod fixer;
4mod maybe;
5mod rule;
6mod rule_collection;
7mod rule_config;
8mod rule_core;
9mod transform;
10
11use serde::Deserialize;
12use serde_yaml::{with::singleton_map_recursive::deserialize, Deserializer, Error as YamlError};
13
14use ast_grep_core::language::Language;
15
16pub use combined::{CombinedScan, PreScan};
17pub use fixer::Fixer;
18pub use rule::referent_rule::GlobalRules;
19pub use rule::DeserializeEnv;
20pub use rule::{Rule, RuleSerializeError, SerializableRule};
21pub use rule_collection::RuleCollection;
22pub use rule_config::{RuleConfig, RuleConfigError, SerializableRuleConfig, Severity};
23pub use rule_core::{RuleCore, RuleCoreError, SerializableRuleCore};
24pub use transform::Transformation;
25
26pub fn from_str<'de, T: Deserialize<'de>>(s: &'de str) -> Result<T, YamlError> {
27  let deserializer = Deserializer::from_str(s);
28  deserialize(deserializer)
29}
30
31pub fn from_yaml_string<'a, L: Language + Deserialize<'a>>(
32  yamls: &'a str,
33  registration: &GlobalRules<L>,
34) -> Result<Vec<RuleConfig<L>>, RuleConfigError> {
35  let mut ret = vec![];
36  for yaml in Deserializer::from_str(yamls) {
37    let config = RuleConfig::deserialize(yaml, registration)?;
38    ret.push(config);
39  }
40  Ok(ret)
41}
42#[cfg(test)]
43mod test {
44
45  use super::*;
46  use ast_grep_core::language::TSLanguage;
47  use std::path::Path;
48
49  #[derive(Clone, Deserialize, PartialEq, Eq)]
50  pub enum TypeScript {
51    Tsx,
52  }
53  impl Language for TypeScript {
54    fn from_path<P: AsRef<Path>>(_path: P) -> Option<Self> {
55      Some(TypeScript::Tsx)
56    }
57    fn get_ts_language(&self) -> TSLanguage {
58      tree_sitter_typescript::language_tsx().into()
59    }
60  }
61
62  fn test_rule_match(yaml: &str, source: &str) {
63    let globals = GlobalRules::default();
64    let config = &from_yaml_string::<TypeScript>(yaml, &globals).expect("rule should parse")[0];
65    let grep = config.language.ast_grep(source);
66    assert!(grep.root().find(&config.matcher).is_some());
67  }
68
69  fn test_rule_unmatch(yaml: &str, source: &str) {
70    let globals = GlobalRules::default();
71    let config = &from_yaml_string::<TypeScript>(yaml, &globals).expect("rule should parse")[0];
72    let grep = config.language.ast_grep(source);
73    assert!(grep.root().find(&config.matcher).is_none());
74  }
75
76  fn make_yaml(rule: &str) -> String {
77    format!(
78      r"
79id: test
80message: test rule
81severity: info
82language: Tsx
83rule:
84{rule}
85"
86    )
87  }
88
89  #[test]
90  fn test_deserialize_rule_config() {
91    let yaml = &make_yaml(
92      "
93  pattern: let a = 123
94",
95    );
96    test_rule_match(yaml, "let a = 123; let b = 33;");
97    test_rule_match(yaml, "class B { func() {let a = 123; }}");
98    test_rule_unmatch(yaml, "const a = 33");
99  }
100
101  #[test]
102  fn test_deserialize_nested() {
103    let yaml = &make_yaml(
104      "
105  all:
106    - pattern: let $A = 123
107    - pattern: let a = $B
108",
109    );
110    test_rule_match(yaml, "let a = 123; let b = 33;");
111    test_rule_match(yaml, "class B { func() {let a = 123; }}");
112    test_rule_unmatch(yaml, "const a = 33");
113    test_rule_unmatch(yaml, "let a = 33");
114  }
115
116  #[test]
117  fn test_deserialize_kind() {
118    let yaml = &make_yaml(
119      "
120    kind: class_body
121",
122    );
123    test_rule_match(yaml, "class B { func() {let a = 123; }}");
124    test_rule_unmatch(yaml, "const B = { func() {let a = 123; }}");
125  }
126
127  #[test]
128  fn test_deserialize_inside() {
129    let yaml = &make_yaml(
130      "
131  all:
132    - inside:
133        kind: class_body
134        stopBy: end
135    - pattern: let a = 123
136",
137    );
138    test_rule_unmatch(yaml, "let a = 123; let b = 33;");
139    test_rule_match(yaml, "class B { func() {let a = 123; }}");
140    test_rule_unmatch(yaml, "let a = 123");
141  }
142
143  #[test]
144  fn test_deserialize_not_inside() {
145    let yaml = &make_yaml(
146      "
147  all:
148    - not:
149        inside:
150          kind: class_body
151          stopBy: end
152    - pattern: let a = 123
153",
154    );
155    test_rule_match(yaml, "let a = 123; let b = 33;");
156    test_rule_unmatch(yaml, "class B { func() {let a = 123; }}");
157    test_rule_unmatch(yaml, "let a = 13");
158  }
159
160  #[test]
161  fn test_deserialize_meta_var() {
162    let yaml = &make_yaml(
163      "
164  all:
165    - inside:
166        any:
167          - pattern: function $A($$$) { $$$ }
168          - pattern: let $A = ($$$) => $$$
169        stopBy: end
170    - pattern: $A($$$)
171",
172    );
173    test_rule_match(yaml, "function recursion() { recursion() }");
174    test_rule_match(yaml, "let recursion = () => { recursion() }");
175    test_rule_unmatch(yaml, "function callOther() { other() }");
176  }
177
178  #[test]
179  fn test_deserialize_constraints() {
180    let yaml = r"
181id: test
182message: test rule
183severity: info
184language: Tsx
185rule:
186  all:
187    - pattern: console.log($A)
188    - inside:
189        pattern: function $B() {$$$}
190        stopBy: end
191constraints:
192  B:
193    regex: test
194";
195    test_rule_match(yaml, "function test() { console.log(1) }");
196    test_rule_match(yaml, "function test() { console.log(2) }");
197    test_rule_unmatch(yaml, "function tt() { console.log(2) }");
198  }
199
200  // https://github.com/ast-grep/ast-grep/issues/813
201  #[test]
202  fn test_util_rule_with_vaargs() {
203    let yaml = r"
204id: sibling
205language: Tsx
206utils:
207  utilpat:
208    pattern: '$A($$$B);'
209rule:
210  matches: utilpat
211  follows:
212    matches: utilpat
213    stopBy: end
214";
215    test_rule_match(yaml, "a();a(123);a();a(123)");
216  }
217}