mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
Respect #(deprecated)
attribute in configuration options (#8035)
This commit is contained in:
parent
2e225d7538
commit
a85ed309ea
9 changed files with 272 additions and 65 deletions
|
@ -18,6 +18,7 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
|
|||
Ok(quote! {
|
||||
impl crate::configuration::CombinePluginOptions for #ident {
|
||||
fn combine(self, other: Self) -> Self {
|
||||
#[allow(deprecated)]
|
||||
Self {
|
||||
#(
|
||||
#output
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use proc_macro2::TokenTree;
|
||||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::meta::ParseNestedMeta;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::token::Comma;
|
||||
use syn::{
|
||||
AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, ExprLit, Field,
|
||||
Fields, Lit, LitStr, Meta, Path, PathArguments, PathSegment, Token, Type, TypePath,
|
||||
Fields, Lit, LitStr, Meta, Path, PathArguments, PathSegment, Type, TypePath,
|
||||
};
|
||||
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
|
||||
pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
||||
pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
|
||||
let DeriveInput {
|
||||
ident,
|
||||
data,
|
||||
|
@ -190,9 +189,30 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
|
|||
default,
|
||||
value_type,
|
||||
example,
|
||||
} = attr.parse_args::<FieldAttributes>()?;
|
||||
} = parse_field_attributes(attr)?;
|
||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||
|
||||
let deprecated = if let Some(deprecated) = field
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("deprecated"))
|
||||
{
|
||||
fn quote_option(option: Option<String>) -> TokenStream {
|
||||
match option {
|
||||
None => quote!(None),
|
||||
Some(value) => quote!(Some(#value)),
|
||||
}
|
||||
}
|
||||
|
||||
let deprecated = parse_deprecated_attribute(deprecated)?;
|
||||
let note = quote_option(deprecated.note);
|
||||
let since = quote_option(deprecated.since);
|
||||
|
||||
quote!(Some(crate::options_base::Deprecated { since: #since, message: #note }))
|
||||
} else {
|
||||
quote!(None)
|
||||
};
|
||||
|
||||
Ok(quote_spanned!(
|
||||
ident.span() => {
|
||||
visit.record_field(#kebab_name, crate::options_base::OptionField{
|
||||
|
@ -200,6 +220,7 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
|
|||
default: &#default,
|
||||
value_type: &#value_type,
|
||||
example: &#example,
|
||||
deprecated: #deprecated
|
||||
})
|
||||
}
|
||||
))
|
||||
|
@ -212,39 +233,109 @@ struct FieldAttributes {
|
|||
example: String,
|
||||
}
|
||||
|
||||
impl Parse for FieldAttributes {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let default = _parse_key_value(input, "default")?;
|
||||
input.parse::<Comma>()?;
|
||||
let value_type = _parse_key_value(input, "value_type")?;
|
||||
input.parse::<Comma>()?;
|
||||
let example = _parse_key_value(input, "example")?;
|
||||
if !input.is_empty() {
|
||||
input.parse::<Comma>()?;
|
||||
fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes> {
|
||||
let mut default = None;
|
||||
let mut value_type = None;
|
||||
let mut example = None;
|
||||
|
||||
attribute.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("default") {
|
||||
default = Some(get_string_literal(&meta, "default", "option")?.value());
|
||||
} else if meta.path.is_ident("value_type") {
|
||||
value_type = Some(get_string_literal(&meta, "value_type", "option")?.value());
|
||||
} else if meta.path.is_ident("example") {
|
||||
let example_text = get_string_literal(&meta, "value_type", "option")?.value();
|
||||
example = Some(dedent(&example_text).trim_matches('\n').to_string());
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
meta.path.span(),
|
||||
format!(
|
||||
"Deprecated meta {:?} is not supported by ruff's option macro.",
|
||||
meta.path.get_ident()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
default,
|
||||
value_type,
|
||||
example: dedent(&example).trim_matches('\n').to_string(),
|
||||
})
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let Some(default) = default else {
|
||||
return Err(syn::Error::new(attribute.span(), "Mandatory `default` field is missing in `#[option]` attribute. Specify the default using `#[option(default=\"..\")]`."));
|
||||
};
|
||||
|
||||
let Some(value_type) = value_type else {
|
||||
return Err(syn::Error::new(attribute.span(), "Mandatory `value_type` field is missing in `#[option]` attribute. Specify the value type using `#[option(value_type=\"..\")]`."));
|
||||
};
|
||||
|
||||
let Some(example) = example else {
|
||||
return Err(syn::Error::new(attribute.span(), "Mandatory `example` field is missing in `#[option]` attribute. Add an example using `#[option(example=\"..\")]`."));
|
||||
};
|
||||
|
||||
Ok(FieldAttributes {
|
||||
default,
|
||||
value_type,
|
||||
example,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_deprecated_attribute(attribute: &Attribute) -> syn::Result<DeprecatedAttribute> {
|
||||
let mut deprecated = DeprecatedAttribute::default();
|
||||
attribute.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("note") {
|
||||
deprecated.note = Some(get_string_literal(&meta, "note", "deprecated")?.value());
|
||||
} else if meta.path.is_ident("since") {
|
||||
deprecated.since = Some(get_string_literal(&meta, "since", "deprecated")?.value());
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
meta.path.span(),
|
||||
format!(
|
||||
"Deprecated meta {:?} is not supported by ruff's option macro.",
|
||||
meta.path.get_ident()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(deprecated)
|
||||
}
|
||||
|
||||
fn get_string_literal(
|
||||
meta: &ParseNestedMeta,
|
||||
meta_name: &str,
|
||||
attribute_name: &str,
|
||||
) -> syn::Result<syn::LitStr> {
|
||||
let expr: syn::Expr = meta.value()?.parse()?;
|
||||
|
||||
let mut value = &expr;
|
||||
while let syn::Expr::Group(e) = value {
|
||||
value = &e.expr;
|
||||
}
|
||||
|
||||
if let syn::Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(lit), ..
|
||||
}) = value
|
||||
{
|
||||
let suffix = lit.suffix();
|
||||
if !suffix.is_empty() {
|
||||
return Err(syn::Error::new(
|
||||
lit.span(),
|
||||
format!("unexpected suffix `{suffix}` on string literal"),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(lit.clone())
|
||||
} else {
|
||||
Err(syn::Error::new(
|
||||
expr.span(),
|
||||
format!("expected {attribute_name} attribute to be a string: `{meta_name} = \"...\"`"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result<String> {
|
||||
let ident: proc_macro2::Ident = input.parse()?;
|
||||
if ident != name {
|
||||
return Err(syn::Error::new(
|
||||
ident.span(),
|
||||
format!("Expected `{name}` name"),
|
||||
));
|
||||
}
|
||||
|
||||
input.parse::<Token![=]>()?;
|
||||
let value: Lit = input.parse()?;
|
||||
|
||||
match &value {
|
||||
Lit::Str(v) => Ok(v.value()),
|
||||
_ => Err(syn::Error::new(value.span(), "Expected literal string")),
|
||||
}
|
||||
#[derive(Default, Debug)]
|
||||
struct DeprecatedAttribute {
|
||||
since: Option<String>,
|
||||
note: Option<String>,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue