1use crate::DeserializeEnv;
8use crate::maybe::Maybe;
9use crate::rule::{Relation, Rule, RuleSerializeError, StopBy};
10use crate::transform::Transformation;
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14use thread_ast_engine::replacer::{Content, Replacer, TemplateFix, TemplateFixError};
15use thread_ast_engine::{Doc, Language, Matcher, NodeMatch};
16
17use std::ops::Range;
18use thread_utilities::{RapidMap, RapidSet};
19
20#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
23#[serde(untagged)]
24pub enum SerializableFixer {
25 Str(String),
26 Config(Box<SerializableFixConfig>),
27 List(Vec<SerializableFixConfig>),
28}
29
30#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
31#[serde(rename_all = "camelCase")]
32pub struct SerializableFixConfig {
33 template: String,
34 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
35 expand_end: Maybe<Relation>,
36 #[serde(default, skip_serializing_if = "Maybe::is_absent")]
37 expand_start: Maybe<Relation>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 title: Option<String>,
40}
41
42#[derive(Error, Debug)]
43pub enum FixerError {
44 #[error("Fixer template is invalid.")]
45 InvalidTemplate(#[from] TemplateFixError),
46 #[error("Fixer expansion contains invalid rule.")]
47 WrongExpansion(#[from] RuleSerializeError),
48 #[error("Rewriter must have exactly one fixer.")]
49 InvalidRewriter,
50 #[error("Fixer in list must have title.")]
51 MissingTitle,
52}
53
54#[derive(Clone, Debug)]
55struct Expansion {
56 matches: Rule,
57 stop_by: StopBy,
58}
59
60impl Expansion {
61 fn parse<L: Language>(
62 relation: &Maybe<Relation>,
63 env: &DeserializeEnv<L>,
64 ) -> Result<Option<Self>, FixerError> {
65 let inner = match relation {
66 Maybe::Absent => return Ok(None),
67 Maybe::Present(r) => r.clone(),
68 };
69 let stop_by = StopBy::try_from(inner.stop_by, env)?;
70 let matches = env.deserialize_rule(inner.rule)?;
71 Ok(Some(Self { matches, stop_by }))
72 }
73}
74
75#[derive(Clone, Debug)]
76pub struct Fixer {
77 template: TemplateFix,
78 expand_start: Option<Expansion>,
79 expand_end: Option<Expansion>,
80 title: Option<String>,
81}
82
83impl Fixer {
84 fn do_parse<L: Language>(
85 serialized: &SerializableFixConfig,
86 env: &DeserializeEnv<L>,
87 transform: &Option<RapidMap<String, Transformation>>,
88 ) -> Result<Self, FixerError> {
89 let SerializableFixConfig {
90 template: fix,
91 expand_end,
92 expand_start,
93 title,
94 } = serialized;
95 let expand_start = Expansion::parse(expand_start, env)?;
96 let expand_end = Expansion::parse(expand_end, env)?;
97 let template = if let Some(trans) = transform {
98 let keys: Vec<std::sync::Arc<str>> = trans
99 .keys()
100 .map(|k| std::sync::Arc::from(k.as_str()))
101 .collect();
102 TemplateFix::with_transform(fix, &env.lang, &keys)
103 } else {
104 TemplateFix::try_new(fix, &env.lang)?
105 };
106 Ok(Self {
107 template,
108 expand_start,
109 expand_end,
110 title: title.clone(),
111 })
112 }
113
114 pub fn parse<L: Language>(
115 fixer: &SerializableFixer,
116 env: &DeserializeEnv<L>,
117 transform: &Option<RapidMap<String, Transformation>>,
118 ) -> Result<Vec<Self>, FixerError> {
119 let ret = match fixer {
120 SerializableFixer::Str(fix) => Self::with_transform(fix, env, transform),
121 SerializableFixer::Config(cfg) => Self::do_parse(cfg, env, transform),
122 SerializableFixer::List(list) => {
123 return Self::parse_list(list, env, transform);
124 }
125 };
126 Ok(vec![ret?])
127 }
128
129 fn parse_list<L: Language>(
130 list: &[SerializableFixConfig],
131 env: &DeserializeEnv<L>,
132 transform: &Option<RapidMap<String, Transformation>>,
133 ) -> Result<Vec<Self>, FixerError> {
134 list.iter()
135 .map(|cfg| {
136 if cfg.title.is_none() {
137 return Err(FixerError::MissingTitle);
138 }
139 Self::do_parse(cfg, env, transform)
140 })
141 .collect()
142 }
143
144 pub(crate) fn with_transform<L: Language>(
145 fix: &str,
146 env: &DeserializeEnv<L>,
147 transform: &Option<RapidMap<String, Transformation>>,
148 ) -> Result<Self, FixerError> {
149 let template = if let Some(trans) = transform {
150 let keys: Vec<std::sync::Arc<str>> = trans
151 .keys()
152 .map(|k| std::sync::Arc::from(k.as_str()))
153 .collect();
154 TemplateFix::with_transform(fix, &env.lang, &keys)
155 } else {
156 TemplateFix::try_new(fix, &env.lang)?
157 };
158 Ok(Self {
159 template,
160 expand_end: None,
161 expand_start: None,
162 title: None,
163 })
164 }
165
166 pub fn from_str<L: Language>(src: &str, lang: &L) -> Result<Self, FixerError> {
167 let template = TemplateFix::try_new(src, lang)?;
168 Ok(Self {
169 template,
170 expand_start: None,
171 expand_end: None,
172 title: None,
173 })
174 }
175
176 pub fn title(&self) -> Option<&str> {
177 self.title.as_deref()
178 }
179
180 pub(crate) fn used_vars(&self) -> RapidSet<&str> {
181 self.template.used_vars()
182 }
183}
184
185impl<D, C> Replacer<D> for Fixer
186where
187 D: Doc<Source = C>,
188 C: Content,
189{
190 fn generate_replacement(&self, nm: &NodeMatch<'_, D>) -> Vec<C::Underlying> {
191 self.template.generate_replacement(nm)
193 }
194 fn get_replaced_range(&self, nm: &NodeMatch<'_, D>, matcher: impl Matcher) -> Range<usize> {
195 let range = nm.range();
196 if self.expand_start.is_none() && self.expand_end.is_none() {
197 return if let Some(len) = matcher.get_match_len(nm.get_node().clone()) {
198 range.start..range.start + len
199 } else {
200 range
201 };
202 }
203 let start = expand_start(self.expand_start.as_ref(), nm);
204 let end = expand_end(self.expand_end.as_ref(), nm);
205 start..end
206 }
207}
208
209fn expand_start<D: Doc>(expansion: Option<&Expansion>, nm: &NodeMatch<'_, D>) -> usize {
210 let node = nm.get_node();
211 let mut env = std::borrow::Cow::Borrowed(nm.get_env());
212 let Some(start) = expansion else {
213 return node.range().start;
214 };
215 let node = start.stop_by.find(
216 || node.prev(),
217 || node.prev_all(),
218 |n| start.matches.match_node_with_env(n, &mut env),
219 );
220 node.map(|n| n.range().start)
221 .unwrap_or_else(|| nm.range().start)
222}
223
224fn expand_end<D: Doc>(expansion: Option<&Expansion>, nm: &NodeMatch<'_, D>) -> usize {
225 let node = nm.get_node();
226 let mut env = std::borrow::Cow::Borrowed(nm.get_env());
227 let Some(end) = expansion else {
228 return node.range().end;
229 };
230 let node = end.stop_by.find(
231 || node.next(),
232 || node.next_all(),
233 |n| end.matches.match_node_with_env(n, &mut env),
234 );
235 node.map(|n| n.range().end)
236 .unwrap_or_else(|| nm.range().end)
237}
238
239#[cfg(test)]
240mod test {
241 use super::*;
242 use crate::from_str;
243 use crate::maybe::Maybe;
244 use crate::test::TypeScript;
245 use thread_ast_engine::tree_sitter::LanguageExt;
246
247 #[test]
248 fn test_parse() {
249 let fixer: SerializableFixer = from_str("test").expect("should parse");
250 assert!(matches!(fixer, SerializableFixer::Str(_)));
251 }
252
253 fn parse(config: SerializableFixConfig) -> Result<Fixer, FixerError> {
254 let config = SerializableFixer::Config(Box::new(config));
255 let env = DeserializeEnv::new(TypeScript::Tsx);
256 let fixer = Fixer::parse(&config, &env, &Some(Default::default()))?.remove(0);
257 Ok(fixer)
258 }
259
260 #[test]
261 fn test_deserialize_object() -> Result<(), serde_yaml::Error> {
262 let src = "{template: 'abc', expandEnd: {regex: ',', stopBy: neighbor}}";
263 let SerializableFixer::Config(cfg) = from_str(src)? else {
264 panic!("wrong parsing")
265 };
266 assert_eq!(cfg.template, "abc");
267 let Maybe::Present(relation) = cfg.expand_end else {
268 panic!("wrong parsing")
269 };
270 let rule = relation.rule;
271 assert_eq!(rule.regex, Maybe::Present(",".to_string()));
272 assert!(rule.pattern.is_absent());
273 Ok(())
274 }
275
276 #[test]
277 fn test_parse_config() -> Result<(), FixerError> {
278 let relation = from_str("{regex: ',', stopBy: neighbor}").expect("should deser");
279 let config = SerializableFixConfig {
280 expand_end: Maybe::Present(relation),
281 expand_start: Maybe::Absent,
282 template: "abcd".to_string(),
283 title: None,
284 };
285 let ret = parse(config)?;
286 assert!(ret.expand_start.is_none());
287 assert!(ret.expand_end.is_some());
288 assert!(matches!(ret.template, TemplateFix::Textual(_)));
289 Ok(())
290 }
291
292 #[test]
293 fn test_parse_str() -> Result<(), FixerError> {
294 let config = SerializableFixer::Str("abcd".to_string());
295 let env = DeserializeEnv::new(TypeScript::Tsx);
296 let ret = Fixer::parse(&config, &env, &None)?.remove(0);
297 assert!(ret.expand_end.is_none());
298 assert!(ret.expand_start.is_none());
299 assert!(matches!(ret.template, TemplateFix::Textual(_)));
300 Ok(())
301 }
302
303 #[test]
304 fn test_replace_fixer() -> Result<(), FixerError> {
305 let expand_end = from_str("{regex: ',', stopBy: neighbor}").expect("should word");
306 let config = SerializableFixConfig {
307 expand_end: Maybe::Present(expand_end),
308 expand_start: Maybe::Absent,
309 template: "var $A = 456".to_string(),
310 title: None,
311 };
312 let fixer = parse(config)?;
313 let grep = TypeScript::Tsx.ast_grep("let a = 123");
314 let node = grep.root().find("let $A = 123").expect("should found");
315 let edit = fixer.generate_replacement(&node);
316 assert_eq!(String::from_utf8_lossy(&edit), "var a = 456");
317 Ok(())
318 }
319
320 #[test]
321 fn test_replace_range() -> Result<(), FixerError> {
322 use thread_ast_engine::matcher::KindMatcher;
323 let expand_end = from_str("{regex: ',', stopBy: neighbor}").expect("should word");
324 let config = SerializableFixConfig {
325 expand_end: Maybe::Present(expand_end),
326 expand_start: Maybe::Absent,
327 template: "c: 456".to_string(),
328 title: None,
329 };
330 let fixer = parse(config)?;
331 let grep = TypeScript::Tsx.ast_grep("var a = { b: 123, }");
332 let matcher = KindMatcher::new("pair", &TypeScript::Tsx);
333 let node = grep.root().find(&matcher).expect("should found");
334 let edit = node.make_edit(&matcher, &fixer);
335 let text = String::from_utf8_lossy(&edit.inserted_text);
336 assert_eq!(text, "c: 456");
337 assert_eq!(edit.position, 10);
338 assert_eq!(edit.deleted_length, 7);
339 Ok(())
340 }
341
342 #[test]
343 fn test_fixer_list() -> Result<(), FixerError> {
344 let config: SerializableFixer = from_str(
345 r"
346- { template: 'abc', title: 'fixer 1'}
347- { template: 'def', title: 'fixer 2'}",
348 )
349 .expect("should parse");
350 let env = DeserializeEnv::new(TypeScript::Tsx);
351 let fixers = Fixer::parse(&config, &env, &Some(Default::default()))?;
352 assert_eq!(fixers.len(), 2);
353 let config: SerializableFixer = from_str(
354 r"
355- { template: 'abc', title: 'fixer 1'}
356- { template: 'def'}",
357 )
358 .expect("should parse");
359 let env = DeserializeEnv::new(TypeScript::Tsx);
360 let ret = Fixer::parse(&config, &env, &Some(Default::default()));
361 assert!(ret.is_err());
362 Ok(())
363 }
364}