@@ -4,7 +4,7 @@ use anyhow::{anyhow, bail, Result};
44use darling:: { FromMeta , ToTokens } ;
55use proc_macro2:: TokenStream ;
66use quote:: quote;
7- use syn:: { Attribute , ItemImpl , Lit , Meta , NestedMeta } ;
7+ use syn:: { Attribute , AttributeArgs , ItemImpl , Lit , Meta , NestedMeta } ;
88
99use crate :: { constant:: Constant , method} ;
1010
@@ -15,14 +15,77 @@ pub enum Visibility {
1515 Private ,
1616}
1717
18+ #[ derive( Debug , Copy , Clone , FromMeta ) ]
19+ pub enum RenameRule {
20+ #[ darling( rename = "none" ) ]
21+ None ,
22+ #[ darling( rename = "camelCase" ) ]
23+ Camel ,
24+ #[ darling( rename = "snake_case" ) ]
25+ Snake ,
26+ }
27+
28+ impl Default for RenameRule {
29+ fn default ( ) -> Self {
30+ RenameRule :: Camel
31+ }
32+ }
33+
34+ impl RenameRule {
35+ /// Change case of an identifier.
36+ ///
37+ /// Magic methods are handled specially to make sure they're always cased
38+ /// correctly.
39+ pub fn rename ( & self , name : impl AsRef < str > ) -> String {
40+ let name = name. as_ref ( ) ;
41+ match self {
42+ RenameRule :: None => name. to_string ( ) ,
43+ rule => match name {
44+ "__construct" => "__construct" . to_string ( ) ,
45+ "__destruct" => "__destruct" . to_string ( ) ,
46+ "__call" => "__call" . to_string ( ) ,
47+ "__call_static" => "__callStatic" . to_string ( ) ,
48+ "__get" => "__get" . to_string ( ) ,
49+ "__set" => "__set" . to_string ( ) ,
50+ "__isset" => "__isset" . to_string ( ) ,
51+ "__unset" => "__unset" . to_string ( ) ,
52+ "__sleep" => "__sleep" . to_string ( ) ,
53+ "__wakeup" => "__wakeup" . to_string ( ) ,
54+ "__serialize" => "__serialize" . to_string ( ) ,
55+ "__unserialize" => "__unserialize" . to_string ( ) ,
56+ "__to_string" => "__toString" . to_string ( ) ,
57+ "__invoke" => "__invoke" . to_string ( ) ,
58+ "__set_state" => "__set_state" . to_string ( ) ,
59+ "__clone" => "__clone" . to_string ( ) ,
60+ "__debug_info" => "__debugInfo" . to_string ( ) ,
61+ field => match rule {
62+ Self :: Camel => ident_case:: RenameRule :: CamelCase . apply_to_field ( field) ,
63+ Self :: Snake => ident_case:: RenameRule :: SnakeCase . apply_to_field ( field) ,
64+ Self :: None => unreachable ! ( ) ,
65+ } ,
66+ } ,
67+ }
68+ }
69+ }
70+
1871#[ derive( Debug ) ]
1972pub enum ParsedAttribute {
2073 Default ( HashMap < String , Lit > ) ,
2174 Optional ( String ) ,
2275 Visibility ( Visibility ) ,
76+ Rename ( String ) ,
77+ }
78+
79+ #[ derive( Default , Debug , FromMeta ) ]
80+ #[ darling( default ) ]
81+ pub struct AttrArgs {
82+ rename_methods : Option < RenameRule > ,
2383}
2484
25- pub fn parser ( input : ItemImpl ) -> Result < TokenStream > {
85+ pub fn parser ( args : AttributeArgs , input : ItemImpl ) -> Result < TokenStream > {
86+ let args = AttrArgs :: from_list ( & args)
87+ . map_err ( |e| anyhow ! ( "Unable to parse attribute arguments: {:?}" , e) ) ?;
88+
2689 let ItemImpl { self_ty, items, .. } = input;
2790 let class_name = self_ty. to_token_stream ( ) . to_string ( ) ;
2891
@@ -61,7 +124,8 @@ pub fn parser(input: ItemImpl) -> Result<TokenStream> {
61124 }
62125 }
63126 syn:: ImplItem :: Method ( mut method) => {
64- let ( sig, method) = method:: parser ( & mut method) ?;
127+ let ( sig, method) =
128+ method:: parser ( & mut method, args. rename_methods . unwrap_or_default ( ) ) ?;
65129 class. methods . push ( method) ;
66130 sig
67131 }
@@ -107,6 +171,61 @@ pub fn parse_attribute(attr: &Attribute) -> Result<ParsedAttribute> {
107171 "public" => ParsedAttribute :: Visibility ( Visibility :: Public ) ,
108172 "protected" => ParsedAttribute :: Visibility ( Visibility :: Protected ) ,
109173 "private" => ParsedAttribute :: Visibility ( Visibility :: Private ) ,
174+ "rename" => {
175+ let ident = if let Meta :: List ( list) = meta {
176+ if let Some ( NestedMeta :: Lit ( lit) ) = list. nested . first ( ) {
177+ String :: from_value ( lit) . ok ( )
178+ } else {
179+ None
180+ }
181+ } else {
182+ None
183+ }
184+ . ok_or_else ( || anyhow ! ( "Invalid argument given for `#[rename] macro." ) ) ?;
185+
186+ ParsedAttribute :: Rename ( ident)
187+ }
110188 attr => bail ! ( "Invalid attribute `#[{}]`." , attr) ,
111189 } )
112190}
191+
192+ #[ cfg( test) ]
193+ mod tests {
194+ use super :: RenameRule ;
195+
196+ #[ test]
197+ fn test_rename_magic ( ) {
198+ for & ( magic, expected) in & [
199+ ( "__construct" , "__construct" ) ,
200+ ( "__destruct" , "__destruct" ) ,
201+ ( "__call" , "__call" ) ,
202+ ( "__call_static" , "__callStatic" ) ,
203+ ( "__get" , "__get" ) ,
204+ ( "__set" , "__set" ) ,
205+ ( "__isset" , "__isset" ) ,
206+ ( "__unset" , "__unset" ) ,
207+ ( "__sleep" , "__sleep" ) ,
208+ ( "__wakeup" , "__wakeup" ) ,
209+ ( "__serialize" , "__serialize" ) ,
210+ ( "__unserialize" , "__unserialize" ) ,
211+ ( "__to_string" , "__toString" ) ,
212+ ( "__invoke" , "__invoke" ) ,
213+ ( "__set_state" , "__set_state" ) ,
214+ ( "__clone" , "__clone" ) ,
215+ ( "__debug_info" , "__debugInfo" ) ,
216+ ] {
217+ assert_eq ! ( magic, RenameRule :: None . rename( magic) ) ;
218+ assert_eq ! ( expected, RenameRule :: Camel . rename( magic) ) ;
219+ assert_eq ! ( expected, RenameRule :: Snake . rename( magic) ) ;
220+ }
221+ }
222+
223+ #[ test]
224+ fn test_rename_php_methods ( ) {
225+ for & ( original, camel, snake) in & [ ( "get_name" , "getName" , "get_name" ) ] {
226+ assert_eq ! ( original, RenameRule :: None . rename( original) ) ;
227+ assert_eq ! ( camel, RenameRule :: Camel . rename( original) ) ;
228+ assert_eq ! ( snake, RenameRule :: Snake . rename( original) ) ;
229+ }
230+ }
231+ }
0 commit comments