Skip to main content

ryo_query_language/
formatter.rs

1//! Source code formatter using to_syn + prettyplease.
2//!
3//! Pure* 型を Rust ソースコード文字列に変換する。
4//! ryo-source の ToSyn トレイトを使用し、prettyplease でフォーマット。
5
6use quote::ToTokens;
7use ryo_source::pure::{
8    PureAttrMeta, PureAttribute, PureEnum, PureFn, PureItem, PureStruct, PureTrait, ToSyn,
9    ToSynError,
10};
11
12/// Pure型をフォーマット済みソースコードに変換するヘルパー
13pub struct SourceFormatter;
14
15impl SourceFormatter {
16    /// syn::Item を prettyplease でフォーマット
17    fn format_item(item: syn::Item) -> String {
18        let file = syn::File {
19            shebang: None,
20            attrs: vec![],
21            items: vec![item],
22        };
23        prettyplease::unparse(&file)
24    }
25
26    /// syn::Signature を文字列に変換(シグネチャのみ)
27    fn format_signature(sig: &syn::Signature) -> String {
28        sig.to_token_stream().to_string()
29    }
30
31    /// PureFn → 完全なソース(body含む)
32    pub fn format_fn_full(f: &PureFn) -> Result<String, ToSynError> {
33        Ok(Self::format_item(syn::Item::Fn(f.to_syn()?)))
34    }
35
36    /// PureFn → シグネチャのみ(Defモード用)
37    pub fn format_fn_signature(f: &PureFn) -> Result<String, ToSynError> {
38        Ok(Self::format_signature(&f.to_syn()?.sig))
39    }
40
41    /// PureStruct → 完全なソース
42    pub fn format_struct(s: &PureStruct) -> Result<String, ToSynError> {
43        Ok(Self::format_item(syn::Item::Struct(s.to_syn()?)))
44    }
45
46    /// PureEnum → 完全なソース
47    pub fn format_enum(e: &PureEnum) -> Result<String, ToSynError> {
48        Ok(Self::format_item(syn::Item::Enum(e.to_syn()?)))
49    }
50
51    /// PureTrait → 完全なソース
52    pub fn format_trait(t: &PureTrait) -> Result<String, ToSynError> {
53        Ok(Self::format_item(syn::Item::Trait(t.to_syn()?)))
54    }
55
56    /// PureItem → 完全なソース
57    pub fn format_item_source(item: &PureItem) -> Result<String, ToSynError> {
58        Ok(Self::format_item(item.to_syn()?))
59    }
60
61    /// アトリビュートからドキュメントを抽出
62    pub fn extract_doc(attrs: &[PureAttribute]) -> Option<String> {
63        let docs: Vec<String> = attrs
64            .iter()
65            .filter(|a| a.path == "doc")
66            .filter_map(|a| {
67                if let PureAttrMeta::NameValue(val) = &a.meta {
68                    Some(val.trim_matches('"').to_string())
69                } else {
70                    None
71                }
72            })
73            .collect();
74
75        if docs.is_empty() {
76            None
77        } else {
78            Some(docs.join("\n"))
79        }
80    }
81
82    /// アトリビュートから spec を抽出
83    pub fn extract_spec(attrs: &[PureAttribute]) -> Option<String> {
84        let specs: Vec<String> = attrs
85            .iter()
86            .filter(|a| a.path == "spec")
87            .map(|a| format!("#[spec({:?})]", a.meta))
88            .collect();
89
90        if specs.is_empty() {
91            None
92        } else {
93            Some(specs.join("\n"))
94        }
95    }
96
97    /// ドキュメントとspec両方を抽出
98    pub fn extract_doc_and_spec(attrs: &[PureAttribute]) -> Option<String> {
99        let mut lines = Vec::new();
100
101        // spec first
102        if let Some(spec) = Self::extract_spec(attrs) {
103            lines.push(spec);
104        }
105
106        // then doc
107        if let Some(doc) = Self::extract_doc(attrs) {
108            lines.push(doc);
109        }
110
111        if lines.is_empty() {
112            None
113        } else {
114            Some(lines.join("\n"))
115        }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use ryo_source::pure::{PureBlock, PureFields, PureGenerics, PureVis};
123
124    #[test]
125    fn test_format_fn_signature() {
126        let f = PureFn {
127            name: "hello".to_string(),
128            vis: PureVis::Public,
129            is_async: false,
130            is_const: false,
131            is_unsafe: false,
132            is_async_inferred: false,
133            abi: None,
134            generics: PureGenerics::default(),
135            params: vec![],
136            ret: None,
137            body: PureBlock::default(),
138            attrs: vec![],
139        };
140        let sig = SourceFormatter::format_fn_signature(&f).unwrap();
141        assert!(sig.contains("fn hello"));
142    }
143
144    #[test]
145    fn test_format_struct() {
146        let s = PureStruct {
147            name: "MyStruct".to_string(),
148            vis: PureVis::Public,
149            generics: PureGenerics::default(),
150            fields: PureFields::Unit,
151            attrs: vec![],
152        };
153        let source = SourceFormatter::format_struct(&s).unwrap();
154        assert!(source.contains("pub struct MyStruct"));
155    }
156}