1crate::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 file_path: PathBuf,
15
16 crate_path: PathBuf,
18
19 raw_range: TextRange,
22
23 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 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 if let Some(ref docs) = self.docs {
84 for line in docs.lines() {
85 writeln!(f, "{}", line)?;
86 }
87 }
88
89 if let Some(ref attrs) = self.attributes {
91 for line in attrs.lines() {
92 writeln!(f, "{}", line)?;
93 }
94 }
95
96 let sig = self.signature_text.trim_end();
99
100 if self.methods.is_empty() && self.type_aliases.is_empty() {
102 write!(f, "{} {{}}", sig)?;
103 return Ok(());
104 }
105
106 writeln!(f, "{} {{", sig)?;
108 if self.methods.is_empty() && self.type_aliases.is_empty() {
110 write!(f, "{} {{}}", sig)?;
111 return Ok(());
112 }
113
114 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 let cleaned = line.replace("{ /* ... */ }", "{}");
133 writeln!(f, " {}", cleaned)?;
134 }
135 }
136
137 write!(f, "}}")?;
139 Ok(())
140 }
141}
142
143#[cfg(test)]
146mod test_impl_block_interface_real {
147 use super::*;
148 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 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 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 fn should_skip_item(_node: &SyntaxNode, _options: &ConsolidationOptions) -> bool {
226 false
228 }
229
230 fn extract_docs(_node: &SyntaxNode) -> Option<String> {
231 None
235 }
236
237 fn gather_all_attrs(_node: &SyntaxNode) -> Option<String> {
238 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 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 let attrs = gather_all_attrs(impl_ast.syntax());
295
296 let raw_sig = generate_impl_signature(&impl_ast, docs.as_ref());
300
301 let final_sig = raw_sig.replacen("{", "\n{", 1);
305
306 let methods = gather_impl_methods(&impl_ast, &options, &PathBuf::from("FAKE"), &PathBuf::from("FAKE"));
310
311 let aliases = gather_assoc_type_aliases(&impl_ast, &options, &PathBuf::from("FAKE"), &PathBuf::from("FAKE"));
314
315 let ib = ImplBlockInterface::new_for_test(docs, attrs, final_sig, methods, aliases);
318
319 let actual_output = format!("{}", ib);
322
323 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 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 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 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 let methods = gather_methods(&impl_ast, &options, &file_path, &crate_path);
392 let aliases = gather_type_aliases(&impl_ast, &options);
393
394 let ib = ImplBlockInterface::new_for_test(docs, attrs, signature, methods, aliases);
396
397 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(); 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 assert_eq!(output, "impl EmptyTrait for Unit {}");
435 }
436}