Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 3 additions & 43 deletions crates/macros/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use syn::{Ident, ItemImpl, Lit};
use crate::constant::PhpConstAttribute;
use crate::function::{Args, CallType, Function, MethodReceiver};
use crate::helpers::get_docs;
use crate::parsing::{PhpRename, RenameRule, Visibility};
use crate::parsing::{MethodRename, PhpRename, Rename, RenameRule, Visibility};
use crate::prelude::*;

/// Method types.
Expand Down Expand Up @@ -184,7 +184,7 @@ impl<'a> ParsedImpl<'a> {
match items {
syn::ImplItem::Const(c) => {
let attr = PhpConstAttribute::from_attributes(&c.attrs)?;
let name = self.rename_constants.rename(c.ident.to_string());
let name = c.ident.rename(&self.rename_constants);
let name = attr.rename.rename(name);
let docs = get_docs(&attr.attrs)?;
c.attrs.retain(|attr| !attr.path().is_ident("php"));
Expand All @@ -197,7 +197,7 @@ impl<'a> ParsedImpl<'a> {
}
syn::ImplItem::Fn(method) => {
let attr = PhpFunctionImplAttribute::from_attributes(&method.attrs)?;
let name = self.rename_methods.rename(method.sig.ident.to_string());
let name = method.sig.ident.rename_method(&self.rename_methods);
let name = attr.rename.rename(name);
let docs = get_docs(&attr.attrs)?;
method.attrs.retain(|attr| !attr.path().is_ident("php"));
Expand Down Expand Up @@ -318,43 +318,3 @@ impl quote::ToTokens for FnBuilder {
.to_tokens(tokens);
}
}

#[cfg(test)]
mod tests {
use super::RenameRule;

#[test]
fn test_rename_magic() {
for &(magic, expected) in &[
("__construct", "__construct"),
("__destruct", "__destruct"),
("__call", "__call"),
("__call_static", "__callStatic"),
("__get", "__get"),
("__set", "__set"),
("__isset", "__isset"),
("__unset", "__unset"),
("__sleep", "__sleep"),
("__wakeup", "__wakeup"),
("__serialize", "__serialize"),
("__unserialize", "__unserialize"),
("__to_string", "__toString"),
("__invoke", "__invoke"),
("__set_state", "__set_state"),
("__clone", "__clone"),
("__debug_info", "__debugInfo"),
] {
assert_eq!(magic, RenameRule::None.rename(magic));
assert_eq!(expected, RenameRule::Camel.rename(magic));
assert_eq!(expected, RenameRule::Snake.rename(magic));
}
}

#[test]
fn test_rename_php_methods() {
let &(original, camel, snake) = &("get_name", "getName", "get_name");
assert_eq!(original, RenameRule::None.rename(original));
assert_eq!(camel, RenameRule::Camel.rename(original));
assert_eq!(snake, RenameRule::Snake.rename(original));
}
}
159 changes: 109 additions & 50 deletions crates/macros/src/parsing.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
use darling::FromMeta;

const MAGIC_METHOD: [&str; 17] = [
"__construct",
"__destruct",
"__call",
"__call_static",
"__get",
"__set",
"__isset",
"__unset",
"__sleep",
"__wakeup",
"__serialize",
"__unserialize",
"__to_string",
"__invoke",
"__set_state",
"__clone",
"__debug_info",
];

#[derive(Debug, FromMeta)]
pub enum Visibility {
#[darling(rename = "public")]
Expand All @@ -10,6 +30,14 @@ pub enum Visibility {
Protected,
}

pub trait Rename {
fn rename(&self, rule: &RenameRule) -> String;
}

pub trait MethodRename: Rename {
fn rename_method(&self, rule: &RenameRule) -> String;
}

#[derive(FromMeta, Debug, Default)]
#[darling(default)]
pub struct PhpRename {
Expand All @@ -24,7 +52,7 @@ impl PhpRename {
let name = name.as_ref();
self.rename
.as_ref()
.map_or_else(|| name.to_string(), |r| r.rename(name))
.map_or_else(|| name.to_string(), |r| name.rename(r))
},
ToString::to_string,
)
Expand Down Expand Up @@ -52,52 +80,66 @@ pub enum RenameRule {
}

impl RenameRule {
/// Change case of an identifier.
///
/// Magic methods are handled specially to make sure they're always cased
/// correctly.
pub fn rename(self, name: impl AsRef<str>) -> String {
let name = name.as_ref();
match self {
RenameRule::None => name.to_string(),
rule => match name {
"__construct" => "__construct".to_string(),
"__destruct" => "__destruct".to_string(),
"__call" => "__call".to_string(),
"__call_static" => "__callStatic".to_string(),
"__get" => "__get".to_string(),
"__set" => "__set".to_string(),
"__isset" => "__isset".to_string(),
"__unset" => "__unset".to_string(),
"__sleep" => "__sleep".to_string(),
"__wakeup" => "__wakeup".to_string(),
"__serialize" => "__serialize".to_string(),
"__unserialize" => "__unserialize".to_string(),
"__to_string" => "__toString".to_string(),
"__invoke" => "__invoke".to_string(),
"__set_state" => "__set_state".to_string(),
"__clone" => "__clone".to_string(),
"__debug_info" => "__debugInfo".to_string(),
field => match rule {
Self::Camel => ident_case::RenameRule::CamelCase.apply_to_field(field),
Self::Pascal => ident_case::RenameRule::PascalCase.apply_to_field(field),
Self::Snake => ident_case::RenameRule::SnakeCase.apply_to_field(field),
Self::ScreamingSnakeCase => {
ident_case::RenameRule::ScreamingSnakeCase.apply_to_field(field)
fn rename(&self, value: impl AsRef<str>) -> 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())
}
}
}
}

impl Rename for &str {
fn rename(&self, rule: &RenameRule) -> String {
rule.rename(self)
}
}

impl Rename for syn::Ident {
fn rename(&self, rule: &RenameRule) -> String {
let s = self.to_string();
rule.rename(s)
}
}

impl MethodRename for syn::Ident {
fn rename_method(&self, rule: &RenameRule) -> String {
self.to_string().as_str().rename_method(rule)
}
}

impl MethodRename for &str {
fn rename_method(&self, rule: &RenameRule) -> String {
match rule {
RenameRule::None => self.to_string(),
_ => {
if MAGIC_METHOD.contains(self) {
match *self {
"__to_string" => "__toString".to_string(),
"__debug_info" => "__debugInfo".to_string(),
"__call_static" => "__callStatic".to_string(),
_ => self.to_string(),
}
Self::None => unreachable!(),
},
},
} else {
self.rename(rule)
}
}
}
}
}

#[cfg(test)]
mod tests {
use super::{PhpRename, RenameRule};
use crate::parsing::{MethodRename, Rename};

use super::{PhpRename, RenameRule, MAGIC_METHOD};

#[test]
fn test_php_rename() {
fn php_rename() {
let rename = PhpRename {
name: Some("test".to_string()),
rename: None,
Expand Down Expand Up @@ -132,7 +174,7 @@ mod tests {
}

#[test]
fn test_rename_magic() {
fn rename_magic_method() {
for &(magic, expected) in &[
("__construct", "__construct"),
("__destruct", "__destruct"),
Expand All @@ -152,25 +194,42 @@ mod tests {
("__clone", "__clone"),
("__debug_info", "__debugInfo"),
] {
assert_eq!(magic, RenameRule::None.rename(magic));
assert_eq!(expected, RenameRule::Camel.rename(magic));
assert_eq!(expected, RenameRule::Pascal.rename(magic));
assert_eq!(expected, RenameRule::Snake.rename(magic));
assert_eq!(expected, RenameRule::ScreamingSnakeCase.rename(magic));
assert_eq!(magic, magic.rename_method(&RenameRule::None));
assert_eq!(expected, magic.rename_method(&RenameRule::Camel));
assert_eq!(expected, magic.rename_method(&RenameRule::Pascal));
assert_eq!(expected, magic.rename_method(&RenameRule::Snake));
assert_eq!(
expected,
magic.rename_method(&RenameRule::ScreamingSnakeCase)
);
}
}

#[test]
fn test_rename_php_methods() {
fn rename_method() {
let &(original, camel, snake, pascal, screaming_snake) =
&("get_name", "getName", "get_name", "GetName", "GET_NAME");
assert_eq!(original, original.rename_method(&RenameRule::None));
assert_eq!(camel, original.rename_method(&RenameRule::Camel));
assert_eq!(pascal, original.rename_method(&RenameRule::Pascal));
assert_eq!(snake, original.rename_method(&RenameRule::Snake));
assert_eq!(
screaming_snake,
original.rename_method(&RenameRule::ScreamingSnakeCase)
);
}

#[test]
fn rename() {
let &(original, camel, snake, pascal, screaming_snake) =
&("get_name", "getName", "get_name", "GetName", "GET_NAME");
assert_eq!(original, RenameRule::None.rename(original));
assert_eq!(camel, RenameRule::Camel.rename(original));
assert_eq!(pascal, RenameRule::Pascal.rename(original));
assert_eq!(snake, RenameRule::Snake.rename(original));
assert_eq!(original, original.rename(&RenameRule::None));
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,
RenameRule::ScreamingSnakeCase.rename(original)
original.rename(&RenameRule::ScreamingSnakeCase)
);
}
}
37 changes: 37 additions & 0 deletions tests/src/integration/magic_method.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

$magicMethod = new MagicMethod();

// __set
$magicMethod->count = 10;
// __get
assert(10 === $magicMethod->count);
assert(null === $magicMethod->test);

//__isset
assert(true === isset($magicMethod->count));
assert(false === isset($magicMethod->noCount));

// __unset
unset($magicMethod->count);
assert(0 === $magicMethod->count);

// __toString
assert("0" === $magicMethod->__toString());
assert("0" === (string) $magicMethod);

// __invoke
assert(34 === $magicMethod(34));

// __debugInfo
$debug = print_r($magicMethod, true);
$expectedDebug = "MagicMethod Object\n(\n [count] => 0\n)\n";
assert($expectedDebug === $debug);

// __call
assert("Hello" === $magicMethod->callMagicMethod(1, 2, 3));
assert(null === $magicMethod->callUndefinedMagicMethod());

// __call_static
assert("Hello from static call 6" === MagicMethod::callStaticSomeMagic(1, 2, 3));
assert(null === MagicMethod::callUndefinedStaticSomeMagic());
4 changes: 4 additions & 0 deletions tests/src/integration/magic_method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[test]
fn magic_method() {
assert!(crate::integration::run_php("magic_method.php"));
}
Loading