1use super::{GeneratedSource, ModuleTree, SourceGenerator};
6use crate::pure::{PureFile, PureItem, PureMod};
7
8#[derive(Debug, Clone, Default)]
36pub struct SingleFileGenerator {
37 pub sort_items: bool,
39}
40
41impl SingleFileGenerator {
42 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn with_sort_items(mut self, sort: bool) -> Self {
52 self.sort_items = sort;
53 self
54 }
55
56 fn tree_to_pure_file(&self, tree: &ModuleTree) -> PureFile {
58 let mut items = Vec::new();
59
60 for u in &tree.uses {
62 items.push(PureItem::Use(u.clone()));
63 }
64
65 items.extend(tree.items.iter().cloned());
67
68 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 fn tree_to_mod_item(&self, tree: &ModuleTree) -> PureItem {
85 let mut content_items = Vec::new();
86
87 for u in &tree.uses {
89 content_items.push(PureItem::Use(u.clone()));
90 }
91
92 content_items.extend(tree.items.iter().cloned());
94
95 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 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 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 fn item_sort_key(&self, item: &PureItem) -> u8 {
136 match item {
137 PureItem::Use(_) => 0, PureItem::Const(_) => 1, PureItem::Static(_) => 2, PureItem::Type(_) => 3, PureItem::Struct(_) => 4, PureItem::Enum(_) => 5, PureItem::Trait(_) => 6, PureItem::Impl(_) => 7, PureItem::Fn(_) => 8, PureItem::Mod(_) => 9, PureItem::Macro(_) => 10, PureItem::Other(_) => 11, PureItem::Verbatim(_) => 12, }
151 }
152
153 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 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 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 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 #[test]
378 fn test_single_file_inline_mod_carries_outer_attrs() {
379 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 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 fn assert_snapshot(tree: &ModuleTree, expected: &str) {
435 let generator = SingleFileGenerator::new();
436 let result = generator.generate(tree).unwrap();
437
438 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}