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 #[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}