spec_drift/parsers/
rust.rs1use crate::domain::{CodeFact, FactKind, Location};
2use crate::error::SpecDriftError;
3use std::path::Path;
4use syn::spanned::Spanned;
5
6pub struct RustParser;
7
8impl RustParser {
9 pub fn parse(path: &Path) -> Result<Vec<CodeFact>, SpecDriftError> {
13 let source = std::fs::read_to_string(path).map_err(|e| SpecDriftError::Io {
14 path: path.to_path_buf(),
15 source: e,
16 })?;
17
18 let file = syn::parse_file(&source).map_err(|e| SpecDriftError::RustParse {
19 path: path.to_path_buf(),
20 source: e,
21 })?;
22
23 let mut facts = Vec::new();
24 collect_items(&file.items, path, &mut facts);
25 Ok(facts)
26 }
27}
28
29fn collect_items(items: &[syn::Item], path: &Path, facts: &mut Vec<CodeFact>) {
30 for item in items {
31 match item {
32 syn::Item::Fn(f) => {
33 push(
34 facts,
35 path,
36 FactKind::Function,
37 f.sig.ident.to_string(),
38 f.span(),
39 );
40 }
41 syn::Item::Struct(s) => {
42 push(facts, path, FactKind::Struct, s.ident.to_string(), s.span());
43 }
44 syn::Item::Enum(e) => {
45 push(facts, path, FactKind::Enum, e.ident.to_string(), e.span());
46 }
47 syn::Item::Trait(t) => {
48 push(facts, path, FactKind::Trait, t.ident.to_string(), t.span());
49 }
50 syn::Item::Type(t) => {
51 push(
52 facts,
53 path,
54 FactKind::TypeAlias,
55 t.ident.to_string(),
56 t.span(),
57 );
58 }
59 syn::Item::Const(c) => {
60 push(
61 facts,
62 path,
63 FactKind::Constant,
64 c.ident.to_string(),
65 c.span(),
66 );
67 }
68 syn::Item::Static(s) => {
69 push(
70 facts,
71 path,
72 FactKind::Constant,
73 s.ident.to_string(),
74 s.span(),
75 );
76 }
77 syn::Item::Macro(m) => {
78 if let Some(ident) = m.ident.as_ref() {
79 push(facts, path, FactKind::Macro, ident.to_string(), m.span());
80 }
81 }
82 syn::Item::Mod(m) => {
83 push(facts, path, FactKind::Module, m.ident.to_string(), m.span());
84 if let Some((_, items)) = &m.content {
85 collect_items(items, path, facts);
86 }
87 }
88 syn::Item::Impl(i) => {
89 for impl_item in &i.items {
90 if let syn::ImplItem::Fn(f) = impl_item {
91 push(
92 facts,
93 path,
94 FactKind::Function,
95 f.sig.ident.to_string(),
96 f.span(),
97 );
98 }
99 }
100 }
101 _ => {}
102 }
103 }
104}
105
106fn push(
107 facts: &mut Vec<CodeFact>,
108 path: &Path,
109 kind: FactKind,
110 name: String,
111 span: proc_macro2::Span,
112) {
113 facts.push(CodeFact {
114 location: Location::new(path, span.start().line as u32),
115 kind,
116 name,
117 });
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn extracts_fn_struct_enum_trait_impl_methods() {
126 let tmp = tempfile::tempdir().unwrap();
127 let path = tmp.path().join("lib.rs");
128 std::fs::write(
129 &path,
130 r#"
131 pub fn hello() {}
132 pub struct Widget;
133 pub enum Color { Red, Blue }
134 pub trait Sing { fn sing(&self); }
135 impl Widget { pub fn make() {} }
136 "#,
137 )
138 .unwrap();
139
140 let facts = RustParser::parse(&path).unwrap();
141 let names: Vec<_> = facts.iter().map(|f| f.name.as_str()).collect();
142 assert!(names.contains(&"hello"));
143 assert!(names.contains(&"Widget"));
144 assert!(names.contains(&"Color"));
145 assert!(names.contains(&"Sing"));
146 assert!(names.contains(&"make"));
147 }
148}