diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 97ea320483..d7f527b4a2 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -14,11 +14,11 @@ proc-macro = true [dependencies] syn = { version = "2.0.100", features = ["full", "extra-traits", "printing"] } darling = "0.20" -ident_case = "1.0.1" quote = "1.0.9" proc-macro2 = "1.0.26" lazy_static = "1.4.0" anyhow = "1.0" +convert_case = "0.8.0" [lints.rust] missing_docs = "warn" diff --git a/crates/macros/src/class.rs b/crates/macros/src/class.rs index e4c05bfcee..2e02d1d36f 100644 --- a/crates/macros/src/class.rs +++ b/crates/macros/src/class.rs @@ -5,7 +5,7 @@ use quote::quote; use syn::{Attribute, Expr, Fields, ItemStruct}; use crate::helpers::get_docs; -use crate::parsing::PhpRename; +use crate::parsing::{PhpRename, RenameRule}; use crate::prelude::*; #[derive(FromAttributes, Debug, Default)] @@ -35,7 +35,7 @@ pub struct ClassEntryAttribute { pub fn parser(mut input: ItemStruct) -> Result { let attr = StructAttributes::from_attributes(&input.attrs)?; let ident = &input.ident; - let name = attr.rename.rename(ident.to_string()); + let name = attr.rename.rename(ident.to_string(), RenameRule::Pascal); let docs = get_docs(&attr.attrs)?; input.attrs.retain(|attr| !attr.path().is_ident("php")); @@ -107,7 +107,9 @@ struct Property<'a> { impl Property<'_> { pub fn name(&self) -> String { - self.attr.rename.rename(self.ident.to_string()) + self.attr + .rename + .rename(self.ident.to_string(), RenameRule::Camel) } } diff --git a/crates/macros/src/constant.rs b/crates/macros/src/constant.rs index d5f6efc23b..bd7262f5e3 100644 --- a/crates/macros/src/constant.rs +++ b/crates/macros/src/constant.rs @@ -4,7 +4,7 @@ use quote::{format_ident, quote}; use syn::ItemConst; use crate::helpers::get_docs; -use crate::parsing::PhpRename; +use crate::parsing::{PhpRename, RenameRule}; use crate::prelude::*; const INTERNAL_CONST_DOC_PREFIX: &str = "_internal_const_docs_"; @@ -23,7 +23,9 @@ pub(crate) struct PhpConstAttribute { pub fn parser(mut item: ItemConst) -> Result { let attr = PhpConstAttribute::from_attributes(&item.attrs)?; - let name = attr.rename.rename(item.ident.to_string()); + let name = attr + .rename + .rename(item.ident.to_string(), RenameRule::ScreamingSnake); let name_ident = format_ident!("{INTERNAL_CONST_NAME_PREFIX}{}", item.ident); let docs = get_docs(&attr.attrs)?; diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index c05c3767a8..1afcfa3b42 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -7,7 +7,7 @@ use syn::spanned::Spanned as _; use syn::{Expr, FnArg, GenericArgument, ItemFn, PatType, PathArguments, Type, TypePath}; use crate::helpers::get_docs; -use crate::parsing::{PhpRename, Visibility}; +use crate::parsing::{PhpRename, RenameRule, Visibility}; use crate::prelude::*; use crate::syn_ext::DropLifetimes; @@ -46,7 +46,9 @@ pub fn parser(mut input: ItemFn) -> Result { let func = Function::new( &input.sig, - php_attr.rename.rename(input.sig.ident.to_string()), + php_attr + .rename + .rename(input.sig.ident.to_string(), RenameRule::Snake), args, php_attr.optional, docs, diff --git a/crates/macros/src/impl_.rs b/crates/macros/src/impl_.rs index ac0e6a8d7e..585ec51c8b 100644 --- a/crates/macros/src/impl_.rs +++ b/crates/macros/src/impl_.rs @@ -8,7 +8,7 @@ use syn::{Expr, Ident, ItemImpl}; use crate::constant::PhpConstAttribute; use crate::function::{Args, CallType, Function, MethodReceiver}; use crate::helpers::get_docs; -use crate::parsing::{MethodRename, PhpRename, Rename, RenameRule, Visibility}; +use crate::parsing::{PhpRename, RenameRule, Visibility}; use crate::prelude::*; /// Method types. @@ -48,8 +48,7 @@ pub fn parser(mut input: ItemImpl) -> Result { let mut parsed = ParsedImpl::new( path, args.rename_methods.unwrap_or(RenameRule::Camel), - args.rename_constants - .unwrap_or(RenameRule::ScreamingSnakeCase), + args.rename_constants.unwrap_or(RenameRule::ScreamingSnake), ); parsed.parse(input.items.iter_mut())?; @@ -184,8 +183,9 @@ impl<'a> ParsedImpl<'a> { match items { syn::ImplItem::Const(c) => { let attr = PhpConstAttribute::from_attributes(&c.attrs)?; - let name = c.ident.rename(self.rename_constants); - let name = attr.rename.rename(name); + let name = attr + .rename + .rename(c.ident.to_string(), self.rename_constants); let docs = get_docs(&attr.attrs)?; c.attrs.retain(|attr| !attr.path().is_ident("php")); @@ -197,8 +197,9 @@ impl<'a> ParsedImpl<'a> { } syn::ImplItem::Fn(method) => { let attr = PhpFunctionImplAttribute::from_attributes(&method.attrs)?; - let name = method.sig.ident.rename_method(self.rename_methods); - let name = attr.rename.rename(name); + let name = attr + .rename + .rename_method(method.sig.ident.to_string(), self.rename_methods); let docs = get_docs(&attr.attrs)?; method.attrs.retain(|attr| !attr.path().is_ident("php")); diff --git a/crates/macros/src/parsing.rs b/crates/macros/src/parsing.rs index b3e1d21e65..78443b50de 100644 --- a/crates/macros/src/parsing.rs +++ b/crates/macros/src/parsing.rs @@ -1,3 +1,4 @@ +use convert_case::{Case, Casing}; use darling::FromMeta; const MAGIC_METHOD: [&str; 17] = [ @@ -46,16 +47,20 @@ pub struct PhpRename { } impl PhpRename { - pub fn rename(&self, name: impl AsRef) -> String { - self.name.as_ref().map_or_else( - || { - let name = name.as_ref(); - self.rename - .as_ref() - .map_or_else(|| name.to_string(), |r| name.rename(*r)) - }, - ToString::to_string, - ) + pub fn rename(&self, name: impl AsRef, default: RenameRule) -> String { + if let Some(name) = self.name.as_ref() { + name.to_string() + } else { + name.as_ref().rename(self.rename.unwrap_or(default)) + } + } + + pub fn rename_method(&self, name: impl AsRef, default: RenameRule) -> String { + if let Some(name) = self.name.as_ref() { + name.to_string() + } else { + name.as_ref().rename_method(self.rename.unwrap_or(default)) + } } } @@ -76,53 +81,45 @@ pub enum RenameRule { Pascal, /// Renames to `UPPER_SNAKE_CASE`. #[darling(rename = "UPPER_CASE")] - ScreamingSnakeCase, + ScreamingSnake, } impl RenameRule { fn rename(self, value: impl AsRef) -> String { match self { Self::None => value.as_ref().to_string(), - Self::Camel => ident_case::RenameRule::CamelCase.apply_to_field(value.as_ref()), - Self::Pascal => ident_case::RenameRule::PascalCase.apply_to_field(value.as_ref()), - Self::Snake => ident_case::RenameRule::SnakeCase.apply_to_field(value.as_ref()), - Self::ScreamingSnakeCase => { - ident_case::RenameRule::ScreamingSnakeCase.apply_to_field(value.as_ref()) - } + Self::Camel => value.as_ref().to_case(Case::Camel), + Self::Pascal => value.as_ref().to_case(Case::Pascal), + Self::Snake => value.as_ref().to_case(Case::Snake), + Self::ScreamingSnake => value.as_ref().to_case(Case::Constant), } } } -impl Rename for &str { - fn rename(&self, rule: RenameRule) -> String { - rule.rename(self) - } -} - -impl Rename for syn::Ident { +impl Rename for T +where + T: ToString, +{ fn rename(&self, rule: RenameRule) -> String { - let s = self.to_string(); - rule.rename(s) + rule.rename(self.to_string()) } } -impl MethodRename for syn::Ident { - fn rename_method(&self, rule: RenameRule) -> String { - self.to_string().as_str().rename_method(rule) - } -} - -impl MethodRename for &str { +impl MethodRename for T +where + T: ToString + Rename, +{ fn rename_method(&self, rule: RenameRule) -> String { + let original = self.to_string(); match rule { - RenameRule::None => (*self).to_string(), + RenameRule::None => original, _ => { - if MAGIC_METHOD.contains(self) { - match *self { + if MAGIC_METHOD.contains(&original.as_str()) { + match original.as_str() { "__to_string" => "__toString".to_string(), "__debug_info" => "__debugInfo".to_string(), "__call_static" => "__callStatic".to_string(), - _ => (*self).to_string(), + _ => original, } } else { self.rename(rule) @@ -144,33 +141,86 @@ mod tests { name: Some("test".to_string()), rename: None, }; - assert_eq!(rename.rename("test"), "test"); - assert_eq!(rename.rename("Test"), "test"); - assert_eq!(rename.rename("TEST"), "test"); + assert_eq!(rename.rename("testCase", RenameRule::Snake), "test"); + assert_eq!(rename.rename("TestCase", RenameRule::Snake), "test"); + assert_eq!(rename.rename("TEST_CASE", RenameRule::Snake), "test"); + + let rename = PhpRename { + name: None, + rename: Some(RenameRule::ScreamingSnake), + }; + assert_eq!(rename.rename("testCase", RenameRule::Snake), "TEST_CASE"); + assert_eq!(rename.rename("TestCase", RenameRule::Snake), "TEST_CASE"); + assert_eq!(rename.rename("TEST_CASE", RenameRule::Snake), "TEST_CASE"); + + let rename = PhpRename { + name: Some("test".to_string()), + rename: Some(RenameRule::ScreamingSnake), + }; + assert_eq!(rename.rename("testCase", RenameRule::Snake), "test"); + assert_eq!(rename.rename("TestCase", RenameRule::Snake), "test"); + assert_eq!(rename.rename("TEST_CASE", RenameRule::Snake), "test"); + + let rename = PhpRename { + name: None, + rename: None, + }; + assert_eq!(rename.rename("testCase", RenameRule::Snake), "test_case"); + assert_eq!(rename.rename("TestCase", RenameRule::Snake), "test_case"); + assert_eq!(rename.rename("TEST_CASE", RenameRule::Snake), "test_case"); + } + + #[test] + fn php_rename_method() { + let rename = PhpRename { + name: Some("test".to_string()), + rename: None, + }; + assert_eq!(rename.rename_method("testCase", RenameRule::Snake), "test"); + assert_eq!(rename.rename_method("TestCase", RenameRule::Snake), "test"); + assert_eq!(rename.rename_method("TEST_CASE", RenameRule::Snake), "test"); let rename = PhpRename { name: None, - rename: Some(RenameRule::ScreamingSnakeCase), + rename: Some(RenameRule::ScreamingSnake), }; - assert_eq!(rename.rename("test"), "TEST"); - assert_eq!(rename.rename("Test"), "TEST"); - assert_eq!(rename.rename("TEST"), "TEST"); + assert_eq!( + rename.rename_method("testCase", RenameRule::Snake), + "TEST_CASE" + ); + assert_eq!( + rename.rename_method("TestCase", RenameRule::Snake), + "TEST_CASE" + ); + assert_eq!( + rename.rename_method("TEST_CASE", RenameRule::Snake), + "TEST_CASE" + ); let rename = PhpRename { name: Some("test".to_string()), - rename: Some(RenameRule::ScreamingSnakeCase), + rename: Some(RenameRule::ScreamingSnake), }; - assert_eq!(rename.rename("test"), "test"); - assert_eq!(rename.rename("Test"), "test"); - assert_eq!(rename.rename("TEST"), "test"); + assert_eq!(rename.rename_method("testCase", RenameRule::Snake), "test"); + assert_eq!(rename.rename_method("TestCase", RenameRule::Snake), "test"); + assert_eq!(rename.rename_method("TEST_CASE", RenameRule::Snake), "test"); let rename = PhpRename { name: None, rename: None, }; - assert_eq!(rename.rename("test"), "test"); - assert_eq!(rename.rename("Test"), "Test"); - assert_eq!(rename.rename("TEST"), "TEST"); + assert_eq!( + rename.rename_method("testCase", RenameRule::Snake), + "test_case" + ); + assert_eq!( + rename.rename_method("TestCase", RenameRule::Snake), + "test_case" + ); + assert_eq!( + rename.rename_method("TEST_CASE", RenameRule::Snake), + "test_case" + ); } #[test] @@ -195,12 +245,53 @@ mod tests { ("__debug_info", "__debugInfo"), ] { assert_eq!(magic, magic.rename_method(RenameRule::None)); + assert_eq!( + magic, + PhpRename { + name: None, + rename: Some(RenameRule::None) + } + .rename_method(magic, RenameRule::ScreamingSnake) + ); + assert_eq!(expected, magic.rename_method(RenameRule::Camel)); + assert_eq!( + expected, + PhpRename { + name: None, + rename: Some(RenameRule::Camel) + } + .rename_method(magic, RenameRule::ScreamingSnake) + ); + assert_eq!(expected, magic.rename_method(RenameRule::Pascal)); + assert_eq!( + expected, + PhpRename { + name: None, + rename: Some(RenameRule::Pascal) + } + .rename_method(magic, RenameRule::ScreamingSnake) + ); + assert_eq!(expected, magic.rename_method(RenameRule::Snake)); assert_eq!( expected, - magic.rename_method(RenameRule::ScreamingSnakeCase) + PhpRename { + name: None, + rename: Some(RenameRule::Snake) + } + .rename_method(magic, RenameRule::ScreamingSnake) + ); + + assert_eq!(expected, magic.rename_method(RenameRule::ScreamingSnake)); + assert_eq!( + expected, + PhpRename { + name: None, + rename: Some(RenameRule::ScreamingSnake) + } + .rename_method(magic, RenameRule::Camel) ); } } @@ -215,7 +306,7 @@ mod tests { assert_eq!(snake, original.rename_method(RenameRule::Snake)); assert_eq!( screaming_snake, - original.rename_method(RenameRule::ScreamingSnakeCase) + original.rename_method(RenameRule::ScreamingSnake) ); } @@ -227,9 +318,6 @@ mod tests { assert_eq!(camel, original.rename(RenameRule::Camel)); assert_eq!(pascal, original.rename(RenameRule::Pascal)); assert_eq!(snake, original.rename(RenameRule::Snake)); - assert_eq!( - screaming_snake, - original.rename(RenameRule::ScreamingSnakeCase) - ); + assert_eq!(screaming_snake, original.rename(RenameRule::ScreamingSnake)); } } diff --git a/guide/src/migration-guides/v0.14.md b/guide/src/migration-guides/v0.14.md index 8cd8f3151a..429158e550 100644 --- a/guide/src/migration-guides/v0.14.md +++ b/guide/src/migration-guides/v0.14.md @@ -204,6 +204,19 @@ the following variants are equivalent: #[php(rename = case, vis = "public")] ``` +### Renaming and Case Changes + +Default case was adjusted to match PSR standards: +- Class names are now `PascalCase` +- Property names are now `camelCase` +- Method names are now `camelCase` +- Constant names are now `UPPER_CASE` +- Function names are now `snake_case` + +This can be changed using the `rename` attribute on the item. +Additionally, the `rename_methods` and `rename_consts` attributes can be used +to change the case of all methods and constants in a class. + #### `name` vs `rename` Previously the (re)name parameter was used to rename items. This has been diff --git a/tests/src/integration/class/class.php b/tests/src/integration/class/class.php index 48bdb945a4..39b97b788e 100644 --- a/tests/src/integration/class/class.php +++ b/tests/src/integration/class/class.php @@ -15,10 +15,11 @@ $class->setNumber(2023); assert($class->getNumber() === 2023); +var_dump($class); // Tests #prop decorator -assert($class->boolean); -$class->boolean = false; -assert($class->boolean === false); +assert($class->booleanProp); +$class->booleanProp = false; +assert($class->booleanProp === false); // Call regular from object assert($class->staticCall('Php') === 'Hello Php'); @@ -31,7 +32,7 @@ $ex = new TestClassExtends(); assert_exception_thrown(fn() => throw $ex); -assert_exception_thrown(fn() => throw_exception()); +assert_exception_thrown(fn() => throwException()); $arrayAccess = new TestClassArrayAccess(); assert_exception_thrown(fn() => $arrayAccess[0] = 'foo'); diff --git a/tests/src/integration/class/mod.rs b/tests/src/integration/class/mod.rs index 4d01af67ca..3f32430e24 100644 --- a/tests/src/integration/class/mod.rs +++ b/tests/src/integration/class/mod.rs @@ -6,7 +6,7 @@ pub struct TestClass { string: String, number: i32, #[php(prop)] - boolean: bool, + boolean_prop: bool, } #[php_impl] @@ -41,7 +41,7 @@ pub fn test_class(string: String, number: i32) -> TestClass { TestClass { string, number, - boolean: true, + boolean_prop: true, } }