From 272211a3baa8bdc087cc1d0686fc571e0d3fa301 Mon Sep 17 00:00:00 2001 From: messense Date: Thu, 5 Jan 2023 12:06:26 +0800 Subject: [PATCH] Move configuration derive to a separate module --- ruff_macros/src/config.rs | 203 +++++++++++++++++++++++++++++++++++++ ruff_macros/src/lib.rs | 208 +------------------------------------- 2 files changed, 207 insertions(+), 204 deletions(-) create mode 100644 ruff_macros/src/config.rs diff --git a/ruff_macros/src/config.rs b/ruff_macros/src/config.rs new file mode 100644 index 0000000000000..b23935ea99d95 --- /dev/null +++ b/ruff_macros/src/config.rs @@ -0,0 +1,203 @@ +use quote::{quote, quote_spanned}; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::token::Comma; +use syn::{ + AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, Field, Fields, Lit, + LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath, +}; + +pub fn derive_impl(input: DeriveInput) -> syn::Result { + let DeriveInput { ident, data, .. } = input; + + match data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => { + let mut output = vec![]; + + for field in fields.named.iter() { + let docs: Vec<&Attribute> = field + .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .collect(); + + if docs.is_empty() { + return Err(syn::Error::new( + field.span(), + "Missing documentation for field", + )); + } + + if let Some(attr) = field.attrs.iter().find(|attr| attr.path.is_ident("option")) { + output.push(handle_option(field, attr, docs)?); + }; + + if field + .attrs + .iter() + .any(|attr| attr.path.is_ident("option_group")) + { + output.push(handle_option_group(field)?); + }; + } + + Ok(quote! { + use crate::settings::options_base::{OptionEntry, OptionField, OptionGroup, ConfigurationOptions}; + + #[automatically_derived] + impl ConfigurationOptions for #ident { + fn get_available_options() -> Vec { + vec![#(#output),*] + } + } + }) + } + _ => Err(syn::Error::new( + ident.span(), + "Can only derive ConfigurationOptions from structs with named fields.", + )), + } +} + +/// For a field with type `Option` where `Foobar` itself is a struct +/// deriving `ConfigurationOptions`, create code that calls retrieves options +/// from that group: `Foobar::get_available_options()` +fn handle_option_group(field: &Field) -> syn::Result { + let ident = field + .ident + .as_ref() + .expect("Expected to handle named fields"); + + match &field.ty { + Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) => match segments.first() { + Some(PathSegment { + ident: type_ident, + arguments: + PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }), + .. + }) if type_ident == "Option" => { + let path = &args[0]; + let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span()); + + Ok(quote_spanned!( + ident.span() => OptionEntry::Group(OptionGroup { + name: #kebab_name, + fields: #path::get_available_options(), + }) + )) + } + _ => Err(syn::Error::new( + ident.span(), + "Expected `Option<_>` as type.", + )), + }, + _ => Err(syn::Error::new(ident.span(), "Expected type.")), + } +} + +/// Parse a `doc` attribute into it a string literal. +fn parse_doc(doc: &Attribute) -> syn::Result { + let doc = doc + .parse_meta() + .map_err(|e| syn::Error::new(doc.span(), e))?; + + match doc { + syn::Meta::NameValue(syn::MetaNameValue { + lit: Lit::Str(lit_str), + .. + }) => Ok(lit_str.value()), + _ => Err(syn::Error::new(doc.span(), "Expected doc attribute.")), + } +} + +/// Parse an `#[option(doc="...", default="...", value_type="...", +/// example="...")]` attribute and return data in the form of an `OptionField`. +fn handle_option( + field: &Field, + attr: &Attribute, + docs: Vec<&Attribute>, +) -> syn::Result { + // Convert the list of `doc` attributes into a single string. + let doc = textwrap::dedent( + &docs + .into_iter() + .map(parse_doc) + .collect::>>()? + .join("\n"), + ) + .trim_matches('\n') + .to_string(); + + let ident = field + .ident + .as_ref() + .expect("Expected to handle named fields"); + + let FieldAttributes { + default, + value_type, + example, + .. + } = attr.parse_args::()?; + let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span()); + + Ok(quote_spanned!( + ident.span() => OptionEntry::Field(OptionField { + name: #kebab_name, + doc: &#doc, + default: &#default, + value_type: &#value_type, + example: &#example, + }) + )) +} + +#[derive(Debug)] +struct FieldAttributes { + default: String, + value_type: String, + example: String, +} + +impl Parse for FieldAttributes { + fn parse(input: ParseStream) -> syn::Result { + let default = _parse_key_value(input, "default")?; + input.parse::()?; + let value_type = _parse_key_value(input, "value_type")?; + input.parse::()?; + let example = _parse_key_value(input, "example")?; + if !input.is_empty() { + input.parse::()?; + } + + Ok(FieldAttributes { + default, + value_type, + example: textwrap::dedent(&example).trim_matches('\n').to_string(), + }) + } +} + +fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result { + let ident: proc_macro2::Ident = input.parse()?; + if ident != name { + return Err(syn::Error::new( + ident.span(), + format!("Expected `{name}` name"), + )); + } + + input.parse::()?; + let value: Lit = input.parse()?; + + match &value { + Lit::Str(v) => Ok(v.value()), + _ => Err(syn::Error::new(value.span(), "Expected literal string")), + } +} diff --git a/ruff_macros/src/lib.rs b/ruff_macros/src/lib.rs index 2ef22a5aad0b2..eed0bec1f2821 100644 --- a/ruff_macros/src/lib.rs +++ b/ruff_macros/src/lib.rs @@ -11,215 +11,15 @@ clippy::too_many_lines )] -use quote::{quote, quote_spanned}; -use syn::parse::{Parse, ParseStream}; -use syn::spanned::Spanned; -use syn::token::Comma; -use syn::{ - parse_macro_input, AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, - Field, Fields, Lit, LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath, -}; +use syn::{parse_macro_input, DeriveInput}; + +mod config; #[proc_macro_derive(ConfigurationOptions, attributes(option, doc, option_group))] pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); - derive_impl(input) + config::derive_impl(input) .unwrap_or_else(syn::Error::into_compile_error) .into() } - -fn derive_impl(input: DeriveInput) -> syn::Result { - let DeriveInput { ident, data, .. } = input; - - match data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => { - let mut output = vec![]; - - for field in fields.named.iter() { - let docs: Vec<&Attribute> = field - .attrs - .iter() - .filter(|attr| attr.path.is_ident("doc")) - .collect(); - - if docs.is_empty() { - return Err(syn::Error::new( - field.span(), - "Missing documentation for field", - )); - } - - if let Some(attr) = field.attrs.iter().find(|attr| attr.path.is_ident("option")) { - output.push(handle_option(field, attr, docs)?); - }; - - if field - .attrs - .iter() - .any(|attr| attr.path.is_ident("option_group")) - { - output.push(handle_option_group(field)?); - }; - } - - Ok(quote! { - use crate::settings::options_base::{OptionEntry, OptionField, OptionGroup, ConfigurationOptions}; - - #[automatically_derived] - impl ConfigurationOptions for #ident { - fn get_available_options() -> Vec { - vec![#(#output),*] - } - } - }) - } - _ => Err(syn::Error::new( - ident.span(), - "Can only derive ConfigurationOptions from structs with named fields.", - )), - } -} - -/// For a field with type `Option` where `Foobar` itself is a struct -/// deriving `ConfigurationOptions`, create code that calls retrieves options -/// from that group: `Foobar::get_available_options()` -fn handle_option_group(field: &Field) -> syn::Result { - let ident = field - .ident - .as_ref() - .expect("Expected to handle named fields"); - - match &field.ty { - Type::Path(TypePath { - path: Path { segments, .. }, - .. - }) => match segments.first() { - Some(PathSegment { - ident: type_ident, - arguments: - PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }), - .. - }) if type_ident == "Option" => { - let path = &args[0]; - let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span()); - - Ok(quote_spanned!( - ident.span() => OptionEntry::Group(OptionGroup { - name: #kebab_name, - fields: #path::get_available_options(), - }) - )) - } - _ => Err(syn::Error::new( - ident.span(), - "Expected `Option<_>` as type.", - )), - }, - _ => Err(syn::Error::new(ident.span(), "Expected type.")), - } -} - -/// Parse a `doc` attribute into it a string literal. -fn parse_doc(doc: &Attribute) -> syn::Result { - let doc = doc - .parse_meta() - .map_err(|e| syn::Error::new(doc.span(), e))?; - - match doc { - syn::Meta::NameValue(syn::MetaNameValue { - lit: Lit::Str(lit_str), - .. - }) => Ok(lit_str.value()), - _ => Err(syn::Error::new(doc.span(), "Expected doc attribute.")), - } -} - -/// Parse an `#[option(doc="...", default="...", value_type="...", -/// example="...")]` attribute and return data in the form of an `OptionField`. -fn handle_option( - field: &Field, - attr: &Attribute, - docs: Vec<&Attribute>, -) -> syn::Result { - // Convert the list of `doc` attributes into a single string. - let doc = textwrap::dedent( - &docs - .into_iter() - .map(parse_doc) - .collect::>>()? - .join("\n"), - ) - .trim_matches('\n') - .to_string(); - - let ident = field - .ident - .as_ref() - .expect("Expected to handle named fields"); - - let FieldAttributes { - default, - value_type, - example, - .. - } = attr.parse_args::()?; - let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span()); - - Ok(quote_spanned!( - ident.span() => OptionEntry::Field(OptionField { - name: #kebab_name, - doc: &#doc, - default: &#default, - value_type: &#value_type, - example: &#example, - }) - )) -} - -#[derive(Debug)] -struct FieldAttributes { - default: String, - value_type: String, - example: String, -} - -impl Parse for FieldAttributes { - fn parse(input: ParseStream) -> syn::Result { - let default = _parse_key_value(input, "default")?; - input.parse::()?; - let value_type = _parse_key_value(input, "value_type")?; - input.parse::()?; - let example = _parse_key_value(input, "example")?; - if !input.is_empty() { - input.parse::()?; - } - - Ok(FieldAttributes { - default, - value_type, - example: textwrap::dedent(&example).trim_matches('\n').to_string(), - }) - } -} - -fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result { - let ident: proc_macro2::Ident = input.parse()?; - if ident != name { - return Err(syn::Error::new( - ident.span(), - format!("Expected `{name}` name"), - )); - } - - input.parse::()?; - let value: Lit = input.parse()?; - - match &value { - Lit::Str(v) => Ok(v.value()), - _ => Err(syn::Error::new(value.span(), "Expected literal string")), - } -}