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