Skip to main content

ryo_source/generator/
single.rs

1//! Single file generator.
2//!
3//! Generates all code into a single lib.rs or main.rs file using nested `mod {}` blocks.
4
5use super::{GeneratedSource, ModuleTree, SourceGenerator};
6use crate::pure::{PureFile, PureItem, PureMod};
7
8/// Generator that outputs all code into a single file.
9///
10/// This is the simplest generator strategy: all modules become nested `mod {}` blocks
11/// in a single lib.rs (or main.rs for binaries).
12///
13/// # Example
14///
15/// ```ignore
16/// use ryo_source::generator::{ModuleTree, SingleFileGenerator};
17///
18/// let tree = ModuleTree::crate_root()
19///     .with_item(PureItem::Struct(config_struct))
20///     .with_child(
21///         ModuleTree::new("utils")
22///             .with_vis(PureVis::Public)
23///             .with_item(PureItem::Fn(helper_fn))
24///     );
25///
26/// let generator = SingleFileGenerator::new();
27/// let result = generator.generate(&tree).unwrap();
28///
29/// // result.source contains:
30/// // struct Config { ... }
31/// // pub mod utils {
32/// //     fn helper() { ... }
33/// // }
34/// ```
35#[derive(Debug, Clone, Default)]
36pub struct SingleFileGenerator {
37    /// Whether to sort items within each module.
38    pub sort_items: bool,
39}
40
41impl SingleFileGenerator {
42    /// Create a new single file generator with default settings.
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Enable or disable item sorting within modules.
48    ///
49    /// When enabled, items are sorted by kind (uses, then types, then functions)
50    /// and then alphabetically by name.
51    pub fn with_sort_items(mut self, sort: bool) -> Self {
52        self.sort_items = sort;
53        self
54    }
55
56    /// Convert a ModuleTree into a PureFile.
57    fn tree_to_pure_file(&self, tree: &ModuleTree) -> PureFile {
58        let mut items = Vec::new();
59
60        // Add uses first (they should be at the top)
61        for u in &tree.uses {
62            items.push(PureItem::Use(u.clone()));
63        }
64
65        // Add items
66        items.extend(tree.items.iter().cloned());
67
68        // Add child modules as inline `mod {}` blocks
69        for child in &tree.children {
70            items.push(self.tree_to_mod_item(child));
71        }
72
73        if self.sort_items {
74            self.sort_pure_items(&mut items);
75        }
76
77        PureFile {
78            attrs: tree.inner_attrs.clone(),
79            items,
80        }
81    }
82
83    /// Convert a ModuleTree into a PureItem::Mod.
84    fn tree_to_mod_item(&self, tree: &ModuleTree) -> PureItem {
85        let mut content_items = Vec::new();
86
87        // Add uses first
88        for u in &tree.uses {
89            content_items.push(PureItem::Use(u.clone()));
90        }
91
92        // Add items
93        content_items.extend(tree.items.iter().cloned());
94
95        // Add nested child modules
96        for child in &tree.children {
97            content_items.push(self.tree_to_mod_item(child));
98        }
99
100        if self.sort_items {
101            self.sort_pure_items(&mut content_items);
102        }
103
104        // For inline `mod foo { ... }`, PureMod.attrs holds both outer attrs
105        // (`#[cfg(test)] mod tests { ... }`) and inner attrs (`#![allow(...)]`).
106        // Each PureAttribute carries its own `is_inner` flag so concatenation
107        // preserves the distinction.
108        let mut mod_attrs = Vec::with_capacity(tree.outer_attrs.len() + tree.inner_attrs.len());
109        mod_attrs.extend(tree.outer_attrs.iter().cloned());
110        mod_attrs.extend(tree.inner_attrs.iter().cloned());
111
112        PureItem::Mod(PureMod {
113            attrs: mod_attrs,
114            vis: tree.vis.clone(),
115            name: tree.name.clone(),
116            items: content_items,
117            scope: Default::default(),
118        })
119    }
120
121    /// Sort items by kind and name.
122    fn sort_pure_items(&self, items: &mut [PureItem]) {
123        items.sort_by(|a, b| {
124            let kind_a = self.item_sort_key(a);
125            let kind_b = self.item_sort_key(b);
126
127            match kind_a.cmp(&kind_b) {
128                std::cmp::Ordering::Equal => Self::item_name(a).cmp(Self::item_name(b)),
129                other => other,
130            }
131        });
132    }
133
134    /// Get sort key for an item (lower = earlier).
135    fn item_sort_key(&self, item: &PureItem) -> u8 {
136        match item {
137            PureItem::Use(_) => 0,       // Uses first
138            PureItem::Const(_) => 1,     // Constants
139            PureItem::Static(_) => 2,    // Statics
140            PureItem::Type(_) => 3,      // Type aliases
141            PureItem::Struct(_) => 4,    // Structs
142            PureItem::Enum(_) => 5,      // Enums
143            PureItem::Trait(_) => 6,     // Traits
144            PureItem::Impl(_) => 7,      // Impls
145            PureItem::Fn(_) => 8,        // Functions
146            PureItem::Mod(_) => 9,       // Modules
147            PureItem::Macro(_) => 10,    // Macros
148            PureItem::Other(_) => 11,    // Other
149            PureItem::Verbatim(_) => 12, // Verbatim (raw source bytes)
150        }
151    }
152
153    /// Get name for sorting.
154    fn item_name(item: &PureItem) -> &str {
155        match item {
156            PureItem::Use(_) => "",
157            PureItem::Struct(s) => &s.name,
158            PureItem::Enum(e) => &e.name,
159            PureItem::Fn(f) => &f.name,
160            PureItem::Trait(t) => &t.name,
161            PureItem::Impl(i) => &i.self_ty,
162            PureItem::Const(c) => &c.name,
163            PureItem::Static(s) => &s.name,
164            PureItem::Type(t) => &t.name,
165            PureItem::Mod(m) => &m.name,
166            PureItem::Macro(m) => &m.path,
167            PureItem::Other(_) => "",
168            PureItem::Verbatim(_) => "",
169        }
170    }
171}
172
173impl SourceGenerator for SingleFileGenerator {
174    fn generate(&self, tree: &ModuleTree) -> Result<GeneratedSource, crate::pure::ToSynError> {
175        let pure_file = self.tree_to_pure_file(tree);
176        let source = pure_file.to_source()?;
177
178        Ok(GeneratedSource { source, pure_file })
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use crate::pure::{
186        PureAttrMeta, PureAttribute, PureBlock, PureFields, PureFn, PureGenerics, PureStruct,
187        PureUse, PureUseTree, PureVis,
188    };
189
190    fn make_struct(name: &str) -> PureItem {
191        PureItem::Struct(PureStruct {
192            attrs: vec![],
193            vis: PureVis::Public,
194            name: name.to_string(),
195            generics: PureGenerics::default(),
196            fields: PureFields::Unit,
197        })
198    }
199
200    fn make_fn(name: &str) -> PureItem {
201        PureItem::Fn(PureFn {
202            attrs: vec![],
203            vis: PureVis::Public,
204            is_async: false,
205            is_async_inferred: false,
206            is_const: false,
207            is_unsafe: false,
208            abi: None,
209            name: name.to_string(),
210            generics: PureGenerics::default(),
211            params: vec![],
212            ret: None,
213            body: PureBlock::default(),
214        })
215    }
216
217    /// Create a PureUse from a path like "std::io".
218    fn make_use(path: &str) -> PureUse {
219        let parts: Vec<&str> = path.split("::").collect();
220        let tree = build_use_tree(&parts);
221        PureUse {
222            vis: PureVis::Private,
223            tree,
224        }
225    }
226
227    fn build_use_tree(parts: &[&str]) -> PureUseTree {
228        if parts.len() == 1 {
229            PureUseTree::Name(parts[0].to_string())
230        } else {
231            PureUseTree::Path {
232                path: parts[0].to_string(),
233                tree: Box::new(build_use_tree(&parts[1..])),
234            }
235        }
236    }
237
238    #[test]
239    fn test_single_file_basic() {
240        let tree = ModuleTree::crate_root().with_item(make_struct("Config"));
241
242        let generator = SingleFileGenerator::new();
243        let result = generator.generate(&tree).unwrap();
244
245        assert!(
246            result.source.contains("struct Config"),
247            "Source: {}",
248            result.source
249        );
250    }
251
252    #[test]
253    fn test_single_file_with_mod() {
254        let tree = ModuleTree::crate_root()
255            .with_item(make_struct("Config"))
256            .with_child(
257                ModuleTree::new("utils")
258                    .with_vis(PureVis::Public)
259                    .with_item(make_fn("helper")),
260            );
261
262        let generator = SingleFileGenerator::new();
263        let result = generator.generate(&tree).unwrap();
264
265        assert!(
266            result.source.contains("struct Config"),
267            "Source: {}",
268            result.source
269        );
270        assert!(
271            result.source.contains("pub mod utils"),
272            "Source: {}",
273            result.source
274        );
275        assert!(
276            result.source.contains("fn helper"),
277            "Source: {}",
278            result.source
279        );
280    }
281
282    #[test]
283    fn test_single_file_nested_mods() {
284        let tree = ModuleTree::crate_root().with_child(ModuleTree::new("a").with_child(
285            ModuleTree::new("b").with_child(ModuleTree::new("c").with_item(make_struct("Deep"))),
286        ));
287
288        let generator = SingleFileGenerator::new();
289        let result = generator.generate(&tree).unwrap();
290
291        assert!(result.source.contains("mod a"), "Source: {}", result.source);
292        assert!(result.source.contains("mod b"), "Source: {}", result.source);
293        assert!(result.source.contains("mod c"), "Source: {}", result.source);
294        assert!(
295            result.source.contains("struct Deep"),
296            "Source: {}",
297            result.source
298        );
299    }
300
301    #[test]
302    fn test_single_file_with_uses() {
303        let tree = ModuleTree::crate_root()
304            .with_use(make_use("std::io"))
305            .with_child(
306                ModuleTree::new("utils")
307                    .with_use(make_use("std::fmt"))
308                    .with_item(make_fn("helper")),
309            );
310
311        let generator = SingleFileGenerator::new();
312        let result = generator.generate(&tree).unwrap();
313
314        assert!(
315            result.source.contains("use std :: io") || result.source.contains("use std::io"),
316            "Source: {}",
317            result.source
318        );
319        assert!(
320            result.source.contains("use std :: fmt") || result.source.contains("use std::fmt"),
321            "Source: {}",
322            result.source
323        );
324    }
325
326    #[test]
327    fn test_generated_code_is_valid_rust() {
328        let tree = ModuleTree::crate_root()
329            .with_item(make_struct("Config"))
330            .with_child(
331                ModuleTree::new("models")
332                    .with_vis(PureVis::Public)
333                    .with_item(make_struct("User"))
334                    .with_item(make_fn("create_user")),
335            )
336            .with_child(ModuleTree::new("utils").with_item(make_fn("helper")));
337
338        let generator = SingleFileGenerator::new();
339        let result = generator.generate(&tree).unwrap();
340
341        // Verify the generated code parses as valid Rust
342        syn::parse_str::<syn::File>(&result.source).unwrap_or_else(|_| {
343            panic!(
344                "Generated code should be valid Rust.\nSource:\n{}",
345                result.source
346            )
347        });
348    }
349
350    #[test]
351    fn test_sort_items() {
352        let tree = ModuleTree::crate_root()
353            .with_item(make_fn("z_function"))
354            .with_item(make_struct("A_Struct"))
355            .with_item(make_fn("a_function"));
356
357        let generator = SingleFileGenerator::new().with_sort_items(true);
358        let result = generator.generate(&tree).unwrap();
359
360        // Struct should come before functions
361        let struct_pos = result.source.find("struct A_Struct").unwrap();
362        let z_fn_pos = result.source.find("fn z_function").unwrap();
363        let a_fn_pos = result.source.find("fn a_function").unwrap();
364
365        assert!(struct_pos < z_fn_pos, "Struct should come before functions");
366        assert!(struct_pos < a_fn_pos, "Struct should come before functions");
367        assert!(
368            a_fn_pos < z_fn_pos,
369            "a_function should come before z_function"
370        );
371    }
372
373    // ========================================================================
374    // R9-pattern API gap regression: outer attrs on inline `mod foo {}`
375    // ========================================================================
376
377    #[test]
378    fn test_single_file_inline_mod_carries_outer_attrs() {
379        // Boundary: ModuleTree::outer_attrs must reach the inline
380        // `mod foo { ... }` PureMod.attrs at the public API layer.
381        // Pre-fix, single.rs only carried `tree.inner_attrs`, silently
382        // dropping outer attrs like `#[cfg(test)] mod tests { ... }`.
383        let cfg_test = PureAttribute {
384            path: "cfg".to_string(),
385            meta: PureAttrMeta::List("test".to_string()),
386            is_inner: false,
387        };
388        let allow_unused = PureAttribute {
389            path: "allow".to_string(),
390            meta: PureAttrMeta::List("unused".to_string()),
391            is_inner: true,
392        };
393
394        let tree = ModuleTree::crate_root()
395            .with_item(make_struct("Config"))
396            .with_child(
397                ModuleTree::new("tests")
398                    .with_vis(PureVis::Private)
399                    .with_outer_attr(cfg_test)
400                    .with_inner_attr(allow_unused)
401                    .with_item(make_fn("smoke")),
402            );
403
404        let generator = SingleFileGenerator::new();
405        let result = generator.generate(&tree).unwrap();
406
407        assert!(
408            result.source.contains("#[cfg(test)]"),
409            "source must contain outer attr on inline `mod tests`, got:\n{}",
410            result.source
411        );
412        assert!(
413            result.source.contains("#![allow(unused)]"),
414            "source must also preserve inner attr inside `mod tests {{ }}`, got:\n{}",
415            result.source
416        );
417        assert!(result.source.contains("mod tests"));
418    }
419
420    // ========================================================================
421    // Snapshot-style Tests
422    // ========================================================================
423
424    /// Normalize source for comparison (remove extra whitespace, normalize formatting)
425    fn normalize(s: &str) -> String {
426        s.lines()
427            .map(|l| l.trim())
428            .filter(|l| !l.is_empty())
429            .collect::<Vec<_>>()
430            .join("\n")
431    }
432
433    /// Assert generated output matches expected (normalized)
434    fn assert_snapshot(tree: &ModuleTree, expected: &str) {
435        let generator = SingleFileGenerator::new();
436        let result = generator.generate(tree).unwrap();
437
438        // First verify it's valid Rust
439        syn::parse_str::<syn::File>(&result.source)
440            .unwrap_or_else(|_| panic!("Generated code must be valid Rust:\n{}", result.source));
441
442        let actual = normalize(&result.source);
443        let expected = normalize(expected);
444
445        assert_eq!(
446            actual, expected,
447            "\n=== ACTUAL ===\n{}\n=== EXPECTED ===\n{}",
448            result.source, expected
449        );
450    }
451
452    mod snapshot_tests {
453        use super::*;
454        use crate::pure::{
455            PureAttrMeta, PureAttribute, PureConst, PureEnum, PureExpr, PureField,
456            PureGenericParam, PureImpl, PureImplItem, PureParam, PureStatic, PureStmt, PureTrait,
457            PureTraitItem, PureTupleField, PureType, PureTypeAlias, PureVariant,
458        };
459
460        // --------------------------------------------------------------------
461        // Basic Structures
462        // --------------------------------------------------------------------
463
464        #[test]
465        fn snapshot_struct_with_fields() {
466            let tree = ModuleTree::crate_root().with_item(PureItem::Struct(PureStruct {
467                attrs: vec![],
468                vis: PureVis::Public,
469                name: "User".to_string(),
470                generics: PureGenerics::default(),
471                fields: PureFields::Named(vec![
472                    PureField {
473                        attrs: vec![],
474                        vis: PureVis::Public,
475                        name: "name".to_string(),
476                        ty: PureType::Path("String".to_string()),
477                    },
478                    PureField {
479                        attrs: vec![],
480                        vis: PureVis::Private,
481                        name: "age".to_string(),
482                        ty: PureType::Path("u32".to_string()),
483                    },
484                ]),
485            }));
486
487            assert_snapshot(
488                &tree,
489                r#"
490                pub struct User {
491                    pub name: String,
492                    age: u32,
493                }
494            "#,
495            );
496        }
497
498        #[test]
499        fn snapshot_struct_with_derive() {
500            let tree = ModuleTree::crate_root().with_item(PureItem::Struct(PureStruct {
501                attrs: vec![PureAttribute {
502                    path: "derive".to_string(),
503                    meta: PureAttrMeta::List("Debug, Clone, PartialEq".to_string()),
504                    is_inner: false,
505                }],
506                vis: PureVis::Public,
507                name: "Config".to_string(),
508                generics: PureGenerics::default(),
509                fields: PureFields::Named(vec![PureField {
510                    attrs: vec![],
511                    vis: PureVis::Public,
512                    name: "value".to_string(),
513                    ty: PureType::Path("i32".to_string()),
514                }]),
515            }));
516
517            assert_snapshot(
518                &tree,
519                r#"
520                #[derive(Debug, Clone, PartialEq)]
521                pub struct Config {
522                    pub value: i32,
523                }
524            "#,
525            );
526        }
527
528        #[test]
529        fn snapshot_tuple_struct() {
530            let tree = ModuleTree::crate_root().with_item(PureItem::Struct(PureStruct {
531                attrs: vec![],
532                vis: PureVis::Public,
533                name: "Point".to_string(),
534                generics: PureGenerics::default(),
535                fields: PureFields::Tuple(vec![
536                    PureTupleField {
537                        attrs: vec![],
538                        vis: PureVis::Private,
539                        ty: PureType::Path("i32".to_string()),
540                    },
541                    PureTupleField {
542                        attrs: vec![],
543                        vis: PureVis::Private,
544                        ty: PureType::Path("i32".to_string()),
545                    },
546                ]),
547            }));
548
549            assert_snapshot(
550                &tree,
551                r#"
552                pub struct Point(i32, i32);
553            "#,
554            );
555        }
556
557        // --------------------------------------------------------------------
558        // Enum
559        // --------------------------------------------------------------------
560
561        #[test]
562        fn snapshot_enum_simple() {
563            let tree = ModuleTree::crate_root().with_item(PureItem::Enum(PureEnum {
564                attrs: vec![],
565                vis: PureVis::Public,
566                name: "Status".to_string(),
567                generics: PureGenerics::default(),
568                variants: vec![
569                    PureVariant {
570                        attrs: vec![],
571                        name: "Pending".to_string(),
572                        fields: PureFields::Unit,
573                        discriminant: None,
574                    },
575                    PureVariant {
576                        attrs: vec![],
577                        name: "Active".to_string(),
578                        fields: PureFields::Unit,
579                        discriminant: None,
580                    },
581                    PureVariant {
582                        attrs: vec![],
583                        name: "Done".to_string(),
584                        fields: PureFields::Unit,
585                        discriminant: None,
586                    },
587                ],
588            }));
589
590            assert_snapshot(
591                &tree,
592                r#"
593                pub enum Status {
594                    Pending,
595                    Active,
596                    Done,
597                }
598            "#,
599            );
600        }
601
602        #[test]
603        fn snapshot_enum_with_data() {
604            let tree = ModuleTree::crate_root().with_item(PureItem::Enum(PureEnum {
605                attrs: vec![],
606                vis: PureVis::Public,
607                name: "Message".to_string(),
608                generics: PureGenerics::default(),
609                variants: vec![
610                    PureVariant {
611                        attrs: vec![],
612                        name: "Text".to_string(),
613                        fields: PureFields::Tuple(vec![PureTupleField {
614                            attrs: vec![],
615                            vis: PureVis::Private,
616                            ty: PureType::Path("String".to_string()),
617                        }]),
618                        discriminant: None,
619                    },
620                    PureVariant {
621                        attrs: vec![],
622                        name: "Number".to_string(),
623                        fields: PureFields::Tuple(vec![PureTupleField {
624                            attrs: vec![],
625                            vis: PureVis::Private,
626                            ty: PureType::Path("i64".to_string()),
627                        }]),
628                        discriminant: None,
629                    },
630                    PureVariant {
631                        attrs: vec![],
632                        name: "Pair".to_string(),
633                        fields: PureFields::Named(vec![
634                            PureField {
635                                attrs: vec![],
636                                vis: PureVis::Private,
637                                name: "x".to_string(),
638                                ty: PureType::Path("i32".to_string()),
639                            },
640                            PureField {
641                                attrs: vec![],
642                                vis: PureVis::Private,
643                                name: "y".to_string(),
644                                ty: PureType::Path("i32".to_string()),
645                            },
646                        ]),
647                        discriminant: None,
648                    },
649                ],
650            }));
651
652            assert_snapshot(
653                &tree,
654                r#"
655                pub enum Message {
656                    Text(String),
657                    Number(i64),
658                    Pair { x: i32, y: i32 },
659                }
660            "#,
661            );
662        }
663
664        // --------------------------------------------------------------------
665        // Impl Block
666        // --------------------------------------------------------------------
667
668        #[test]
669        fn snapshot_impl_block() {
670            let tree = ModuleTree::crate_root()
671                .with_item(make_struct("Counter"))
672                .with_item(PureItem::Impl(PureImpl {
673                    attrs: vec![],
674                    generics: PureGenerics::default(),
675                    is_unsafe: false,
676                    trait_: None,
677                    self_ty: "Counter".to_string(),
678                    items: vec![
679                        PureImplItem::Fn(PureFn {
680                            attrs: vec![],
681                            vis: PureVis::Public,
682                            is_async: false,
683                            is_async_inferred: false,
684                            is_const: false,
685                            is_unsafe: false,
686                            abi: None,
687                            name: "new".to_string(),
688                            generics: PureGenerics::default(),
689                            params: vec![],
690                            ret: Some(PureType::Path("Self".to_string())),
691                            body: PureBlock {
692                                stmts: vec![PureStmt::Expr(PureExpr::Struct {
693                                    path: "Self".to_string(),
694                                    fields: vec![],
695                                    rest: None,
696                                })],
697                            },
698                        }),
699                        PureImplItem::Fn(PureFn {
700                            attrs: vec![],
701                            vis: PureVis::Public,
702                            is_async: false,
703                            is_async_inferred: false,
704                            is_const: false,
705                            is_unsafe: false,
706                            abi: None,
707                            name: "increment".to_string(),
708                            generics: PureGenerics::default(),
709                            params: vec![PureParam::SelfValue {
710                                is_ref: true,
711                                is_mut: true,
712                            }],
713                            ret: None,
714                            body: PureBlock::default(),
715                        }),
716                    ],
717                }));
718
719            assert_snapshot(
720                &tree,
721                r#"
722                pub struct Counter;
723                impl Counter {
724                    pub fn new() -> Self {
725                        Self {}
726                    }
727                    pub fn increment(&mut self) {}
728                }
729            "#,
730            );
731        }
732
733        // --------------------------------------------------------------------
734        // Trait
735        // --------------------------------------------------------------------
736
737        #[test]
738        fn snapshot_trait_definition() {
739            let tree = ModuleTree::crate_root().with_item(PureItem::Trait(PureTrait {
740                attrs: vec![],
741                vis: PureVis::Public,
742                is_unsafe: false,
743                is_auto: false,
744                name: "Drawable".to_string(),
745                generics: PureGenerics::default(),
746                supertraits: vec![],
747                items: vec![PureTraitItem::Fn(PureFn {
748                    attrs: vec![],
749                    vis: PureVis::Private,
750                    is_async: false,
751                    is_async_inferred: false,
752                    is_const: false,
753                    is_unsafe: false,
754                    abi: None,
755                    name: "draw".to_string(),
756                    generics: PureGenerics::default(),
757                    params: vec![PureParam::SelfValue {
758                        is_ref: true,
759                        is_mut: false,
760                    }],
761                    ret: None,
762                    body: PureBlock::default(),
763                })],
764            }));
765
766            assert_snapshot(
767                &tree,
768                r#"
769                pub trait Drawable {
770                    fn draw(&self);
771                }
772            "#,
773            );
774        }
775
776        // --------------------------------------------------------------------
777        // Const and Static
778        // --------------------------------------------------------------------
779
780        #[test]
781        fn snapshot_const_and_static() {
782            let tree = ModuleTree::crate_root()
783                .with_item(PureItem::Const(PureConst {
784                    attrs: vec![],
785                    vis: PureVis::Public,
786                    name: "MAX_SIZE".to_string(),
787                    ty: PureType::Path("usize".to_string()),
788                    value: Some(PureExpr::Lit("1024".to_string())),
789                }))
790                .with_item(PureItem::Static(PureStatic {
791                    attrs: vec![],
792                    vis: PureVis::Public,
793                    is_mut: true,
794                    name: "COUNTER".to_string(),
795                    ty: PureType::Path("i32".to_string()),
796                    value: PureExpr::Lit("0".to_string()),
797                }));
798
799            assert_snapshot(
800                &tree,
801                r#"
802                pub const MAX_SIZE: usize = 1024;
803                pub static mut COUNTER: i32 = 0;
804            "#,
805            );
806        }
807
808        // --------------------------------------------------------------------
809        // Type Alias
810        // --------------------------------------------------------------------
811
812        #[test]
813        fn snapshot_type_alias() {
814            let tree = ModuleTree::crate_root().with_item(PureItem::Type(PureTypeAlias {
815                attrs: vec![],
816                vis: PureVis::Public,
817                name: "Result".to_string(),
818                generics: PureGenerics {
819                    params: vec![PureGenericParam::Type {
820                        name: "T".to_string(),
821                        bounds: vec![],
822                    }],
823                    where_clause: vec![],
824                },
825                ty: PureType::Path("std::result::Result<T, Error>".to_string()),
826            }));
827
828            assert_snapshot(
829                &tree,
830                r#"
831                pub type Result<T> = std::result::Result<T, Error>;
832            "#,
833            );
834        }
835
836        // --------------------------------------------------------------------
837        // Complex Module Hierarchy
838        // --------------------------------------------------------------------
839
840        #[test]
841        fn snapshot_complex_hierarchy() {
842            let tree = ModuleTree::crate_root()
843                .with_use(make_use("std::collections::HashMap"))
844                .with_item(make_struct("App"))
845                .with_child(
846                    ModuleTree::new("models")
847                        .with_vis(PureVis::Public)
848                        .with_use(make_use("serde::Serialize"))
849                        .with_item(PureItem::Struct(PureStruct {
850                            attrs: vec![PureAttribute {
851                                path: "derive".to_string(),
852                                meta: PureAttrMeta::List("Debug".to_string()),
853                                is_inner: false,
854                            }],
855                            vis: PureVis::Public,
856                            name: "User".to_string(),
857                            generics: PureGenerics::default(),
858                            fields: PureFields::Named(vec![PureField {
859                                attrs: vec![],
860                                vis: PureVis::Public,
861                                name: "id".to_string(),
862                                ty: PureType::Path("u64".to_string()),
863                            }]),
864                        }))
865                        .with_child(
866                            ModuleTree::new("dto")
867                                .with_vis(PureVis::Public)
868                                .with_item(make_struct("UserDto")),
869                        ),
870                )
871                .with_child(ModuleTree::new("utils").with_item(make_fn("helper")));
872
873            assert_snapshot(
874                &tree,
875                r#"
876                use std::collections::HashMap;
877                pub struct App;
878                pub mod models {
879                    use serde::Serialize;
880                    #[derive(Debug)]
881                    pub struct User {
882                        pub id: u64,
883                    }
884                    pub mod dto {
885                        pub struct UserDto;
886                    }
887                }
888                mod utils {
889                    pub fn helper() {}
890                }
891            "#,
892            );
893        }
894
895        // --------------------------------------------------------------------
896        // Generics
897        // --------------------------------------------------------------------
898
899        #[test]
900        fn snapshot_generic_struct() {
901            let tree = ModuleTree::crate_root().with_item(PureItem::Struct(PureStruct {
902                attrs: vec![],
903                vis: PureVis::Public,
904                name: "Container".to_string(),
905                generics: PureGenerics {
906                    params: vec![PureGenericParam::Type {
907                        name: "T".to_string(),
908                        bounds: vec!["Clone".to_string()],
909                    }],
910                    where_clause: vec![],
911                },
912                fields: PureFields::Named(vec![PureField {
913                    attrs: vec![],
914                    vis: PureVis::Public,
915                    name: "value".to_string(),
916                    ty: PureType::Path("T".to_string()),
917                }]),
918            }));
919
920            assert_snapshot(
921                &tree,
922                r#"
923                pub struct Container<T: Clone> {
924                    pub value: T,
925                }
926            "#,
927            );
928        }
929
930        // --------------------------------------------------------------------
931        // Async Function
932        // --------------------------------------------------------------------
933
934        #[test]
935        fn snapshot_async_function() {
936            let tree = ModuleTree::crate_root().with_item(PureItem::Fn(PureFn {
937                attrs: vec![],
938                vis: PureVis::Public,
939                is_async: true,
940                is_async_inferred: false,
941                is_const: false,
942                is_unsafe: false,
943                abi: None,
944                name: "fetch_data".to_string(),
945                generics: PureGenerics::default(),
946                params: vec![PureParam::Typed {
947                    name: "url".to_string(),
948                    ty: PureType::Ref {
949                        lifetime: None,
950                        is_mut: false,
951                        ty: Box::new(PureType::Path("str".to_string())),
952                    },
953                    is_mut: false,
954                    pat: None,
955                }],
956                ret: Some(PureType::Path("String".to_string())),
957                body: PureBlock::default(),
958            }));
959
960            assert_snapshot(
961                &tree,
962                r#"
963                pub async fn fetch_data(url: &str) -> String {}
964            "#,
965            );
966        }
967
968        // --------------------------------------------------------------------
969        // Extern Function
970        // --------------------------------------------------------------------
971
972        #[test]
973        fn snapshot_extern_function() {
974            let tree = ModuleTree::crate_root().with_item(PureItem::Fn(PureFn {
975                attrs: vec![],
976                vis: PureVis::Public,
977                is_async: false,
978                is_async_inferred: false,
979                is_const: false,
980                is_unsafe: false,
981                abi: Some("C".to_string()),
982                name: "ffi_call".to_string(),
983                generics: PureGenerics::default(),
984                params: vec![PureParam::Typed {
985                    name: "x".to_string(),
986                    ty: PureType::Path("i32".to_string()),
987                    is_mut: false,
988                    pat: None,
989                }],
990                ret: Some(PureType::Path("i32".to_string())),
991                body: PureBlock {
992                    stmts: vec![PureStmt::Expr(PureExpr::Path("x".to_string()))],
993                },
994            }));
995
996            assert_snapshot(
997                &tree,
998                r#"
999                pub extern "C" fn ffi_call(x: i32) -> i32 {
1000                    x
1001                }
1002            "#,
1003            );
1004        }
1005    }
1006}