fix: properties and methods documented

This commit is contained in:
Nuno Maduro
2024-07-20 17:45:41 +01:00
parent b3c8c24aea
commit 101e26749a
6 changed files with 137 additions and 6 deletions

View File

@ -30,6 +30,7 @@ use Pest\Expectations\HigherOrderExpectation;
use Pest\Expectations\OppositeExpectation; use Pest\Expectations\OppositeExpectation;
use Pest\Matchers\Any; use Pest\Matchers\Any;
use Pest\Support\ExpectationPipeline; use Pest\Support\ExpectationPipeline;
use Pest\Support\Reflection;
use PHPUnit\Architecture\Elements\ObjectDescription; use PHPUnit\Architecture\Elements\ObjectDescription;
use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\ExpectationFailedException;
use ReflectionEnum; use ReflectionEnum;
@ -471,7 +472,7 @@ final class Expectation
$this, $this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter( || 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) 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 && realpath($method->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line
&& $method->getDocComment() === false, && $method->getDocComment() === false,
@ -490,7 +491,7 @@ final class Expectation
$this, $this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter( || 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) 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 && realpath($property->getDeclaringClass()->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line
&& $property->isPromoted() === false && $property->isPromoted() === false

View File

@ -18,6 +18,7 @@ use Pest\Exceptions\InvalidExpectation;
use Pest\Expectation; use Pest\Expectation;
use Pest\Support\Arr; use Pest\Support\Arr;
use Pest\Support\Exporter; use Pest\Support\Exporter;
use Pest\Support\Reflection;
use PHPUnit\Architecture\Elements\ObjectDescription; use PHPUnit\Architecture\Elements\ObjectDescription;
use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\ExpectationFailedException;
@ -107,7 +108,7 @@ final class OppositeExpectation
$this->original, $this->original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter( || 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) 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 && realpath($method->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line
&& $method->getDocComment() !== false, && $method->getDocComment() !== false,
@ -126,7 +127,7 @@ final class OppositeExpectation
$this->original, $this->original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter( || 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) 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 && realpath($property->getDeclaringClass()->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line
&& $property->isPromoted() === false && $property->isPromoted() === false

View File

@ -11,6 +11,7 @@ use Pest\TestSuite;
use ReflectionClass; use ReflectionClass;
use ReflectionException; use ReflectionException;
use ReflectionFunction; use ReflectionFunction;
use ReflectionMethod;
use ReflectionNamedType; use ReflectionNamedType;
use ReflectionParameter; use ReflectionParameter;
use ReflectionProperty; use ReflectionProperty;
@ -213,4 +214,74 @@ final class Reflection
{ {
return (new ReflectionFunction($function))->getStaticVariables()[$key] ?? null; return (new ReflectionFunction($function))->getStaticVariables()[$key] ?? null;
} }
/**
* Get the properties from the given reflection class.
*
* Used by `expect()->toHavePropertiesDocumented()`.
*
* @param ReflectionClass<object> $reflectionClass
* @return array<int, ReflectionProperty>
*/
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<object> $reflectionClass
* @return array<int, ReflectionMethod>
*/
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),
),
);
}
} }

View File

@ -1429,6 +1429,8 @@
PASS Tests\Unit\Support\Reflection PASS Tests\Unit\Support\Reflection
✓ it gets file name from closure ✓ it gets file name from closure
✓ it gets property values ✓ it gets property values
✓ it gets properties from classes
✓ it gets methods from classes
PASS Tests\Unit\Support\Str PASS Tests\Unit\Support\Str
✓ it evaluates the code with ('version()', '__pest_evaluable_version__') ✓ it evaluates the code with ('version()', '__pest_evaluable_version__')
@ -1535,4 +1537,4 @@
WARN Tests\Visual\Version WARN Tests\Visual\Version
- visual snapshot of help command output - visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 24 skipped, 1074 passed (2626 assertions) Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 24 skipped, 1076 passed (2628 assertions)

View File

@ -18,3 +18,59 @@ it('gets property values', function () {
expect($value)->toBe('bar'); 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',
]);
});

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) 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'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();