reinhardt_admin_cli/migrate_v2/rules/
component_props.rs1use syn::visit_mut::{self, VisitMut};
17
18use crate::migrate_v2::rewriter::FileRewriter;
19
20pub struct Rule;
22
23impl FileRewriter for Rule {
24 fn name(&self) -> &'static str {
25 "component_props"
26 }
27
28 fn rewrite(&self, mut file: syn::File) -> syn::File {
29 StructVisitor.visit_file_mut(&mut file);
30 file
31 }
32}
33
34struct StructVisitor;
35
36impl VisitMut for StructVisitor {
37 fn visit_item_struct_mut(&mut self, s: &mut syn::ItemStruct) {
38 let is_props = s.ident.to_string().ends_with("Props");
39 if !is_props {
40 visit_mut::visit_item_struct_mut(self, s);
41 return;
42 }
43 if !has_derive_default(&s.attrs) {
44 visit_mut::visit_item_struct_mut(self, s);
45 return;
46 }
47
48 replace_derive_default_with_bon_builder(&mut s.attrs);
49
50 if let syn::Fields::Named(fields) = &mut s.fields {
51 let mut seen_first_non_option = false;
52 for field in fields.named.iter_mut() {
53 let is_option = is_option_type(&field.ty);
54 let should_default = if is_option {
55 true
56 } else if !seen_first_non_option {
57 seen_first_non_option = true;
58 false
59 } else {
60 true
61 };
62 if should_default {
63 let has_builder_default = field.attrs.iter().any(|a| {
64 a.path().is_ident("builder")
65 && matches!(&a.meta, syn::Meta::List(l) if l.tokens.to_string().contains("default"))
66 });
67 if !has_builder_default {
68 field.attrs.push(syn::parse_quote!(#[builder(default)]));
69 }
70 }
71 }
72 }
73
74 visit_mut::visit_item_struct_mut(self, s);
75 }
76}
77
78fn has_derive_default(attrs: &[syn::Attribute]) -> bool {
79 attrs.iter().any(|a| {
80 a.path().is_ident("derive") && {
81 let mut found = false;
82 let _ = a.parse_nested_meta(|m| {
83 if m.path.is_ident("Default") {
84 found = true;
85 }
86 Ok(())
87 });
88 found
89 }
90 })
91}
92
93fn replace_derive_default_with_bon_builder(attrs: &mut Vec<syn::Attribute>) {
94 let mut derives: Vec<syn::Path> = Vec::new();
98 for a in attrs.iter() {
99 if a.path().is_ident("derive") {
100 let _ = a.parse_nested_meta(|m| {
101 if !m.path.is_ident("Default") {
102 derives.push(m.path.clone());
103 }
104 Ok(())
105 });
106 }
107 }
108 if !derives.iter().any(|p| {
109 p.segments.len() == 2 && p.segments[0].ident == "bon" && p.segments[1].ident == "Builder"
110 }) {
111 derives.push(syn::parse_quote!(bon::Builder));
112 }
113
114 let mut new_attrs: Vec<syn::Attribute> = attrs
115 .iter()
116 .filter(|a| !a.path().is_ident("derive"))
117 .cloned()
118 .collect();
119
120 let insert_pos = attrs
121 .iter()
122 .position(|a| a.path().is_ident("derive"))
123 .unwrap_or(0)
124 .min(new_attrs.len());
125 new_attrs.insert(insert_pos, syn::parse_quote!(#[derive(#(#derives),*)]));
126
127 *attrs = new_attrs;
128}
129
130fn is_option_type(ty: &syn::Type) -> bool {
131 if let syn::Type::Path(p) = ty
132 && let Some(seg) = p.path.segments.last()
133 {
134 return seg.ident == "Option";
135 }
136 false
137}