mirror of
https://github.com/pestphp/pest.git
synced 2026-03-05 23:37:22 +01:00
chore: improves type coverage
This commit is contained in:
@ -51,6 +51,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"pestphp/pest-dev-tools": "^2.0.0",
|
||||
"rector/rector": "^0.15.0",
|
||||
"symfony/process": "^6.2.0"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
@ -66,7 +67,9 @@
|
||||
"bin/pest"
|
||||
],
|
||||
"scripts": {
|
||||
"refacto": "rector",
|
||||
"lint": "pint",
|
||||
"test:refactor": "rector --dry-run",
|
||||
"test:lint": "pint --test",
|
||||
"test:types": "phpstan analyse --ansi --memory-limit=-1 --debug",
|
||||
"test:unit": "php bin/pest --colors=always --exclude-group=integration --compact",
|
||||
@ -75,6 +78,7 @@
|
||||
"test:integration": "php bin/pest --colors=always --group=integration -v",
|
||||
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",
|
||||
"test": [
|
||||
"@rest:refacto",
|
||||
"@test:lint",
|
||||
"@test:types",
|
||||
"@test:unit",
|
||||
|
||||
27
rector.php
Normal file
27
rector.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\Set\ValueObject\LevelSetList;
|
||||
use Rector\Set\ValueObject\SetList;
|
||||
|
||||
return static function (RectorConfig $rectorConfig): void {
|
||||
$rectorConfig->paths([
|
||||
__DIR__.'/src',
|
||||
]);
|
||||
|
||||
$rectorConfig->rules([
|
||||
InlineConstructorDefaultToPropertyRector::class,
|
||||
]);
|
||||
|
||||
$rectorConfig->sets([
|
||||
LevelSetList::UP_TO_PHP_81,
|
||||
SetList::CODE_QUALITY,
|
||||
SetList::DEAD_CODE,
|
||||
SetList::EARLY_RETURN,
|
||||
SetList::TYPE_DECLARATION,
|
||||
SetList::PRIVATIZATION,
|
||||
]);
|
||||
};
|
||||
@ -5,16 +5,17 @@ declare(strict_types=1);
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use NunoMaduro\Collision;
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootExceptionHandler
|
||||
final class BootExceptionHandler implements Bootstrapper
|
||||
{
|
||||
/**
|
||||
* Boots the Exception Handler.
|
||||
*/
|
||||
public function __invoke(): void
|
||||
public function boot(): void
|
||||
{
|
||||
$handler = new Collision\Provider();
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\Support\DatasetInfo;
|
||||
use Pest\Support\Str;
|
||||
use function Pest\testDirectory;
|
||||
@ -15,7 +16,7 @@ use SebastianBergmann\FileIterator\Facade as PhpUnitFileIterator;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootFiles
|
||||
final class BootFiles implements Bootstrapper
|
||||
{
|
||||
/**
|
||||
* The Pest convention.
|
||||
@ -33,7 +34,7 @@ final class BootFiles
|
||||
/**
|
||||
* Boots the Subscribers.
|
||||
*/
|
||||
public function __invoke(): void
|
||||
public function boot(): void
|
||||
{
|
||||
$rootPath = TestSuite::getInstance()->rootPath;
|
||||
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();
|
||||
|
||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\Subscribers;
|
||||
use PHPUnit\Event;
|
||||
use PHPUnit\Event\Subscriber;
|
||||
@ -11,7 +12,7 @@ use PHPUnit\Event\Subscriber;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootSubscribers
|
||||
final class BootSubscribers implements Bootstrapper
|
||||
{
|
||||
/**
|
||||
* The Kernel subscribers.
|
||||
@ -28,7 +29,7 @@ final class BootSubscribers
|
||||
/**
|
||||
* Boots the Subscribers.
|
||||
*/
|
||||
public function __invoke(): void
|
||||
public function boot(): void
|
||||
{
|
||||
foreach (self::SUBSCRIBERS as $subscriber) {
|
||||
Event\Facade::registerSubscriber(
|
||||
|
||||
@ -4,13 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\Support\View;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootView
|
||||
final class BootView implements Bootstrapper
|
||||
{
|
||||
public function __construct(
|
||||
private readonly OutputInterface $output
|
||||
@ -21,7 +22,7 @@ final class BootView
|
||||
/**
|
||||
* Boots the view renderer.
|
||||
*/
|
||||
public function __invoke(): void
|
||||
public function boot(): void
|
||||
{
|
||||
View::renderUsing($this->output);
|
||||
}
|
||||
|
||||
@ -58,6 +58,6 @@ trait Pipeable
|
||||
*/
|
||||
private function pipes(string $name, object $context, string $scope): array
|
||||
{
|
||||
return array_map(fn (Closure $pipe) => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
||||
return array_map(fn (Closure $pipe): \Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,7 +259,7 @@ trait Testable
|
||||
*/
|
||||
private function __callClosure(Closure $closure, array $arguments): mixed
|
||||
{
|
||||
return ExceptionTrace::ensure(fn () => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
||||
return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
16
src/Contracts/Bootstrapper.php
Normal file
16
src/Contracts/Bootstrapper.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface Bootstrapper
|
||||
{
|
||||
/**
|
||||
* Boots the bootstrapper.
|
||||
*/
|
||||
public function boot(): void;
|
||||
}
|
||||
@ -12,11 +12,9 @@ use InvalidArgumentException;
|
||||
final class InvalidExpectationValue extends InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* @return never
|
||||
*
|
||||
* @throws self
|
||||
*/
|
||||
public static function expected(string $type): void
|
||||
public static function expected(string $type): never
|
||||
{
|
||||
throw new self(sprintf('Invalid expectation value type. Expected [%s].', $type));
|
||||
}
|
||||
|
||||
@ -70,10 +70,12 @@ final class Expectation
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
/** @var array<int|string, mixed>|bool $value */
|
||||
$value = json_decode($this->value, true, 512);
|
||||
$this->toBeJson();
|
||||
|
||||
return $this->toBeJson()->and($value);
|
||||
/** @var array<int|string, mixed>|bool $value */
|
||||
$value = json_decode($this->value, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
return $this->and($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -90,9 +90,8 @@ final class OppositeExpectation
|
||||
* Creates a new expectation failed exception with a nice readable message.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
* @return never
|
||||
*/
|
||||
private function throwExpectationFailedException(string $name, array $arguments = []): void
|
||||
private function throwExpectationFailedException(string $name, array $arguments = []): never
|
||||
{
|
||||
$exporter = new Exporter();
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ final class TestCaseFactory
|
||||
|
||||
$methods = array_values(array_filter(
|
||||
$this->methods,
|
||||
fn ($method) => $methodsUsingOnly === [] || in_array($method, $methodsUsingOnly, true)
|
||||
fn ($method): bool => $methodsUsingOnly === [] || in_array($method, $methodsUsingOnly, true)
|
||||
));
|
||||
|
||||
if ($methods !== []) {
|
||||
@ -165,21 +165,21 @@ final class TestCaseFactory
|
||||
$classFQN .= $className;
|
||||
}
|
||||
|
||||
$classAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute) => $attribute::ABOVE_CLASS);
|
||||
$methodAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute) => ! $attribute::ABOVE_CLASS);
|
||||
$classAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute): bool => $attribute::ABOVE_CLASS);
|
||||
$methodAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute): bool => ! $attribute::ABOVE_CLASS);
|
||||
|
||||
$classAttributes = [];
|
||||
|
||||
foreach ($classAvailableAttributes as $attribute) {
|
||||
$classAttributes = array_reduce(
|
||||
$methods,
|
||||
fn (array $carry, TestCaseMethodFactory $methodFactory) => (new $attribute())->__invoke($methodFactory, $carry),
|
||||
fn (array $carry, TestCaseMethodFactory $methodFactory): array => (new $attribute())->__invoke($methodFactory, $carry),
|
||||
$classAttributes
|
||||
);
|
||||
}
|
||||
|
||||
$methodsCode = implode('', array_map(
|
||||
fn (TestCaseMethodFactory $methodFactory) => $methodFactory->buildForEvaluation(
|
||||
fn (TestCaseMethodFactory $methodFactory): string => $methodFactory->buildForEvaluation(
|
||||
$classFQN,
|
||||
self::ANNOTATIONS,
|
||||
$methodAvailableAttributes
|
||||
@ -188,7 +188,7 @@ final class TestCaseFactory
|
||||
));
|
||||
|
||||
$classAttributesCode = implode('', array_map(
|
||||
static fn (string $attribute) => sprintf("\n%s", $attribute),
|
||||
static fn (string $attribute): string => sprintf("\n%s", $attribute),
|
||||
array_unique($classAttributes),
|
||||
));
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ final class TestCaseMethodFactory
|
||||
$testCase->chains->chain($this);
|
||||
$method->chains->chain($this);
|
||||
|
||||
return \Pest\Support\Closure::bind($closure, $this, $this::class)(...func_get_args());
|
||||
return \Pest\Support\Closure::bind($closure, $this, self::class)(...func_get_args());
|
||||
};
|
||||
}
|
||||
|
||||
@ -147,11 +147,11 @@ final class TestCaseMethodFactory
|
||||
}
|
||||
|
||||
$annotations = implode('', array_map(
|
||||
static fn ($annotation) => sprintf("\n * %s", $annotation), $annotations,
|
||||
static fn ($annotation): string => sprintf("\n * %s", $annotation), $annotations,
|
||||
));
|
||||
|
||||
$attributes = implode('', array_map(
|
||||
static fn ($attribute) => sprintf("\n %s", $attribute), $attributes,
|
||||
static fn ($attribute): string => sprintf("\n %s", $attribute), $attributes,
|
||||
));
|
||||
|
||||
return <<<PHP
|
||||
|
||||
@ -89,7 +89,7 @@ if (! function_exists('test')) {
|
||||
*
|
||||
* @return TestCall|TestCase|mixed
|
||||
*/
|
||||
function test(string $description = null, Closure $closure = null)
|
||||
function test(string $description = null, Closure $closure = null): HigherOrderTapProxy|TestCall
|
||||
{
|
||||
if ($description === null && TestSuite::getInstance()->test !== null) {
|
||||
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
|
||||
|
||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\Plugins\Actions\CallsAddsOutput;
|
||||
use Pest\Plugins\Actions\CallsBoot;
|
||||
use Pest\Plugins\Actions\CallsShutdown;
|
||||
@ -49,7 +50,10 @@ final class Kernel
|
||||
public static function boot(): self
|
||||
{
|
||||
foreach (self::BOOTSTRAPPERS as $bootstrapper) {
|
||||
Container::getInstance()->get($bootstrapper)->__invoke();
|
||||
$bootstrapper = Container::getInstance()->get($bootstrapper);
|
||||
assert($bootstrapper instanceof Bootstrapper);
|
||||
|
||||
$bootstrapper->boot();
|
||||
}
|
||||
|
||||
(new CallsBoot())->__invoke();
|
||||
|
||||
@ -154,7 +154,7 @@ final class TestCall
|
||||
|
||||
$condition = is_callable($condition)
|
||||
? $condition
|
||||
: fn () => $condition;
|
||||
: fn (): bool => $condition;
|
||||
|
||||
$message = is_string($conditionOrMessage)
|
||||
? $conditionOrMessage
|
||||
|
||||
@ -90,8 +90,7 @@ final class DatasetsRepository
|
||||
*/
|
||||
public static function resolve(array $dataset, string $currentTestFile): array|null
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
if (empty($dataset)) {
|
||||
if ($dataset === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -177,25 +176,21 @@ final class DatasetsRepository
|
||||
/**
|
||||
* @return Closure|iterable<int|string, mixed>
|
||||
*/
|
||||
private static function getScopedDataset(string $name, string $currentTestFile)
|
||||
private static function getScopedDataset(string $name, string $currentTestFile): Closure|iterable
|
||||
{
|
||||
$matchingDatasets = array_filter(self::$datasets, function (string $key) use ($name, $currentTestFile) {
|
||||
$matchingDatasets = array_filter(self::$datasets, function (string $key) use ($name, $currentTestFile): bool {
|
||||
[$datasetScope, $datasetName] = explode(self::SEPARATOR, $key);
|
||||
|
||||
if ($name !== $datasetName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! str_starts_with($currentTestFile, $datasetScope)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return str_starts_with($currentTestFile, $datasetScope);
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
|
||||
$closestScopeDatasetKey = array_reduce(
|
||||
array_keys($matchingDatasets),
|
||||
fn ($keyA, $keyB) => $keyA !== null && strlen($keyA) > strlen($keyB) ? $keyA : $keyB
|
||||
fn ($keyA, $keyB) => $keyA !== null && strlen((string) $keyA) > strlen($keyB) ? $keyA : $keyB
|
||||
);
|
||||
|
||||
if ($closestScopeDatasetKey === null) {
|
||||
|
||||
@ -42,7 +42,7 @@ final class TestRepository
|
||||
*/
|
||||
public function getFilenames(): array
|
||||
{
|
||||
$testCases = array_filter($this->testCases, static fn (TestCaseFactory $testCase) => $testCase->methodsUsingOnly() !== []);
|
||||
$testCases = array_filter($this->testCases, static fn (TestCaseFactory $testCase): bool => $testCase->methodsUsingOnly() !== []);
|
||||
|
||||
if ($testCases === []) {
|
||||
$testCases = $this->testCases;
|
||||
|
||||
@ -22,8 +22,8 @@ final class ChainableClosure
|
||||
throw ShouldNotHappen::fromMessage('$this not bound to chainable closure.');
|
||||
}
|
||||
|
||||
\Pest\Support\Closure::bind($closure, $this, $this::class)(...func_get_args());
|
||||
\Pest\Support\Closure::bind($next, $this, $this::class)(...func_get_args());
|
||||
\Pest\Support\Closure::bind($closure, $this, self::class)(...func_get_args());
|
||||
\Pest\Support\Closure::bind($next, $this, self::class)(...func_get_args());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ final class Container
|
||||
private static ?Container $instance = null;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
* @var array<string, object|string>
|
||||
*/
|
||||
private array $instances = [];
|
||||
|
||||
@ -25,37 +25,30 @@ final class Container
|
||||
*/
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (static::$instance === null) {
|
||||
static::$instance = new self();
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a dependency from the container.
|
||||
*
|
||||
* @template TObject of object
|
||||
*
|
||||
* @param class-string<TObject> $id
|
||||
* @return TObject
|
||||
*/
|
||||
public function get(string $id): mixed
|
||||
public function get(string $id): object|string
|
||||
{
|
||||
if (! array_key_exists($id, $this->instances)) {
|
||||
/** @var class-string $id */
|
||||
$this->instances[$id] = $this->build($id);
|
||||
}
|
||||
|
||||
/** @var TObject $concrete */
|
||||
$concrete = $this->instances[$id];
|
||||
|
||||
return $concrete;
|
||||
return $this->instances[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given instance to the container.
|
||||
*/
|
||||
public function add(string $id, mixed $instance): void
|
||||
public function add(string $id, object|string $instance): void
|
||||
{
|
||||
$this->instances[$id] = $instance;
|
||||
}
|
||||
@ -68,7 +61,7 @@ final class Container
|
||||
* @param class-string<TObject> $id
|
||||
* @return TObject
|
||||
*/
|
||||
private function build(string $id): mixed
|
||||
private function build(string $id): object
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($id);
|
||||
|
||||
@ -77,7 +70,7 @@ final class Container
|
||||
|
||||
if ($constructor !== null) {
|
||||
$params = array_map(
|
||||
function (ReflectionParameter $param) use ($id) {
|
||||
function (ReflectionParameter $param) use ($id): object|string {
|
||||
$candidate = Reflection::getParameterClassName($param);
|
||||
|
||||
if ($candidate === null) {
|
||||
@ -90,7 +83,6 @@ final class Container
|
||||
}
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->get($candidate);
|
||||
},
|
||||
$constructor->getParameters()
|
||||
|
||||
@ -42,15 +42,15 @@ final class Coverage
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($runtime->hasXdebug()) {
|
||||
if (version_compare((string) phpversion('xdebug'), '3.1', '>=')) {
|
||||
if (! in_array('coverage', xdebug_info('mode'), true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (! $runtime->hasXdebug()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (! version_compare((string) phpversion('xdebug'), '3.1', '>=')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array('coverage', xdebug_info('mode'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user