ryo-source 0.1.0

High-speed Rust AST manipulation engine
Documentation
//! ToSyn implementations for attributes and visibility.

use syn::token;

use super::helpers::{ident, try_parse_path};
use super::{ToSyn, ToSynError};
use crate::pure::ast::{PureAttrMeta, PureAttribute, PureVis};

impl ToSyn for PureAttribute {
    type Output = syn::Attribute;

    fn to_syn(&self) -> Result<syn::Attribute, ToSynError> {
        let meta = match &self.meta {
            PureAttrMeta::Path => syn::Meta::Path(try_parse_path(&self.path)?),
            PureAttrMeta::List(args) => {
                let tokens: proc_macro2::TokenStream =
                    args.parse()
                        .map_err(|e: proc_macro2::LexError| ToSynError::Other {
                            message: format!("Failed to parse attribute args '{}': {}", args, e),
                        })?;
                syn::Meta::List(syn::MetaList {
                    path: try_parse_path(&self.path)?,
                    delimiter: syn::MacroDelimiter::Paren(token::Paren::default()),
                    tokens,
                })
            }
            PureAttrMeta::NameValue(value) => {
                let value_expr: syn::Expr =
                    syn::parse_str(value).map_err(|e| ToSynError::Other {
                        message: format!("Failed to parse attribute value '{}': {}", value, e),
                    })?;
                syn::Meta::NameValue(syn::MetaNameValue {
                    path: try_parse_path(&self.path)?,
                    eq_token: token::Eq::default(),
                    value: value_expr,
                })
            }
        };

        Ok(syn::Attribute {
            pound_token: token::Pound::default(),
            style: if self.is_inner {
                syn::AttrStyle::Inner(token::Not::default())
            } else {
                syn::AttrStyle::Outer
            },
            bracket_token: token::Bracket::default(),
            meta,
        })
    }
}

impl ToSyn for PureVis {
    type Output = syn::Visibility;

    fn to_syn(&self) -> Result<syn::Visibility, ToSynError> {
        match self {
            PureVis::Private => Ok(syn::Visibility::Inherited),
            PureVis::Public => Ok(syn::Visibility::Public(token::Pub::default())),
            PureVis::Crate => Ok(syn::Visibility::Restricted(syn::VisRestricted {
                pub_token: token::Pub::default(),
                paren_token: token::Paren::default(),
                in_token: None,
                path: Box::new(syn::Path {
                    leading_colon: None,
                    segments: std::iter::once(syn::PathSegment {
                        ident: ident("crate"),
                        arguments: syn::PathArguments::None,
                    })
                    .collect(),
                }),
            })),
            PureVis::Super => Ok(syn::Visibility::Restricted(syn::VisRestricted {
                pub_token: token::Pub::default(),
                paren_token: token::Paren::default(),
                in_token: None,
                path: Box::new(syn::Path {
                    leading_colon: None,
                    segments: std::iter::once(syn::PathSegment {
                        ident: ident("super"),
                        arguments: syn::PathArguments::None,
                    })
                    .collect(),
                }),
            })),
            PureVis::In(path) => Ok(syn::Visibility::Restricted(syn::VisRestricted {
                pub_token: token::Pub::default(),
                paren_token: token::Paren::default(),
                in_token: Some(token::In::default()),
                path: Box::new(try_parse_path(path)?),
            })),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use quote::ToTokens;

    #[test]
    fn test_pure_attribute_path() {
        let attr = PureAttribute {
            path: "test".to_string(),
            meta: PureAttrMeta::Path,
            is_inner: false,
        };
        let syn_attr = attr.to_syn().unwrap();
        let output = syn_attr.to_token_stream().to_string();
        assert!(output.contains("test"), "Output: {}", output);
        assert!(!output.contains("("), "Should not have parens: {}", output);
    }

    #[test]
    fn test_pure_attribute_list() {
        let attr = PureAttribute {
            path: "derive".to_string(),
            meta: PureAttrMeta::List("Debug, Clone".to_string()),
            is_inner: false,
        };
        let syn_attr = attr.to_syn().unwrap();
        let output = syn_attr.to_token_stream().to_string();
        assert!(output.contains("derive"), "Output: {}", output);
        assert!(output.contains("Debug"), "Output: {}", output);
        assert!(output.contains("Clone"), "Output: {}", output);
    }

    #[test]
    fn test_pure_attribute_name_value() {
        let attr = PureAttribute {
            path: "doc".to_string(),
            meta: PureAttrMeta::NameValue("\"This is a comment\"".to_string()),
            is_inner: false,
        };
        let syn_attr = attr.to_syn().unwrap();
        let output = syn_attr.to_token_stream().to_string();
        assert!(output.contains("doc"), "Output: {}", output);
        assert!(output.contains("="), "Output: {}", output);
        assert!(output.contains("comment"), "Output: {}", output);
    }

    #[test]
    fn test_pure_attribute_inner() {
        let attr = PureAttribute {
            path: "allow".to_string(),
            meta: PureAttrMeta::List("unused".to_string()),
            is_inner: true,
        };
        let syn_attr = attr.to_syn().unwrap();
        let output = syn_attr.to_token_stream().to_string();
        // Inner attributes have # ! [ ... ] (with spaces in token stream)
        assert!(
            output.contains("# !") || output.contains("#!"),
            "Should be inner: {}",
            output
        );
    }

    #[test]
    fn test_pure_vis_public() {
        let vis = PureVis::Public;
        let syn_vis = vis.to_syn().unwrap();
        let output = syn_vis.to_token_stream().to_string();
        assert_eq!(output.trim(), "pub");
    }

    #[test]
    fn test_pure_vis_crate() {
        let vis = PureVis::Crate;
        let syn_vis = vis.to_syn().unwrap();
        let output = syn_vis.to_token_stream().to_string();
        assert!(output.contains("crate"), "Output: {}", output);
    }
}