1use depyler_hir::hir::{HirClass, HirFunction, HirMethod, HirModule, Type};
8use std::fmt::Write;
9
10#[derive(Debug, Clone)]
12pub struct DocConfig {
13 pub include_python_source: bool,
15 pub generate_examples: bool,
17 pub include_migration_notes: bool,
19 pub generate_module_docs: bool,
21 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
37pub 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 pub fn generate_docs(&self, module: &HirModule) -> String {
58 let mut doc = String::new();
59
60 self.write_module_header(&mut doc, module);
62
63 if self.config.generate_module_docs {
65 self.write_module_docs(&mut doc, module);
66 }
67
68 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 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 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 doc.push_str("```rust\n");
136 doc.push_str(&self.format_function_signature(func));
137 doc.push_str("\n```\n\n");
138
139 if let Some(ref docstring) = func.docstring {
141 doc.push_str(docstring);
142 doc.push_str("\n\n");
143 }
144
145 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(¶m.ty))
150 .expect("write to String");
151 }
152 doc.push('\n');
153 }
154
155 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 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 if self.config.generate_examples {
179 self.write_function_example(doc, func);
180 }
181
182 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 if let Some(ref docstring) = class.docstring {
195 doc.push_str(docstring);
196 doc.push_str("\n\n");
197 }
198
199 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 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 doc.push_str("```rust\n");
230 doc.push_str(&self.format_method_signature(method));
231 doc.push_str("\n```\n");
232
233 if let Some(ref docstring) = method.docstring {
235 doc.push_str(docstring);
236 doc.push('\n');
237 }
238
239 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 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 let args: Vec<String> = func
292 .params
293 .iter()
294 .map(|param| self.example_value_for_type(¶m.name, ¶m.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(¶m.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(¶m.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), 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 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 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 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 doc.push_str(&self.generate_docs(module));
474
475 doc
476 }
477
478 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 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(¶m.name, ¶m.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![], };
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 #[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 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 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}