1use anyhow::{bail, Context, Result};
2use async_trait::async_trait;
3use cargo_metadata::MetadataCommand;
4use serde::Deserialize;
5use std::collections::HashSet;
6use std::path::{Path, PathBuf};
7use std::process::Stdio;
8use syn::{Item, ItemImpl, ItemTrait};
9use tokio::io::{AsyncBufReadExt, BufReader};
10use tokio::process::Command;
11
12use super::{BuildStatus, TypeAnalyzer};
13
14fn get_host_platform() -> Result<String> {
15 let output = std::process::Command::new("rustc")
16 .args(["-vV"])
17 .output()
18 .context("failed to run rustc -vV")?;
19
20 let stdout = String::from_utf8(output.stdout).context("rustc output is not valid UTF-8")?;
21
22 for line in stdout.lines() {
23 if let Some(host) = line.strip_prefix("host: ") {
24 return Ok(host.to_string());
25 }
26 }
27
28 bail!("could not determine host platform from rustc -vV output")
29}
30
31pub struct RustAnalyzer {
32 workspace_root: PathBuf,
33}
34
35struct ItemWithImpls {
36 item: Item,
37 impls: Vec<ItemImpl>,
38 traits: Vec<ItemTrait>,
39}
40
41impl RustAnalyzer {
42 pub fn new(workspace_root: PathBuf) -> Self {
43 RustAnalyzer { workspace_root }
44 }
45}
46
47#[async_trait]
48impl TypeAnalyzer for RustAnalyzer {
49 async fn search_types_by_name(&mut self, type_name: &str) -> Result<Vec<String>> {
50 let platform = get_host_platform()?;
51 let metadata = MetadataCommand::new()
52 .current_dir(&self.workspace_root)
53 .other_options(vec!["--filter-platform".to_string(), platform])
54 .exec()
55 .context("failed to run cargo metadata")?;
56
57 let mut results = Vec::new();
58 let limit = 20;
59
60 let (workspace_packages, dependency_packages): (Vec<_>, Vec<_>) =
62 metadata.packages.iter().partition(|pkg| {
63 pkg.manifest_path
64 .as_std_path()
65 .starts_with(&self.workspace_root)
66 });
67
68 for package in &workspace_packages {
70 let crate_name = package.name.replace('-', "_");
71 let crate_root = package
72 .manifest_path
73 .parent()
74 .context("manifest has no parent directory")?;
75
76 let found = search_crate_for_type(&crate_root.into(), &crate_name, type_name, limit);
77 results.extend(found);
78
79 if results.len() >= limit {
80 results.truncate(limit);
81 break;
82 }
83 }
84
85 if results.is_empty() {
87 for package in &dependency_packages {
88 let crate_name = package.name.replace('-', "_");
89 let crate_root = package
90 .manifest_path
91 .parent()
92 .context("manifest has no parent directory")?;
93
94 let found =
95 search_crate_for_type(&crate_root.into(), &crate_name, type_name, limit);
96 results.extend(found);
97
98 if results.len() >= limit {
99 results.truncate(limit);
100 break;
101 }
102 }
103 }
104
105 if results.is_empty() {
106 bail!("no types found matching '{}'", type_name);
107 }
108
109 Ok(results)
110 }
111
112 async fn get_type_docs(&mut self, type_path: &str) -> Result<String> {
113 let parts: Vec<&str> = type_path.split("::").collect();
114 if parts.is_empty() {
115 bail!("empty type path");
116 }
117
118 let crate_name = parts[0];
119 let item_path = &parts[1..];
120
121 let source_path = find_crate_source(crate_name, &self.workspace_root)?;
122 let item_with_impls = find_item_in_source(&source_path, item_path)?;
123 let code_outline = format_item_as_code(&item_with_impls);
124
125 Ok(code_outline)
126 }
127
128 async fn get_build_status(&mut self) -> Result<BuildStatus> {
129 let mut child = Command::new("cargo")
130 .args(["check", "--tests", "--message-format=json"])
131 .current_dir(&self.workspace_root)
132 .stdout(Stdio::piped())
133 .stderr(Stdio::null())
134 .spawn()
135 .context("failed to spawn cargo check")?;
136
137 let stdout = child.stdout.take().context("failed to capture stdout")?;
138 let reader = BufReader::new(stdout);
139 let mut lines = reader.lines();
140
141 let mut errors = Vec::new();
142 let mut warnings = Vec::new();
143
144 while let Some(line) = lines.next_line().await? {
145 let Ok(message) = serde_json::from_str::<CargoMessage>(&line) else {
146 continue;
147 };
148
149 let Some(compiler_message) = message.message else {
150 continue;
151 };
152
153 let formatted = format_compiler_message(&compiler_message);
154
155 match compiler_message.level.as_str() {
156 "error" => errors.push(formatted),
157 "warning" => warnings.push(formatted),
158 _ => {}
159 }
160 }
161
162 child.wait().await?;
163
164 Ok(BuildStatus { errors, warnings })
165 }
166}
167
168#[derive(Deserialize)]
169struct CargoMessage {
170 message: Option<CompilerMessage>,
171}
172
173#[derive(Deserialize)]
174struct CompilerMessage {
175 message: String,
176 level: String,
177 spans: Vec<CompilerSpan>,
178}
179
180#[derive(Deserialize)]
181struct CompilerSpan {
182 file_name: String,
183 line_start: u32,
184 column_start: u32,
185}
186
187fn format_compiler_message(msg: &CompilerMessage) -> String {
188 if msg.spans.is_empty() {
189 return msg.message.clone();
190 }
191
192 let span = &msg.spans[0];
193 format!(
194 "{}:{}:{}: {}",
195 span.file_name, span.line_start, span.column_start, msg.message
196 )
197}
198
199fn search_crate_for_type(
200 crate_root: &PathBuf,
201 crate_name: &str,
202 type_name: &str,
203 limit: usize,
204) -> Vec<String> {
205 let crate_root = crate_root.clone();
207 let crate_name = crate_name.to_string();
208 let type_name = type_name.to_string();
209
210 std::thread::Builder::new()
211 .stack_size(8 * 1024 * 1024) .spawn(move || search_crate_for_type_inner(&crate_root, &crate_name, &type_name, limit))
213 .ok()
214 .and_then(|handle| handle.join().ok())
215 .unwrap_or_default()
216}
217
218fn search_crate_for_type_inner(
219 crate_root: &PathBuf,
220 crate_name: &str,
221 type_name: &str,
222 limit: usize,
223) -> Vec<String> {
224 let lib_path = crate_root.join("src").join("lib.rs");
225 if !lib_path.exists() {
226 return Vec::new();
227 }
228
229 let Ok(content) = std::fs::read_to_string(&lib_path) else {
230 return Vec::new();
231 };
232
233 let file_size = content.len();
234
235 if file_size > 500_000 {
236 return Vec::new();
237 }
238
239 let Ok(file) = syn::parse_file(&content) else {
240 return Vec::new();
241 };
242
243 let mut results = Vec::new();
244 let src_dir = crate_root.join("src");
245 collect_matching_types_iterative(
246 &file.items,
247 type_name,
248 crate_name,
249 &src_dir,
250 &mut results,
251 limit,
252 );
253
254 results
255}
256
257struct TypeSearchWork {
259 items: Vec<Item>,
260 path: String,
261 dir: PathBuf,
262}
263
264fn collect_matching_types_iterative(
265 initial_items: &[Item],
266 type_name: &str,
267 initial_path: &str,
268 initial_dir: &Path,
269 results: &mut Vec<String>,
270 limit: usize,
271) {
272 let mut work_stack: Vec<TypeSearchWork> = vec![TypeSearchWork {
274 items: initial_items.to_vec(),
275 path: initial_path.to_string(),
276 dir: initial_dir.to_path_buf(),
277 }];
278
279 while let Some(work) = work_stack.pop() {
280 if results.len() >= limit {
281 break;
282 }
283
284 for item in &work.items {
285 if results.len() >= limit {
286 break;
287 }
288
289 match item {
290 Item::Struct(s) if s.ident == type_name => {
291 results.push(format!("{}::{}", work.path, s.ident));
292 }
293 Item::Enum(e) if e.ident == type_name => {
294 results.push(format!("{}::{}", work.path, e.ident));
295 }
296 Item::Trait(t) if t.ident == type_name => {
297 results.push(format!("{}::{}", work.path, t.ident));
298 }
299 Item::Type(t) if t.ident == type_name => {
300 results.push(format!("{}::{}", work.path, t.ident));
301 }
302 Item::Union(u) if u.ident == type_name => {
303 results.push(format!("{}::{}", work.path, u.ident));
304 }
305 Item::Fn(f) if f.sig.ident == type_name => {
306 results.push(format!("{}::{}", work.path, f.sig.ident));
307 }
308 Item::Mod(m) => {
309 let nested_path = format!("{}::{}", work.path, m.ident);
310
311 if let Some((_, nested_items)) = &m.content {
312 work_stack.push(TypeSearchWork {
313 items: nested_items.clone(),
314 path: nested_path,
315 dir: work.dir.clone(),
316 });
317 continue;
318 }
319
320 let module_name = m.ident.to_string();
321 let nested_items = match load_module_items(&module_name, &work.dir) {
322 Ok(items) => items,
323 Err(_e) => {
324 continue;
326 }
327 };
328 let module_dir = resolve_module_dir(&module_name, &work.dir);
329 work_stack.push(TypeSearchWork {
330 items: nested_items,
331 path: nested_path,
332 dir: module_dir,
333 });
334 }
335 _ => {}
336 }
337 }
338 }
339}
340
341fn find_crate_source(crate_name: &str, workspace_root: &PathBuf) -> Result<PathBuf> {
342 let platform = get_host_platform()?;
343 let metadata = MetadataCommand::new()
344 .current_dir(workspace_root)
345 .other_options(vec!["--filter-platform".to_string(), platform])
346 .exec()
347 .context("failed to run cargo metadata")?;
348
349 for package in &metadata.packages {
350 if crate_name == package.name.replace('-', "_") {
351 let parent = package
352 .manifest_path
353 .parent()
354 .context("manifest has no parent directory")?;
355 return Ok(parent.into());
356 }
357 }
358
359 bail!("crate '{}' not found in dependencies", crate_name)
360}
361
362fn find_item_in_source(crate_root: &PathBuf, item_path: &[&str]) -> Result<ItemWithImpls> {
363 let lib_path = crate_root.join("src").join("lib.rs");
364 if !lib_path.exists() {
365 bail!("lib.rs not found at {:?}", lib_path);
366 }
367
368 let content = std::fs::read_to_string(&lib_path).context("failed to read source file")?;
369
370 let file = syn::parse_file(&content).context("failed to parse source file")?;
371
372 if item_path.is_empty() {
373 bail!("no item specified");
374 }
375
376 let src_dir = crate_root.join("src");
377 let item = search_items_iterative(&file.items, item_path, &src_dir)?;
378 let item_name = extract_item_name(&item)?;
379 let impls = collect_impls_for_item_iterative(&file.items, &item_name, &src_dir);
380
381 let mut traits = Vec::new();
382 let mut seen_traits = HashSet::new();
383
384 for impl_item in &impls {
385 if let Some(trait_path) = extract_trait_path_from_impl(impl_item) {
386 if !is_std_trait(&trait_path) {
387 let trait_name = trait_path.split("::").last().unwrap_or(&trait_path);
388 if seen_traits.insert(trait_name.to_string()) {
389 if let Some(trait_def) =
390 find_trait_in_items_iterative(&file.items, trait_name, &src_dir)
391 {
392 traits.push(trait_def);
393 }
394 }
395 }
396 }
397 }
398
399 Ok(ItemWithImpls {
400 item,
401 impls,
402 traits,
403 })
404}
405
406struct ItemSearchWork {
408 items: Vec<Item>,
409 depth: usize,
410 dir: PathBuf,
411}
412
413fn search_items_iterative(
414 initial_items: &[Item],
415 path: &[&str],
416 initial_dir: &Path,
417) -> Result<Item> {
418 if path.is_empty() {
419 bail!("empty path");
420 }
421
422 let mut work_stack: Vec<ItemSearchWork> = vec![ItemSearchWork {
423 items: initial_items.to_vec(),
424 depth: 0,
425 dir: initial_dir.to_path_buf(),
426 }];
427
428 while let Some(work) = work_stack.pop() {
429 if work.depth >= path.len() {
430 continue;
431 }
432
433 let target_name = path[work.depth];
434 let is_final = work.depth == path.len() - 1;
435
436 for item in &work.items {
437 let matches = match item {
438 Item::Struct(s) => s.ident == target_name && is_final,
439 Item::Enum(e) => e.ident == target_name && is_final,
440 Item::Trait(t) => t.ident == target_name && is_final,
441 Item::Type(t) => t.ident == target_name && is_final,
442 Item::Union(u) => u.ident == target_name && is_final,
443 Item::Fn(f) => f.sig.ident == target_name && is_final,
444 Item::Mod(m) if m.ident == target_name && !is_final => {
445 if let Some((_, nested_items)) = &m.content {
446 work_stack.push(ItemSearchWork {
447 items: nested_items.clone(),
448 depth: work.depth + 1,
449 dir: work.dir.clone(),
450 });
451 continue;
452 }
453 let Ok(nested_items) = load_module_items(&m.ident.to_string(), &work.dir)
454 else {
455 continue;
456 };
457 let module_dir = resolve_module_dir(&m.ident.to_string(), &work.dir);
458 work_stack.push(ItemSearchWork {
459 items: nested_items,
460 depth: work.depth + 1,
461 dir: module_dir,
462 });
463 continue;
464 }
465 _ => false,
466 };
467
468 if matches {
469 return Ok(item.clone());
470 }
471 }
472
473 if is_final {
475 if let Some(found) = search_all_modules_iterative(&work.items, target_name, &work.dir) {
476 return Ok(found);
477 }
478 }
479 }
480
481 bail!("item '{}' not found", path.last().unwrap_or(&""))
482}
483
484fn extract_item_name(item: &Item) -> Result<String> {
485 let name = match item {
486 Item::Struct(s) => s.ident.to_string(),
487 Item::Enum(e) => e.ident.to_string(),
488 Item::Trait(t) => t.ident.to_string(),
489 Item::Type(t) => t.ident.to_string(),
490 Item::Union(u) => u.ident.to_string(),
491 Item::Fn(f) => f.sig.ident.to_string(),
492 _ => bail!("unsupported item type for name extraction"),
493 };
494 Ok(name)
495}
496
497struct ModuleSearchWork {
499 items: Vec<Item>,
500 dir: PathBuf,
501}
502
503fn search_all_modules_iterative(
504 initial_items: &[Item],
505 target_name: &str,
506 initial_dir: &Path,
507) -> Option<Item> {
508 let mut work_stack: Vec<ModuleSearchWork> = vec![ModuleSearchWork {
509 items: initial_items.to_vec(),
510 dir: initial_dir.to_path_buf(),
511 }];
512
513 while let Some(work) = work_stack.pop() {
514 for item in &work.items {
515 match item {
516 Item::Struct(s) if s.ident == target_name => return Some(item.clone()),
517 Item::Enum(e) if e.ident == target_name => return Some(item.clone()),
518 Item::Trait(t) if t.ident == target_name => return Some(item.clone()),
519 Item::Type(t) if t.ident == target_name => return Some(item.clone()),
520 Item::Union(u) if u.ident == target_name => return Some(item.clone()),
521 Item::Mod(m) => {
522 if let Some((_, nested_items)) = &m.content {
523 work_stack.push(ModuleSearchWork {
524 items: nested_items.clone(),
525 dir: work.dir.clone(),
526 });
527 continue;
528 }
529
530 let Ok(nested_items) = load_module_items(&m.ident.to_string(), &work.dir)
531 else {
532 continue;
533 };
534 let module_dir = resolve_module_dir(&m.ident.to_string(), &work.dir);
535 work_stack.push(ModuleSearchWork {
536 items: nested_items,
537 dir: module_dir,
538 });
539 }
540 _ => {}
541 }
542 }
543 }
544 None
545}
546
547struct ImplSearchWork {
549 items: Vec<Item>,
550 dir: PathBuf,
551}
552
553fn collect_impls_for_item_iterative(
554 initial_items: &[Item],
555 target_name: &str,
556 initial_dir: &Path,
557) -> Vec<ItemImpl> {
558 let mut impls = Vec::new();
559 let mut work_stack: Vec<ImplSearchWork> = vec![ImplSearchWork {
560 items: initial_items.to_vec(),
561 dir: initial_dir.to_path_buf(),
562 }];
563
564 while let Some(work) = work_stack.pop() {
565 for item in &work.items {
566 if let Item::Impl(impl_item) = item {
567 if impl_matches_type(impl_item, target_name) {
568 impls.push(impl_item.clone());
569 }
570 continue;
571 }
572
573 let Item::Mod(m) = item else {
574 continue;
575 };
576
577 if let Some((_, nested_items)) = &m.content {
578 work_stack.push(ImplSearchWork {
579 items: nested_items.clone(),
580 dir: work.dir.clone(),
581 });
582 continue;
583 }
584
585 let Ok(nested_items) = load_module_items(&m.ident.to_string(), &work.dir) else {
586 continue;
587 };
588 let module_dir = resolve_module_dir(&m.ident.to_string(), &work.dir);
589 work_stack.push(ImplSearchWork {
590 items: nested_items,
591 dir: module_dir,
592 });
593 }
594 }
595
596 impls
597}
598
599fn impl_matches_type(impl_item: &ItemImpl, target_name: &str) -> bool {
600 if let syn::Type::Path(type_path) = &*impl_item.self_ty {
601 if let Some(segment) = type_path.path.segments.last() {
602 return segment.ident == target_name;
603 }
604 }
605 false
606}
607
608fn extract_trait_path_from_impl(impl_item: &ItemImpl) -> Option<String> {
609 impl_item.trait_.as_ref().map(|(_, path, _)| {
610 path.segments
611 .iter()
612 .map(|seg| seg.ident.to_string())
613 .collect::<Vec<_>>()
614 .join("::")
615 })
616}
617
618fn is_std_trait(trait_path: &str) -> bool {
619 trait_path.starts_with("std::")
620 || trait_path.starts_with("core::")
621 || trait_path.starts_with("alloc::")
622}
623
624fn load_module_items(module_name: &str, current_dir: &Path) -> Result<Vec<Item>> {
625 let file_name = module_name.strip_prefix("r#").unwrap_or(module_name);
627
628 let mod_file = current_dir.join(format!("{}.rs", file_name));
629 let mod_dir_file = current_dir.join(file_name).join("mod.rs");
630
631 let path = if mod_file.exists() {
632 mod_file
633 } else if mod_dir_file.exists() {
634 mod_dir_file
635 } else {
636 bail!(
637 "module file not found for '{}' in {:?}",
638 module_name,
639 current_dir
640 );
641 };
642
643 let content =
644 std::fs::read_to_string(&path).context(format!("failed to read module file {:?}", path))?;
645
646 let file_size = content.len();
647
648 if file_size > 500_000 {
649 bail!("skipping large module (>500KB)");
650 }
651
652 let file =
653 syn::parse_file(&content).context(format!("failed to parse module file {:?}", path))?;
654
655 Ok(file.items)
656}
657
658fn resolve_module_dir(module_name: &str, current_dir: &Path) -> PathBuf {
659 let file_name = module_name.strip_prefix("r#").unwrap_or(module_name);
661
662 let mod_file = current_dir.join(format!("{}.rs", file_name));
663 if mod_file.exists() {
664 return current_dir.join(file_name);
665 }
666 current_dir.join(file_name)
667}
668
669struct TraitSearchWork {
671 items: Vec<Item>,
672 dir: PathBuf,
673}
674
675fn find_trait_in_items_iterative(
676 initial_items: &[Item],
677 trait_name: &str,
678 initial_dir: &Path,
679) -> Option<ItemTrait> {
680 let mut work_stack: Vec<TraitSearchWork> = vec![TraitSearchWork {
681 items: initial_items.to_vec(),
682 dir: initial_dir.to_path_buf(),
683 }];
684
685 while let Some(work) = work_stack.pop() {
686 for item in &work.items {
687 match item {
688 Item::Trait(t) if t.ident == trait_name => {
689 return Some(t.clone());
690 }
691 Item::Mod(m) => {
692 if let Some((_, nested_items)) = &m.content {
693 work_stack.push(TraitSearchWork {
694 items: nested_items.clone(),
695 dir: work.dir.clone(),
696 });
697 continue;
698 }
699
700 let Ok(nested_items) = load_module_items(&m.ident.to_string(), &work.dir)
701 else {
702 continue;
703 };
704 let module_dir = resolve_module_dir(&m.ident.to_string(), &work.dir);
705 work_stack.push(TraitSearchWork {
706 items: nested_items,
707 dir: module_dir,
708 });
709 }
710 _ => {}
711 }
712 }
713 }
714 None
715}
716
717fn format_item_as_code(item_with_impls: &ItemWithImpls) -> String {
718 let mut formatted_items = Vec::new();
719
720 for trait_item in &item_with_impls.traits {
721 let item = Item::Trait(strip_trait_bodies(trait_item));
722 let file = syn::File {
723 shebang: None,
724 attrs: vec![],
725 items: vec![item],
726 };
727 let formatted = prettyplease::unparse(&file);
728 let formatted = replace_empty_bodies_with_semicolons(&formatted);
729 formatted_items.push(add_blank_lines_between_items(&formatted));
730 }
731
732 let main_item = strip_implementations(&item_with_impls.item);
733 let file = syn::File {
734 shebang: None,
735 attrs: vec![],
736 items: vec![main_item],
737 };
738 formatted_items.push(prettyplease::unparse(&file));
739
740 for impl_item in &item_with_impls.impls {
741 let item = Item::Impl(strip_impl_bodies(impl_item));
742 let file = syn::File {
743 shebang: None,
744 attrs: vec![],
745 items: vec![item],
746 };
747 let formatted = prettyplease::unparse(&file);
748 let formatted = replace_empty_bodies_with_semicolons(&formatted);
749 formatted_items.push(add_blank_lines_between_items(&formatted));
750 }
751
752 let combined = formatted_items.join("\n\n");
753 format!("```rust\n{}```", combined)
754}
755
756fn strip_implementations(item: &Item) -> Item {
757 match item {
758 Item::Fn(f) => {
759 let mut func = f.clone();
760 func.block = Box::new(syn::Block {
761 brace_token: syn::token::Brace::default(),
762 stmts: vec![],
763 });
764 Item::Fn(func)
765 }
766 _ => item.clone(),
767 }
768}
769
770fn add_blank_lines_between_items(code: &str) -> String {
771 let lines: Vec<&str> = code.lines().collect();
772 let mut result = Vec::new();
773 let mut i = 0;
774
775 while i < lines.len() {
776 result.push(lines[i].to_string());
777
778 if i + 1 < lines.len() {
779 let current_line = lines[i].trim();
780 let next_line = lines[i + 1].trim();
781
782 let current_is_doc_comment = current_line.starts_with("///");
783 let next_is_doc_comment = next_line.starts_with("///");
784
785 if current_is_doc_comment && next_is_doc_comment {
786 i += 1;
787 continue;
788 }
789
790 let current_ends_item = current_line.ends_with('}') || current_line.ends_with(';');
791 let next_starts_new_item = next_line.starts_with("///")
792 || next_line.starts_with("#[")
793 || next_line.starts_with("pub fn")
794 || next_line.starts_with("fn")
795 || next_line.starts_with("async fn")
796 || next_line.starts_with("pub async fn")
797 || next_line.starts_with("pub(crate) fn")
798 || next_line.starts_with("pub(super) fn")
799 || next_line.starts_with("unsafe fn")
800 || next_line.starts_with("pub unsafe fn");
801
802 if current_ends_item && next_starts_new_item && !next_line.is_empty() {
803 result.push(String::new());
804 }
805 }
806
807 i += 1;
808 }
809
810 result.join("\n")
811}
812
813fn replace_empty_bodies_with_semicolons(code: &str) -> String {
814 let lines: Vec<&str> = code.lines().collect();
815 let mut result: Vec<String> = Vec::new();
816 let mut i = 0;
817
818 while i < lines.len() {
819 let line = lines[i];
820 let trimmed = line.trim();
821
822 if trimmed == "{}" {
823 if let Some(last_line) = result.last_mut() {
824 let last_trimmed = last_line.trim_end();
825 if last_trimmed.ends_with(',') {
826 let without_comma = &last_trimmed[..last_trimmed.len() - 1];
827 *last_line = format!(
828 "{}{}",
829 &last_line[..last_line.len() - last_trimmed.len()],
830 without_comma.to_string() + ";"
831 );
832 } else {
833 *last_line = last_line.trim_end().to_string() + ";";
834 }
835 }
836 i += 1;
837 continue;
838 }
839
840 if trimmed.ends_with(" {}") {
841 let without_braces = &trimmed[..trimmed.len() - 3];
842 result.push(format!(
843 "{}{}",
844 &line[..line.len() - trimmed.len()],
845 without_braces.to_string() + ";"
846 ));
847 } else {
848 result.push(line.to_string());
849 }
850
851 i += 1;
852 }
853
854 result.join("\n")
855}
856
857fn strip_impl_bodies(impl_item: &ItemImpl) -> ItemImpl {
858 let mut impl_clone = impl_item.clone();
859
860 for item in &mut impl_clone.items {
861 if let syn::ImplItem::Fn(method) = item {
862 method.block = syn::Block {
863 brace_token: syn::token::Brace::default(),
864 stmts: vec![],
865 };
866 }
867 }
868
869 impl_clone
870}
871
872fn strip_trait_bodies(trait_item: &ItemTrait) -> ItemTrait {
873 let mut trait_clone = trait_item.clone();
874
875 for item in &mut trait_clone.items {
876 if let syn::TraitItem::Fn(method) = item {
877 method.default = None;
878 }
879 }
880
881 trait_clone
882}