workspacer_syntax/
generate_struct_signature.rs

1// ---------------- [ File: workspacer-syntax/src/generate_struct_signature.rs ]
2crate::ix!();
3
4#[derive(Debug, Clone)]
5pub struct StructSignatureGenerator(ast::Struct);
6
7impl GenerateSignature for ast::Struct {
8    fn generate_signature_with_opts(&self, opts: &SignatureOptions) -> String {
9        trace!("Generating signature for ast::Struct with opts: {:?}", opts);
10
11        let doc_text = if *opts.include_docs() {
12            extract_docs(&self.syntax())
13                .map(|d| format!("{}\n", d))
14                .unwrap_or_default()
15        } else {
16            "".to_string()
17        };
18
19        let vis_str = self
20            .visibility()
21            .map(|v| format!("{} ", v.syntax().text()))
22            .unwrap_or_default();
23
24        let name_str = self
25            .name()
26            .map(|n| n.to_string())
27            .unwrap_or_else(|| "<unknown_struct>".to_string());
28
29        let generic_params_raw = self
30            .generic_param_list()
31            .map(|g| g.syntax().text().to_string())
32            .unwrap_or_default();
33
34        let where_clause = full_clean_where_clause(&self.where_clause());
35
36        // If fully_expand is true, gather and align the actual fields; otherwise, we display a placeholder.
37        let fields_text = {
38            if let Some(fl) = self.field_list() {
39                match fl {
40                    ast::FieldList::RecordFieldList(rfl) => {
41                        align_record_fields(&rfl)
42                    }
43                    ast::FieldList::TupleFieldList(tfl) => {
44                        align_tuple_fields(&tfl)
45                    }
46                }
47            } else {
48                // no fields => e.g. `struct Foo;`
49                ";".to_string()
50            }
51        };
52
53        let core = format!("{vis_str}struct {name_str}{generic_params_raw} {where_clause} {fields_text}");
54
55        let final_sig = format!("{doc_text}{core}");
56        post_process_spacing(&final_sig)
57    }
58}
59
60/// Aligns record fields so that field names and colons line up in a neat column:
61/// ```ignore
62/// {
63///     path:                    Option<PathBuf>,
64///     include_private:         bool,
65///     show_items_with_no_data: bool,
66/// }
67/// ```
68fn align_record_fields(rfl: &ast::RecordFieldList) -> String {
69    // 1) Gather the fields into a vector of (field_name, field_type)
70    let mut fields_info = Vec::new();
71    for field in rfl.fields() {
72        let fname = field
73            .name()
74            .map(|n| n.text().to_string())
75            .unwrap_or_default();
76        let fty = field
77            .ty()
78            .map(|t| t.syntax().text().to_string())
79            .unwrap_or_default();
80
81        fields_info.push((fname, fty));
82    }
83
84    if fields_info.is_empty() {
85        return "{ }".to_string();
86    }
87
88    // 2) Find the max length of any field name. We'll align the colon after that.
89    let max_name_len = fields_info.iter().map(|(n, _)| n.len()).max().unwrap_or(0);
90
91    // 3) Build the final lines, each with `    {field_name}{spaces}: {field_type},`
92    let mut lines = Vec::new();
93    for (name, ftype) in fields_info {
94        let space_count = if max_name_len > name.len() {
95            max_name_len - name.len()
96        } else {
97            0
98        };
99        let spacing = " ".repeat(space_count);
100        // so we get something like: `    path{...}: Option<PathBuf>,`
101        let line = format!("    {name}:{spacing} {ftype},");
102        lines.push(line);
103    }
104
105    let joined = lines.join("\n");
106    format!("{{\n{}\n}}", joined)
107}
108
109/// Aligns tuple fields so that the entire `vis + type` is lined up in a neat column. For example:
110/// ```ignore
111/// (
112///     pub(in xyz) i32,
113///     bool,
114///     AnotherType,
115/// );
116/// ```
117fn align_tuple_fields(tfl: &ast::TupleFieldList) -> String {
118    // 1) Gather the fields into a vector of strings, each "vis + type"
119    let mut fields_info = Vec::new();
120    for field in tfl.fields() {
121        let vis = field
122            .visibility()
123            .map(|v| format!("{} ", v.syntax().text()))
124            .unwrap_or_default();
125        let fty = field
126            .ty()
127            .map(|t| t.syntax().text().to_string())
128            .unwrap_or_default();
129
130        // Combine them into one string: e.g. "pub(in foo) MyType"
131        let combined = format!("{}{}", vis, fty);
132        fields_info.push(combined);
133    }
134
135    if fields_info.is_empty() {
136        return "();".to_string();
137    }
138
139    // 2) Find the max length of that combined "vis + type" text
140    let max_field_len = fields_info.iter().map(|s| s.len()).max().unwrap_or(0);
141
142    // 3) Build final lines with left alignment
143    let mut lines = Vec::new();
144    for combined in fields_info {
145        let space_count = if max_field_len > combined.len() {
146            max_field_len - combined.len()
147        } else {
148            0
149        };
150        let spacing = " ".repeat(space_count);
151        // e.g. `    pub(in foo) i32,`
152        let line = format!("    {combined}{spacing},");
153        lines.push(line);
154    }
155
156    let joined = lines.join("\n");
157    format!("(\n{}\n);", joined)
158}
159
160/// A small helper to do final spacing adjustments if desired.
161fn post_process_spacing(sig: &str) -> String {
162    // For example, you can remove trailing blank lines or compress double newlines.
163    // For this snippet, we just return `sig` as-is.
164    sig.to_string()
165}