Skip to main content

cargo_mate/tools/
macro_expand.rs

1use super::{Tool, ToolError, Result, OutputFormat, parse_output_format};
2use clap::{Arg, ArgMatches, Command};
3use std::path::Path;
4use std::fs;
5use std::collections::HashMap;
6use colored::*;
7use syn::{parse_file, visit::Visit, ItemMacro, Macro};
8use quote::{quote, ToTokens};
9use serde::{Serialize, Deserialize};
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct MacroCall {
12    pub name: String,
13    pub args: String,
14    pub span_placeholder: String,
15    pub file: String,
16    pub line: usize,
17    pub column: usize,
18}
19#[derive(Debug, Clone)]
20pub struct ExpansionStep {
21    pub before: String,
22    pub after: String,
23    pub macro_name: String,
24    pub description: String,
25    pub line: usize,
26}
27#[derive(Debug, Clone)]
28pub struct ValidationIssue {
29    pub issue: String,
30    pub severity: String,
31    pub line: usize,
32    pub suggestion: String,
33}
34#[derive(Debug, Clone)]
35pub struct MacroDependency {
36    pub name: String,
37    pub dependency_type: String,
38    pub location: String,
39}
40#[derive(Debug, Clone, PartialEq)]
41pub enum MacroType {
42    Declarative,
43    Procedural,
44    BuiltIn,
45    Unknown,
46}
47pub struct MacroExpandTool;
48impl MacroExpandTool {
49    pub fn new() -> Self {
50        Self
51    }
52    fn parse_macro_calls(&self, file_path: &str) -> Result<Vec<MacroCall>> {
53        if !Path::new(file_path).exists() {
54            return Err(
55                ToolError::InvalidArguments(format!("File not found: {}", file_path)),
56            );
57        }
58        let content = fs::read_to_string(file_path)?;
59        let ast = parse_file(&content)
60            .map_err(|e| ToolError::ExecutionFailed(
61                format!("Failed to parse Rust file: {}", e),
62            ))?;
63        let mut visitor = MacroCallVisitor::new(file_path.to_string());
64        visitor.visit_file(&ast);
65        Ok(visitor.macro_calls)
66    }
67    fn expand_macro_step_by_step(
68        &self,
69        macro_call: &MacroCall,
70    ) -> Result<Vec<ExpansionStep>> {
71        let mut steps = Vec::new();
72        if let Ok(macro_def) = self
73            .find_macro_definition(&macro_call.name, &macro_call.file)
74        {
75            steps
76                .push(ExpansionStep {
77                    before: macro_call.args.clone(),
78                    after: self.show_pattern_match(&macro_call.args, &macro_def)?,
79                    macro_name: macro_call.name.clone(),
80                    description: "Pattern matching and hygiene application".to_string(),
81                    line: macro_call.line,
82                });
83            steps
84                .push(ExpansionStep {
85                    before: steps.last().unwrap().after.clone(),
86                    after: self.apply_hygiene(&steps.last().unwrap().after)?,
87                    macro_name: macro_call.name.clone(),
88                    description: "Hygiene application".to_string(),
89                    line: macro_call.line,
90                });
91        } else {
92            steps
93                .push(ExpansionStep {
94                    before: macro_call.args.clone(),
95                    after: format!("/* {} expanded */", macro_call.name),
96                    macro_name: macro_call.name.clone(),
97                    description: "Macro expansion (definition not found)".to_string(),
98                    line: macro_call.line,
99                });
100        }
101        Ok(steps)
102    }
103    fn find_macro_definition(
104        &self,
105        macro_name: &str,
106        file_path: &str,
107    ) -> Result<String> {
108        let content = fs::read_to_string(file_path)?;
109        let macro_pattern = format!(
110            "macro_rules!\\s*{}\\s*\\{{", regex::escape(macro_name)
111        );
112        let re = regex::Regex::new(&macro_pattern).unwrap();
113        if let Some(mat) = re.find(&content) {
114            let start = mat.start();
115            let mut brace_count = 0;
116            let mut end = start;
117            for (i, c) in content[start..].chars().enumerate() {
118                match c {
119                    '{' => brace_count += 1,
120                    '}' => brace_count -= 1,
121                    _ => {}
122                }
123                end = start + i;
124                if brace_count == 0 {
125                    break;
126                }
127            }
128            Ok(content[start..=end].to_string())
129        } else {
130            Err(
131                ToolError::ExecutionFailed(
132                    format!("Macro definition for '{}' not found", macro_name),
133                ),
134            )
135        }
136    }
137    fn show_pattern_match(&self, args: &str, macro_def: &str) -> Result<String> {
138        let args = args.trim_matches(&['(', ')'][..]);
139        if macro_def.contains("$x:expr") {
140            let elements: Vec<&str> = args.split(',').collect();
141            let mut result = "{\n    let mut v = Vec::new();".to_string();
142            for elem in elements {
143                result.push_str(&format!("\n    v.push({});", elem.trim()));
144            }
145            result.push_str("\n    v\n}");
146            Ok(result)
147        } else {
148            Ok(format!("/* Pattern matched: {} */", args))
149        }
150    }
151    fn apply_hygiene(&self, code: &str) -> Result<String> {
152        let mut result = code
153            .replace("Vec::", "::alloc::vec::Vec::")
154            .replace("vec!", "::alloc::vec!");
155        if result.contains("::alloc::vec::Vec::new()") {
156            result = result
157                .replace("::alloc::vec::Vec::new()", "::alloc::vec::Vec::new");
158        }
159        Ok(result)
160    }
161    fn generate_expanded_code(&self, file_path: &str) -> Result<String> {
162        let macro_calls = self.parse_macro_calls(file_path)?;
163        let content = fs::read_to_string(file_path)?;
164        if macro_calls.is_empty() {
165            return Ok(content);
166        }
167        let mut expanded = content.clone();
168        for call in macro_calls {
169            if let Ok(steps) = self.expand_macro_step_by_step(&call) {
170                if let Some(final_step) = steps.last() {
171                    let macro_call_pattern = format!("{}!{}", call.name, call.args);
172                    expanded = expanded.replace(&macro_call_pattern, &final_step.after);
173                }
174            }
175        }
176        Ok(expanded)
177    }
178    fn highlight_differences(&self, original: &str, expanded: &str) -> Result<String> {
179        let original_lines: Vec<&str> = original.lines().collect();
180        let expanded_lines: Vec<&str> = expanded.lines().collect();
181        let mut result = String::new();
182        for (i, (orig, exp)) in original_lines
183            .iter()
184            .zip(expanded_lines.iter())
185            .enumerate()
186        {
187            if orig != exp {
188                result
189                    .push_str(
190                        &format!(
191                            "{} {} {}\n", format!("{}:", i + 1) .yellow(), "-".red(),
192                            orig.red()
193                        ),
194                    );
195                result
196                    .push_str(
197                        &format!(
198                            "{} {} {}\n", format!("{}:", i + 1) .yellow(), "+".green(),
199                            exp.green()
200                        ),
201                    );
202            } else {
203                result
204                    .push_str(
205                        &format!("{}   {}\n", format!("{}:", i + 1) .blue(), orig),
206                    );
207            }
208        }
209        Ok(result)
210    }
211    fn extract_macro_dependencies(
212        &self,
213        macro_call: &MacroCall,
214    ) -> Vec<MacroDependency> {
215        let mut deps = Vec::new();
216        if macro_call.args.contains("vec!") {
217            deps.push(MacroDependency {
218                name: "vec".to_string(),
219                dependency_type: "Built-in macro".to_string(),
220                location: "std library".to_string(),
221            });
222        }
223        if macro_call.name.contains("println") || macro_call.name.contains("print") {
224            deps.push(MacroDependency {
225                name: "print".to_string(),
226                dependency_type: "Built-in macro".to_string(),
227                location: "std library".to_string(),
228            });
229        }
230        deps
231    }
232    fn validate_macro_expansion(
233        &self,
234        expanded_code: &str,
235    ) -> Result<Vec<ValidationIssue>> {
236        let mut issues = Vec::new();
237        if let Err(_) = syn::parse_file(expanded_code) {
238            issues
239                .push(ValidationIssue {
240                    issue: "Expanded code contains syntax errors".to_string(),
241                    severity: "High".to_string(),
242                    line: 0,
243                    suggestion: "Review macro definition and arguments".to_string(),
244                });
245        }
246        if expanded_code.contains("unresolved name") {
247            issues
248                .push(ValidationIssue {
249                    issue: "Unresolved names in expanded code".to_string(),
250                    severity: "Medium".to_string(),
251                    line: 0,
252                    suggestion: "Check macro hygiene and imports".to_string(),
253                });
254        }
255        Ok(issues)
256    }
257    fn classify_macro(&self, macro_name: &str) -> MacroType {
258        match macro_name {
259            "println" | "print" | "format" | "vec" | "assert" | "panic" => {
260                MacroType::BuiltIn
261            }
262            name if name.contains("macro_rules!") => MacroType::Declarative,
263            name if name.contains("#[") => MacroType::Procedural,
264            _ => MacroType::Unknown,
265        }
266    }
267    fn format_expanded_code(&self, code: &str, format: &str) -> Result<String> {
268        match format {
269            "rust" => Ok(code.to_string()),
270            "html" => self.format_as_html(code),
271            "json" => self.format_as_json(code),
272            _ => Ok(code.to_string()),
273        }
274    }
275    fn format_as_html(&self, code: &str) -> Result<String> {
276        let html = format!(
277            "<!DOCTYPE html>
278<html>
279<head>
280    <title>Macro Expansion</title>
281    <style>
282        .code {{ font-family: 'Monaco', 'Menlo', monospace; background: #f5f5f5; padding: 1em; }}
283        .expanded {{ color: #28a745; }}
284        .original {{ color: #dc3545; }}
285    </style>
286</head>
287<body>
288    <h1>Macro Expansion Result</h1>
289    <pre class=\"code\">{}</pre>
290</body>
291</html>",
292            code.replace("<", "&lt;").replace(">", "&gt;")
293        );
294        Ok(html)
295    }
296    fn format_as_json(&self, code: &str) -> Result<String> {
297        let json = serde_json::json!(
298            { "expanded_code" : code, "timestamp" : chrono::Utc::now().to_rfc3339(),
299            "tool" : "macro-expand" }
300        );
301        Ok(serde_json::to_string_pretty(&json).unwrap())
302    }
303}
304struct MacroCallVisitor {
305    macro_calls: Vec<MacroCall>,
306    current_file: String,
307}
308impl MacroCallVisitor {
309    fn new(file_path: String) -> Self {
310        Self {
311            macro_calls: Vec::new(),
312            current_file: file_path,
313        }
314    }
315}
316impl<'ast> Visit<'ast> for MacroCallVisitor {
317    fn visit_macro(&mut self, node: &'ast Macro) {
318        if let Some(last_segment) = node.path.segments.last() {
319            let macro_name = last_segment.ident.to_string();
320            let args = quote!(# node).to_string();
321            self.macro_calls
322                .push(MacroCall {
323                    name: macro_name,
324                    args: format!("({})", args),
325                    span_placeholder: "span_info_unavailable".to_string(),
326                    file: self.current_file.clone(),
327                    line: 0,
328                    column: 0,
329                });
330        }
331        syn::visit::visit_macro(self, node);
332    }
333}
334impl Tool for MacroExpandTool {
335    fn name(&self) -> &'static str {
336        "macro-expand"
337    }
338    fn description(&self) -> &'static str {
339        "Better macro expansion viewer with step-by-step expansion and syntax highlighting"
340    }
341    fn command(&self) -> Command {
342        Command::new(self.name())
343            .about(self.description())
344            .long_about(
345                "Provide a better macro expansion viewer with syntax highlighting, step-by-step expansion, and interactive exploration.\n\
346                 \n\
347                 This tool helps you understand complex macros by:\n\
348                 • Expanding procedural and declarative macros\n\
349                 • Showing intermediate expansion steps\n\
350                 • Syntax highlighting for expanded code\n\
351                 • Comparing original vs expanded code\n\
352                 \n\
353                 EXAMPLES:\n\
354                 cm tool macro-expand --input src/lib.rs --step-by-step\n\
355                 cm tool macro-expand --input src/main.rs --macro my_macro --highlight\n\
356                 cm tool macro-expand --input src/lib.rs --diff --validate",
357            )
358            .args(
359                &[
360                    Arg::new("input")
361                        .long("input")
362                        .short('i')
363                        .help("Input Rust file to analyze")
364                        .required(true),
365                    Arg::new("macro")
366                        .long("macro")
367                        .short('m')
368                        .help("Specific macro to expand (expand all if not specified)"),
369                    Arg::new("step-by-step")
370                        .long("step-by-step")
371                        .help("Show step-by-step expansion")
372                        .action(clap::ArgAction::SetTrue),
373                    Arg::new("highlight")
374                        .long("highlight")
375                        .help("Highlight expanded code with syntax highlighting")
376                        .action(clap::ArgAction::SetTrue),
377                    Arg::new("diff")
378                        .long("diff")
379                        .help("Show diff between original and expanded")
380                        .action(clap::ArgAction::SetTrue),
381                    Arg::new("validate")
382                        .long("validate")
383                        .help("Validate that expanded code compiles")
384                        .action(clap::ArgAction::SetTrue),
385                    Arg::new("interactive")
386                        .long("interactive")
387                        .help("Interactive macro exploration mode")
388                        .action(clap::ArgAction::SetTrue),
389                    Arg::new("output")
390                        .long("output")
391                        .short('o')
392                        .help("Output file for expanded code")
393                        .default_value("expanded.rs"),
394                    Arg::new("format")
395                        .long("format")
396                        .short('f')
397                        .help("Output format: rust, html, json")
398                        .default_value("rust"),
399                ],
400            )
401            .args(&super::common_options())
402    }
403    fn execute(&self, matches: &ArgMatches) -> Result<()> {
404        let input = matches.get_one::<String>("input").unwrap();
405        let specific_macro = matches.get_one::<String>("macro");
406        let step_by_step = matches.get_flag("step-by-step");
407        let highlight = matches.get_flag("highlight");
408        let diff = matches.get_flag("diff");
409        let validate = matches.get_flag("validate");
410        let interactive = matches.get_flag("interactive");
411        let output_file = matches.get_one::<String>("output").unwrap();
412        let format = matches.get_one::<String>("format").unwrap();
413        let verbose = matches.get_flag("verbose");
414        let dry_run = matches.get_flag("dry-run");
415        let output_format = parse_output_format(matches);
416        if dry_run {
417            println!("šŸ” Would analyze macro expansion in: {}", input);
418            return Ok(());
419        }
420        match output_format {
421            OutputFormat::Human => {
422                println!(
423                    "šŸ” {} - {}", "Macro Expansion Analysis".bold(), self.description()
424                    .cyan()
425                );
426                match self.parse_macro_calls(input) {
427                    Ok(macro_calls) => {
428                        println!("\nšŸ“ File: {}", input.bold());
429                        println!(
430                            "šŸ” Macros Found: {}", macro_calls.len().to_string().cyan()
431                        );
432                        if macro_calls.is_empty() {
433                            println!("āœ… No macro calls found in the file.");
434                            return Ok(());
435                        }
436                        let filtered_calls: Vec<_> = if let Some(macro_name) = specific_macro {
437                            macro_calls
438                                .into_iter()
439                                .filter(|call| call.name == *macro_name)
440                                .collect()
441                        } else {
442                            macro_calls
443                        };
444                        if filtered_calls.is_empty() {
445                            if let Some(name) = specific_macro {
446                                println!(
447                                    "āŒ Macro '{}' not found in the file.", name.red()
448                                );
449                            }
450                            return Ok(());
451                        }
452                        let mut macro_types = HashMap::new();
453                        for call in &filtered_calls {
454                            let macro_type = self.classify_macro(&call.name);
455                            macro_types
456                                .entry(format!("{:?}", macro_type))
457                                .or_insert_with(Vec::new)
458                                .push(call.name.clone());
459                        }
460                        println!("\nšŸ“Š Expansion Summary:");
461                        for (type_name, names) in &macro_types {
462                            println!("  • {}: {}", type_name, names.len());
463                        }
464                        for (i, call) in filtered_calls.iter().enumerate() {
465                            println!("\nšŸ”¬ Macro: {}!{}", call.name.bold(), call.args);
466                            println!(
467                                "   šŸ“ Line {}, Column {}", call.line, call.column
468                            );
469                            if step_by_step {
470                                match self.expand_macro_step_by_step(call) {
471                                    Ok(steps) => {
472                                        for (step_num, step) in steps.iter().enumerate() {
473                                            println!(
474                                                "\n   Step {} - {}:", step_num + 1, step.description.bold()
475                                            );
476                                            println!("   ```rust");
477                                            for line in step.after.lines() {
478                                                println!("   {}", line);
479                                            }
480                                            println!("   ```");
481                                        }
482                                    }
483                                    Err(e) => {
484                                        if verbose {
485                                            println!("   āš ļø  Could not expand step by step: {}", e);
486                                        }
487                                    }
488                                }
489                            }
490                            let deps = self.extract_macro_dependencies(call);
491                            if !deps.is_empty() {
492                                println!("\n   šŸ“š Dependencies:");
493                                for dep in &deps {
494                                    println!(
495                                        "     • {} ({})", dep.name.cyan(), dep.dependency_type
496                                    );
497                                }
498                            }
499                            if validate {
500                                if let Ok(steps) = self.expand_macro_step_by_step(call) {
501                                    if let Some(final_step) = steps.last() {
502                                        match self.validate_macro_expansion(&final_step.after) {
503                                            Ok(issues) => {
504                                                if issues.is_empty() {
505                                                    println!(
506                                                        "\n   āœ… Validation: Expanded code compiles successfully"
507                                                    );
508                                                } else {
509                                                    for issue in issues {
510                                                        let severity_color = match issue.severity.as_str() {
511                                                            "High" => issue.severity.red().bold(),
512                                                            "Medium" => issue.severity.yellow().bold(),
513                                                            _ => issue.severity.green().bold(),
514                                                        };
515                                                        println!(
516                                                            "\n   🚨 Validation Issue [{}]: {}", severity_color, issue
517                                                            .issue
518                                                        );
519                                                        println!("      šŸ’” {}", issue.suggestion.cyan());
520                                                    }
521                                                }
522                                            }
523                                            Err(e) => {
524                                                if verbose {
525                                                    println!("\n   āš ļø  Validation failed: {}", e);
526                                                }
527                                            }
528                                        }
529                                    }
530                                }
531                            }
532                            if let Ok(steps) = self.expand_macro_step_by_step(call) {
533                                if let Some(final_step) = steps.last() {
534                                    let original_size = call.args.len();
535                                    let expanded_size = final_step.after.len();
536                                    let ratio = if original_size > 0 {
537                                        expanded_size as f64 / original_size as f64
538                                    } else {
539                                        1.0
540                                    };
541                                    println!("\nšŸ“ˆ Expansion Metrics:");
542                                    println!("   • Original size: {} chars", original_size);
543                                    println!("   • Expanded size: {} chars", expanded_size);
544                                    println!("   • Expansion ratio: {:.1}x", ratio);
545                                }
546                            }
547                            if i < filtered_calls.len() - 1 {
548                                println!("{}", "─".repeat(50).blue());
549                            }
550                        }
551                        if let Ok(expanded) = self.generate_expanded_code(input) {
552                            if diff {
553                                println!("\nšŸ“‹ Original vs Expanded Comparison:");
554                                match self
555                                    .highlight_differences(
556                                        &fs::read_to_string(input).unwrap_or_default(),
557                                        &expanded,
558                                    )
559                                {
560                                    Ok(diff_output) => {
561                                        for line in diff_output.lines().take(20) {
562                                            println!("   {}", line);
563                                        }
564                                        if diff_output.lines().count() > 20 {
565                                            println!(
566                                                "   ... (truncated - {} more lines)", diff_output.lines()
567                                                .count() - 20
568                                            );
569                                        }
570                                    }
571                                    Err(e) => {
572                                        if verbose {
573                                            println!("   āš ļø  Could not generate diff: {}", e);
574                                        }
575                                    }
576                                }
577                            }
578                            if let Err(e) = fs::write(output_file, &expanded) {
579                                if verbose {
580                                    println!("āš ļø  Could not write to output file: {}", e);
581                                }
582                            } else if verbose {
583                                println!(
584                                    "\nšŸ’¾ Expanded code written to: {}", output_file
585                                );
586                            }
587                        }
588                    }
589                    Err(e) => {
590                        return Err(
591                            ToolError::ExecutionFailed(
592                                format!("Failed to analyze macros: {}", e),
593                            ),
594                        );
595                    }
596                }
597            }
598            OutputFormat::Json => {
599                let macro_calls = self.parse_macro_calls(input)?;
600                let mut json_output = serde_json::json!(
601                    { "file" : input, "macro_calls" : macro_calls.len(), "macros" :
602                    macro_calls, }
603                );
604                if let Ok(expanded) = self.generate_expanded_code(input) {
605                    json_output["expanded_code"] = expanded.into();
606                }
607                println!("{}", serde_json::to_string_pretty(& json_output).unwrap());
608            }
609            OutputFormat::Table => {
610                let macro_calls = self.parse_macro_calls(input)?;
611                println!(
612                    "ā”Œā”€ Macro Expansion Analysis ──────────────────────┐"
613                );
614                println!("│ File: {:<45} │", input);
615                println!("│ Macros Found: {:<36} │", macro_calls.len());
616                for call in macro_calls.iter().take(5) {
617                    println!(
618                        "│ • {:<45} │", format!("{}!{}", call.name, call.args)
619                    );
620                }
621                if macro_calls.len() > 5 {
622                    println!(
623                        "│ ... and {} more {:<32} │", macro_calls.len() - 5, ""
624                    );
625                }
626                println!(
627                    "ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜"
628                );
629            }
630        }
631        Ok(())
632    }
633}