diff --git a/src/Expectation.php b/src/Expectation.php index 1455f81e..a16a49fd 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -30,6 +30,7 @@ use Pest\Expectations\HigherOrderExpectation; use Pest\Expectations\OppositeExpectation; use Pest\Matchers\Any; use Pest\Support\ExpectationPipeline; +use Pest\Support\Reflection; use PHPUnit\Architecture\Elements\ObjectDescription; use PHPUnit\Framework\ExpectationFailedException; use ReflectionEnum; @@ -471,7 +472,7 @@ final class Expectation $this, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || array_filter( - $object->reflectionClass->getMethods(), + Reflection::getMethodsFromReflectionClass($object->reflectionClass), fn (ReflectionMethod $method): bool => (enum_exists($object->name) === false || in_array($method->name, ['from', 'tryFrom', 'cases'], true) === false) && realpath($method->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line && $method->getDocComment() === false, @@ -490,7 +491,7 @@ final class Expectation $this, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || array_filter( - $object->reflectionClass->getProperties(), + Reflection::getPropertiesFromReflectionClass($object->reflectionClass), fn (ReflectionProperty $property): bool => (enum_exists($object->name) === false || in_array($property->name, ['value', 'name'], true) === false) && realpath($property->getDeclaringClass()->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line && $property->isPromoted() === false diff --git a/src/Expectations/OppositeExpectation.php b/src/Expectations/OppositeExpectation.php index 1f07f8fe..eba4ac5a 100644 --- a/src/Expectations/OppositeExpectation.php +++ b/src/Expectations/OppositeExpectation.php @@ -18,6 +18,7 @@ use Pest\Exceptions\InvalidExpectation; use Pest\Expectation; use Pest\Support\Arr; use Pest\Support\Exporter; +use Pest\Support\Reflection; use PHPUnit\Architecture\Elements\ObjectDescription; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\ExpectationFailedException; @@ -107,7 +108,7 @@ final class OppositeExpectation $this->original, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || array_filter( - $object->reflectionClass->getMethods(), + Reflection::getMethodsFromReflectionClass($object->reflectionClass), fn (ReflectionMethod $method): bool => (enum_exists($object->name) === false || in_array($method->name, ['from', 'tryFrom', 'cases'], true) === false) && realpath($method->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line && $method->getDocComment() !== false, @@ -126,7 +127,7 @@ final class OppositeExpectation $this->original, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || array_filter( - $object->reflectionClass->getProperties(), + Reflection::getPropertiesFromReflectionClass($object->reflectionClass), fn (ReflectionProperty $property): bool => (enum_exists($object->name) === false || in_array($property->name, ['value', 'name'], true) === false) && realpath($property->getDeclaringClass()->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line && $property->isPromoted() === false diff --git a/src/Support/Reflection.php b/src/Support/Reflection.php index 68581b85..5ffc26ae 100644 --- a/src/Support/Reflection.php +++ b/src/Support/Reflection.php @@ -11,6 +11,7 @@ use Pest\TestSuite; use ReflectionClass; use ReflectionException; use ReflectionFunction; +use ReflectionMethod; use ReflectionNamedType; use ReflectionParameter; use ReflectionProperty; @@ -213,4 +214,74 @@ final class Reflection { return (new ReflectionFunction($function))->getStaticVariables()[$key] ?? null; } + + /** + * Get the properties from the given reflection class. + * + * Used by `expect()->toHavePropertiesDocumented()`. + * + * @param ReflectionClass $reflectionClass + * @return array + */ + public static function getPropertiesFromReflectionClass(ReflectionClass $reflectionClass): array + { + $getProperties = fn (ReflectionClass $reflectionClass): array => array_filter( + array_map( + fn (ReflectionProperty $property): \ReflectionProperty => $property, + $reflectionClass->getProperties(), + ), fn (ReflectionProperty $property): bool => $property->getDeclaringClass()->getName() === $reflectionClass->getName(), + ); + + $propertiesFromTraits = []; + foreach ($reflectionClass->getTraits() as $trait) { + $propertiesFromTraits = array_merge($propertiesFromTraits, $getProperties($trait)); + } + + $propertiesFromTraits = array_map( + fn (ReflectionProperty $property): string => $property->getName(), + $propertiesFromTraits, + ); + + return array_values( + array_filter( + $getProperties($reflectionClass), + fn (ReflectionProperty $property): bool => ! in_array($property->getName(), $propertiesFromTraits, true), + ), + ); + } + + /** + * Get the methods from the given reflection class. + * + * Used by `expect()->toHaveMethodsDocumented()`. + * + * @param ReflectionClass $reflectionClass + * @return array + */ + public static function getMethodsFromReflectionClass(ReflectionClass $reflectionClass): array + { + $getMethods = fn (ReflectionClass $reflectionClass): array => array_filter( + array_map( + fn (ReflectionMethod $method): \ReflectionMethod => $method, + $reflectionClass->getMethods(), + ), fn (ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() === $reflectionClass->getName(), + ); + + $methodsFromTraits = []; + foreach ($reflectionClass->getTraits() as $trait) { + $methodsFromTraits = array_merge($methodsFromTraits, $getMethods($trait)); + } + + $methodsFromTraits = array_map( + fn (ReflectionMethod $method): string => $method->getName(), + $methodsFromTraits, + ); + + return array_values( + array_filter( + $getMethods($reflectionClass), + fn (ReflectionMethod $method): bool => ! in_array($method->getName(), $methodsFromTraits, true), + ), + ); + } } diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 0b9ef423..a42466d5 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1429,6 +1429,8 @@ PASS Tests\Unit\Support\Reflection ✓ it gets file name from closure ✓ it gets property values + ✓ it gets properties from classes + ✓ it gets methods from classes PASS Tests\Unit\Support\Str ✓ it evaluates the code with ('version()', '__pest_evaluable_version__') @@ -1535,4 +1537,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 24 skipped, 1074 passed (2626 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 24 skipped, 1076 passed (2628 assertions) \ No newline at end of file diff --git a/tests/Unit/Support/Reflection.php b/tests/Unit/Support/Reflection.php index c9d87806..18594bc2 100644 --- a/tests/Unit/Support/Reflection.php +++ b/tests/Unit/Support/Reflection.php @@ -18,3 +18,59 @@ it('gets property values', function () { expect($value)->toBe('bar'); }); + +class Asd +{ + protected $foo = 'bar'; + + public function getFoo() + { + return $this->foo; + } +} + +trait Zxc +{ + protected $baz = 'qux'; + + public function getBaz() + { + return $this->baz; + } +} + +class Qwe extends Asd +{ + use Zxc; + + protected $bar = 'baz'; + + public function getBar() + { + return $this->bar; + } +} + +it('gets properties from classes', function () { + $reflectionClass = new ReflectionClass(Qwe::class); + + $properties = Reflection::getPropertiesFromReflectionClass($reflectionClass); + + $properties = array_map(fn ($property) => $property->getName(), $properties); + + expect($properties)->toBe([ + 'bar', + ]); +}); + +it('gets methods from classes', function () { + $reflectionClass = new ReflectionClass(Qwe::class); + + $methods = Reflection::getMethodsFromReflectionClass($reflectionClass); + + $methods = array_map(fn ($method) => $method->getName(), $methods); + + expect($methods)->toBe([ + 'getBar', + ]); +}); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index f29c72f1..53be829f 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 19 skipped, 1060 passed (2594 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 19 skipped, 1062 passed (2596 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows();