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