refacto: 100% type coverage

This commit is contained in:
Nuno Maduro
2023-06-15 15:16:04 +02:00
parent 4f3796ed2e
commit 54f4ee57ad
17 changed files with 96 additions and 124 deletions

View File

@ -50,9 +50,11 @@
},
"require-dev": {
"pestphp/pest-dev-tools": "^2.12.0",
"pestphp/pest-plugin-type-coverage": "^2.0.0",
"symfony/process": "^6.3.0"
},
"minimum-stability": "stable",
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true,
"preferred-install": "dist",

View File

@ -381,9 +381,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => str_contains((string) file_get_contents($object->path), 'declare(strict_types=1);'),
'to use strict types',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, '<?php');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
);
}
@ -396,9 +394,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isFinal(),
'to be final',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -411,9 +407,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isReadOnly(),
'to be readonly',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -426,9 +420,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isTrait(),
'to be trait',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -449,9 +441,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isAbstract(),
'to be abstract',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -464,9 +454,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isEnum(),
'to be enum',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -487,9 +475,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isInterface(),
'to be interface',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -512,9 +498,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isSubclassOf($class),
sprintf("to extend '%s'", $class),
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -527,9 +511,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->getParentClass() === false,
'to extend nothing',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -542,9 +524,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() === [],
'to implement nothing',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -562,9 +542,7 @@ final class Expectation
fn (ObjectDescription $object): bool => count($interfaces) === count($object->reflectionClass->getInterfaceNames())
&& array_diff($interfaces, $object->reflectionClass->getInterfaceNames()) === [],
"to only implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -577,9 +555,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => str_ends_with($object->reflectionClass->getName(), $suffix),
"to have suffix '{$suffix}'",
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -592,9 +568,7 @@ final class Expectation
$this,
fn (ObjectDescription $object): bool => str_starts_with($object->reflectionClass->getName(), $suffix),
"to have prefix '{$suffix}'",
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -619,9 +593,7 @@ final class Expectation
return true;
},
"to implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}

View File

@ -84,9 +84,7 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => ! str_contains((string) file_get_contents($object->path), 'declare(strict_types=1);'),
'not to use strict types',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, '<?php');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
);
}
@ -99,9 +97,7 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isFinal(),
'not to be final',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -114,9 +110,7 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isReadOnly(),
'not to be readonly',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -129,9 +123,7 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isTrait(),
'not to be trait',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -144,9 +136,7 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isAbstract(),
'not to be abstract',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -159,9 +149,7 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isEnum(),
'not to be enum',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -174,9 +162,7 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isInterface(),
'not to be interface',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -191,9 +177,7 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isSubclassOf($class),
sprintf("not to extend '%s'", $class),
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -206,14 +190,14 @@ final class OppositeExpectation
$this->original,
fn (ObjectDescription $object): bool => $object->reflectionClass->getParentClass() !== false,
'to extend a class',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target not to implement the given interfaces.
*
* @param array<int, class-string>|string $interfaces
*/
public function toImplement(array|string $interfaces): ArchExpectation
{
@ -230,10 +214,8 @@ final class OppositeExpectation
return true;
},
"not to implement '".implode("', '", (array) $interfaces)."'",
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
"not to implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
@ -243,31 +225,42 @@ final class OppositeExpectation
public function toImplementNothing(): ArchExpectation
{
return Targeted::make(
$this,
$this->original,
fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() !== [],
'to implement an interface',
FileLineFinder::where(function (string $line): bool {
return str_contains($line, 'class');
}),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
public function toOnlyImplement(array|string $interfaces): ArchExpectation
/**
* Not supported.
*
* @param array<int, class-string>|string $interfaces
*/
public function toOnlyImplement(array|string $interfaces): never
{
throw InvalidExpectation::fromMethods(['not', 'toOnlyImplement']);
}
/**
* Not supported.
*/
public function toHavePrefix(string $suffix): never
{
throw InvalidExpectation::fromMethods(['not', 'toHavePrefix']);
}
/**
* Not supported.
*/
public function toHaveSuffix(string $suffix): never
{
throw InvalidExpectation::fromMethods(['not', 'toHaveSuffix']);
}
/**
* Not supported.
*
* @param array<int, string>|string $targets
*/
public function toOnlyUse(array|string $targets): never
@ -275,6 +268,9 @@ final class OppositeExpectation
throw InvalidExpectation::fromMethods(['not', 'toOnlyUse']);
}
/**
* Not supported.
*/
public function toUseNothing(): never
{
throw InvalidExpectation::fromMethods(['not', 'toUseNothing']);
@ -358,8 +354,13 @@ final class OppositeExpectation
$exporter = Exporter::default();
$toString = fn ($argument): string => $exporter->shortenedExport($argument);
$toString = fn (mixed $argument): string => $exporter->shortenedExport($argument);
throw new ExpectationFailedException(sprintf('Expecting %s not %s %s.', $toString($this->original->value), strtolower((string) preg_replace('/(?<!\ )[A-Z]/', ' $0', $name)), implode(' ', array_map(fn ($argument): string => $toString($argument), $arguments))));
throw new ExpectationFailedException(sprintf(
'Expecting %s not %s %s.',
$toString($this->original->value),
strtolower((string) preg_replace('/(?<!\ )[A-Z]/', ' $0', $name)),
implode(' ', array_map(fn (mixed $argument): string => $toString($argument), $arguments)),
));
}
}

View File

@ -98,7 +98,7 @@ final class TestCaseFactory
{
if ('\\' === DIRECTORY_SEPARATOR) {
// In case Windows, strtolower drive name, like in UsesCall.
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', static fn ($match): string => strtolower($match['drive']), $filename);
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', static fn (array $match): string => strtolower($match['drive']), $filename);
}
$filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
@ -134,7 +134,7 @@ final class TestCaseFactory
$hasPrintableTestCaseClassFQN = sprintf('\%s', HasPrintableTestCaseName::class);
$traitsCode = sprintf('use %s;', implode(', ', array_map(
static fn ($trait): string => sprintf('\%s', $trait), $this->traits))
static fn (string $trait): string => sprintf('\%s', $trait), $this->traits))
);
$partsFQN = explode('\\', $classFQN);

View File

@ -147,11 +147,11 @@ final class TestCaseMethodFactory
}
$annotations = implode('', array_map(
static fn ($annotation): string => sprintf("\n * %s", $annotation), $annotations,
static fn (string $annotation): string => sprintf("\n * %s", $annotation), $annotations,
));
$attributes = implode('', array_map(
static fn ($attribute): string => sprintf("\n %s", $attribute), $attributes,
static fn (string $attribute): string => sprintf("\n %s", $attribute), $attributes,
));
return <<<PHP

View File

@ -66,11 +66,11 @@ final class UsesCall
*/
public function in(string ...$targets): void
{
$targets = array_map(function ($path): string {
$targets = array_map(function (string $path): string {
$startChar = DIRECTORY_SEPARATOR;
if ('\\' === DIRECTORY_SEPARATOR || preg_match('~\A[A-Z]:(?![^/\\\\])~i', $path) > 0) {
$path = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', fn ($match): string => strtolower($match['drive']), $path);
$path = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', fn (array $match): string => strtolower($match['drive']), $path);
$startChar = strtolower((string) preg_replace('~^([a-z]+:\\\).*$~i', '$1', __DIR__));
}

View File

@ -50,7 +50,7 @@ final class Coverage implements AddsOutput, HandlesArguments
*/
public function handleArguments(array $originals): array
{
$arguments = [...[''], ...array_values(array_filter($originals, function ($original): bool {
$arguments = [...[''], ...array_values(array_filter($originals, function (string $original): bool {
foreach ([self::COVERAGE_OPTION, self::MIN_OPTION] as $option) {
if ($original === sprintf('--%s', $option)) {
return true;

View File

@ -115,13 +115,13 @@ final class Parallel implements HandlesArguments
private function runTestSuiteInParallel(array $arguments): int
{
$handlers = array_filter(
array_map(fn ($handler): object|string => Container::getInstance()->get($handler), self::HANDLERS),
fn ($handler): bool => $handler instanceof HandlesArguments,
array_map(fn (string $handler): object|string => Container::getInstance()->get($handler), self::HANDLERS),
fn (object|string $handler): bool => $handler instanceof HandlesArguments,
);
$filteredArguments = array_reduce(
$handlers,
fn ($arguments, HandlesArguments $handler): array => $handler->handleArguments($arguments),
fn (array $arguments, HandlesArguments $handler): array => $handler->handleArguments($arguments),
$arguments
);
@ -139,13 +139,13 @@ final class Parallel implements HandlesArguments
private function runWorkerHandlers(array $arguments): array
{
$handlers = array_filter(
array_map(fn ($handler): object|string => Container::getInstance()->get($handler), self::HANDLERS),
fn ($handler): bool => $handler instanceof HandlersWorkerArguments,
array_map(fn (string $handler): object|string => Container::getInstance()->get($handler), self::HANDLERS),
fn (object|string $handler): bool => $handler instanceof HandlersWorkerArguments,
);
return array_reduce(
$handlers,
fn ($arguments, HandlersWorkerArguments $handler): array => $handler->handleWorkerArguments($arguments),
fn (array $arguments, HandlersWorkerArguments $handler): array => $handler->handleWorkerArguments($arguments),
$arguments
);
}

View File

@ -30,7 +30,7 @@ final class Parallel implements HandlesArguments
*/
public function handleArguments(array $arguments): array
{
$args = array_reduce(self::ARGS_TO_REMOVE, fn ($args, $arg): array => $this->popArgument($arg, $args), $arguments);
$args = array_reduce(self::ARGS_TO_REMOVE, fn (array $args, string $arg): array => $this->popArgument($arg, $args), $arguments);
return $this->pushArgument('--runner='.WrapperRunner::class, $args);
}

View File

@ -23,6 +23,7 @@ use ParaTest\WrapperRunner\WrapperWorker;
use Pest\Result;
use Pest\TestSuite;
use PHPUnit\Event\Facade as EventFacade;
use PHPUnit\Event\TestRunner\WarningTriggered;
use PHPUnit\Runner\CodeCoverage;
use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
use PHPUnit\TestRunner\TestResult\TestResult;
@ -317,7 +318,10 @@ final class WrapperRunner implements RunnerInterface
$testResultSum->testTriggeredPhpunitErrorEvents(),
$testResultSum->testTriggeredPhpunitWarningEvents(),
$testResultSum->testRunnerTriggeredDeprecationEvents(),
array_values(array_filter($testResultSum->testRunnerTriggeredWarningEvents(), fn ($event): bool => ! str_contains($event->message(), 'No tests found'))),
array_values(array_filter(
$testResultSum->testRunnerTriggeredWarningEvents(),
fn (WarningTriggered $event): bool => ! str_contains($event->message(), 'No tests found')
)),
);
$this->printer->printResults(

View File

@ -66,11 +66,11 @@ final class DatasetsRepository
}
/**
* @return Closure|array<int|string, mixed>|never
* @return Closure|array<int|string, mixed>
*
* @throws ShouldNotHappen
*/
public static function get(string $filename, string $description)
public static function get(string $filename, string $description): Closure|array
{
$dataset = self::$withs[$filename.self::SEPARATOR.$description];
@ -138,7 +138,7 @@ final class DatasetsRepository
/**
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
* @return array<array<mixed>>
* @return array<int, array<int, mixed>>
*/
private static function processDatasets(array $datasets, string $currentTestFile): array
{
@ -193,7 +193,7 @@ final class DatasetsRepository
$closestScopeDatasetKey = array_reduce(
array_keys($matchingDatasets),
fn ($keyA, $keyB) => $keyA !== null && strlen((string) $keyA) > strlen($keyB) ? $keyA : $keyB
fn (string|int|null $keyA, string|int|null $keyB): string|int|null => $keyA !== null && strlen((string) $keyA) > strlen((string) $keyB) ? $keyA : $keyB
);
if ($closestScopeDatasetKey === null) {

View File

@ -159,10 +159,9 @@ final class Coverage
* ['11', '20..25', '50', '60..80'];
* ```
*
* @param File $file
* @return array<int, string>
*/
public static function getMissingCoverage($file): array
public static function getMissingCoverage(File $file): array
{
$shouldBeNewLine = true;

View File

@ -18,11 +18,9 @@ final class ExceptionTrace
/**
* Ensures the given closure reports the good execution context.
*
* @return mixed
*
* @throws Throwable
*/
public static function ensure(Closure $closure)
public static function ensure(Closure $closure): mixed
{
try {
return $closure();

View File

@ -84,6 +84,6 @@ final class ExpectationPipeline
*/
public function carry(): Closure
{
return fn ($stack, $pipe): Closure => fn () => $pipe($stack, ...$this->passables);
return fn (mixed $stack, callable $pipe): Closure => fn () => $pipe($stack, ...$this->passables);
}
}

View File

@ -24,9 +24,8 @@ final class Reflection
* Calls the given method with args on the given object.
*
* @param array<int, mixed> $args
* @return mixed
*/
public static function call(object $object, string $method, array $args = [])
public static function call(object $object, string $method, array $args = []): mixed
{
$reflectionClass = new ReflectionClass($object);
@ -53,9 +52,8 @@ final class Reflection
* Bind a callable to the TestCase and return the result.
*
* @param array<int, mixed> $args
* @return mixed
*/
public static function bindCallable(callable $callable, array $args = [])
public static function bindCallable(callable $callable, array $args = []): mixed
{
return Closure::fromCallable($callable)->bindTo(TestSuite::getInstance()->test)(...$args);
}
@ -63,10 +61,8 @@ final class Reflection
/**
* Bind a callable to the TestCase and return the result,
* passing in the current dataset values as arguments.
*
* @return mixed
*/
public static function bindCallableWithData(callable $callable)
public static function bindCallableWithData(callable $callable): mixed
{
$test = TestSuite::getInstance()->test;
@ -87,10 +83,8 @@ final class Reflection
/**
* Gets the property value from of the given object.
*
* @return mixed
*/
public static function getPropertyValue(object $object, string $property)
public static function getPropertyValue(object $object, string $property): mixed
{
$reflectionClass = new ReflectionClass($object);
@ -206,10 +200,7 @@ final class Reflection
return $arguments;
}
/**
* @return mixed
*/
public static function getFunctionVariable(Closure $function, string $key)
public static function getFunctionVariable(Closure $function, string $key): mixed
{
return (new ReflectionFunction($function))->getStaticVariables()[$key] ?? null;
}

View File

@ -59,13 +59,18 @@ final class GitDirtyTestCaseFilter implements TestCaseFilter
$dirtyFiles[substr($dirtyFile, 3)] = trim(substr($dirtyFile, 0, 3));
}
$dirtyFiles = array_filter($dirtyFiles, fn ($status): bool => $status !== 'D');
$dirtyFiles = array_filter($dirtyFiles, fn (string $status): bool => $status !== 'D');
$dirtyFiles = array_map(fn ($file, $status): string => in_array($status, ['R', 'RM'], true) ? explode(' -> ', $file)[1] : $file, array_keys($dirtyFiles), $dirtyFiles);
$dirtyFiles = array_map(
fn (string $file, string $status): string => in_array($status, ['R', 'RM'], true)
? explode(' -> ', $file)[1]
: $file, array_keys($dirtyFiles), $dirtyFiles,
);
$dirtyFiles = array_filter(
$dirtyFiles,
fn ($file): bool => str_starts_with('.'.DIRECTORY_SEPARATOR.$file, TestSuite::getInstance()->testPath) || str_starts_with($file, TestSuite::getInstance()->testPath)
fn (string $file): bool => str_starts_with('.'.DIRECTORY_SEPARATOR.$file, TestSuite::getInstance()->testPath)
|| str_starts_with($file, TestSuite::getInstance()->testPath)
);
$dirtyFiles = array_values($dirtyFiles);

View File

@ -18,7 +18,7 @@ $run = function () {
test('parallel', function () use ($run) {
expect($run('--exclude-group=integration'))
->toContain('Tests: 1 deprecated, 3 warnings, 4 incomplete, 1 notice, 8 todos, 15 skipped, 707 passed (1720 assertions)')
->toContain('Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 15 skipped, 720 passed (1740 assertions)')
->toContain('Parallel: 3 processes');
})->skipOnWindows();