1use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum TemplateKind {
28 Grep,
30 Ast,
32 Semantic,
34}
35
36impl TemplateKind {
37 pub fn as_payload_kind(&self) -> &'static str {
39 match self {
40 TemplateKind::Grep => "grep",
41 TemplateKind::Ast => "ast",
42 TemplateKind::Semantic => "semantic",
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct QueryTemplate {
50 pub id: &'static str,
52 pub description: &'static str,
54 pub template: &'static str,
56 #[serde(skip)]
58 pub params: &'static [Param],
59 pub kind: TemplateKind,
61 #[serde(skip)]
63 pub example: &'static [&'static str],
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct Param {
69 pub name: &'static str,
71 pub description: &'static str,
73 pub example: &'static str,
75}
76
77impl QueryTemplate {
78 pub fn render(&self, values: &[&str]) -> String {
80 let mut result = self.template.to_string();
81
82 for (i, value) in values.iter().enumerate() {
83 if i < self.params.len() {
84 let placeholder = format!("{{{}}}", self.params[i].name);
85 result = result.replace(&placeholder, value);
86 }
87 }
88
89 result
90 }
91
92 pub fn all() -> &'static [QueryTemplate] {
94 TEMPLATES
95 }
96
97 pub fn by_kind(kind: TemplateKind) -> impl Iterator<Item = &'static QueryTemplate> {
99 TEMPLATES.iter().filter(move |t| t.kind == kind)
100 }
101
102 pub fn find(id: &str) -> Option<&'static QueryTemplate> {
104 TEMPLATES.iter().find(|t| t.id == id)
105 }
106
107 #[allow(dead_code)]
109 pub fn random_example(&self) -> String {
110 self.render(self.example)
111 }
112}
113
114const TEMPLATES: &[QueryTemplate] = &[
116 QueryTemplate {
118 id: "grep_find_occurrences",
119 description: "Find all occurrences of a string pattern in a file",
120 template: "Find all occurrences of {PATTERN} in {FILE}",
121 params: &[
122 Param {
123 name: "PATTERN",
124 description: "String or regex pattern to search for",
125 example: "async fn",
126 },
127 Param {
128 name: "FILE",
129 description: "File path to search in",
130 example: "src/main.rs",
131 },
132 ],
133 kind: TemplateKind::Grep,
134 example: &["async fn", "src/main.rs"],
135 },
136 QueryTemplate {
137 id: "grep_find_regex",
138 description: "Find all lines matching a regex pattern in a file",
139 template: "Find all lines matching {REGEX} in {FILE}",
140 params: &[
141 Param {
142 name: "REGEX",
143 description: "Regular expression pattern",
144 example: r"\bpub\s+fn\b",
145 },
146 Param {
147 name: "FILE",
148 description: "File path to search in",
149 example: "src/lib.rs",
150 },
151 ],
152 kind: TemplateKind::Grep,
153 example: &[r"\bpub\s+fn\b", "src/lib.rs"],
154 },
155 QueryTemplate {
156 id: "grep_count_occurrences",
157 description: "Count occurrences of a pattern in a file",
158 template: "Count occurrences of {PATTERN} in {FILE}",
159 params: &[
160 Param {
161 name: "PATTERN",
162 description: "Pattern to count",
163 example: "TODO",
164 },
165 Param {
166 name: "FILE",
167 description: "File path",
168 example: "src/main.rs",
169 },
170 ],
171 kind: TemplateKind::Grep,
172 example: &["TODO", "src/main.rs"],
173 },
174 QueryTemplate {
175 id: "grep_find_functions",
176 description: "Find all function definitions matching a pattern",
177 template: "Find all {VISIBILITY} functions matching {PATTERN} in {FILE}",
178 params: &[
179 Param {
180 name: "VISIBILITY",
181 description: "public, private, or all (leave empty)",
182 example: "public",
183 },
184 Param {
185 name: "PATTERN",
186 description: "Pattern in function definition",
187 example: "async fn",
188 },
189 Param {
190 name: "FILE",
191 description: "File path",
192 example: "src/main.rs",
193 },
194 ],
195 kind: TemplateKind::Grep,
196 example: &["public", "async fn", "src/main.rs"],
197 },
198 QueryTemplate {
199 id: "grep_find_structs",
200 description: "Find all struct definitions",
201 template: "Find all structs in {FILE}",
202 params: &[Param {
203 name: "FILE",
204 description: "File path",
205 example: "src/main.rs",
206 }],
207 kind: TemplateKind::Grep,
208 example: &["src/main.rs"],
209 },
210 QueryTemplate {
211 id: "grep_find_enums",
212 description: "Find all enum definitions",
213 template: "Find all enums in {FILE}",
214 params: &[Param {
215 name: "FILE",
216 description: "File path",
217 example: "src/main.rs",
218 }],
219 kind: TemplateKind::Grep,
220 example: &["src/main.rs"],
221 },
222 QueryTemplate {
223 id: "grep_find_traits",
224 description: "Find all trait definitions",
225 template: "Find all traits in {FILE}",
226 params: &[Param {
227 name: "FILE",
228 description: "File path",
229 example: "src/main.rs",
230 }],
231 kind: TemplateKind::Grep,
232 example: &["src/main.rs"],
233 },
234 QueryTemplate {
235 id: "grep_find_impls",
236 description: "Find all impl blocks",
237 template: "Find all impl blocks in {FILE}",
238 params: &[Param {
239 name: "FILE",
240 description: "File path",
241 example: "src/main.rs",
242 }],
243 kind: TemplateKind::Grep,
244 example: &["src/main.rs"],
245 },
246 QueryTemplate {
247 id: "grep_find_imports",
248 description: "Find all use/import statements",
249 template: "Find all imports in {FILE}",
250 params: &[Param {
251 name: "FILE",
252 description: "File path",
253 example: "src/main.rs",
254 }],
255 kind: TemplateKind::Grep,
256 example: &["src/main.rs"],
257 },
258 QueryTemplate {
259 id: "grep_find_tests",
260 description: "Find all test functions",
261 template: "Find all test functions in {FILE}",
262 params: &[Param {
263 name: "FILE",
264 description: "File path",
265 example: "src/main.rs",
266 }],
267 kind: TemplateKind::Grep,
268 example: &["src/main.rs"],
269 },
270 QueryTemplate {
271 id: "grep_find_error_handling",
272 description: "Find error handling patterns (Result, ?, unwrap)",
273 template: "Find all error handling patterns in {FILE}",
274 params: &[Param {
275 name: "FILE",
276 description: "File path",
277 example: "src/main.rs",
278 }],
279 kind: TemplateKind::Grep,
280 example: &["src/main.rs"],
281 },
282 QueryTemplate {
283 id: "grep_find_macros",
284 description: "Find macro invocations",
285 template: "Find all macro calls in {FILE}",
286 params: &[Param {
287 name: "FILE",
288 description: "File path",
289 example: "src/main.rs",
290 }],
291 kind: TemplateKind::Grep,
292 example: &["src/main.rs"],
293 },
294 QueryTemplate {
295 id: "grep_find_comments",
296 description: "Find comments in code",
297 template: "Find all comments in {FILE}",
298 params: &[Param {
299 name: "FILE",
300 description: "File path",
301 example: "src/main.rs",
302 }],
303 kind: TemplateKind::Grep,
304 example: &["src/main.rs"],
305 },
306 QueryTemplate {
307 id: "grep_find_todos",
308 description: "Find TODO/FIXME comments",
309 template: "Find all TODO comments in {FILE}",
310 params: &[Param {
311 name: "FILE",
312 description: "File path",
313 example: "src/main.rs",
314 }],
315 kind: TemplateKind::Grep,
316 example: &["src/main.rs"],
317 },
318 QueryTemplate {
319 id: "grep_find_string_literals",
320 description: "Find string literals",
321 template: "Find all string literals in {FILE}",
322 params: &[Param {
323 name: "FILE",
324 description: "File path",
325 example: "src/main.rs",
326 }],
327 kind: TemplateKind::Grep,
328 example: &["src/main.rs"],
329 },
330 QueryTemplate {
332 id: "ast_list_functions",
333 description: "List all function names in a file",
334 template: "List all function names in {FILE}",
335 params: &[Param {
336 name: "FILE",
337 description: "File path",
338 example: "src/main.rs",
339 }],
340 kind: TemplateKind::Ast,
341 example: &["src/main.rs"],
342 },
343 QueryTemplate {
344 id: "ast_list_structs",
345 description: "List all structs and their fields",
346 template: "List all structs and fields in {FILE}",
347 params: &[Param {
348 name: "FILE",
349 description: "File path",
350 example: "src/main.rs",
351 }],
352 kind: TemplateKind::Ast,
353 example: &["src/main.rs"],
354 },
355 QueryTemplate {
356 id: "ast_list_enums",
357 description: "List all enums and their variants",
358 template: "List all enums and variants in {FILE}",
359 params: &[Param {
360 name: "FILE",
361 description: "File path",
362 example: "src/main.rs",
363 }],
364 kind: TemplateKind::Ast,
365 example: &["src/main.rs"],
366 },
367 QueryTemplate {
368 id: "ast_list_impls",
369 description: "List all impl blocks for a type",
370 template: "List all impl blocks for {TYPE} in {FILE}",
371 params: &[
372 Param {
373 name: "TYPE",
374 description: "Type name to find impls for",
375 example: "MyStruct",
376 },
377 Param {
378 name: "FILE",
379 description: "File path",
380 example: "src/main.rs",
381 },
382 ],
383 kind: TemplateKind::Ast,
384 example: &["MyStruct", "src/main.rs"],
385 },
386 QueryTemplate {
387 id: "ast_function_signatures",
388 description: "Get function signatures (name, params, return type)",
389 template: "List all function signatures in {FILE}",
390 params: &[Param {
391 name: "FILE",
392 description: "File path",
393 example: "src/main.rs",
394 }],
395 kind: TemplateKind::Ast,
396 example: &["src/main.rs"],
397 },
398 QueryTemplate {
399 id: "ast_pub_functions",
400 description: "List public function signatures",
401 template: "List all public function signatures in {FILE}",
402 params: &[Param {
403 name: "FILE",
404 description: "File path",
405 example: "src/main.rs",
406 }],
407 kind: TemplateKind::Ast,
408 example: &["src/main.rs"],
409 },
410 QueryTemplate {
411 id: "ast_async_functions",
412 description: "List async function signatures",
413 template: "List all async function signatures in {FILE}",
414 params: &[Param {
415 name: "FILE",
416 description: "File path",
417 example: "src/main.rs",
418 }],
419 kind: TemplateKind::Ast,
420 example: &["src/main.rs"],
421 },
422 QueryTemplate {
423 id: "ast_struct_fields",
424 description: "Get struct field names and types",
425 template: "What fields does {STRUCT} have in {FILE}?",
426 params: &[
427 Param {
428 name: "STRUCT",
429 description: "Struct name",
430 example: "Config",
431 },
432 Param {
433 name: "FILE",
434 description: "File path",
435 example: "src/main.rs",
436 },
437 ],
438 kind: TemplateKind::Ast,
439 example: &["Config", "src/main.rs"],
440 },
441 QueryTemplate {
442 id: "ast_enum_variants",
443 description: "Get enum variant names",
444 template: "What variants does {ENUM} have in {FILE}?",
445 params: &[
446 Param {
447 name: "ENUM",
448 description: "Enum name",
449 example: "Status",
450 },
451 Param {
452 name: "FILE",
453 description: "File path",
454 example: "src/main.rs",
455 },
456 ],
457 kind: TemplateKind::Ast,
458 example: &["Status", "src/main.rs"],
459 },
460 QueryTemplate {
461 id: "ast_trait_impls",
462 description: "Find trait implementations",
463 template: "What traits does {TYPE} implement in {FILE}?",
464 params: &[
465 Param {
466 name: "TYPE",
467 description: "Type name",
468 example: "MyStruct",
469 },
470 Param {
471 name: "FILE",
472 description: "File path",
473 example: "src/main.rs",
474 },
475 ],
476 kind: TemplateKind::Ast,
477 example: &["MyStruct", "src/main.rs"],
478 },
479 QueryTemplate {
480 id: "ast_error_handling_count",
481 description: "Count error handling patterns",
482 template: "Count the error handling patterns (Result, ?, unwrap, expect) in {FILE}",
483 params: &[Param {
484 name: "FILE",
485 description: "File path",
486 example: "src/main.rs",
487 }],
488 kind: TemplateKind::Ast,
489 example: &["src/main.rs"],
490 },
491 QueryTemplate {
492 id: "ast_method_names",
493 description: "List all method names in impl blocks",
494 template: "List all methods defined in impl blocks in {FILE}",
495 params: &[Param {
496 name: "FILE",
497 description: "File path",
498 example: "src/main.rs",
499 }],
500 kind: TemplateKind::Ast,
501 example: &["src/main.rs"],
502 },
503];
504
505#[derive(Debug, Clone)]
507pub struct GeneratedQuery {
508 pub query: String,
510 pub template_id: &'static str,
512 pub kind: TemplateKind,
514 pub file: String,
516}
517
518impl GeneratedQuery {
519 pub fn render(template: &'static QueryTemplate, values: &[&str]) -> Self {
521 let file = values.last().unwrap_or(&"").to_string();
522
523 Self {
524 query: template.render(values),
525 template_id: template.id,
526 kind: template.kind,
527 file,
528 }
529 }
530}
531
532pub struct TemplateIter {
534 index: usize,
535 kind: Option<TemplateKind>,
536}
537
538impl Iterator for TemplateIter {
539 type Item = &'static QueryTemplate;
540
541 fn next(&mut self) -> Option<Self::Item> {
542 while self.index < TEMPLATES.len() {
543 let template = &TEMPLATES[self.index];
544 self.index += 1;
545
546 if let Some(kind) = self.kind {
547 if template.kind == kind {
548 return Some(template);
549 }
550 } else {
551 return Some(template);
552 }
553 }
554 None
555 }
556}
557
558impl QueryTemplate {
559 pub fn iter() -> TemplateIter {
561 TemplateIter {
562 index: 0,
563 kind: None,
564 }
565 }
566
567 pub fn iter_kind(kind: TemplateKind) -> TemplateIter {
569 TemplateIter {
570 index: 0,
571 kind: Some(kind),
572 }
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn render_simple_template() {
582 let template = QueryTemplate::find("grep_find_occurrences").unwrap();
583 let rendered = template.render(&["fn", "src/main.rs"]);
584 assert_eq!(rendered, "Find all occurrences of fn in src/main.rs");
585 }
586
587 #[test]
588 fn find_template_by_id() {
589 let template = QueryTemplate::find("ast_list_functions").unwrap();
590 assert_eq!(template.kind, TemplateKind::Ast);
591 }
592
593 #[test]
594 fn filter_templates_by_kind() {
595 let grep_count = QueryTemplate::iter_kind(TemplateKind::Grep).count();
596 let ast_count = QueryTemplate::iter_kind(TemplateKind::Ast).count();
597
598 assert!(grep_count > 0);
599 assert!(ast_count > 0);
600 }
601
602 #[test]
603 fn template_has_examples() {
604 for template in QueryTemplate::all() {
605 assert!(
606 !template.example.is_empty(),
607 "Template {} has no examples",
608 template.id
609 );
610 }
611 }
612
613 #[test]
614 fn generated_query_has_fields() {
615 let template = QueryTemplate::find("grep_find_occurrences").unwrap();
616 let r#gen = GeneratedQuery::render(template, &["async fn", "src/lib.rs"]);
617
618 assert_eq!(
619 r#gen.query,
620 "Find all occurrences of async fn in src/lib.rs"
621 );
622 assert_eq!(r#gen.template_id, "grep_find_occurrences");
623 assert_eq!(r#gen.kind, TemplateKind::Grep);
624 assert_eq!(r#gen.file, "src/lib.rs");
625 }
626
627 #[test]
628 fn template_kind_to_payload_kind() {
629 assert_eq!(TemplateKind::Grep.as_payload_kind(), "grep");
630 assert_eq!(TemplateKind::Ast.as_payload_kind(), "ast");
631 assert_eq!(TemplateKind::Semantic.as_payload_kind(), "semantic");
632 }
633}