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