Skip to content

Commit ff231ec

Browse files
vodikdavidcole1340
andauthored
Naively change the case of method identifiers to comply with PSR-1 (#63)
* Change the case of method identifiers to comply with PSR-1 * Expose methods as camelCase and add attributes for controlling renaming Add an attribute to methods to control the name they're exported under and an attribute to php_impl to override automatic case conversion conventions. * Cargo fmt * Add option for no method renaming Default to camel case renaming Co-authored-by: David Cole <david.cole1340@gmail.com>
1 parent 1483ef7 commit ff231ec

File tree

4 files changed

+135
-8
lines changed

4 files changed

+135
-8
lines changed

ext-php-rs-derive/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ proc-macro = true
1414
[dependencies]
1515
syn = { version = "1.0.68", features = ["full", "extra-traits"] }
1616
darling = "0.12"
17+
ident_case = "1.0.1"
1718
quote = "1.0.9"
1819
proc-macro2 = "1.0.26"
1920
lazy_static = "1.4.0"

ext-php-rs-derive/src/impl_.rs

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::{anyhow, bail, Result};
44
use darling::{FromMeta, ToTokens};
55
use proc_macro2::TokenStream;
66
use quote::quote;
7-
use syn::{Attribute, ItemImpl, Lit, Meta, NestedMeta};
7+
use syn::{Attribute, AttributeArgs, ItemImpl, Lit, Meta, NestedMeta};
88

99
use 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)]
1972
pub 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+
}

ext-php-rs-derive/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ pub fn php_startup(_: TokenStream, input: TokenStream) -> TokenStream {
9393
}
9494

9595
#[proc_macro_attribute]
96-
pub fn php_impl(_: TokenStream, input: TokenStream) -> TokenStream {
96+
pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
97+
let args = parse_macro_input!(args as AttributeArgs);
9798
let input = parse_macro_input!(input as ItemImpl);
9899

99-
match impl_::parser(input) {
100+
match impl_::parser(args, input) {
100101
Ok(parsed) => parsed,
101102
Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(),
102103
}

ext-php-rs-derive/src/method.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::collections::HashMap;
44

55
use crate::{
66
function,
7-
impl_::{parse_attribute, ParsedAttribute, Visibility},
7+
impl_::{parse_attribute, ParsedAttribute, RenameRule, Visibility},
88
};
99
use proc_macro2::{Ident, Span, TokenStream};
1010
use quote::quote;
@@ -34,16 +34,21 @@ pub struct Method {
3434
pub visibility: Visibility,
3535
}
3636

37-
pub fn parser(input: &mut ImplItemMethod) -> Result<(TokenStream, Method)> {
37+
pub fn parser(
38+
input: &mut ImplItemMethod,
39+
rename_rule: RenameRule,
40+
) -> Result<(TokenStream, Method)> {
3841
let mut defaults = HashMap::new();
3942
let mut optional = None;
4043
let mut visibility = Visibility::Public;
44+
let mut identifier = None;
4145

4246
for attr in input.attrs.iter() {
4347
match parse_attribute(attr)? {
4448
ParsedAttribute::Default(list) => defaults = list,
4549
ParsedAttribute::Optional(name) => optional = Some(name),
4650
ParsedAttribute::Visibility(vis) => visibility = vis,
51+
ParsedAttribute::Rename(ident) => identifier = Some(ident),
4752
}
4853
}
4954

@@ -92,8 +97,9 @@ pub fn parser(input: &mut ImplItemMethod) -> Result<(TokenStream, Method)> {
9297
}
9398
};
9499

100+
let name = identifier.unwrap_or_else(|| rename_rule.rename(ident.to_string()));
95101
let method = Method {
96-
name: ident.to_string(),
102+
name,
97103
ident: internal_ident.to_string(),
98104
args,
99105
optional,

0 commit comments

Comments
 (0)