Skip to main content

depyler_tooling/
documentation.rs

1//! Documentation generation for transpiled Rust code
2//!
3//! This module provides tools to generate comprehensive documentation
4//! for transpiled Python code, including module docs, function signatures,
5//! and usage examples.
6
7use depyler_hir::hir::{HirClass, HirFunction, HirMethod, HirModule, Type};
8use std::fmt::Write;
9
10/// Documentation generator configuration
11#[derive(Debug, Clone)]
12pub struct DocConfig {
13    /// Include Python source as reference
14    pub include_python_source: bool,
15    /// Generate usage examples
16    pub generate_examples: bool,
17    /// Include type migration notes
18    pub include_migration_notes: bool,
19    /// Generate module-level documentation
20    pub generate_module_docs: bool,
21    /// Include performance notes
22    pub include_performance_notes: bool,
23}
24
25impl Default for DocConfig {
26    fn default() -> Self {
27        Self {
28            include_python_source: true,
29            generate_examples: true,
30            include_migration_notes: true,
31            generate_module_docs: true,
32            include_performance_notes: false,
33        }
34    }
35}
36
37/// Documentation generator
38pub struct DocGenerator {
39    config: DocConfig,
40    python_source: Option<String>,
41}
42
43impl DocGenerator {
44    pub fn new(config: DocConfig) -> Self {
45        Self {
46            config,
47            python_source: None,
48        }
49    }
50
51    pub fn with_python_source(mut self, source: String) -> Self {
52        self.python_source = Some(source);
53        self
54    }
55
56    /// Generate documentation for a HIR module
57    pub fn generate_docs(&self, module: &HirModule) -> String {
58        let mut doc = String::new();
59
60        // Module header
61        self.write_module_header(&mut doc, module);
62
63        // Module-level documentation
64        if self.config.generate_module_docs {
65            self.write_module_docs(&mut doc, module);
66        }
67
68        // Function documentation
69        if !module.functions.is_empty() {
70            doc.push_str("\n## Functions\n\n");
71            for func in &module.functions {
72                self.write_function_docs(&mut doc, func);
73            }
74        }
75
76        // Class documentation
77        if !module.classes.is_empty() {
78            doc.push_str("\n## Classes\n\n");
79            for class in &module.classes {
80                self.write_class_docs(&mut doc, class);
81            }
82        }
83
84        // Migration notes
85        if self.config.include_migration_notes && !module.functions.is_empty() {
86            doc.push_str("\n## Migration Notes\n\n");
87            self.write_migration_notes(&mut doc, module);
88        }
89
90        doc
91    }
92
93    fn write_module_header(&self, doc: &mut String, _module: &HirModule) {
94        doc.push_str("# Generated Rust Documentation\n\n");
95        doc.push_str("This documentation was automatically generated from Python source code ");
96        doc.push_str("by the Depyler transpiler.\n\n");
97
98        if self.config.include_python_source {
99            if let Some(python_source) = &self.python_source {
100                doc.push_str("<details>\n");
101                doc.push_str("<summary>Original Python Source</summary>\n\n");
102                doc.push_str("```python\n");
103                doc.push_str(python_source);
104                doc.push_str("\n```\n\n");
105                doc.push_str("</details>\n\n");
106            }
107        }
108    }
109
110    fn write_module_docs(&self, doc: &mut String, module: &HirModule) {
111        doc.push_str("## Module Overview\n\n");
112
113        let func_count = module.functions.len();
114        let class_count = module.classes.len();
115        let import_count = module.imports.len();
116
117        writeln!(doc, "- **Functions**: {}", func_count).expect("write to String");
118        writeln!(doc, "- **Classes**: {}", class_count).expect("write to String");
119        writeln!(doc, "- **Imports**: {}", import_count).expect("write to String");
120        doc.push('\n');
121
122        if !module.imports.is_empty() {
123            doc.push_str("### Dependencies\n\n");
124            for import in &module.imports {
125                writeln!(doc, "- `{}`", import.module).expect("write to String");
126            }
127            doc.push('\n');
128        }
129    }
130
131    fn write_function_docs(&self, doc: &mut String, func: &HirFunction) {
132        writeln!(doc, "### `{}`\n", func.name).expect("write to String");
133
134        // Function signature
135        doc.push_str("```rust\n");
136        doc.push_str(&self.format_function_signature(func));
137        doc.push_str("\n```\n\n");
138
139        // Docstring
140        if let Some(ref docstring) = func.docstring {
141            doc.push_str(docstring);
142            doc.push_str("\n\n");
143        }
144
145        // Parameters
146        if !func.params.is_empty() {
147            doc.push_str("**Parameters:**\n");
148            for param in &func.params {
149                writeln!(doc, "- `{}`: {}", param.name, self.format_type(&param.ty))
150                    .expect("write to String");
151            }
152            doc.push('\n');
153        }
154
155        // Return type
156        if !matches!(func.ret_type, Type::None) {
157            writeln!(doc, "**Returns:** {}\n", self.format_type(&func.ret_type))
158                .expect("write to String");
159        }
160
161        // Properties
162        doc.push_str("**Properties:**\n");
163        if func.properties.is_pure {
164            doc.push_str("- Pure function (no side effects)\n");
165        }
166        if func.properties.always_terminates {
167            doc.push_str("- Always terminates\n");
168        }
169        if func.properties.panic_free {
170            doc.push_str("- Panic-free\n");
171        }
172        if func.properties.is_async {
173            doc.push_str("- Async function\n");
174        }
175        doc.push('\n');
176
177        // Usage example
178        if self.config.generate_examples {
179            self.write_function_example(doc, func);
180        }
181
182        // Performance notes
183        if self.config.include_performance_notes {
184            self.write_performance_notes(doc, func);
185        }
186
187        doc.push_str("---\n\n");
188    }
189
190    fn write_class_docs(&self, doc: &mut String, class: &HirClass) {
191        writeln!(doc, "### `{}`\n", class.name).expect("write to String");
192
193        // Class docstring
194        if let Some(ref docstring) = class.docstring {
195            doc.push_str(docstring);
196            doc.push_str("\n\n");
197        }
198
199        // Fields
200        if !class.fields.is_empty() {
201            doc.push_str("**Fields:**\n");
202            for field in &class.fields {
203                writeln!(
204                    doc,
205                    "- `{}`: {}",
206                    field.name,
207                    self.format_type(&field.field_type)
208                )
209                .expect("write to String");
210            }
211            doc.push('\n');
212        }
213
214        // Methods
215        if !class.methods.is_empty() {
216            doc.push_str("**Methods:**\n\n");
217            for method in &class.methods {
218                self.write_method_docs(doc, method);
219            }
220        }
221
222        doc.push_str("---\n\n");
223    }
224
225    fn write_method_docs(&self, doc: &mut String, method: &HirMethod) {
226        writeln!(doc, "#### `{}`", method.name).expect("write to String");
227
228        // Method signature
229        doc.push_str("```rust\n");
230        doc.push_str(&self.format_method_signature(method));
231        doc.push_str("\n```\n");
232
233        // Docstring
234        if let Some(ref docstring) = method.docstring {
235            doc.push_str(docstring);
236            doc.push('\n');
237        }
238
239        // Method type
240        if method.is_static {
241            doc.push_str("- **Static method**\n");
242        } else if method.is_classmethod {
243            doc.push_str("- **Class method**\n");
244        } else if method.is_property {
245            doc.push_str("- **Property getter**\n");
246        }
247
248        doc.push('\n');
249    }
250
251    fn write_migration_notes(&self, doc: &mut String, module: &HirModule) {
252        doc.push_str("### Python to Rust Migration\n\n");
253
254        doc.push_str("When migrating from Python to the generated Rust code, note:\n\n");
255        doc.push_str("1. **Type Safety**: All types are now statically checked at compile time\n");
256        doc.push_str("2. **Memory Management**: Rust's ownership system ensures memory safety\n");
257        doc.push_str(
258            "3. **Error Handling**: Python exceptions are converted to Rust `Result` types\n",
259        );
260        doc.push_str("4. **Performance**: Expect significant performance improvements\n\n");
261
262        // Specific migration notes for functions
263        for func in &module.functions {
264            if func
265                .params
266                .iter()
267                .any(|param| matches!(param.ty, Type::List(_)))
268            {
269                writeln!(
270                    doc,
271                    "- `{}`: List parameters are passed as slices (`&[T]`) for efficiency",
272                    func.name
273                )
274                .expect("write to String");
275            }
276            if matches!(func.ret_type, Type::Optional(_)) {
277                writeln!(
278                    doc,
279                    "- `{}`: Returns `Option<T>` instead of potentially `None`",
280                    func.name
281                )
282                .expect("write to String");
283            }
284        }
285    }
286
287    fn write_function_example(&self, doc: &mut String, func: &HirFunction) {
288        doc.push_str("**Example:**\n\n```rust\n");
289
290        // Generate a simple example
291        let args: Vec<String> = func
292            .params
293            .iter()
294            .map(|param| self.example_value_for_type(&param.name, &param.ty))
295            .collect();
296
297        if matches!(func.ret_type, Type::None) {
298            writeln!(doc, "{}({});", func.name, args.join(", ")).expect("write to String");
299        } else {
300            writeln!(doc, "let result = {}({});", func.name, args.join(", "))
301                .expect("write to String");
302        }
303
304        doc.push_str("```\n\n");
305    }
306
307    fn write_performance_notes(&self, doc: &mut String, func: &HirFunction) {
308        doc.push_str("**Performance Notes:**\n");
309
310        if func.properties.max_stack_depth.is_some() {
311            doc.push_str("- May have deep recursion, consider iterative implementation\n");
312        }
313
314        if func
315            .params
316            .iter()
317            .any(|param| matches!(param.ty, Type::String))
318        {
319            doc.push_str("- String parameters use `&str` for zero-copy performance\n");
320        }
321
322        doc.push('\n');
323    }
324
325    fn format_function_signature(&self, func: &HirFunction) -> String {
326        let params: Vec<String> = func
327            .params
328            .iter()
329            .map(|param| format!("{}: {}", param.name, self.format_type(&param.ty)))
330            .collect();
331
332        if matches!(func.ret_type, Type::None) {
333            format!("fn {}({})", func.name, params.join(", "))
334        } else {
335            format!(
336                "fn {}({}) -> {}",
337                func.name,
338                params.join(", "),
339                self.format_type(&func.ret_type)
340            )
341        }
342    }
343
344    fn format_method_signature(&self, method: &HirMethod) -> String {
345        let self_param = if method.is_static { "" } else { "&self, " };
346
347        let params: Vec<String> = method
348            .params
349            .iter()
350            .map(|param| format!("{}: {}", param.name, self.format_type(&param.ty)))
351            .collect();
352
353        let all_params = if params.is_empty() {
354            self_param.trim_end_matches(", ").to_string()
355        } else {
356            format!("{}{}", self_param, params.join(", "))
357        };
358
359        if matches!(method.ret_type, Type::None) {
360            format!("fn {}({})", method.name, all_params)
361        } else {
362            format!(
363                "fn {}({}) -> {}",
364                method.name,
365                all_params,
366                self.format_type(&method.ret_type)
367            )
368        }
369    }
370
371    fn format_type(&self, ty: &Type) -> String {
372        format_type_inner(ty)
373    }
374}
375
376fn format_type_inner(ty: &Type) -> String {
377    match ty {
378        Type::Unknown => "?".to_string(),
379        Type::None => "()".to_string(),
380        Type::Bool => "bool".to_string(),
381        Type::Int => "i32".to_string(),
382        Type::Float => "f64".to_string(),
383        Type::String => "&str".to_string(),
384        Type::List(inner) => format!("&[{}]", format_type_inner(inner)),
385        Type::Dict(key, val) => format!(
386            "HashMap<{}, {}>",
387            format_type_inner(key),
388            format_type_inner(val)
389        ),
390        Type::Tuple(types) => {
391            let inner: Vec<String> = types.iter().map(format_type_inner).collect();
392            format!("({})", inner.join(", "))
393        }
394        Type::Set(inner) => format!("HashSet<{}>", format_type_inner(inner)),
395        Type::Optional(inner) => format!("Option<{}>", format_type_inner(inner)),
396        Type::Final(inner) => format_type_inner(inner), // Unwrap Final to get the actual type
397        Type::Custom(name) => name.clone(),
398        Type::Union(types) => {
399            let variants: Vec<String> = types.iter().map(format_type_inner).collect();
400            format!("Union<{}>", variants.join(", "))
401        }
402        Type::Generic { base, params } => {
403            if params.is_empty() {
404                base.clone()
405            } else {
406                let args_str: Vec<String> = params.iter().map(format_type_inner).collect();
407                format!("{}<{}>", base, args_str.join(", "))
408            }
409        }
410        Type::Function { params, ret } => {
411            let param_types: Vec<String> = params.iter().map(format_type_inner).collect();
412            format!(
413                "fn({}) -> {}",
414                param_types.join(", "),
415                format_type_inner(ret)
416            )
417        }
418        Type::TypeVar(name) => name.clone(),
419        Type::Array {
420            element_type,
421            size: _,
422        } => format!("&[{}]", format_type_inner(element_type)),
423        Type::UnificationVar(id) => {
424            // UnificationVar should never appear in documentation generation
425            panic!("BUG: UnificationVar({}) encountered during documentation. Type inference incomplete.", id)
426        }
427    }
428}
429
430impl DocGenerator {
431    fn example_value_for_type(&self, name: &str, ty: &Type) -> String {
432        match ty {
433            Type::Bool => "true".to_string(),
434            Type::Int => "42".to_string(),
435            Type::Float => "3.14".to_string(),
436            Type::String => "\"example\"".to_string(),
437            Type::List(_) => "&vec![1, 2, 3]".to_string(),
438            Type::Dict(_, _) => "&HashMap::new()".to_string(),
439            Type::Optional(_) => "Some(value)".to_string(),
440            _ => name.to_string(),
441        }
442    }
443
444    /// Generate API reference documentation
445    pub fn generate_api_reference(&self, module: &HirModule) -> String {
446        let mut doc = String::new();
447
448        doc.push_str("# API Reference\n\n");
449        doc.push_str("## Table of Contents\n\n");
450
451        // Generate TOC
452        if !module.functions.is_empty() {
453            doc.push_str("### Functions\n");
454            for func in &module.functions {
455                writeln!(doc, "- [`{}`](#{})", func.name, func.name.to_lowercase())
456                    .expect("write to String");
457            }
458            doc.push('\n');
459        }
460
461        if !module.classes.is_empty() {
462            doc.push_str("### Classes\n");
463            for class in &module.classes {
464                writeln!(doc, "- [`{}`](#{})", class.name, class.name.to_lowercase())
465                    .expect("write to String");
466            }
467            doc.push('\n');
468        }
469
470        doc.push_str("\n---\n\n");
471
472        // Generate detailed docs
473        doc.push_str(&self.generate_docs(module));
474
475        doc
476    }
477
478    /// Generate usage guide with examples
479    pub fn generate_usage_guide(&self, module: &HirModule) -> String {
480        let mut doc = String::new();
481
482        doc.push_str("# Usage Guide\n\n");
483        doc.push_str("This guide provides examples of how to use the generated Rust code.\n\n");
484
485        doc.push_str("## Quick Start\n\n");
486        doc.push_str("```rust\n");
487        doc.push_str("// Import the generated module\n");
488        doc.push_str("use generated_module::*;\n\n");
489
490        // Show example usage of main functions
491        for func in module.functions.iter().take(3) {
492            let args: Vec<String> = func
493                .params
494                .iter()
495                .map(|param| self.example_value_for_type(&param.name, &param.ty))
496                .collect();
497
498            writeln!(doc, "// Using {}", func.name).expect("write to String");
499            writeln!(doc, "let result = {}({});", func.name, args.join(", "))
500                .expect("write to String");
501            doc.push('\n');
502        }
503
504        doc.push_str("```\n\n");
505
506        doc
507    }
508}
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513    use depyler_hir::hir::ConstGeneric;
514    use depyler_hir::hir::*;
515    use smallvec::smallvec;
516
517    fn create_test_function(name: &str) -> HirFunction {
518        HirFunction {
519            name: name.to_string(),
520            params: smallvec![
521                HirParam::new("x".to_string(), Type::Int),
522                HirParam::new("y".to_string(), Type::Int),
523            ],
524            ret_type: Type::Int,
525            body: vec![],
526            properties: FunctionProperties::default(),
527            annotations: Default::default(),
528            docstring: Some("Test function that adds two numbers.".to_string()),
529        }
530    }
531
532    fn create_test_module() -> HirModule {
533        HirModule {
534            functions: vec![
535                create_test_function("add"),
536                create_test_function("multiply"),
537            ],
538            imports: vec![],
539            type_aliases: vec![],
540            protocols: vec![],
541            classes: vec![],
542            constants: vec![],
543            top_level_stmts: vec![],
544        }
545    }
546
547    #[test]
548    fn test_basic_doc_generation() {
549        let config = DocConfig::default();
550        let generator = DocGenerator::new(config);
551        let module = create_test_module();
552
553        let docs = generator.generate_docs(&module);
554
555        assert!(docs.contains("# Generated Rust Documentation"));
556        assert!(docs.contains("## Functions"));
557        assert!(docs.contains("### `add`"));
558        assert!(docs.contains("### `multiply`"));
559        assert!(docs.contains("Test function that adds two numbers."));
560    }
561
562    #[test]
563    fn test_with_python_source() {
564        let config = DocConfig::default();
565        let python_source = "def add(x: int, y: int) -> int:\n    return x + y";
566        let generator = DocGenerator::new(config).with_python_source(python_source.to_string());
567        let module = create_test_module();
568
569        let docs = generator.generate_docs(&module);
570
571        assert!(docs.contains("Original Python Source"));
572        assert!(docs.contains(python_source));
573    }
574
575    #[test]
576    fn test_function_signature_formatting() {
577        let generator = DocGenerator::new(DocConfig::default());
578        let func = create_test_function("test");
579
580        let sig = generator.format_function_signature(&func);
581        assert_eq!(sig, "fn test(x: i32, y: i32) -> i32");
582    }
583
584    #[test]
585    fn test_type_formatting() {
586        let generator = DocGenerator::new(DocConfig::default());
587
588        assert_eq!(generator.format_type(&Type::Int), "i32");
589        assert_eq!(generator.format_type(&Type::String), "&str");
590        assert_eq!(
591            generator.format_type(&Type::List(Box::new(Type::Int))),
592            "&[i32]"
593        );
594        assert_eq!(
595            generator.format_type(&Type::Optional(Box::new(Type::String))),
596            "Option<&str>"
597        );
598    }
599
600    #[test]
601    fn test_api_reference_generation() {
602        let config = DocConfig::default();
603        let generator = DocGenerator::new(config);
604        let module = create_test_module();
605
606        let api_ref = generator.generate_api_reference(&module);
607
608        assert!(api_ref.contains("# API Reference"));
609        assert!(api_ref.contains("## Table of Contents"));
610        assert!(api_ref.contains("### Functions"));
611        assert!(api_ref.contains("- [`add`](#add)"));
612        assert!(api_ref.contains("- [`multiply`](#multiply)"));
613    }
614
615    #[test]
616    fn test_usage_guide_generation() {
617        let config = DocConfig::default();
618        let generator = DocGenerator::new(config);
619        let module = create_test_module();
620
621        let guide = generator.generate_usage_guide(&module);
622
623        assert!(guide.contains("# Usage Guide"));
624        assert!(guide.contains("## Quick Start"));
625        assert!(guide.contains("// Using add"));
626        assert!(guide.contains("let result = add(42, 42);"));
627    }
628
629    #[test]
630    fn test_class_documentation() {
631        let config = DocConfig::default();
632        let generator = DocGenerator::new(config);
633
634        let class = HirClass {
635            name: "TestClass".to_string(),
636            fields: vec![HirField {
637                name: "value".to_string(),
638                field_type: Type::Int,
639                default_value: None,
640                is_class_var: false,
641            }],
642            methods: vec![HirMethod {
643                name: "get_value".to_string(),
644                params: smallvec![],
645                ret_type: Type::Int,
646                body: vec![],
647                is_static: false,
648                is_classmethod: false,
649                is_property: false,
650                is_async: false,
651                docstring: Some("Get the value.".to_string()),
652            }],
653            base_classes: vec![],
654            is_dataclass: false,
655            docstring: Some("A test class.".to_string()),
656            type_params: vec![], // DEPYLER-0739
657        };
658
659        let module = HirModule {
660            functions: vec![],
661            imports: vec![],
662            type_aliases: vec![],
663            protocols: vec![],
664            classes: vec![class],
665            constants: vec![],
666            top_level_stmts: vec![],
667        };
668
669        let docs = generator.generate_docs(&module);
670
671        assert!(docs.contains("## Classes"));
672        assert!(docs.contains("### `TestClass`"));
673        assert!(docs.contains("A test class."));
674        assert!(docs.contains("**Fields:**"));
675        assert!(docs.contains("- `value`: i32"));
676        assert!(docs.contains("**Methods:**"));
677        assert!(docs.contains("#### `get_value`"));
678        assert!(docs.contains("Get the value."));
679    }
680
681    #[test]
682    fn test_migration_notes() {
683        let config = DocConfig {
684            include_migration_notes: true,
685            ..Default::default()
686        };
687        let generator = DocGenerator::new(config);
688
689        let func = HirFunction {
690            name: "process_list".to_string(),
691            params: smallvec![HirParam::new(
692                "items".to_string(),
693                Type::List(Box::new(Type::Int))
694            ),],
695            ret_type: Type::Optional(Box::new(Type::Int)),
696            body: vec![],
697            properties: FunctionProperties::default(),
698            annotations: Default::default(),
699            docstring: None,
700        };
701
702        let module = HirModule {
703            functions: vec![func],
704            imports: vec![],
705            type_aliases: vec![],
706            protocols: vec![],
707            classes: vec![],
708            constants: vec![],
709            top_level_stmts: vec![],
710        };
711
712        let docs = generator.generate_docs(&module);
713
714        assert!(docs.contains("## Migration Notes"));
715        assert!(docs.contains("Python to Rust Migration"));
716        assert!(docs.contains("process_list`: List parameters are passed as slices"));
717        assert!(docs.contains("process_list`: Returns `Option<T>` instead of potentially `None`"));
718    }
719
720    // ============================================================
721    // DEPYLER-COVERAGE-95: Additional comprehensive tests
722    // ============================================================
723
724    #[test]
725    fn test_doc_config_default() {
726        let config = DocConfig::default();
727        assert!(config.include_python_source);
728        assert!(config.generate_examples);
729        assert!(config.include_migration_notes);
730        assert!(config.generate_module_docs);
731        assert!(!config.include_performance_notes);
732    }
733
734    #[test]
735    fn test_doc_config_clone() {
736        let config = DocConfig::default();
737        let cloned = config.clone();
738        assert_eq!(config.include_python_source, cloned.include_python_source);
739    }
740
741    #[test]
742    fn test_doc_generator_new() {
743        let config = DocConfig::default();
744        let generator = DocGenerator::new(config);
745        // Generator created successfully
746        assert!(generator.python_source.is_none());
747    }
748
749    #[test]
750    fn test_format_type_unknown() {
751        let result = format_type_inner(&Type::Unknown);
752        assert_eq!(result, "?");
753    }
754
755    #[test]
756    fn test_format_type_none() {
757        let result = format_type_inner(&Type::None);
758        assert_eq!(result, "()");
759    }
760
761    #[test]
762    fn test_format_type_bool() {
763        let result = format_type_inner(&Type::Bool);
764        assert_eq!(result, "bool");
765    }
766
767    #[test]
768    fn test_format_type_float() {
769        let result = format_type_inner(&Type::Float);
770        assert_eq!(result, "f64");
771    }
772
773    #[test]
774    fn test_format_type_dict() {
775        let result = format_type_inner(&Type::Dict(Box::new(Type::String), Box::new(Type::Int)));
776        assert_eq!(result, "HashMap<&str, i32>");
777    }
778
779    #[test]
780    fn test_format_type_tuple() {
781        let result = format_type_inner(&Type::Tuple(vec![Type::Int, Type::String]));
782        assert_eq!(result, "(i32, &str)");
783    }
784
785    #[test]
786    fn test_format_type_set() {
787        let result = format_type_inner(&Type::Set(Box::new(Type::Int)));
788        assert_eq!(result, "HashSet<i32>");
789    }
790
791    #[test]
792    fn test_format_type_final() {
793        let result = format_type_inner(&Type::Final(Box::new(Type::Int)));
794        assert_eq!(result, "i32");
795    }
796
797    #[test]
798    fn test_format_type_custom() {
799        let result = format_type_inner(&Type::Custom("MyType".to_string()));
800        assert_eq!(result, "MyType");
801    }
802
803    #[test]
804    fn test_format_type_union() {
805        let result = format_type_inner(&Type::Union(vec![Type::Int, Type::String]));
806        assert_eq!(result, "Union<i32, &str>");
807    }
808
809    #[test]
810    fn test_format_type_generic_no_params() {
811        let result = format_type_inner(&Type::Generic {
812            base: "Vec".to_string(),
813            params: vec![],
814        });
815        assert_eq!(result, "Vec");
816    }
817
818    #[test]
819    fn test_format_type_generic_with_params() {
820        let result = format_type_inner(&Type::Generic {
821            base: "Vec".to_string(),
822            params: vec![Type::Int],
823        });
824        assert_eq!(result, "Vec<i32>");
825    }
826
827    #[test]
828    fn test_format_type_function() {
829        let result = format_type_inner(&Type::Function {
830            params: vec![Type::Int, Type::String],
831            ret: Box::new(Type::Bool),
832        });
833        assert_eq!(result, "fn(i32, &str) -> bool");
834    }
835
836    #[test]
837    fn test_format_type_typevar() {
838        let result = format_type_inner(&Type::TypeVar("T".to_string()));
839        assert_eq!(result, "T");
840    }
841
842    #[test]
843    fn test_format_type_array() {
844        let result = format_type_inner(&Type::Array {
845            element_type: Box::new(Type::Int),
846            size: ConstGeneric::Literal(10),
847        });
848        assert_eq!(result, "&[i32]");
849    }
850
851    #[test]
852    #[should_panic(expected = "BUG: UnificationVar")]
853    fn test_format_type_unification_var_panics() {
854        let _ = format_type_inner(&Type::UnificationVar(42));
855    }
856
857    #[test]
858    fn test_example_value_for_bool() {
859        let generator = DocGenerator::new(DocConfig::default());
860        let result = generator.example_value_for_type("flag", &Type::Bool);
861        assert_eq!(result, "true");
862    }
863
864    #[test]
865    fn test_example_value_for_int() {
866        let generator = DocGenerator::new(DocConfig::default());
867        let result = generator.example_value_for_type("num", &Type::Int);
868        assert_eq!(result, "42");
869    }
870
871    #[test]
872    fn test_example_value_for_float() {
873        let generator = DocGenerator::new(DocConfig::default());
874        let result = generator.example_value_for_type("val", &Type::Float);
875        assert_eq!(result, "3.14");
876    }
877
878    #[test]
879    fn test_example_value_for_string() {
880        let generator = DocGenerator::new(DocConfig::default());
881        let result = generator.example_value_for_type("s", &Type::String);
882        assert_eq!(result, "\"example\"");
883    }
884
885    #[test]
886    fn test_example_value_for_list() {
887        let generator = DocGenerator::new(DocConfig::default());
888        let result = generator.example_value_for_type("items", &Type::List(Box::new(Type::Int)));
889        assert_eq!(result, "&vec![1, 2, 3]");
890    }
891
892    #[test]
893    fn test_example_value_for_dict() {
894        let generator = DocGenerator::new(DocConfig::default());
895        let result = generator.example_value_for_type(
896            "map",
897            &Type::Dict(Box::new(Type::String), Box::new(Type::Int)),
898        );
899        assert_eq!(result, "&HashMap::new()");
900    }
901
902    #[test]
903    fn test_example_value_for_optional() {
904        let generator = DocGenerator::new(DocConfig::default());
905        let result = generator.example_value_for_type("opt", &Type::Optional(Box::new(Type::Int)));
906        assert_eq!(result, "Some(value)");
907    }
908
909    #[test]
910    fn test_example_value_for_unknown_type() {
911        let generator = DocGenerator::new(DocConfig::default());
912        let result = generator.example_value_for_type("custom_var", &Type::Custom("Foo".into()));
913        assert_eq!(result, "custom_var");
914    }
915
916    #[test]
917    fn test_function_signature_no_return() {
918        let generator = DocGenerator::new(DocConfig::default());
919        let func = HirFunction {
920            name: "greet".to_string(),
921            params: smallvec![HirParam::new("name".to_string(), Type::String)],
922            ret_type: Type::None,
923            body: vec![],
924            properties: FunctionProperties::default(),
925            annotations: Default::default(),
926            docstring: None,
927        };
928        let sig = generator.format_function_signature(&func);
929        assert_eq!(sig, "fn greet(name: &str)");
930    }
931
932    #[test]
933    fn test_method_signature_static() {
934        let generator = DocGenerator::new(DocConfig::default());
935        let method = HirMethod {
936            name: "create".to_string(),
937            params: smallvec![HirParam::new("value".to_string(), Type::Int)],
938            ret_type: Type::Custom("Self".to_string()),
939            body: vec![],
940            is_static: true,
941            is_classmethod: false,
942            is_property: false,
943            is_async: false,
944            docstring: None,
945        };
946        let sig = generator.format_method_signature(&method);
947        assert_eq!(sig, "fn create(value: i32) -> Self");
948    }
949
950    #[test]
951    fn test_method_signature_instance() {
952        let generator = DocGenerator::new(DocConfig::default());
953        let method = HirMethod {
954            name: "get_value".to_string(),
955            params: smallvec![],
956            ret_type: Type::Int,
957            body: vec![],
958            is_static: false,
959            is_classmethod: false,
960            is_property: false,
961            is_async: false,
962            docstring: None,
963        };
964        let sig = generator.format_method_signature(&method);
965        assert_eq!(sig, "fn get_value(&self) -> i32");
966    }
967
968    #[test]
969    fn test_method_signature_instance_with_params() {
970        let generator = DocGenerator::new(DocConfig::default());
971        let method = HirMethod {
972            name: "set_value".to_string(),
973            params: smallvec![HirParam::new("value".to_string(), Type::Int)],
974            ret_type: Type::None,
975            body: vec![],
976            is_static: false,
977            is_classmethod: false,
978            is_property: false,
979            is_async: false,
980            docstring: None,
981        };
982        let sig = generator.format_method_signature(&method);
983        assert_eq!(sig, "fn set_value(&self, value: i32)");
984    }
985
986    #[test]
987    fn test_module_docs_with_imports() {
988        let generator = DocGenerator::new(DocConfig::default());
989        let module = HirModule {
990            functions: vec![],
991            imports: vec![
992                Import {
993                    module: "std".to_string(),
994                    alias: None,
995                    items: vec![],
996                },
997                Import {
998                    module: "json".to_string(),
999                    alias: None,
1000                    items: vec![],
1001                },
1002            ],
1003            type_aliases: vec![],
1004            protocols: vec![],
1005            classes: vec![],
1006            constants: vec![],
1007            top_level_stmts: vec![],
1008        };
1009
1010        let docs = generator.generate_docs(&module);
1011        assert!(docs.contains("### Dependencies"));
1012        assert!(docs.contains("- `std`"));
1013        assert!(docs.contains("- `json`"));
1014    }
1015
1016    #[test]
1017    fn test_function_with_all_properties() {
1018        let config = DocConfig {
1019            generate_examples: true,
1020            include_performance_notes: true,
1021            ..Default::default()
1022        };
1023        let generator = DocGenerator::new(config);
1024
1025        let props = FunctionProperties {
1026            is_pure: true,
1027            always_terminates: true,
1028            panic_free: true,
1029            is_async: true,
1030            max_stack_depth: Some(10),
1031            ..Default::default()
1032        };
1033
1034        let func = HirFunction {
1035            name: "pure_func".to_string(),
1036            params: smallvec![HirParam::new("s".to_string(), Type::String)],
1037            ret_type: Type::Int,
1038            body: vec![],
1039            properties: props,
1040            annotations: Default::default(),
1041            docstring: None,
1042        };
1043
1044        let module = HirModule {
1045            functions: vec![func],
1046            imports: vec![],
1047            type_aliases: vec![],
1048            protocols: vec![],
1049            classes: vec![],
1050            constants: vec![],
1051            top_level_stmts: vec![],
1052        };
1053
1054        let docs = generator.generate_docs(&module);
1055        assert!(docs.contains("Pure function (no side effects)"));
1056        assert!(docs.contains("Always terminates"));
1057        assert!(docs.contains("Panic-free"));
1058        assert!(docs.contains("Async function"));
1059        assert!(docs.contains("Performance Notes"));
1060        assert!(docs.contains("deep recursion"));
1061        assert!(docs.contains("String parameters use"));
1062    }
1063
1064    #[test]
1065    fn test_static_method_documentation() {
1066        let generator = DocGenerator::new(DocConfig::default());
1067
1068        let class = HirClass {
1069            name: "Factory".to_string(),
1070            fields: vec![],
1071            methods: vec![HirMethod {
1072                name: "create".to_string(),
1073                params: smallvec![],
1074                ret_type: Type::Custom("Self".to_string()),
1075                body: vec![],
1076                is_static: true,
1077                is_classmethod: false,
1078                is_property: false,
1079                is_async: false,
1080                docstring: None,
1081            }],
1082            base_classes: vec![],
1083            is_dataclass: false,
1084            docstring: None,
1085            type_params: vec![],
1086        };
1087
1088        let module = HirModule {
1089            functions: vec![],
1090            imports: vec![],
1091            type_aliases: vec![],
1092            protocols: vec![],
1093            classes: vec![class],
1094            constants: vec![],
1095            top_level_stmts: vec![],
1096        };
1097
1098        let docs = generator.generate_docs(&module);
1099        assert!(docs.contains("**Static method**"));
1100    }
1101
1102    #[test]
1103    fn test_classmethod_documentation() {
1104        let generator = DocGenerator::new(DocConfig::default());
1105
1106        let class = HirClass {
1107            name: "Builder".to_string(),
1108            fields: vec![],
1109            methods: vec![HirMethod {
1110                name: "from_config".to_string(),
1111                params: smallvec![],
1112                ret_type: Type::Custom("Self".to_string()),
1113                body: vec![],
1114                is_static: false,
1115                is_classmethod: true,
1116                is_property: false,
1117                is_async: false,
1118                docstring: None,
1119            }],
1120            base_classes: vec![],
1121            is_dataclass: false,
1122            docstring: None,
1123            type_params: vec![],
1124        };
1125
1126        let module = HirModule {
1127            functions: vec![],
1128            imports: vec![],
1129            type_aliases: vec![],
1130            protocols: vec![],
1131            classes: vec![class],
1132            constants: vec![],
1133            top_level_stmts: vec![],
1134        };
1135
1136        let docs = generator.generate_docs(&module);
1137        assert!(docs.contains("**Class method**"));
1138    }
1139
1140    #[test]
1141    fn test_property_documentation() {
1142        let generator = DocGenerator::new(DocConfig::default());
1143
1144        let class = HirClass {
1145            name: "Container".to_string(),
1146            fields: vec![],
1147            methods: vec![HirMethod {
1148                name: "size".to_string(),
1149                params: smallvec![],
1150                ret_type: Type::Int,
1151                body: vec![],
1152                is_static: false,
1153                is_classmethod: false,
1154                is_property: true,
1155                is_async: false,
1156                docstring: None,
1157            }],
1158            base_classes: vec![],
1159            is_dataclass: false,
1160            docstring: None,
1161            type_params: vec![],
1162        };
1163
1164        let module = HirModule {
1165            functions: vec![],
1166            imports: vec![],
1167            type_aliases: vec![],
1168            protocols: vec![],
1169            classes: vec![class],
1170            constants: vec![],
1171            top_level_stmts: vec![],
1172        };
1173
1174        let docs = generator.generate_docs(&module);
1175        assert!(docs.contains("**Property getter**"));
1176    }
1177
1178    #[test]
1179    fn test_no_python_source_when_disabled() {
1180        let config = DocConfig {
1181            include_python_source: false,
1182            ..Default::default()
1183        };
1184        let generator = DocGenerator::new(config).with_python_source("def foo(): pass".to_string());
1185        let module = create_test_module();
1186
1187        let docs = generator.generate_docs(&module);
1188        assert!(!docs.contains("Original Python Source"));
1189    }
1190
1191    #[test]
1192    fn test_no_migration_notes_when_disabled() {
1193        let config = DocConfig {
1194            include_migration_notes: false,
1195            ..Default::default()
1196        };
1197        let generator = DocGenerator::new(config);
1198        let module = create_test_module();
1199
1200        let docs = generator.generate_docs(&module);
1201        assert!(!docs.contains("## Migration Notes"));
1202    }
1203
1204    #[test]
1205    fn test_no_examples_when_disabled() {
1206        let config = DocConfig {
1207            generate_examples: false,
1208            ..Default::default()
1209        };
1210        let generator = DocGenerator::new(config);
1211        let module = create_test_module();
1212
1213        let docs = generator.generate_docs(&module);
1214        assert!(!docs.contains("**Example:**"));
1215    }
1216
1217    #[test]
1218    fn test_no_module_docs_when_disabled() {
1219        let config = DocConfig {
1220            generate_module_docs: false,
1221            ..Default::default()
1222        };
1223        let generator = DocGenerator::new(config);
1224        let module = create_test_module();
1225
1226        let docs = generator.generate_docs(&module);
1227        assert!(!docs.contains("## Module Overview"));
1228    }
1229
1230    #[test]
1231    fn test_empty_module() {
1232        let generator = DocGenerator::new(DocConfig::default());
1233        let module = HirModule {
1234            functions: vec![],
1235            imports: vec![],
1236            type_aliases: vec![],
1237            protocols: vec![],
1238            classes: vec![],
1239            constants: vec![],
1240            top_level_stmts: vec![],
1241        };
1242
1243        let docs = generator.generate_docs(&module);
1244        assert!(docs.contains("# Generated Rust Documentation"));
1245        assert!(!docs.contains("## Functions"));
1246        assert!(!docs.contains("## Classes"));
1247        assert!(!docs.contains("## Migration Notes"));
1248    }
1249
1250    #[test]
1251    fn test_api_reference_empty_module() {
1252        let generator = DocGenerator::new(DocConfig::default());
1253        let module = HirModule {
1254            functions: vec![],
1255            imports: vec![],
1256            type_aliases: vec![],
1257            protocols: vec![],
1258            classes: vec![],
1259            constants: vec![],
1260            top_level_stmts: vec![],
1261        };
1262
1263        let api_ref = generator.generate_api_reference(&module);
1264        assert!(api_ref.contains("# API Reference"));
1265        assert!(api_ref.contains("## Table of Contents"));
1266    }
1267
1268    #[test]
1269    fn test_api_reference_with_classes() {
1270        let generator = DocGenerator::new(DocConfig::default());
1271
1272        let class = HirClass {
1273            name: "Point".to_string(),
1274            fields: vec![],
1275            methods: vec![],
1276            base_classes: vec![],
1277            is_dataclass: false,
1278            docstring: None,
1279            type_params: vec![],
1280        };
1281
1282        let module = HirModule {
1283            functions: vec![],
1284            imports: vec![],
1285            type_aliases: vec![],
1286            protocols: vec![],
1287            classes: vec![class],
1288            constants: vec![],
1289            top_level_stmts: vec![],
1290        };
1291
1292        let api_ref = generator.generate_api_reference(&module);
1293        assert!(api_ref.contains("### Classes"));
1294        assert!(api_ref.contains("- [`Point`](#point)"));
1295    }
1296
1297    #[test]
1298    fn test_usage_guide_with_many_functions() {
1299        let generator = DocGenerator::new(DocConfig::default());
1300
1301        let module = HirModule {
1302            functions: vec![
1303                create_test_function("func1"),
1304                create_test_function("func2"),
1305                create_test_function("func3"),
1306                create_test_function("func4"),
1307                create_test_function("func5"),
1308            ],
1309            imports: vec![],
1310            type_aliases: vec![],
1311            protocols: vec![],
1312            classes: vec![],
1313            constants: vec![],
1314            top_level_stmts: vec![],
1315        };
1316
1317        let guide = generator.generate_usage_guide(&module);
1318        // Only first 3 functions shown
1319        assert!(guide.contains("// Using func1"));
1320        assert!(guide.contains("// Using func2"));
1321        assert!(guide.contains("// Using func3"));
1322        assert!(!guide.contains("// Using func4"));
1323    }
1324
1325    #[test]
1326    fn test_class_without_docstring() {
1327        let generator = DocGenerator::new(DocConfig::default());
1328
1329        let class = HirClass {
1330            name: "Bare".to_string(),
1331            fields: vec![],
1332            methods: vec![],
1333            base_classes: vec![],
1334            is_dataclass: false,
1335            docstring: None,
1336            type_params: vec![],
1337        };
1338
1339        let module = HirModule {
1340            functions: vec![],
1341            imports: vec![],
1342            type_aliases: vec![],
1343            protocols: vec![],
1344            classes: vec![class],
1345            constants: vec![],
1346            top_level_stmts: vec![],
1347        };
1348
1349        let docs = generator.generate_docs(&module);
1350        assert!(docs.contains("### `Bare`"));
1351    }
1352
1353    #[test]
1354    fn test_function_without_params() {
1355        let generator = DocGenerator::new(DocConfig::default());
1356
1357        let func = HirFunction {
1358            name: "no_args".to_string(),
1359            params: smallvec![],
1360            ret_type: Type::Int,
1361            body: vec![],
1362            properties: FunctionProperties::default(),
1363            annotations: Default::default(),
1364            docstring: None,
1365        };
1366
1367        let sig = generator.format_function_signature(&func);
1368        assert_eq!(sig, "fn no_args() -> i32");
1369
1370        let module = HirModule {
1371            functions: vec![func],
1372            imports: vec![],
1373            type_aliases: vec![],
1374            protocols: vec![],
1375            classes: vec![],
1376            constants: vec![],
1377            top_level_stmts: vec![],
1378        };
1379
1380        let docs = generator.generate_docs(&module);
1381        assert!(!docs.contains("**Parameters:**"));
1382    }
1383
1384    #[test]
1385    fn test_format_type_nested_generic() {
1386        let result = format_type_inner(&Type::Generic {
1387            base: "Result".to_string(),
1388            params: vec![Type::Int, Type::String],
1389        });
1390        assert_eq!(result, "Result<i32, &str>");
1391    }
1392
1393    #[test]
1394    fn test_format_type_nested_optional_list() {
1395        let result = format_type_inner(&Type::Optional(Box::new(Type::List(Box::new(Type::Int)))));
1396        assert_eq!(result, "Option<&[i32]>");
1397    }
1398}