thread_rule_engine/transform/
mod.rs1mod parse;
8mod rewrite;
9mod string_case;
10mod trans;
11
12use crate::{DeserializeEnv, RuleCore};
13
14use thread_ast_engine::Doc;
15use thread_ast_engine::Language;
16use thread_ast_engine::meta_var::MetaVarEnv;
17use thread_ast_engine::meta_var::MetaVariable;
18
19use parse::ParseTransError;
20use schemars::JsonSchema;
21use serde::{Deserialize, Serialize};
22use thiserror::Error;
23use thread_utilities::RapidMap;
24
25pub use trans::Trans;
26
27#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)]
28#[serde(untagged)]
29pub enum Transformation {
30 Simplified(String),
31 Object(Trans<String>),
32}
33
34impl Transformation {
35 pub fn parse<L: Language>(&self, lang: &L) -> Result<Trans<MetaVariable>, TransformError> {
36 match self {
37 Transformation::Simplified(s) => {
38 let t: Trans<String> = s.parse()?;
39 t.parse(lang)
40 }
41 Transformation::Object(t) => t.parse(lang),
42 }
43 }
44}
45
46#[derive(Error, Debug)]
47pub enum TransformError {
48 #[error("Cannot parse transform string.")]
49 Parse(#[from] ParseTransError),
50 #[error("`{0}` has a cyclic dependency.")]
51 Cyclic(String),
52 #[error("Transform var `{0}` has already defined.")]
53 AlreadyDefined(String),
54 #[error("source `{0}` should be $-prefixed.")]
55 MalformedVar(String),
56}
57
58#[derive(Clone, Debug)]
59pub struct Transform {
60 transforms: Vec<(String, Trans<MetaVariable>)>,
61}
62
63impl Transform {
64 pub fn deserialize<L: Language>(
65 map: &RapidMap<String, Transformation>,
66 env: &DeserializeEnv<L>,
67 ) -> Result<Self, TransformError> {
68 let map: Result<_, _> = map
69 .iter()
70 .map(|(key, val)| val.parse(&env.lang).map(|t| (key.to_string(), t)))
71 .collect();
72 let map = map?;
73 let order = env
74 .get_transform_order(&map)
75 .map_err(TransformError::Cyclic)?;
76 let transforms = order
77 .iter()
78 .map(|&key| (key.to_string(), map[key].clone()))
79 .collect();
80 Ok(Self { transforms })
81 }
82
83 pub fn apply_transform<'c, D: Doc>(
84 &self,
85 env: &mut MetaVarEnv<'c, D>,
86 rewriters: &RapidMap<String, RuleCore>,
87 enclosing_env: &MetaVarEnv<'c, D>,
88 ) {
89 let mut ctx = Ctx {
90 env,
91 rewriters,
92 enclosing_env,
93 };
94 for (key, tr) in &self.transforms {
95 tr.insert(key, &mut ctx);
96 }
97 }
98
99 pub(crate) fn keys(&self) -> impl Iterator<Item = &String> {
100 self.transforms.iter().map(|t| &t.0)
101 }
102
103 pub(crate) fn values(&self) -> impl Iterator<Item = &Trans<MetaVariable>> {
104 self.transforms.iter().map(|t| &t.1)
105 }
106}
107
108struct Ctx<'b, 'c, D: Doc> {
110 rewriters: &'b RapidMap<String, RuleCore>,
111 env: &'b mut MetaVarEnv<'c, D>,
112 enclosing_env: &'b MetaVarEnv<'c, D>,
113}
114
115#[cfg(test)]
116mod test {
117 use super::*;
118 use crate::from_str;
119 use crate::test::TypeScript;
120 use thread_ast_engine::tree_sitter::LanguageExt;
121
122 #[test]
123 fn test_transform_str() {}
124
125 #[test]
126 fn test_single_cyclic_transform() {
127 let mut trans = RapidMap::default();
128 let trans_a = from_str("substring: {source: $A}").unwrap();
129 trans.insert("A".into(), trans_a);
130 let env = DeserializeEnv::new(TypeScript::Tsx);
131 match Transform::deserialize(&trans, &env) {
132 Err(TransformError::Cyclic(a)) => assert_eq!(a, "A"),
133 _ => panic!("unexpected error"),
134 }
135 }
136
137 #[test]
138 fn test_cyclic_transform() {
139 let mut trans = RapidMap::default();
140 let trans_a = from_str("substring: {source: $B}").unwrap();
141 trans.insert("A".into(), trans_a);
142 let trans_b = from_str("substring: {source: $A}").unwrap();
143 trans.insert("B".into(), trans_b);
144 let env = DeserializeEnv::new(TypeScript::Tsx);
145 let ret = Transform::deserialize(&trans, &env);
146 assert!(matches!(ret, Err(TransformError::Cyclic(_))));
147 }
148
149 #[test]
150 fn test_transform_use_matched() {
151 let mut trans = RapidMap::default();
152 let trans_a = from_str("substring: {source: $C}").unwrap();
153 trans.insert("A".into(), trans_a);
154 let trans_b = from_str("substring: {source: $A}").unwrap();
155 trans.insert("B".into(), trans_b);
156 let env = DeserializeEnv::new(TypeScript::Tsx);
157 let ret = Transform::deserialize(&trans, &env);
158 assert!(ret.is_ok());
159 }
160
161 #[test]
162 fn test_transform_indentation() {
163 let src = "
164if (true) {
165 let a = {
166 b: 123
167 }
168}
169";
170 let expected = "{
171 b: 123
172}";
173 let mut trans = RapidMap::default();
174 let tr = from_str("{ substring: { source: $A } }").expect("should work");
175 trans.insert("TR".into(), tr);
176 let grep = TypeScript::Tsx.ast_grep(src);
177 let root = grep.root();
178 let mut nm = root.find("let a = $A").expect("should find");
179 let env = DeserializeEnv::new(TypeScript::Tsx);
180 let trans = Transform::deserialize(&trans, &env).expect("should deserialize");
181 trans.apply_transform(nm.get_env_mut(), &Default::default(), &Default::default());
182 let actual = nm.get_env().get_transformed("TR").expect("should have TR");
183 let actual = std::str::from_utf8(actual).expect("should work");
184 assert_eq!(actual, expected);
185 }
186}