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