1
- use proc_macro2:: TokenTree ;
1
+ use proc_macro2:: { TokenStream , TokenTree } ;
2
2
use quote:: { quote, quote_spanned} ;
3
- use syn:: parse :: { Parse , ParseStream } ;
3
+ use syn:: meta :: ParseNestedMeta ;
4
4
use syn:: spanned:: Spanned ;
5
- use syn:: token:: Comma ;
6
5
use syn:: {
7
6
AngleBracketedGenericArguments , Attribute , Data , DataStruct , DeriveInput , ExprLit , Field ,
8
- Fields , Lit , LitStr , Meta , Path , PathArguments , PathSegment , Token , Type , TypePath ,
7
+ Fields , Lit , LitStr , Meta , Path , PathArguments , PathSegment , Type , TypePath ,
9
8
} ;
10
9
11
10
use ruff_python_trivia:: textwrap:: dedent;
12
11
13
- pub ( crate ) fn derive_impl ( input : DeriveInput ) -> syn:: Result < proc_macro2 :: TokenStream > {
12
+ pub ( crate ) fn derive_impl ( input : DeriveInput ) -> syn:: Result < TokenStream > {
14
13
let DeriveInput {
15
14
ident,
16
15
data,
@@ -190,16 +189,38 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
190
189
default,
191
190
value_type,
192
191
example,
193
- } = attr . parse_args :: < FieldAttributes > ( ) ?;
192
+ } = parse_field_attributes ( attr ) ?;
194
193
let kebab_name = LitStr :: new ( & ident. to_string ( ) . replace ( '_' , "-" ) , ident. span ( ) ) ;
195
194
195
+ let deprecated = if let Some ( deprecated) = field
196
+ . attrs
197
+ . iter ( )
198
+ . find ( |attr| attr. path ( ) . is_ident ( "deprecated" ) )
199
+ {
200
+ fn quote_option ( option : Option < String > ) -> TokenStream {
201
+ match option {
202
+ None => quote ! ( None ) ,
203
+ Some ( value) => quote ! ( Some ( #value) ) ,
204
+ }
205
+ }
206
+
207
+ let deprecated = parse_deprecated_attribute ( deprecated) ?;
208
+ let note = quote_option ( deprecated. note ) ;
209
+ let since = quote_option ( deprecated. since ) ;
210
+
211
+ quote ! ( Some ( crate :: options_base:: Deprecated { since: #since, message: #note } ) )
212
+ } else {
213
+ quote ! ( None )
214
+ } ;
215
+
196
216
Ok ( quote_spanned ! (
197
217
ident. span( ) => {
198
218
visit. record_field( #kebab_name, crate :: options_base:: OptionField {
199
219
doc: & #doc,
200
220
default : & #default ,
201
221
value_type: & #value_type,
202
222
example: & #example,
223
+ deprecated: #deprecated
203
224
} )
204
225
}
205
226
) )
@@ -212,39 +233,109 @@ struct FieldAttributes {
212
233
example : String ,
213
234
}
214
235
215
- impl Parse for FieldAttributes {
216
- fn parse ( input : ParseStream ) -> syn:: Result < Self > {
217
- let default = _parse_key_value ( input, "default" ) ?;
218
- input. parse :: < Comma > ( ) ?;
219
- let value_type = _parse_key_value ( input, "value_type" ) ?;
220
- input. parse :: < Comma > ( ) ?;
221
- let example = _parse_key_value ( input, "example" ) ?;
222
- if !input. is_empty ( ) {
223
- input. parse :: < Comma > ( ) ?;
236
+ fn parse_field_attributes ( attribute : & Attribute ) -> syn:: Result < FieldAttributes > {
237
+ let mut default = None ;
238
+ let mut value_type = None ;
239
+ let mut example = None ;
240
+
241
+ attribute. parse_nested_meta ( |meta| {
242
+ if meta. path . is_ident ( "default" ) {
243
+ default = Some ( get_string_literal ( & meta, "default" , "option" ) ?. value ( ) ) ;
244
+ } else if meta. path . is_ident ( "value_type" ) {
245
+ value_type = Some ( get_string_literal ( & meta, "value_type" , "option" ) ?. value ( ) ) ;
246
+ } else if meta. path . is_ident ( "example" ) {
247
+ let example_text = get_string_literal ( & meta, "value_type" , "option" ) ?. value ( ) ;
248
+ example = Some ( dedent ( & example_text) . trim_matches ( '\n' ) . to_string ( ) ) ;
249
+ } else {
250
+ return Err ( syn:: Error :: new (
251
+ meta. path . span ( ) ,
252
+ format ! (
253
+ "Deprecated meta {:?} is not supported by ruff's option macro." ,
254
+ meta. path. get_ident( )
255
+ ) ,
256
+ ) ) ;
224
257
}
225
258
226
- Ok ( Self {
227
- default,
228
- value_type,
229
- example : dedent ( & example) . trim_matches ( '\n' ) . to_string ( ) ,
230
- } )
231
- }
259
+ Ok ( ( ) )
260
+ } ) ?;
261
+
262
+ let Some ( default) = default else {
263
+ return Err ( syn:: Error :: new ( attribute. span ( ) , "Mandatory `default` field is missing in `#[option]` attribute. Specify the default using `#[option(default=\" ..\" )]`." ) ) ;
264
+ } ;
265
+
266
+ let Some ( value_type) = value_type else {
267
+ return Err ( syn:: Error :: new ( attribute. span ( ) , "Mandatory `value_type` field is missing in `#[option]` attribute. Specify the value type using `#[option(value_type=\" ..\" )]`." ) ) ;
268
+ } ;
269
+
270
+ let Some ( example) = example else {
271
+ return Err ( syn:: Error :: new ( attribute. span ( ) , "Mandatory `example` field is missing in `#[option]` attribute. Add an example using `#[option(example=\" ..\" )]`." ) ) ;
272
+ } ;
273
+
274
+ Ok ( FieldAttributes {
275
+ default,
276
+ value_type,
277
+ example,
278
+ } )
232
279
}
233
280
234
- fn _parse_key_value ( input : ParseStream , name : & str ) -> syn:: Result < String > {
235
- let ident: proc_macro2:: Ident = input. parse ( ) ?;
236
- if ident != name {
237
- return Err ( syn:: Error :: new (
238
- ident. span ( ) ,
239
- format ! ( "Expected `{name}` name" ) ,
240
- ) ) ;
281
+ fn parse_deprecated_attribute ( attribute : & Attribute ) -> syn:: Result < DeprecatedAttribute > {
282
+ let mut deprecated = DeprecatedAttribute :: default ( ) ;
283
+ attribute. parse_nested_meta ( |meta| {
284
+ if meta. path . is_ident ( "note" ) {
285
+ deprecated. note = Some ( get_string_literal ( & meta, "note" , "deprecated" ) ?. value ( ) ) ;
286
+ } else if meta. path . is_ident ( "since" ) {
287
+ deprecated. since = Some ( get_string_literal ( & meta, "since" , "deprecated" ) ?. value ( ) ) ;
288
+ } else {
289
+ return Err ( syn:: Error :: new (
290
+ meta. path . span ( ) ,
291
+ format ! (
292
+ "Deprecated meta {:?} is not supported by ruff's option macro." ,
293
+ meta. path. get_ident( )
294
+ ) ,
295
+ ) ) ;
296
+ }
297
+
298
+ Ok ( ( ) )
299
+ } ) ?;
300
+
301
+ Ok ( deprecated)
302
+ }
303
+
304
+ fn get_string_literal (
305
+ meta : & ParseNestedMeta ,
306
+ meta_name : & str ,
307
+ attribute_name : & str ,
308
+ ) -> syn:: Result < syn:: LitStr > {
309
+ let expr: syn:: Expr = meta. value ( ) ?. parse ( ) ?;
310
+
311
+ let mut value = & expr;
312
+ while let syn:: Expr :: Group ( e) = value {
313
+ value = & e. expr ;
241
314
}
242
315
243
- input. parse :: < Token ! [ =] > ( ) ?;
244
- let value: Lit = input. parse ( ) ?;
316
+ if let syn:: Expr :: Lit ( ExprLit {
317
+ lit : Lit :: Str ( lit) , ..
318
+ } ) = value
319
+ {
320
+ let suffix = lit. suffix ( ) ;
321
+ if !suffix. is_empty ( ) {
322
+ return Err ( syn:: Error :: new (
323
+ lit. span ( ) ,
324
+ format ! ( "unexpected suffix `{suffix}` on string literal" ) ,
325
+ ) ) ;
326
+ }
245
327
246
- match & value {
247
- Lit :: Str ( v) => Ok ( v. value ( ) ) ,
248
- _ => Err ( syn:: Error :: new ( value. span ( ) , "Expected literal string" ) ) ,
328
+ Ok ( lit. clone ( ) )
329
+ } else {
330
+ Err ( syn:: Error :: new (
331
+ expr. span ( ) ,
332
+ format ! ( "expected {attribute_name} attribute to be a string: `{meta_name} = \" ...\" `" ) ,
333
+ ) )
249
334
}
250
335
}
336
+
337
+ #[ derive( Default , Debug ) ]
338
+ struct DeprecatedAttribute {
339
+ since : Option < String > ,
340
+ note : Option < String > ,
341
+ }
0 commit comments