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 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 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 fn item_sort_key(&self, item: &PureItem) -> u8 {
127 match item {
128 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, }
141 }
142
143 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 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 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 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 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 fn assert_snapshot(tree: &ModuleTree, expected: &str) {
376 let generator = SingleFileGenerator::new();
377 let result = generator.generate(tree).unwrap();
378
379 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}