@@ -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,64 @@ pub enum Visibility {
1515 Private ,
1616}
1717
18+ #[ derive( Debug , Copy , Clone , FromMeta ) ]
19+ pub enum RenameRule {
20+ #[ darling( rename = "camelCase" ) ]
21+ Camel ,
22+ #[ darling( rename = "snake_case" ) ]
23+ Snake ,
24+ }
25+
26+ impl RenameRule {
27+ /// Change case of an identifier.
28+ ///
29+ /// Magic methods are handled specially to make sure they're always cased
30+ /// correctly.
31+ pub fn rename ( & self , name : impl AsRef < str > ) -> String {
32+ match name. as_ref ( ) {
33+ "__construct" => "__construct" . to_string ( ) ,
34+ "__destruct" => "__destruct" . to_string ( ) ,
35+ "__call" => "__call" . to_string ( ) ,
36+ "__call_static" => "__callStatic" . to_string ( ) ,
37+ "__get" => "__get" . to_string ( ) ,
38+ "__set" => "__set" . to_string ( ) ,
39+ "__isset" => "__isset" . to_string ( ) ,
40+ "__unset" => "__unset" . to_string ( ) ,
41+ "__sleep" => "__sleep" . to_string ( ) ,
42+ "__wakeup" => "__wakeup" . to_string ( ) ,
43+ "__serialize" => "__serialize" . to_string ( ) ,
44+ "__unserialize" => "__unserialize" . to_string ( ) ,
45+ "__to_string" => "__toString" . to_string ( ) ,
46+ "__invoke" => "__invoke" . to_string ( ) ,
47+ "__set_state" => "__set_state" . to_string ( ) ,
48+ "__clone" => "__clone" . to_string ( ) ,
49+ "__debug_info" => "__debugInfo" . to_string ( ) ,
50+ field => match self {
51+ Self :: Camel => ident_case:: RenameRule :: CamelCase . apply_to_field ( field) ,
52+ Self :: Snake => ident_case:: RenameRule :: SnakeCase . apply_to_field ( field) ,
53+ } ,
54+ }
55+ }
56+ }
57+
1858#[ derive( Debug ) ]
1959pub enum ParsedAttribute {
2060 Default ( HashMap < String , Lit > ) ,
2161 Optional ( String ) ,
2262 Visibility ( Visibility ) ,
63+ Rename ( String ) ,
2364}
2465
25- pub fn parser ( input : ItemImpl ) -> Result < TokenStream > {
66+ #[ derive( Default , Debug , FromMeta ) ]
67+ #[ darling( default ) ]
68+ pub struct AttrArgs {
69+ rename_methods : Option < RenameRule > ,
70+ }
71+
72+ pub fn parser ( args : AttributeArgs , input : ItemImpl ) -> Result < TokenStream > {
73+ let args = AttrArgs :: from_list ( & args)
74+ . map_err ( |e| anyhow ! ( "Unable to parse attribute arguments: {:?}" , e) ) ?;
75+
2676 let ItemImpl { self_ty, items, .. } = input;
2777 let class_name = self_ty. to_token_stream ( ) . to_string ( ) ;
2878
@@ -61,7 +111,7 @@ pub fn parser(input: ItemImpl) -> Result<TokenStream> {
61111 }
62112 }
63113 syn:: ImplItem :: Method ( mut method) => {
64- let ( sig, method) = method:: parser ( & mut method) ?;
114+ let ( sig, method) = method:: parser ( & mut method, args . rename_methods . unwrap_or ( RenameRule :: Camel ) ) ?;
65115 class. methods . push ( method) ;
66116 sig
67117 }
@@ -107,6 +157,61 @@ pub fn parse_attribute(attr: &Attribute) -> Result<ParsedAttribute> {
107157 "public" => ParsedAttribute :: Visibility ( Visibility :: Public ) ,
108158 "protected" => ParsedAttribute :: Visibility ( Visibility :: Protected ) ,
109159 "private" => ParsedAttribute :: Visibility ( Visibility :: Private ) ,
160+ "rename" => {
161+ let ident = if let Meta :: List ( list) = meta {
162+ if let Some ( NestedMeta :: Lit ( lit) ) = list. nested . first ( ) {
163+ String :: from_value ( lit) . ok ( )
164+ } else {
165+ None
166+ }
167+ } else {
168+ None
169+ }
170+ . ok_or_else ( || anyhow ! ( "Invalid argument given for `#[rename] macro." ) ) ?;
171+
172+ ParsedAttribute :: Rename ( ident)
173+ }
110174 attr => bail ! ( "Invalid attribute `#[{}]`." , attr) ,
111175 } )
112176}
177+
178+ #[ cfg( test) ]
179+ mod tests {
180+ use super :: RenameRule ;
181+
182+ #[ test]
183+ fn test_rename_magic ( ) {
184+ for & ( magic, expected) in & [
185+ ( "__construct" , "__construct" ) ,
186+ ( "__destruct" , "__destruct" ) ,
187+ ( "__call" , "__call" ) ,
188+ ( "__call_static" , "__callStatic" ) ,
189+ ( "__get" , "__get" ) ,
190+ ( "__set" , "__set" ) ,
191+ ( "__isset" , "__isset" ) ,
192+ ( "__unset" , "__unset" ) ,
193+ ( "__sleep" , "__sleep" ) ,
194+ ( "__wakeup" , "__wakeup" ) ,
195+ ( "__serialize" , "__serialize" ) ,
196+ ( "__unserialize" , "__unserialize" ) ,
197+ ( "__to_string" , "__toString" ) ,
198+ ( "__invoke" , "__invoke" ) ,
199+ ( "__set_state" , "__set_state" ) ,
200+ ( "__clone" , "__clone" ) ,
201+ ( "__debug_info" , "__debugInfo" ) ,
202+ ] {
203+ assert_eq ! ( expected, RenameRule :: Camel . rename( magic) ) ;
204+ assert_eq ! ( expected, RenameRule :: Snake . rename( magic) ) ;
205+ }
206+ }
207+
208+ #[ test]
209+ fn test_rename_php_methods ( ) {
210+ for & ( original, camel, snake) in & [
211+ ( "get_name" , "getName" , "get_name" ) ,
212+ ] {
213+ assert_eq ! ( camel, RenameRule :: Camel . rename( original) ) ;
214+ assert_eq ! ( snake, RenameRule :: Snake . rename( original) ) ;
215+ }
216+ }
217+ }
0 commit comments