workspacer_consolidate/
impl_block_interface.rs

1// ---------------- [ File: workspacer-consolidate/src/impl_block_interface.rs ]
2crate::ix!();
3
4#[derive(Serialize,Deserialize,Clone,Getters,Debug)]
5#[getset(get="pub")]
6pub struct ImplBlockInterface {
7    docs:           Option<String>,
8    attributes:     Option<String>,
9    signature_text: String,
10    methods:        Vec<CrateInterfaceItem<ast::Fn>>,
11    type_aliases:   Vec<CrateInterfaceItem<ast::TypeAlias>>,
12
13    /// The file from which this impl block was parsed
14    file_path: PathBuf,
15
16    /// The crate path that owns this impl block
17    crate_path: PathBuf,
18
19    /// The raw (untrimmed) range. Many tests expect to confirm it 
20    /// matches the node’s actual text_range().
21    raw_range: TextRange,
22
23    /// The *trimmed* range, excluding leading/trailing normal comments 
24    /// & whitespace. We'll use this in gather_interstitial_segments.
25    effective_range: TextRange,
26}
27
28impl ImplBlockInterface {
29    pub fn new_with_paths_and_range(
30        docs:           Option<String>,
31        attributes:     Option<String>,
32        signature_text: String,
33        methods:        Vec<CrateInterfaceItem<ast::Fn>>,
34        type_aliases:   Vec<CrateInterfaceItem<ast::TypeAlias>>,
35        file_path:      PathBuf,
36        crate_path:     PathBuf,
37        raw_range:      TextRange,
38        effective_range: TextRange,
39    ) -> Self {
40        Self {
41            docs,
42            attributes,
43            signature_text,
44            methods,
45            type_aliases,
46            file_path,
47            crate_path,
48            raw_range,
49            effective_range,
50        }
51    }
52
53    #[cfg(test)]
54    pub fn new_for_test(
55        docs: Option<String>,
56        attributes: Option<String>,
57        signature_text: String,
58        methods: Vec<CrateInterfaceItem<ast::Fn>>,
59        type_aliases: Vec<CrateInterfaceItem<ast::TypeAlias>>,
60    ) -> Self {
61        Self::new_with_paths_and_range(
62            docs,
63            attributes,
64            signature_text,
65            methods,
66            type_aliases,
67            PathBuf::from("TEST_ONLY_file_path.rs"),
68            PathBuf::from("TEST_ONLY_crate_path"),
69            TextRange::new(0.into(), 0.into()),
70            TextRange::new(0.into(), 0.into()),
71        )
72    }
73
74    /// For interstitial logic, we want the *trimmed* range:
75    pub fn text_range(&self) -> &TextRange {
76        &self.effective_range
77    }
78}
79
80impl fmt::Display for ImplBlockInterface {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        // 1) Print doc lines (if any), one per line:
83        if let Some(ref docs) = self.docs {
84            for line in docs.lines() {
85                writeln!(f, "{}", line)?;
86            }
87        }
88
89        // 2) Print attributes (if any), one per line:
90        if let Some(ref attrs) = self.attributes {
91            for line in attrs.lines() {
92                writeln!(f, "{}", line)?;
93            }
94        }
95
96        // 3) Trim any trailing spaces from the signature to avoid double-spaces.
97        //    Then print "impl Something for T {" on one line
98        let sig = self.signature_text.trim_end();
99
100        // If no items, use one-line form:
101        if self.methods.is_empty() && self.type_aliases.is_empty() {
102            write!(f, "{} {{}}", sig)?;
103            return Ok(());
104        }
105
106        // multi-line form:
107        writeln!(f, "{} {{", sig)?;
108        // If no items, use one-line form: "impl X for Y {}"
109        if self.methods.is_empty() && self.type_aliases.is_empty() {
110            write!(f, "{} {{}}", sig)?;
111            return Ok(());
112        }
113
114        // 4) Per test `test_impl_block_interface_real_code`, the order must be
115        //    (a) methods first, then (b) type aliases. Also remove any trailing newline from item lines,
116        //    and do not add extra newlines between them.
117
118        for ta in &self.type_aliases {
119            writeln!(f, "")?;
120            let item_str = format!("{}", ta);
121            for line in item_str.lines() {
122                writeln!(f, "    {}", line)?;
123            }
124        }
125
126        for m in &self.methods {
127            writeln!(f, "")?;
128            let item_str = format!("{}", m);
129            for line in item_str.lines() {
130                // Remove any "/* ... */" placeholders, if you do that in your real code:
131                // (If not needed, remove this replacement step.)
132                let cleaned = line.replace("{ /* ... */ }", "{}");
133                writeln!(f, "    {}", cleaned)?;
134            }
135        }
136
137        // 5) Close brace with no trailing newline
138        write!(f, "}}")?;
139        Ok(())
140    }
141}
142
143// Demonstrates testing the real `ImplBlockInterface` by parsing
144// a snippet of Rust code and extracting the items via your actual code.
145#[cfg(test)]
146mod test_impl_block_interface_real {
147    use super::*;
148    // ^ Adjust imports to whatever your crate's structure is:
149    //   - The real `ImplBlockInterface`
150    //   - real `extract_docs`, `gather_all_attrs`
151    //   - real `generate_impl_signature`
152    //   - real `gather_impl_methods`, `gather_assoc_type_aliases`
153    //   - real `ConsolidationOptions`
154
155    /// Parses the snippet and returns the first `ast::Impl` node, if any.
156    fn parse_first_impl(snippet: &str) -> Option<ast::Impl> {
157        let parse = SourceFile::parse(snippet,Edition::Edition2024);
158        let file_syntax = parse.tree().syntax().clone();
159        for node in file_syntax.descendants() {
160            if let Some(impl_node) = ast::Impl::cast(node) {
161                return Some(impl_node);
162            }
163        }
164        None
165    }
166
167    /// For demonstration, we define minimal versions of your real gather logic here.
168    /// In real code, you might call `gather_impl_methods(&impl_ast, &options)` etc.
169    fn gather_methods(
170        impl_ast:   &ast::Impl,
171        options:    &ConsolidationOptions,
172        file_path:  &PathBuf,
173        crate_path: &PathBuf,
174
175    ) -> Vec<CrateInterfaceItem<ast::Fn>> {
176
177        let mut result = Vec::new();
178        if let Some(assoc_items) = impl_ast.assoc_item_list() {
179            for item in assoc_items.assoc_items() {
180                if let Some(fn_ast) = ast::Fn::cast(item.syntax().clone()) {
181                    // This specialized helper will set the correct `body_source`
182                    // if `.with_fn_bodies()` is enabled, etc.
183                    let fn_item = gather_fn_item(&fn_ast, options, file_path, crate_path);
184                    result.push(fn_item);
185                }
186            }
187        }
188        result
189    }
190
191    fn gather_type_aliases(impl_ast: &ast::Impl, options: &ConsolidationOptions)
192        -> Vec<CrateInterfaceItem<ast::TypeAlias>>
193    {
194        let mut result = vec![];
195        if let Some(assoc_items) = impl_ast.assoc_item_list() {
196            for child in assoc_items.syntax().children() {
197                if child.kind() == ra_ap_syntax::SyntaxKind::TYPE_ALIAS {
198                    if let Some(ty_ast) = ast::TypeAlias::cast(child.clone()) {
199                        if should_skip_item(&child, options) {
200                            continue;
201                        }
202                        let docs = if *options.include_docs() {
203                            extract_docs(&child)
204                        } else {
205                            None
206                        };
207                        let attrs = gather_all_attrs(&child);
208                        let alias_item = CrateInterfaceItem::new_for_test(
209                            ty_ast,
210                            docs,
211                            attrs,
212                            None,
213                            Some(options.clone())
214                        );
215                        result.push(alias_item);
216                    }
217                }
218            }
219        }
220        result
221    }
222
223    /// Minimal stubs for skip logic, doc extraction, attribute gathering, etc.
224    /// In actual code, you’d import the real versions.
225    fn should_skip_item(_node: &SyntaxNode, _options: &ConsolidationOptions) -> bool {
226        // For demonstration, we'll skip nothing
227        false
228    }
229
230    fn extract_docs(_node: &SyntaxNode) -> Option<String> {
231        // In real usage, you'd have a function that reads doc comments.
232        // For demonstration, we rely on `#[doc="..."]` or `///...`.
233        // Return None or Some(...) as you see fit.
234        None
235    }
236
237    fn gather_all_attrs(_node: &SyntaxNode) -> Option<String> {
238        // Real code might parse and convert them into lines. For demonstration:
239        None
240    }
241
242    #[traced_test]
243    fn test_impl_block_broken_example_from_crate() {
244        info!("Testing an impl block that has doc lines + multi-line where + body.");
245
246        let snippet = indoc! {r#"
247            impl CrateHandle 
248            {
249                /// Initializes a crate handle from a given crate_path
250                pub fn new_sync<P>(crate_path: &P) -> Result<Self,CrateError> 
251                where 
252                    for<'async_trait> 
253                        P
254                            : HasCargoTomlPathBuf 
255                            + HasCargoTomlPathBufSync 
256                            + AsRef<Path> 
257                            + Send 
258                            + Sync
259                            + 'async_trait,
260
261                            CrateError
262                                : From<<P as HasCargoTomlPathBuf>::Error> 
263                                + From<<P as HasCargoTomlPathBufSync>::Error>
264                {
265
266                    let cargo_toml_path = crate_path.cargo_toml_path_buf_sync()?;
267
268                    let cargo_toml_handle = Arc::new(AsyncMutex::new(CargoToml::new_sync(cargo_toml_path)?));
269
270                    Ok(Self {
271                        cargo_toml_handle,
272                        crate_path: crate_path.as_ref().to_path_buf(),
273                    })
274                }
275            }
276        "#};
277
278        let impl_ast = parse_first_impl(snippet)
279            .expect("Expected to parse an impl block from snippet");
280
281        //debug!("impl_ast = {:#?}", impl_ast);
282
283        // Must enable docs + fn bodies:
284        let options = ConsolidationOptions::new()
285            .with_docs()
286            .with_fn_bodies();  
287
288        debug!("options = {:#?}", options);
289
290        let docs  = extract_docs(impl_ast.syntax());
291
292        //debug!("docs = {:#?}", docs);
293
294        let attrs = gather_all_attrs(impl_ast.syntax());
295
296        //debug!("attrs = {:#?}", attrs);
297
298        // Produce "impl CrateHandle"
299        let raw_sig = generate_impl_signature(&impl_ast, docs.as_ref());
300
301        //debug!("raw_sig = {:#?}", raw_sig);
302
303        // Insert a newline between "impl CrateHandle" and "{", to match the test's expected format:
304        let final_sig = raw_sig.replacen("{", "\n{", 1);
305
306        //debug!("final_sig = {:#?}", final_sig);
307
308        // Gather methods & type aliases
309        let methods = gather_impl_methods(&impl_ast, &options, &PathBuf::from("FAKE"), &PathBuf::from("FAKE"));
310
311        //debug!("methods = {:#?}", methods);
312
313        let aliases = gather_assoc_type_aliases(&impl_ast, &options, &PathBuf::from("FAKE"), &PathBuf::from("FAKE"));
314
315        //debug!("aliases = {:#?}", aliases);
316
317        let ib = ImplBlockInterface::new_for_test(docs, attrs, final_sig, methods, aliases);
318
319        //debug!("ib = {:#?}", ib);
320
321        let actual_output = format!("{}", ib);
322
323        //WARNING: dont touch this! or your tests will break and you will be sad
324        //
325        // The "expected" text includes the entire body and exact spacing
326        let expected_output = indoc! {r#"
327            impl CrateHandle {
328
329                /// Initializes a crate handle from a given crate_path
330                pub fn new_sync<P>(crate_path: &P) -> Result<Self,CrateError> where 
331                        for<'async_trait> 
332                            P
333                                : HasCargoTomlPathBuf 
334                                + HasCargoTomlPathBufSync 
335                                + AsRef<Path> 
336                                + Send 
337                                + Sync
338                                + 'async_trait,
339                
340                                CrateError
341                                    : From<<P as HasCargoTomlPathBuf>::Error> 
342                                    + From<<P as HasCargoTomlPathBufSync>::Error>
343                {
344                    let cargo_toml_path = crate_path.cargo_toml_path_buf_sync()?;
345                
346                    let cargo_toml_handle = Arc::new(AsyncMutex::new(CargoToml::new_sync(cargo_toml_path)?));
347                
348                    Ok(Self {
349                        cargo_toml_handle,
350                        crate_path: crate_path.as_ref().to_path_buf(),
351                    })
352                }
353            }"#};
354
355        debug!("ACTUAL impl block:\n{actual_output}\n---");
356        debug!("EXPECTED:\n{expected_output}\n---");
357
358        assert_eq!(actual_output, expected_output, "Mismatch in final impl block");
359    }
360
361    #[test]
362    fn test_impl_block_real_code() {
363        // A snippet with doc lines, attributes, a couple of methods, and a type alias
364        let snippet = r#"
365            /// This is doc line
366            #[some_attr]
367            impl MyTrait for MyType {
368                fn do_stuff(&self) {}
369                type AliasA = i32;
370            }
371        "#;
372
373        let impl_ast = parse_first_impl(snippet).expect("Expected an impl");
374        let mut options = ConsolidationOptions::new().with_docs();
375
376        // Gather real docs & attributes from the impl node itself
377        let docs = if *options.include_docs() {
378            extract_docs(impl_ast.syntax())
379        } else {
380            None
381        };
382        let attrs = gather_all_attrs(impl_ast.syntax());
383
384        // Generate the signature line: "impl MyTrait for MyType"
385        let signature = generate_impl_signature(&impl_ast, docs.as_ref());
386
387        let file_path  = PathBuf::from("dummy");
388        let crate_path = PathBuf::from("dummy_crate");
389
390        // Gather methods & type aliases
391        let methods = gather_methods(&impl_ast, &options, &file_path, &crate_path);
392        let aliases = gather_type_aliases(&impl_ast, &options);
393
394        // Finally build the real ImplBlockInterface
395        let ib = ImplBlockInterface::new_for_test(docs, attrs, signature, methods, aliases);
396
397        // Format and compare with expected
398        let output = format!("{}", ib);
399        let expected = indoc!{
400            r#"impl MyTrait for MyType {
401
402                type AliasA = i32;
403
404                fn do_stuff(&self) {}
405            }"#
406        };
407
408        assert_eq!(output, expected);
409    }
410
411    #[test]
412    fn test_impl_block_empty() {
413        let snippet = r#"
414            impl EmptyTrait for Unit {}
415        "#;
416
417        let impl_ast = parse_first_impl(snippet).expect("Expected an impl");
418        let options = ConsolidationOptions::new(); // no docs
419
420        let docs = extract_docs(impl_ast.syntax());
421        let attrs = gather_all_attrs(impl_ast.syntax());
422        let signature = generate_impl_signature(&impl_ast, docs.as_ref());
423
424        let file_path  = PathBuf::from("dummy");
425        let crate_path = PathBuf::from("dummy_crate");
426
427        let methods = gather_methods(&impl_ast, &options, &file_path, &crate_path);
428        let aliases = gather_type_aliases(&impl_ast, &options);
429
430        let ib = ImplBlockInterface::new_for_test(docs, attrs, signature, methods, aliases);
431        let output = format!("{}", ib);
432
433        // No items => "impl EmptyTrait for Unit {}"
434        assert_eq!(output, "impl EmptyTrait for Unit {}");
435    }
436}