diff --git a/composer.json b/composer.json index 0e962b5c..3347466c 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..589b8cd0 --- /dev/null +++ b/rector.php @@ -0,0 +1,27 @@ +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, + ]); +}; diff --git a/src/Bootstrappers/BootExceptionHandler.php b/src/Bootstrappers/BootExceptionHandler.php index a504ee7e..99119f41 100644 --- a/src/Bootstrappers/BootExceptionHandler.php +++ b/src/Bootstrappers/BootExceptionHandler.php @@ -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(); diff --git a/src/Bootstrappers/BootFiles.php b/src/Bootstrappers/BootFiles.php index 7629e7ed..44235b80 100644 --- a/src/Bootstrappers/BootFiles.php +++ b/src/Bootstrappers/BootFiles.php @@ -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(); diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php index 815c7821..e9819fa4 100644 --- a/src/Bootstrappers/BootSubscribers.php +++ b/src/Bootstrappers/BootSubscribers.php @@ -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( diff --git a/src/Bootstrappers/BootView.php b/src/Bootstrappers/BootView.php index 226b7982..2ae4c509 100644 --- a/src/Bootstrappers/BootView.php +++ b/src/Bootstrappers/BootView.php @@ -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); } diff --git a/src/Concerns/Pipeable.php b/src/Concerns/Pipeable.php index 0a29d6a1..c23427cd 100644 --- a/src/Concerns/Pipeable.php +++ b/src/Concerns/Pipeable.php @@ -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] ?? []); } } diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 36d47e38..f7075c28 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -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)); } /** diff --git a/src/Contracts/Bootstrapper.php b/src/Contracts/Bootstrapper.php new file mode 100644 index 00000000..520aede1 --- /dev/null +++ b/src/Contracts/Bootstrapper.php @@ -0,0 +1,16 @@ +|bool $value */ - $value = json_decode($this->value, true, 512); + $this->toBeJson(); - return $this->toBeJson()->and($value); + /** @var array|bool $value */ + $value = json_decode($this->value, true, 512, JSON_THROW_ON_ERROR); + + return $this->and($value); } /** diff --git a/src/Expectations/OppositeExpectation.php b/src/Expectations/OppositeExpectation.php index beed91a8..9a8fefbe 100644 --- a/src/Expectations/OppositeExpectation.php +++ b/src/Expectations/OppositeExpectation.php @@ -90,9 +90,8 @@ final class OppositeExpectation * Creates a new expectation failed exception with a nice readable message. * * @param array $arguments - * @return never */ - private function throwExpectationFailedException(string $name, array $arguments = []): void + private function throwExpectationFailedException(string $name, array $arguments = []): never { $exporter = new Exporter(); diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index 4591d9f1..e1b4ea3a 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -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), )); diff --git a/src/Factories/TestCaseMethodFactory.php b/src/Factories/TestCaseMethodFactory.php index 09cf51e2..59b6c6c7 100644 --- a/src/Factories/TestCaseMethodFactory.php +++ b/src/Factories/TestCaseMethodFactory.php @@ -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 <<test !== null) { return new HigherOrderTapProxy(TestSuite::getInstance()->test); diff --git a/src/Kernel.php b/src/Kernel.php index 76ab91f1..bf7b200f 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -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(); diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 215b4de8..d24c593b 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -154,7 +154,7 @@ final class TestCall $condition = is_callable($condition) ? $condition - : fn () => $condition; + : fn (): bool => $condition; $message = is_string($conditionOrMessage) ? $conditionOrMessage diff --git a/src/Repositories/DatasetsRepository.php b/src/Repositories/DatasetsRepository.php index 3c3f5fc1..83eddac2 100644 --- a/src/Repositories/DatasetsRepository.php +++ b/src/Repositories/DatasetsRepository.php @@ -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 */ - 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) { diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index ff302cf7..acf163b9 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -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; diff --git a/src/Support/ChainableClosure.php b/src/Support/ChainableClosure.php index 3c228872..55dcca3a 100644 --- a/src/Support/ChainableClosure.php +++ b/src/Support/ChainableClosure.php @@ -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()); }; } diff --git a/src/Support/Container.php b/src/Support/Container.php index 8f865ce9..3afa9818 100644 --- a/src/Support/Container.php +++ b/src/Support/Container.php @@ -16,7 +16,7 @@ final class Container private static ?Container $instance = null; /** - * @var array + * @var array */ 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 $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 $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() diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 743c73ab..c0e78e67 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -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); } /**