ast_grep_config/
lib.rs

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