clnrm_template/
debug.rs

1//! Template debugging and error reporting utilities
2//!
3//! Provides tools for template development, debugging, and troubleshooting:
4//! - Template syntax analysis
5//! - Variable usage tracking
6//! - Error location reporting
7//! - Template performance profiling
8//! - Development-time validation
9
10use crate::context::TemplateContext;
11use crate::error::{Result, TemplateError};
12use std::collections::{HashMap, HashSet};
13use std::path::Path;
14
15/// Template debugging information
16#[derive(Debug, Clone)]
17pub struct DebugInfo {
18    /// Template name
19    pub template_name: String,
20    /// Template source content
21    pub source: String,
22    /// Variables used in template
23    pub variables_used: HashSet<String>,
24    /// Functions called in template
25    pub functions_used: HashSet<String>,
26    /// Blocks defined in template
27    pub blocks_defined: HashSet<String>,
28    /// Templates extended (for inheritance)
29    pub extends_templates: Vec<String>,
30    /// Templates included
31    pub includes_templates: Vec<String>,
32    /// Syntax errors found
33    pub syntax_errors: Vec<String>,
34    /// Performance metrics
35    pub render_time_ms: Option<u64>,
36    /// Memory usage estimate
37    pub memory_usage: Option<usize>,
38}
39
40/// Template debugger for development and troubleshooting
41#[derive(Debug)]
42pub struct TemplateDebugger {
43    /// Enable verbose debugging
44    verbose: bool,
45    /// Track variable usage
46    track_variables: bool,
47    /// Track function calls
48    track_functions: bool,
49    /// Enable syntax validation
50    validate_syntax: bool,
51    /// Performance profiling
52    profile_performance: bool,
53}
54
55impl Default for TemplateDebugger {
56    fn default() -> Self {
57        Self {
58            verbose: false,
59            track_variables: true,
60            track_functions: true,
61            validate_syntax: true,
62            profile_performance: false,
63        }
64    }
65}
66
67impl TemplateDebugger {
68    /// Create new template debugger
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    /// Enable verbose debugging output
74    pub fn verbose(mut self, verbose: bool) -> Self {
75        self.verbose = verbose;
76        self
77    }
78
79    /// Enable/disable variable usage tracking
80    pub fn track_variables(mut self, track: bool) -> Self {
81        self.track_variables = track;
82        self
83    }
84
85    /// Enable/disable function call tracking
86    pub fn track_functions(mut self, track: bool) -> Self {
87        self.track_functions = track;
88        self
89    }
90
91    /// Enable/disable syntax validation
92    pub fn validate_syntax(mut self, validate: bool) -> Self {
93        self.validate_syntax = validate;
94        self
95    }
96
97    /// Enable/disable performance profiling
98    pub fn profile_performance(mut self, profile: bool) -> Self {
99        self.profile_performance = profile;
100        self
101    }
102
103    /// Analyze template for debugging information
104    ///
105    /// # Arguments
106    /// * `template_content` - Template source content
107    /// * `template_name` - Template name for reporting
108    pub fn analyze(&self, template_content: &str, template_name: &str) -> Result<DebugInfo> {
109        let mut info = DebugInfo {
110            template_name: template_name.to_string(),
111            source: template_content.to_string(),
112            variables_used: HashSet::new(),
113            functions_used: HashSet::new(),
114            blocks_defined: HashSet::new(),
115            extends_templates: Vec::new(),
116            includes_templates: Vec::new(),
117            syntax_errors: Vec::new(),
118            render_time_ms: None,
119            memory_usage: None,
120        };
121
122        // Analyze template syntax
123        if self.validate_syntax {
124            self.validate_template_syntax(template_content, &mut info)?;
125        }
126
127        // Extract variable usage
128        if self.track_variables {
129            self.extract_variables(template_content, &mut info);
130        }
131
132        // Extract function calls
133        if self.track_functions {
134            self.extract_functions(template_content, &mut info);
135        }
136
137        // Extract template composition info
138        self.extract_composition_info(template_content, &mut info);
139
140        Ok(info)
141    }
142
143    /// Validate template syntax
144    fn validate_template_syntax(&self, content: &str, info: &mut DebugInfo) -> Result<()> {
145        // Basic syntax validation for Tera templates
146        let mut errors = Vec::new();
147
148        // Check for unmatched braces
149        self.check_brace_matching(content, &mut errors);
150
151        // Check for invalid variable syntax
152        self.check_variable_syntax(content, &mut errors);
153
154        // Check for invalid function calls
155        self.check_function_syntax(content, &mut errors);
156
157        info.syntax_errors = errors;
158        Ok(())
159    }
160
161    /// Check for balanced braces and brackets
162    fn check_brace_matching(&self, content: &str, errors: &mut Vec<String>) {
163        let mut stack = Vec::new();
164
165        for (i, ch) in content.char_indices() {
166            match ch {
167                '{' => {
168                    if let Some(next) = content.chars().nth(i + 1) {
169                        if next == '{' || next == '%' || next == '#' {
170                            stack.push((ch, i));
171                        }
172                    }
173                }
174                '%' | '#' => {
175                    // Check if this is part of a Tera tag
176                    if i > 0 && content.chars().nth(i - 1) == Some('{') {
177                        stack.push((ch, i));
178                    }
179                }
180                '}' => {
181                    if let Some(next) = content.chars().nth(i + 1) {
182                        if next == '}' {
183                            if let Some((open, _open_pos)) = stack.pop() {
184                                if !matches!((open, ch), ('{', '}')) {
185                                    errors.push(format!(
186                                        "Unmatched braces at position {}: found '{}' but expected matching '{}'",
187                                        i, ch, open
188                                    ));
189                                }
190                            } else {
191                                errors.push(format!("Unmatched closing brace at position {}", i));
192                            }
193                        }
194                    }
195                }
196                _ => {}
197            }
198        }
199
200        // Check for unclosed tags
201        for (open, pos) in stack {
202            errors.push(format!("Unclosed '{}' at position {}", open, pos));
203        }
204    }
205
206    /// Check variable syntax
207    fn check_variable_syntax(&self, content: &str, errors: &mut Vec<String>) {
208        // Simple regex for Tera variables: {{ variable }} or {{ variable.nested }}
209        let var_regex = regex::Regex::new(r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)").unwrap();
210
211        for cap in var_regex.captures_iter(content) {
212            if let Some(var_name) = cap.get(1) {
213                let var = var_name.as_str();
214                // Check for obviously invalid variable names
215                if var.contains(" ") {
216                    errors.push(format!("Invalid variable name '{}' contains spaces", var));
217                }
218            }
219        }
220    }
221
222    /// Check function call syntax
223    fn check_function_syntax(&self, content: &str, _errors: &mut [String]) {
224        // Simple regex for function calls: function_name(args)
225        let func_regex = regex::Regex::new(r"([a-zA-Z_][a-zA-Z0-9_]*)\s*\(").unwrap();
226
227        for cap in func_regex.captures_iter(content) {
228            if let Some(func_name) = cap.get(1) {
229                let func = func_name.as_str();
230                // Check for unknown functions (basic check)
231                let known_functions = [
232                    "env",
233                    "now_rfc3339",
234                    "sha256",
235                    "toml_encode",
236                    "fake_name",
237                    "fake_email",
238                    "uuid_v4",
239                    "include",
240                    "extends",
241                ];
242
243                if !known_functions.contains(&func) && !func.starts_with("fake_") {
244                    // This is just a warning for unknown functions
245                    if self.verbose {
246                        eprintln!("Warning: Unknown function '{}' in template", func);
247                    }
248                }
249            }
250        }
251    }
252
253    /// Extract variables used in template
254    fn extract_variables(&self, content: &str, info: &mut DebugInfo) {
255        // Extract variables from {{ variable }} patterns
256        let var_regex = regex::Regex::new(r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)").unwrap();
257
258        for cap in var_regex.captures_iter(content) {
259            if let Some(var_name) = cap.get(1) {
260                info.variables_used.insert(var_name.as_str().to_string());
261            }
262        }
263    }
264
265    /// Extract function calls from template
266    fn extract_functions(&self, content: &str, info: &mut DebugInfo) {
267        // Extract function calls
268        let func_regex = regex::Regex::new(r"([a-zA-Z_][a-zA-Z0-9_]*)\s*\(").unwrap();
269
270        for cap in func_regex.captures_iter(content) {
271            if let Some(func_name) = cap.get(1) {
272                info.functions_used.insert(func_name.as_str().to_string());
273            }
274        }
275    }
276
277    /// Extract template composition information
278    fn extract_composition_info(&self, content: &str, info: &mut DebugInfo) {
279        // Extract extends declarations
280        let extends_regex = regex::Regex::new(r#"extends\s*\(\s*["']([^"']+)["']\s*\)"#).unwrap();
281
282        for cap in extends_regex.captures_iter(content) {
283            if let Some(template) = cap.get(1) {
284                info.extends_templates.push(template.as_str().to_string());
285            }
286        }
287
288        // Extract include declarations
289        let include_regex = regex::Regex::new(r#"include\s*\(\s*["']([^"']+)["']\s*\)"#).unwrap();
290
291        for cap in include_regex.captures_iter(content) {
292            if let Some(template) = cap.get(1) {
293                info.includes_templates.push(template.as_str().to_string());
294            }
295        }
296
297        // Extract block definitions
298        let block_regex = regex::Regex::new(r#"block\s*\(\s*["']([^"']+)["']\s*\)"#).unwrap();
299
300        for cap in block_regex.captures_iter(content) {
301            if let Some(block_name) = cap.get(1) {
302                info.blocks_defined.insert(block_name.as_str().to_string());
303            }
304        }
305    }
306
307    /// Debug template rendering with context
308    ///
309    /// # Arguments
310    /// * `template_content` - Template content
311    /// * `context` - Template context
312    /// * `template_name` - Template name
313    pub fn debug_render(
314        &self,
315        template_content: &str,
316        context: &TemplateContext,
317        template_name: &str,
318    ) -> Result<DebugInfo> {
319        let mut info = self.analyze(template_content, template_name)?;
320
321        if self.profile_performance {
322            let start = std::time::Instant::now();
323
324            // Attempt to render (this may fail)
325            let result = crate::render_with_context(template_content, context);
326
327            let elapsed = start.elapsed();
328            info.render_time_ms = Some(elapsed.as_millis() as u64);
329
330            if let Err(e) = result {
331                info.syntax_errors.push(e.to_string());
332            }
333        }
334
335        if self.verbose {
336            self.print_debug_info(&info);
337        }
338
339        Ok(info)
340    }
341
342    /// Print debug information to stderr
343    fn print_debug_info(&self, info: &DebugInfo) {
344        eprintln!("=== Template Debug Info ===");
345        eprintln!("Template: {}", info.template_name);
346        eprintln!("Variables used: {:?}", info.variables_used);
347        eprintln!("Functions used: {:?}", info.functions_used);
348        eprintln!("Blocks defined: {:?}", info.blocks_defined);
349        eprintln!("Extends: {:?}", info.extends_templates);
350        eprintln!("Includes: {:?}", info.includes_templates);
351
352        if !info.syntax_errors.is_empty() {
353            eprintln!("Syntax errors:");
354            for error in &info.syntax_errors {
355                eprintln!("  - {}", error);
356            }
357        }
358
359        if let Some(time) = info.render_time_ms {
360            eprintln!("Render time: {}ms", time);
361        }
362    }
363
364    /// Find unused variables in template compared to context
365    ///
366    /// # Arguments
367    /// * `debug_info` - Debug info from template analysis
368    /// * `context` - Template context
369    pub fn find_unused_variables(
370        &self,
371        debug_info: &DebugInfo,
372        context: &TemplateContext,
373    ) -> Vec<String> {
374        let mut unused = Vec::new();
375        for var_name in context.vars.keys() {
376            if !debug_info.variables_used.contains(var_name) {
377                unused.push(var_name.clone());
378            }
379        }
380        unused
381    }
382
383    /// Find missing variables (used in template but not in context)
384    ///
385    /// # Arguments
386    /// * `debug_info` - Debug info from template analysis
387    /// * `context` - Template context
388    pub fn find_missing_variables(
389        &self,
390        debug_info: &DebugInfo,
391        context: &TemplateContext,
392    ) -> Vec<String> {
393        let mut missing = Vec::new();
394        for var_name in &debug_info.variables_used {
395            if !context.vars.contains_key(var_name) {
396                missing.push(var_name.clone());
397            }
398        }
399        missing
400    }
401}
402
403/// Template analyzer for static analysis
404pub struct TemplateAnalyzer {
405    debugger: TemplateDebugger,
406}
407
408impl TemplateAnalyzer {
409    /// Create new template analyzer
410    pub fn new() -> Self {
411        Self {
412            debugger: TemplateDebugger::new(),
413        }
414    }
415
416    /// Analyze template file
417    ///
418    /// # Arguments
419    /// * `file_path` - Path to template file
420    pub fn analyze_file<P: AsRef<Path>>(&self, file_path: P) -> Result<DebugInfo> {
421        let content = std::fs::read_to_string(&file_path)
422            .map_err(|e| TemplateError::IoError(format!("Failed to read template file: {}", e)))?;
423
424        let file_name = file_path
425            .as_ref()
426            .file_stem()
427            .and_then(|s| s.to_str())
428            .unwrap_or("unknown");
429
430        self.debugger.analyze(&content, file_name)
431    }
432
433    /// Analyze all templates in directory
434    ///
435    /// # Arguments
436    /// * `dir_path` - Directory path to scan
437    pub fn analyze_directory<P: AsRef<Path>>(
438        &self,
439        dir_path: P,
440    ) -> Result<HashMap<String, DebugInfo>> {
441        use walkdir::WalkDir;
442
443        let mut results = HashMap::new();
444
445        for entry in WalkDir::new(dir_path).into_iter().filter_map(|e| e.ok()) {
446            if entry.file_type().is_file() {
447                let path = entry.path();
448                if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
449                    if matches!(ext, "toml" | "tera" | "tpl" | "template") {
450                        if let Ok(info) = self.analyze_file(path) {
451                            let name = info.template_name.clone();
452                            results.insert(name, info);
453                        }
454                    }
455                }
456            }
457        }
458
459        Ok(results)
460    }
461
462    /// Find unused variables in context
463    ///
464    /// # Arguments
465    /// * `template_info` - Template debug information
466    /// * `context` - Template context
467    pub fn find_unused_variables(
468        &self,
469        template_info: &DebugInfo,
470        context: &TemplateContext,
471    ) -> Vec<String> {
472        let used_vars: HashSet<String> = template_info.variables_used.iter().cloned().collect();
473        let context_vars: HashSet<String> = context.vars.keys().cloned().collect();
474
475        context_vars.difference(&used_vars).cloned().collect()
476    }
477
478    /// Find potentially missing variables
479    ///
480    /// # Arguments
481    /// * `template_info` - Template debug information
482    /// * `context` - Template context
483    pub fn find_missing_variables(
484        &self,
485        template_info: &DebugInfo,
486        context: &TemplateContext,
487    ) -> Vec<String> {
488        let used_vars: HashSet<String> = template_info.variables_used.iter().cloned().collect();
489        let context_vars: HashSet<String> = context.vars.keys().cloned().collect();
490
491        used_vars.difference(&context_vars).cloned().collect()
492    }
493}
494
495/// Template linting rules for code quality
496pub mod lint {
497    use super::*;
498
499    /// Lint rule trait for custom validation
500    pub trait LintRule {
501        /// Check template for lint violations
502        ///
503        /// # Arguments
504        /// * `info` - Template debug information
505        fn check(&self, info: &DebugInfo) -> Vec<String>;
506    }
507
508    /// Rule to detect unused variables
509    pub struct UnusedVariablesRule;
510
511    impl LintRule for UnusedVariablesRule {
512        fn check(&self, _info: &DebugInfo) -> Vec<String> {
513            // This would need context information to determine unused vars
514            // For now, return empty
515            Vec::new()
516        }
517    }
518
519    /// Rule to detect deprecated function usage
520    pub struct DeprecatedFunctionsRule;
521
522    impl LintRule for DeprecatedFunctionsRule {
523        fn check(&self, info: &DebugInfo) -> Vec<String> {
524            let deprecated = ["old_function", "deprecated_helper"];
525            let mut violations = Vec::new();
526
527            for func in &info.functions_used {
528                if deprecated.contains(&func.as_str()) {
529                    violations.push(format!("Deprecated function '{}' used", func));
530                }
531            }
532
533            violations
534        }
535    }
536
537    /// Rule to detect overly complex templates
538    pub struct ComplexityRule {
539        max_complexity: usize,
540    }
541
542    impl ComplexityRule {
543        pub fn new(max_complexity: usize) -> Self {
544            Self { max_complexity }
545        }
546    }
547
548    impl LintRule for ComplexityRule {
549        fn check(&self, info: &DebugInfo) -> Vec<String> {
550            let mut violations = Vec::new();
551
552            // Simple complexity metric: count of functions + variables + blocks
553            let complexity =
554                info.functions_used.len() + info.variables_used.len() + info.blocks_defined.len();
555
556            if complexity > self.max_complexity {
557                violations.push(format!(
558                    "Template complexity {} exceeds maximum {}",
559                    complexity, self.max_complexity
560                ));
561            }
562
563            violations
564        }
565    }
566
567    /// Rule to detect missing variable documentation
568    pub struct UndocumentedVariablesRule;
569
570    impl LintRule for UndocumentedVariablesRule {
571        fn check(&self, info: &DebugInfo) -> Vec<String> {
572            // Check if variables are documented in comments
573            let mut violations = Vec::new();
574
575            for var in &info.variables_used {
576                // Look for variable documentation in comments
577                let doc_pattern = format!("{{# {} #}}", var);
578                if !info.source.contains(&doc_pattern) {
579                    violations.push(format!("Variable '{}' is not documented", var));
580                }
581            }
582
583            violations
584        }
585    }
586}
587
588/// Template linter for comprehensive template validation
589pub struct TemplateLinter {
590    /// Lint rules to apply
591    rules: Vec<Box<dyn lint::LintRule>>,
592    /// Template debugger for analysis
593    debugger: TemplateDebugger,
594}
595
596impl Default for TemplateLinter {
597    fn default() -> Self {
598        Self {
599            rules: Vec::new(),
600            debugger: TemplateDebugger::new(),
601        }
602    }
603}
604
605impl TemplateLinter {
606    /// Create new template linter
607    pub fn new() -> Self {
608        Self::default()
609    }
610
611    /// Add lint rule
612    pub fn with_rule<R: lint::LintRule + 'static>(mut self, rule: R) -> Self {
613        self.rules.push(Box::new(rule));
614        self
615    }
616
617    /// Add common lint rules for production templates
618    pub fn with_production_rules(mut self) -> Self {
619        self.rules.push(Box::new(lint::DeprecatedFunctionsRule));
620        self.rules.push(Box::new(lint::ComplexityRule::new(50))); // Max 50 complexity points
621        self.rules.push(Box::new(lint::UndocumentedVariablesRule));
622        self
623    }
624
625    /// Lint template content
626    ///
627    /// # Arguments
628    /// * `template_content` - Template source content
629    /// * `template_name` - Template name for reporting
630    pub fn lint(&self, template_content: &str, template_name: &str) -> Result<Vec<String>> {
631        let mut violations = Vec::new();
632
633        // Analyze template
634        let info = self.debugger.analyze(template_content, template_name)?;
635
636        // Apply all lint rules
637        for rule in &self.rules {
638            violations.extend(rule.check(&info));
639        }
640
641        Ok(violations)
642    }
643
644    /// Lint template file
645    ///
646    /// # Arguments
647    /// * `file_path` - Path to template file
648    pub fn lint_file<P: AsRef<Path>>(&self, file_path: P) -> Result<Vec<String>> {
649        let content = std::fs::read_to_string(&file_path)
650            .map_err(|e| TemplateError::IoError(format!("Failed to read template file: {}", e)))?;
651
652        let file_name = file_path
653            .as_ref()
654            .file_stem()
655            .and_then(|s| s.to_str())
656            .unwrap_or("unknown");
657
658        self.lint(&content, file_name)
659    }
660
661    /// Lint all templates in directory
662    ///
663    /// # Arguments
664    /// * `dir_path` - Directory path to scan
665    pub fn lint_directory<P: AsRef<Path>>(
666        &self,
667        dir_path: P,
668    ) -> Result<HashMap<String, Vec<String>>> {
669        use walkdir::WalkDir;
670
671        let mut results = HashMap::new();
672
673        for entry in WalkDir::new(dir_path).into_iter().filter_map(|e| e.ok()) {
674            if entry.file_type().is_file() {
675                let path = entry.path();
676                if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
677                    if matches!(ext, "toml" | "tera" | "tpl" | "template") {
678                        match self.lint_file(path) {
679                            Ok(violations) => {
680                                let name = path
681                                    .file_stem()
682                                    .and_then(|s| s.to_str())
683                                    .unwrap_or("unknown")
684                                    .to_string();
685                                results.insert(name, violations);
686                            }
687                            Err(e) => {
688                                eprintln!("Warning: Failed to lint template {:?}: {}", path, e);
689                            }
690                        }
691                    }
692                }
693            }
694        }
695
696        Ok(results)
697    }
698}
699
700/// Template validation tools for development workflows
701pub struct DebugTemplateValidator {
702    /// Base validator
703    validator: crate::validation::TemplateValidator,
704    /// Linter for code quality
705    linter: TemplateLinter,
706    /// Debugger for analysis
707    debugger: TemplateDebugger,
708}
709
710impl Default for DebugTemplateValidator {
711    fn default() -> Self {
712        Self::new()
713    }
714}
715
716impl DebugTemplateValidator {
717    /// Create new template validator
718    pub fn new() -> Self {
719        Self {
720            validator: crate::validation::TemplateValidator::new(),
721            linter: TemplateLinter::new(),
722            debugger: TemplateDebugger::new(),
723        }
724    }
725
726    /// Configure base validator
727    pub fn with_validator<F>(mut self, f: F) -> Self
728    where
729        F: FnOnce(crate::validation::TemplateValidator) -> crate::validation::TemplateValidator,
730    {
731        self.validator = f(self.validator);
732        self
733    }
734
735    /// Configure linter
736    pub fn with_linter<F>(mut self, f: F) -> Self
737    where
738        F: FnOnce(TemplateLinter) -> TemplateLinter,
739    {
740        self.linter = f(self.linter);
741        self
742    }
743
744    /// Configure debugger
745    pub fn with_debugger<F>(mut self, f: F) -> Self
746    where
747        F: FnOnce(TemplateDebugger) -> TemplateDebugger,
748    {
749        self.debugger = f(self.debugger);
750        self
751    }
752
753    /// Validate template with comprehensive checking
754    ///
755    /// # Arguments
756    /// * `template` - Template content
757    /// * `context` - Template context
758    /// * `name` - Template name
759    pub fn validate_template(
760        &self,
761        template: &str,
762        context: &TemplateContext,
763        name: &str,
764    ) -> Result<ValidationReport> {
765        let mut report = ValidationReport {
766            template_name: name.to_string(),
767            syntax_valid: true,
768            syntax_errors: Vec::new(),
769            lint_violations: Vec::new(),
770            unused_variables: Vec::new(),
771            missing_variables: Vec::new(),
772            performance_metrics: None,
773        };
774
775        // Check syntax and structure
776        let debug_info = self.debugger.analyze(template, name)?;
777        report.syntax_errors = debug_info.syntax_errors.clone();
778
779        if !report.syntax_errors.is_empty() {
780            report.syntax_valid = false;
781        }
782
783        // Run linting
784        report.lint_violations = self.linter.lint(template, name)?;
785
786        // Check variable usage
787        report.unused_variables = self.debugger.find_unused_variables(&debug_info, context);
788        report.missing_variables = self.debugger.find_missing_variables(&debug_info, context);
789
790        // Performance profiling
791        if self.debugger.profile_performance {
792            let render_result = crate::render_with_context(template, context);
793            if let Ok(rendered) = render_result {
794                report.performance_metrics = Some(PerformanceMetrics {
795                    render_time_ms: 0, // Would need actual timing
796                    template_size: template.len(),
797                    output_size: rendered.len(),
798                });
799            }
800        }
801
802        Ok(report)
803    }
804
805    /// Validate template file
806    ///
807    /// # Arguments
808    /// * `file_path` - Path to template file
809    /// * `context` - Template context
810    pub fn validate_file<P: AsRef<Path>>(
811        &self,
812        file_path: P,
813        context: &TemplateContext,
814    ) -> Result<ValidationReport> {
815        let content = std::fs::read_to_string(&file_path)
816            .map_err(|e| TemplateError::IoError(format!("Failed to read template file: {}", e)))?;
817
818        let file_name = file_path
819            .as_ref()
820            .file_stem()
821            .and_then(|s| s.to_str())
822            .unwrap_or("unknown");
823
824        self.validate_template(&content, context, file_name)
825    }
826}
827
828/// Validation report for template analysis
829#[derive(Debug, Clone)]
830pub struct ValidationReport {
831    /// Template name
832    pub template_name: String,
833    /// Whether template syntax is valid
834    pub syntax_valid: bool,
835    /// Syntax errors found
836    pub syntax_errors: Vec<String>,
837    /// Lint violations found
838    pub lint_violations: Vec<String>,
839    /// Variables defined in context but not used
840    pub unused_variables: Vec<String>,
841    /// Variables used in template but not in context
842    pub missing_variables: Vec<String>,
843    /// Performance metrics (if profiling enabled)
844    pub performance_metrics: Option<PerformanceMetrics>,
845}
846
847/// Performance metrics for template rendering
848#[derive(Debug, Clone)]
849pub struct PerformanceMetrics {
850    /// Render time in milliseconds
851    pub render_time_ms: u64,
852    /// Template size in bytes
853    pub template_size: usize,
854    /// Output size in bytes
855    pub output_size: usize,
856}
857
858impl Default for TemplateAnalyzer {
859    fn default() -> Self {
860        Self::new()
861    }
862}
863
864#[cfg(test)]
865mod tests {
866    use super::*;
867
868    #[test]
869    fn test_template_analysis() {
870        let debugger = TemplateDebugger::new();
871        let template = r#"
872{{ service.name }}
873{{ fake_name() }}
874{% block content %}
875Hello {{ user }}
876{% endblock %}
877        "#;
878
879        let info = debugger.analyze(template, "test").unwrap();
880
881        assert!(info.variables_used.contains("service.name"));
882        assert!(info.variables_used.contains("user"));
883        assert!(info.functions_used.contains("fake_name"));
884        assert!(info.blocks_defined.contains("content"));
885    }
886
887    #[test]
888    fn test_syntax_validation() {
889        let debugger = TemplateDebugger::new();
890        let invalid_template = r#"{{ unclosed_variable"#;
891
892        let info = debugger.analyze(invalid_template, "test").unwrap();
893        assert!(!info.syntax_errors.is_empty());
894    }
895
896    #[test]
897    fn test_lint_rules() {
898        let debugger = TemplateDebugger::new();
899        let template = r#"
900{{ deprecated_function() }}
901{{ another_old_func() }}
902        "#;
903
904        let info = debugger.analyze(template, "test").unwrap();
905
906        let deprecated_rule = lint::DeprecatedFunctionsRule;
907        let violations = deprecated_rule.check(&info);
908
909        // Should detect deprecated functions
910        assert!(!violations.is_empty());
911    }
912
913    #[test]
914    fn test_template_linter() {
915        let linter = TemplateLinter::new().with_production_rules();
916
917        let template = r#"
918{{ deprecated_function() }}
919Very complex template with many variables and functions
920        "#;
921
922        let violations = linter.lint(template, "test").unwrap();
923        assert!(!violations.is_empty()); // Should detect deprecated function
924    }
925}