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: &[
203 Param {
204 name: "FILE",
205 description: "File path",
206 example: "src/main.rs",
207 },
208 ],
209 kind: TemplateKind::Grep,
210 example: &["src/main.rs"],
211 },
212 QueryTemplate {
213 id: "grep_find_enums",
214 description: "Find all enum definitions",
215 template: "Find all enums in {FILE}",
216 params: &[
217 Param {
218 name: "FILE",
219 description: "File path",
220 example: "src/main.rs",
221 },
222 ],
223 kind: TemplateKind::Grep,
224 example: &["src/main.rs"],
225 },
226 QueryTemplate {
227 id: "grep_find_traits",
228 description: "Find all trait definitions",
229 template: "Find all traits in {FILE}",
230 params: &[
231 Param {
232 name: "FILE",
233 description: "File path",
234 example: "src/main.rs",
235 },
236 ],
237 kind: TemplateKind::Grep,
238 example: &["src/main.rs"],
239 },
240 QueryTemplate {
241 id: "grep_find_impls",
242 description: "Find all impl blocks",
243 template: "Find all impl blocks in {FILE}",
244 params: &[
245 Param {
246 name: "FILE",
247 description: "File path",
248 example: "src/main.rs",
249 },
250 ],
251 kind: TemplateKind::Grep,
252 example: &["src/main.rs"],
253 },
254 QueryTemplate {
255 id: "grep_find_imports",
256 description: "Find all use/import statements",
257 template: "Find all imports in {FILE}",
258 params: &[
259 Param {
260 name: "FILE",
261 description: "File path",
262 example: "src/main.rs",
263 },
264 ],
265 kind: TemplateKind::Grep,
266 example: &["src/main.rs"],
267 },
268 QueryTemplate {
269 id: "grep_find_tests",
270 description: "Find all test functions",
271 template: "Find all test functions in {FILE}",
272 params: &[
273 Param {
274 name: "FILE",
275 description: "File path",
276 example: "src/main.rs",
277 },
278 ],
279 kind: TemplateKind::Grep,
280 example: &["src/main.rs"],
281 },
282 QueryTemplate {
283 id: "grep_find_error_handling",
284 description: "Find error handling patterns (Result, ?, unwrap)",
285 template: "Find all error handling patterns in {FILE}",
286 params: &[
287 Param {
288 name: "FILE",
289 description: "File path",
290 example: "src/main.rs",
291 },
292 ],
293 kind: TemplateKind::Grep,
294 example: &["src/main.rs"],
295 },
296 QueryTemplate {
297 id: "grep_find_macros",
298 description: "Find macro invocations",
299 template: "Find all macro calls in {FILE}",
300 params: &[
301 Param {
302 name: "FILE",
303 description: "File path",
304 example: "src/main.rs",
305 },
306 ],
307 kind: TemplateKind::Grep,
308 example: &["src/main.rs"],
309 },
310 QueryTemplate {
311 id: "grep_find_comments",
312 description: "Find comments in code",
313 template: "Find all comments in {FILE}",
314 params: &[
315 Param {
316 name: "FILE",
317 description: "File path",
318 example: "src/main.rs",
319 },
320 ],
321 kind: TemplateKind::Grep,
322 example: &["src/main.rs"],
323 },
324 QueryTemplate {
325 id: "grep_find_todos",
326 description: "Find TODO/FIXME comments",
327 template: "Find all TODO comments in {FILE}",
328 params: &[
329 Param {
330 name: "FILE",
331 description: "File path",
332 example: "src/main.rs",
333 },
334 ],
335 kind: TemplateKind::Grep,
336 example: &["src/main.rs"],
337 },
338 QueryTemplate {
339 id: "grep_find_string_literals",
340 description: "Find string literals",
341 template: "Find all string literals in {FILE}",
342 params: &[
343 Param {
344 name: "FILE",
345 description: "File path",
346 example: "src/main.rs",
347 },
348 ],
349 kind: TemplateKind::Grep,
350 example: &["src/main.rs"],
351 },
352
353 QueryTemplate {
355 id: "ast_list_functions",
356 description: "List all function names in a file",
357 template: "List all function names in {FILE}",
358 params: &[
359 Param {
360 name: "FILE",
361 description: "File path",
362 example: "src/main.rs",
363 },
364 ],
365 kind: TemplateKind::Ast,
366 example: &["src/main.rs"],
367 },
368 QueryTemplate {
369 id: "ast_list_structs",
370 description: "List all structs and their fields",
371 template: "List all structs and fields in {FILE}",
372 params: &[
373 Param {
374 name: "FILE",
375 description: "File path",
376 example: "src/main.rs",
377 },
378 ],
379 kind: TemplateKind::Ast,
380 example: &["src/main.rs"],
381 },
382 QueryTemplate {
383 id: "ast_list_enums",
384 description: "List all enums and their variants",
385 template: "List all enums and variants in {FILE}",
386 params: &[
387 Param {
388 name: "FILE",
389 description: "File path",
390 example: "src/main.rs",
391 },
392 ],
393 kind: TemplateKind::Ast,
394 example: &["src/main.rs"],
395 },
396 QueryTemplate {
397 id: "ast_list_impls",
398 description: "List all impl blocks for a type",
399 template: "List all impl blocks for {TYPE} in {FILE}",
400 params: &[
401 Param {
402 name: "TYPE",
403 description: "Type name to find impls for",
404 example: "MyStruct",
405 },
406 Param {
407 name: "FILE",
408 description: "File path",
409 example: "src/main.rs",
410 },
411 ],
412 kind: TemplateKind::Ast,
413 example: &["MyStruct", "src/main.rs"],
414 },
415 QueryTemplate {
416 id: "ast_function_signatures",
417 description: "Get function signatures (name, params, return type)",
418 template: "List all function signatures in {FILE}",
419 params: &[
420 Param {
421 name: "FILE",
422 description: "File path",
423 example: "src/main.rs",
424 },
425 ],
426 kind: TemplateKind::Ast,
427 example: &["src/main.rs"],
428 },
429 QueryTemplate {
430 id: "ast_pub_functions",
431 description: "List public function signatures",
432 template: "List all public function signatures in {FILE}",
433 params: &[
434 Param {
435 name: "FILE",
436 description: "File path",
437 example: "src/main.rs",
438 },
439 ],
440 kind: TemplateKind::Ast,
441 example: &["src/main.rs"],
442 },
443 QueryTemplate {
444 id: "ast_async_functions",
445 description: "List async function signatures",
446 template: "List all async function signatures in {FILE}",
447 params: &[
448 Param {
449 name: "FILE",
450 description: "File path",
451 example: "src/main.rs",
452 },
453 ],
454 kind: TemplateKind::Ast,
455 example: &["src/main.rs"],
456 },
457 QueryTemplate {
458 id: "ast_struct_fields",
459 description: "Get struct field names and types",
460 template: "What fields does {STRUCT} have in {FILE}?",
461 params: &[
462 Param {
463 name: "STRUCT",
464 description: "Struct name",
465 example: "Config",
466 },
467 Param {
468 name: "FILE",
469 description: "File path",
470 example: "src/main.rs",
471 },
472 ],
473 kind: TemplateKind::Ast,
474 example: &["Config", "src/main.rs"],
475 },
476 QueryTemplate {
477 id: "ast_enum_variants",
478 description: "Get enum variant names",
479 template: "What variants does {ENUM} have in {FILE}?",
480 params: &[
481 Param {
482 name: "ENUM",
483 description: "Enum name",
484 example: "Status",
485 },
486 Param {
487 name: "FILE",
488 description: "File path",
489 example: "src/main.rs",
490 },
491 ],
492 kind: TemplateKind::Ast,
493 example: &["Status", "src/main.rs"],
494 },
495 QueryTemplate {
496 id: "ast_trait_impls",
497 description: "Find trait implementations",
498 template: "What traits does {TYPE} implement in {FILE}?",
499 params: &[
500 Param {
501 name: "TYPE",
502 description: "Type name",
503 example: "MyStruct",
504 },
505 Param {
506 name: "FILE",
507 description: "File path",
508 example: "src/main.rs",
509 },
510 ],
511 kind: TemplateKind::Ast,
512 example: &["MyStruct", "src/main.rs"],
513 },
514 QueryTemplate {
515 id: "ast_error_handling_count",
516 description: "Count error handling patterns",
517 template: "Count the error handling patterns (Result, ?, unwrap, expect) in {FILE}",
518 params: &[
519 Param {
520 name: "FILE",
521 description: "File path",
522 example: "src/main.rs",
523 },
524 ],
525 kind: TemplateKind::Ast,
526 example: &["src/main.rs"],
527 },
528 QueryTemplate {
529 id: "ast_method_names",
530 description: "List all method names in impl blocks",
531 template: "List all methods defined in impl blocks in {FILE}",
532 params: &[
533 Param {
534 name: "FILE",
535 description: "File path",
536 example: "src/main.rs",
537 },
538 ],
539 kind: TemplateKind::Ast,
540 example: &["src/main.rs"],
541 },
542];
543
544#[derive(Debug, Clone)]
546pub struct GeneratedQuery {
547 pub query: String,
549 pub template_id: &'static str,
551 pub kind: TemplateKind,
553 pub file: String,
555}
556
557impl GeneratedQuery {
558 pub fn render(template: &'static QueryTemplate, values: &[&str]) -> Self {
560 let file = values.last().unwrap_or(&"").to_string();
561
562 Self {
563 query: template.render(values),
564 template_id: template.id,
565 kind: template.kind,
566 file,
567 }
568 }
569}
570
571pub struct TemplateIter {
573 index: usize,
574 kind: Option<TemplateKind>,
575}
576
577impl Iterator for TemplateIter {
578 type Item = &'static QueryTemplate;
579
580 fn next(&mut self) -> Option<Self::Item> {
581 while self.index < TEMPLATES.len() {
582 let template = &TEMPLATES[self.index];
583 self.index += 1;
584
585 if let Some(kind) = self.kind {
586 if template.kind == kind {
587 return Some(template);
588 }
589 } else {
590 return Some(template);
591 }
592 }
593 None
594 }
595}
596
597impl QueryTemplate {
598 pub fn iter() -> TemplateIter {
600 TemplateIter { index: 0, kind: None }
601 }
602
603 pub fn iter_kind(kind: TemplateKind) -> TemplateIter {
605 TemplateIter { index: 0, kind: Some(kind) }
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612
613 #[test]
614 fn render_simple_template() {
615 let template = QueryTemplate::find("grep_find_occurrences").unwrap();
616 let rendered = template.render(&["fn", "src/main.rs"]);
617 assert_eq!(rendered, "Find all occurrences of fn in src/main.rs");
618 }
619
620 #[test]
621 fn find_template_by_id() {
622 let template = QueryTemplate::find("ast_list_functions").unwrap();
623 assert_eq!(template.kind, TemplateKind::Ast);
624 }
625
626 #[test]
627 fn filter_templates_by_kind() {
628 let grep_count = QueryTemplate::iter_kind(TemplateKind::Grep).count();
629 let ast_count = QueryTemplate::iter_kind(TemplateKind::Ast).count();
630
631 assert!(grep_count > 0);
632 assert!(ast_count > 0);
633 }
634
635 #[test]
636 fn template_has_examples() {
637 for template in QueryTemplate::all() {
638 assert!(!template.example.is_empty(), "Template {} has no examples", template.id);
639 }
640 }
641
642 #[test]
643 fn generated_query_has_fields() {
644 let template = QueryTemplate::find("grep_find_occurrences").unwrap();
645 let r#gen = GeneratedQuery::render(template, &["async fn", "src/lib.rs"]);
646
647 assert_eq!(gen.query, "Find all occurrences of async fn in src/lib.rs");
648 assert_eq!(gen.template_id, "grep_find_occurrences");
649 assert_eq!(gen.kind, TemplateKind::Grep);
650 assert_eq!(gen.file, "src/lib.rs");
651 }
652
653 #[test]
654 fn template_kind_to_payload_kind() {
655 assert_eq!(TemplateKind::Grep.as_payload_kind(), "grep");
656 assert_eq!(TemplateKind::Ast.as_payload_kind(), "ast");
657 assert_eq!(TemplateKind::Semantic.as_payload_kind(), "semantic");
658 }
659}