ryo-query-language 0.1.0

RyoQL - Structured code query language for AI agents
Documentation
//! Source code formatter using to_syn + prettyplease.
//!
//! Pure* 型を Rust ソースコード文字列に変換する。
//! ryo-source の ToSyn トレイトを使用し、prettyplease でフォーマット。

use quote::ToTokens;
use ryo_source::pure::{
    PureAttrMeta, PureAttribute, PureEnum, PureFn, PureItem, PureStruct, PureTrait, ToSyn,
    ToSynError,
};

/// Pure型をフォーマット済みソースコードに変換するヘルパー
pub struct SourceFormatter;

impl SourceFormatter {
    /// syn::Item を prettyplease でフォーマット
    fn format_item(item: syn::Item) -> String {
        let file = syn::File {
            shebang: None,
            attrs: vec![],
            items: vec![item],
        };
        prettyplease::unparse(&file)
    }

    /// syn::Signature を文字列に変換(シグネチャのみ)
    fn format_signature(sig: &syn::Signature) -> String {
        sig.to_token_stream().to_string()
    }

    /// PureFn → 完全なソース(body含む)
    pub fn format_fn_full(f: &PureFn) -> Result<String, ToSynError> {
        Ok(Self::format_item(syn::Item::Fn(f.to_syn()?)))
    }

    /// PureFn → シグネチャのみ(Defモード用)
    pub fn format_fn_signature(f: &PureFn) -> Result<String, ToSynError> {
        Ok(Self::format_signature(&f.to_syn()?.sig))
    }

    /// PureStruct → 完全なソース
    pub fn format_struct(s: &PureStruct) -> Result<String, ToSynError> {
        Ok(Self::format_item(syn::Item::Struct(s.to_syn()?)))
    }

    /// PureEnum → 完全なソース
    pub fn format_enum(e: &PureEnum) -> Result<String, ToSynError> {
        Ok(Self::format_item(syn::Item::Enum(e.to_syn()?)))
    }

    /// PureTrait → 完全なソース
    pub fn format_trait(t: &PureTrait) -> Result<String, ToSynError> {
        Ok(Self::format_item(syn::Item::Trait(t.to_syn()?)))
    }

    /// PureItem → 完全なソース
    pub fn format_item_source(item: &PureItem) -> Result<String, ToSynError> {
        Ok(Self::format_item(item.to_syn()?))
    }

    /// アトリビュートからドキュメントを抽出
    pub fn extract_doc(attrs: &[PureAttribute]) -> Option<String> {
        let docs: Vec<String> = attrs
            .iter()
            .filter(|a| a.path == "doc")
            .filter_map(|a| {
                if let PureAttrMeta::NameValue(val) = &a.meta {
                    Some(val.trim_matches('"').to_string())
                } else {
                    None
                }
            })
            .collect();

        if docs.is_empty() {
            None
        } else {
            Some(docs.join("\n"))
        }
    }

    /// アトリビュートから spec を抽出
    pub fn extract_spec(attrs: &[PureAttribute]) -> Option<String> {
        let specs: Vec<String> = attrs
            .iter()
            .filter(|a| a.path == "spec")
            .map(|a| format!("#[spec({:?})]", a.meta))
            .collect();

        if specs.is_empty() {
            None
        } else {
            Some(specs.join("\n"))
        }
    }

    /// ドキュメントとspec両方を抽出
    pub fn extract_doc_and_spec(attrs: &[PureAttribute]) -> Option<String> {
        let mut lines = Vec::new();

        // spec first
        if let Some(spec) = Self::extract_spec(attrs) {
            lines.push(spec);
        }

        // then doc
        if let Some(doc) = Self::extract_doc(attrs) {
            lines.push(doc);
        }

        if lines.is_empty() {
            None
        } else {
            Some(lines.join("\n"))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ryo_source::pure::{PureBlock, PureFields, PureGenerics, PureVis};

    #[test]
    fn test_format_fn_signature() {
        let f = PureFn {
            name: "hello".to_string(),
            vis: PureVis::Public,
            is_async: false,
            is_const: false,
            is_unsafe: false,
            is_async_inferred: false,
            abi: None,
            generics: PureGenerics::default(),
            params: vec![],
            ret: None,
            body: PureBlock::default(),
            attrs: vec![],
        };
        let sig = SourceFormatter::format_fn_signature(&f).unwrap();
        assert!(sig.contains("fn hello"));
    }

    #[test]
    fn test_format_struct() {
        let s = PureStruct {
            name: "MyStruct".to_string(),
            vis: PureVis::Public,
            generics: PureGenerics::default(),
            fields: PureFields::Unit,
            attrs: vec![],
        };
        let source = SourceFormatter::format_struct(&s).unwrap();
        assert!(source.contains("pub struct MyStruct"));
    }
}