refactor: PHP 8 features

This commit is contained in:
Nuno Maduro
2021-10-24 18:29:59 +01:00
parent e8c2fe6e35
commit 2b687a7269
43 changed files with 283 additions and 635 deletions

View File

@ -15,7 +15,7 @@ trait Extendable
/**
* @var array<string, Closure>
*/
private static $extends = [];
private static array $extends = [];
/**
* Register a custom extend.

View File

@ -13,61 +13,42 @@ use PHPUnit\Framework\ExecutionOrderDependency;
use Throwable;
/**
* To avoid inheritance conflicts, all the fields related to Pest only will be prefixed by double underscore.
*
* @internal
*/
trait Testable
{
/**
* The test case description. Contains the first
* argument of global functions like `it` and `test`.
*
* @var string
* The Test Case description.
*/
private $__description;
private string $__description;
/**
* Holds the test closure function.
*
* @var Closure
* The Test Case "test" closure.
*/
private $__test;
private Closure $__test;
/**
* Holds a global/shared beforeEach ("set up") closure if one has been
* defined.
*
* @var Closure|null
* The Test Case "setUp" closure.
*/
private $__beforeEach = null;
private ?Closure $__beforeEach = null;
/**
* Holds a global/shared afterEach ("tear down") closure if one has been
* defined.
*
* @var Closure|null
* The Test Case "tearDown" closure.
*/
private $__afterEach = null;
private ?Closure $__afterEach = null;
/**
* Holds a global/shared beforeAll ("set up before") closure if one has been
* defined.
*
* @var Closure|null
* The Test Case "setUpBeforeClass" closure.
*/
private static $__beforeAll = null;
private static ?Closure $__beforeAll = null;
/**
* Holds a global/shared afterAll ("tear down after") closure if one has
* been defined.
*
* @var Closure|null
* The test "tearDownAfterClass" closure.
*/
private static $__afterAll = null;
private static ?Closure $__afterAll = null;
/**
* Creates a new instance of the test case.
* Creates a new Test Case instance.
*/
public function __construct(Closure $test, string $description, array $data)
{
@ -82,7 +63,7 @@ trait Testable
}
/**
* Adds the groups to the current test case.
* Adds groups to the Test Case.
*/
public function addGroups(array $groups): void
{
@ -92,14 +73,14 @@ trait Testable
}
/**
* Add dependencies to the test case and map them to instances of ExecutionOrderDependency.
* Adds dependencies to the Test Case.
*/
public function addDependencies(array $tests): void
{
$className = get_class($this);
$className = $this::class;
$tests = array_map(function (string $test) use ($className): ExecutionOrderDependency {
if (strpos($test, '::') === false) {
$tests = array_map(static function (string $test) use ($className): ExecutionOrderDependency {
if (!str_contains($test, '::')) {
$test = "{$className}::{$test}";
}
@ -110,8 +91,7 @@ trait Testable
}
/**
* Add a shared/"global" before all test hook that will execute **before**
* the test defined `beforeAll` hook(s).
* Adds a new "setUpBeforeClass" to the Test Case.
*/
public function __addBeforeAll(?Closure $hook): void
{
@ -125,8 +105,7 @@ trait Testable
}
/**
* Add a shared/"global" after all test hook that will execute **before**
* the test defined `afterAll` hook(s).
* Adds a new "tearDownAfterClass" to the Test Case.
*/
public function __addAfterAll(?Closure $hook): void
{
@ -140,8 +119,7 @@ trait Testable
}
/**
* Add a shared/"global" before each test hook that will execute **before**
* the test defined `beforeEach` hook.
* Adds a new "setUp" to the Test Case.
*/
public function __addBeforeEach(?Closure $hook): void
{
@ -149,8 +127,7 @@ trait Testable
}
/**
* Add a shared/"global" after each test hook that will execute **before**
* the test defined `afterEach` hook.
* Adds a new "tearDown" to the Test Case.
*/
public function __addAfterEach(?Closure $hook): void
{
@ -158,7 +135,7 @@ trait Testable
}
/**
* Add a shared/global hook and compose them if more than one is passed.
* Adds a new "hook" to the Test Case.
*/
private function __addHook(string $property, ?Closure $hook): void
{
@ -172,9 +149,7 @@ trait Testable
}
/**
* Returns the test case name. Note that, in Pest
* we ignore withDataset argument as the description
* already contains the dataset description.
* Gets the Test Case name.
*/
public function getName(bool $withDataSet = true): string
{
@ -183,13 +158,16 @@ trait Testable
: $this->__description;
}
public static function __getFileName(): string
/**
* Gets the Test Case filename.
*/
public static function __getFilename(): string
{
return self::$__filename;
}
/**
* This method is called before the first test of this test class is run.
* This method is called before the first test of this Test Case is run.
*/
public static function setUpBeforeClass(): void
{
@ -205,7 +183,7 @@ trait Testable
}
/**
* This method is called after the last test of this test class is run.
* This method is called after the last test of this Test Case is run.
*/
public static function tearDownAfterClass(): void
{
@ -221,7 +199,7 @@ trait Testable
}
/**
* Gets executed before the test.
* Gets executed before the Test Case.
*/
protected function setUp(): void
{
@ -239,7 +217,7 @@ trait Testable
}
/**
* Gets executed after the test.
* Gets executed after the Test Case.
*/
protected function tearDown(): void
{
@ -257,7 +235,7 @@ trait Testable
}
/**
* Returns the test case as string.
* Gets the Test Case filename and description.
*/
public function toString(): string
{
@ -269,13 +247,11 @@ trait Testable
}
/**
* Runs the test.
*
* @return mixed
* Executes the Test Case current test.
*
* @throws Throwable
*/
public function __test()
public function __test(): mixed
{
return $this->__callClosure($this->__test, $this->__resolveTestArguments(func_get_args()));
}
@ -287,23 +263,20 @@ trait Testable
*/
private function __resolveTestArguments(array $arguments): array
{
return array_map(function ($data) {
return $data instanceof Closure ? $this->__callClosure($data, []) : $data;
}, $arguments);
return array_map(fn ($data) => $data instanceof Closure ? $this->__callClosure($data, []) : $data, $arguments);
}
/**
* @return mixed
*
* @throws Throwable
*/
private function __callClosure(Closure $closure, array $arguments)
private function __callClosure(Closure $closure, array $arguments): mixed
{
return ExceptionTrace::ensure(function () use ($closure, $arguments) {
return call_user_func_array(Closure::bind($closure, $this, get_class($this)), $arguments);
});
return ExceptionTrace::ensure(fn () => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
}
/**
* Gets the Test Case name that should be used by printers.
*/
public function getPrintableTestCaseName(): string
{
return ltrim(self::class, 'P\\');

View File

@ -20,12 +20,9 @@ final class Help
' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)',
];
/** @var OutputInterface */
private $output;
public function __construct(OutputInterface $output)
public function __construct(private OutputInterface $output)
{
$this->output = $output;
// ..
}
public function __invoke(): void

View File

@ -25,12 +25,9 @@ final class Thanks
' <options=bold>https://github.com/sponsors/nunomaduro</>',
];
/** @var OutputInterface */
private $output;
public function __construct(OutputInterface $output)
public function __construct(private OutputInterface $output)
{
$this->output = $output;
// ..
}
/**

View File

@ -20,14 +20,14 @@ final class Datasets
*
* @var array<int|string, Closure|iterable<int|string, mixed>>
*/
private static $datasets = [];
private static array $datasets = [];
/**
* Sets the given.
*
* @param Closure|iterable<int|string, mixed> $data
*/
public static function set(string $name, $data): void
public static function set(string $name, Closure|iterable $data): void
{
if (array_key_exists($name, self::$datasets)) {
throw new DatasetAlreadyExist($name);
@ -39,7 +39,7 @@ final class Datasets
/**
* @return Closure|iterable<int|string, mixed>
*/
public static function get(string $name)
public static function get(string $name): Closure|iterable
{
if (!array_key_exists($name, self::$datasets)) {
throw new DatasetDoesNotExist($name);
@ -161,10 +161,9 @@ final class Datasets
}
/**
* @param int|string $key
* @param array<int, mixed> $data
*/
private static function getDataSetDescription($key, array $data): string
private static function getDataSetDescription(int|string $key, array $data): string
{
$exporter = new Exporter();

View File

@ -11,22 +11,14 @@ namespace Pest;
*/
final class Each
{
/**
* @var Expectation
*/
private $original;
/**
* @var bool
*/
private $opposite = false;
private bool $opposite = false;
/**
* Creates an expectation on each item of the iterable "value".
*/
public function __construct(Expectation $original)
public function __construct(private Expectation $original)
{
$this->original = $original;
// ..
}
/**

View File

@ -27,9 +27,7 @@ final class DatasetMissing extends BadFunctionCallException implements Exception
"A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s",
$name,
count($args),
implode(', ', array_map(static function (string $arg, string $type): string {
return sprintf('%s $%s', $type, $arg);
}, array_keys($args), $args)),
implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($args), $args)),
$file,
));
}

View File

@ -29,37 +29,26 @@ use Throwable;
*/
final class Expectation
{
use Extendable {
use RetrievesValues, Extendable {
__call as __extendsCall;
}
use RetrievesValues;
/**
* The expectation value.
*
* @readonly
*
* @var mixed
*/
public $value;
/**
* The exporter instance, if any.
*
* @readonly
*
* @var Exporter|null
*/
private $exporter;
private ?Exporter $exporter = null;
/**
* Creates a new expectation.
*
* @param TValue $value
*/
public function __construct($value)
{
$this->value = $value;
public function __construct(
public mixed $value
) {
// ..
}
/**
@ -69,7 +58,7 @@ final class Expectation
*
* @return Expectation<TValue>
*/
public function and($value): Expectation
public function and(mixed $value): Expectation
{
return new self($value);
}
@ -103,9 +92,9 @@ final class Expectation
/**
* Send the expectation value to Ray along with all given arguments.
*
* @param mixed $arguments
* @param ...mixed $arguments
*/
public function ray(...$arguments): self
public function ray(mixed ...$arguments): self
{
if (function_exists('ray')) {
// @phpstan-ignore-next-line
@ -146,9 +135,9 @@ final class Expectation
*
* @template TSequenceValue
*
* @param callable(self, self): void|TSequenceValue ...$callbacks
* @param (callable(self, self): void)|TSequenceValue ...$callbacks
*/
public function sequence(...$callbacks): Expectation
public function sequence(mixed ...$callbacks): Expectation
{
if (!is_iterable($this->value)) {
throw new BadMethodCallException('Expectation value is not iterable.');
@ -187,16 +176,14 @@ final class Expectation
*
* @template TMatchSubject of array-key
*
* @param callable(): TMatchSubject|TMatchSubject $subject
* @param (callable(): TMatchSubject)|TMatchSubject $subject
* @param array<TMatchSubject, (callable(Expectation<TValue>): mixed)|TValue> $expressions
*/
public function match($subject, array $expressions): Expectation
public function match(mixed $subject, array $expressions): Expectation
{
$subject = is_callable($subject)
? $subject
: function () use ($subject) {
return $subject;
};
: fn () => $subject;
$subject = $subject();
@ -229,15 +216,15 @@ final class Expectation
/**
* Apply the callback if the given "condition" is falsy.
*
* @param (callable(): bool)|bool $condition
* @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback
*/
public function unless($condition, callable $callback): Expectation
public function unless(callable|bool $condition, callable $callback): Expectation
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line
return $condition; // @phpstan-ignore-line
};
return $this->when(!$condition(), $callback);
@ -246,15 +233,15 @@ final class Expectation
/**
* Apply the callback if the given "condition" is truthy.
*
* @param (callable(): bool)|bool $condition
* @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback
*/
public function when($condition, callable $callback): Expectation
public function when(callable|bool $condition, callable $callback): Expectation
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line
return $condition; // @phpstan-ignore-line
};
if ($condition()) {
@ -268,10 +255,8 @@ final class Expectation
* Asserts that two variables have the same type and
* value. Used on objects, it asserts that two
* variables reference the same object.
*
* @param mixed $expected
*/
public function toBe($expected): Expectation
public function toBe(mixed $expected): Expectation
{
Assert::assertSame($expected, $this->value);
@ -330,10 +315,8 @@ final class Expectation
/**
* Asserts that the value is greater than $expected.
*
* @param int|float $expected
*/
public function toBeGreaterThan($expected): Expectation
public function toBeGreaterThan(int|float $expected): Expectation
{
Assert::assertGreaterThan($expected, $this->value);
@ -342,10 +325,8 @@ final class Expectation
/**
* Asserts that the value is greater than or equal to $expected.
*
* @param int|float $expected
*/
public function toBeGreaterThanOrEqual($expected): Expectation
public function toBeGreaterThanOrEqual(int|float $expected): Expectation
{
Assert::assertGreaterThanOrEqual($expected, $this->value);
@ -354,10 +335,8 @@ final class Expectation
/**
* Asserts that the value is less than or equal to $expected.
*
* @param int|float $expected
*/
public function toBeLessThan($expected): Expectation
public function toBeLessThan(int|float $expected): Expectation
{
Assert::assertLessThan($expected, $this->value);
@ -366,10 +345,8 @@ final class Expectation
/**
* Asserts that the value is less than $expected.
*
* @param int|float $expected
*/
public function toBeLessThanOrEqual($expected): Expectation
public function toBeLessThanOrEqual(int|float $expected): Expectation
{
Assert::assertLessThanOrEqual($expected, $this->value);
@ -378,10 +355,8 @@ final class Expectation
/**
* Asserts that $needle is an element of the value.
*
* @param mixed $needles
*/
public function toContain(...$needles): Expectation
public function toContain(mixed ...$needles): Expectation
{
foreach ($needles as $needle) {
if (is_string($this->value)) {
@ -456,10 +431,8 @@ final class Expectation
/**
* Asserts that the value contains the property $name.
*
* @param mixed $value
*/
public function toHaveProperty(string $name, $value = null): Expectation
public function toHaveProperty(string $name, mixed $value = null): Expectation
{
$this->toBeObject();
@ -489,10 +462,8 @@ final class Expectation
/**
* Asserts that two variables have the same value.
*
* @param mixed $expected
*/
public function toEqual($expected): Expectation
public function toEqual(mixed $expected): Expectation
{
Assert::assertEquals($expected, $this->value);
@ -507,10 +478,8 @@ final class Expectation
* are sorted before they are compared. When $expected and $this->value
* are objects, each object is converted to an array containing all
* private, protected and public attributes.
*
* @param mixed $expected
*/
public function toEqualCanonicalizing($expected): Expectation
public function toEqualCanonicalizing(mixed $expected): Expectation
{
Assert::assertEqualsCanonicalizing($expected, $this->value);
@ -520,10 +489,8 @@ final class Expectation
/**
* Asserts that the absolute difference between the value and $expected
* is lower than $delta.
*
* @param mixed $expected
*/
public function toEqualWithDelta($expected, float $delta): Expectation
public function toEqualWithDelta(mixed $expected, float $delta): Expectation
{
Assert::assertEqualsWithDelta($expected, $this->value, $delta);
@ -555,9 +522,9 @@ final class Expectation
/**
* Asserts that the value is an instance of $class.
*
* @param string $class
* @param class-string $class
*/
public function toBeInstanceOf($class): Expectation
public function toBeInstanceOf(string $class): Expectation
{
/* @phpstan-ignore-next-line */
Assert::assertInstanceOf($class, $this->value);
@ -708,11 +675,8 @@ final class Expectation
/**
* Asserts that the value array has the provided $key.
*
* @param string|int $key
* @param mixed $value
*/
public function toHaveKey($key, $value = null): Expectation
public function toHaveKey(string|int $key, mixed $value = null): Expectation
{
if (is_object($this->value) && method_exists($this->value, 'toArray')) {
$array = $this->value->toArray();
@ -812,9 +776,9 @@ final class Expectation
/**
* Asserts that the value array matches the given array subset.
*
* @param array<int|string, mixed> $array
* @param iterable<int|string, mixed> $array
*/
public function toMatchArray($array): Expectation
public function toMatchArray(iterable|object $array): Expectation
{
if (is_object($this->value) && method_exists($this->value, 'toArray')) {
$valueAsArray = $this->value->toArray();
@ -843,9 +807,9 @@ final class Expectation
* Asserts that the value object matches a subset
* of the properties of an given object.
*
* @param array<string, mixed>|object $object
* @param iterable<string, mixed>|object $object
*/
public function toMatchObject($object): Expectation
public function toMatchObject(iterable|object $object): Expectation
{
foreach ((array) $object as $property => $value) {
Assert::assertTrue(property_exists($this->value, $property));
@ -891,7 +855,7 @@ final class Expectation
*
* @param (Closure(Throwable): mixed)|string $exception
*/
public function toThrow($exception, string $exceptionMessage = null): Expectation
public function toThrow(callable|string $exception, string $exceptionMessage = null): Expectation
{
$callback = NullClosure::create();
@ -938,10 +902,8 @@ final class Expectation
/**
* Exports the given value.
*
* @param mixed $value
*/
private function export($value): string
private function export(mixed $value): string
{
if ($this->exporter === null) {
$this->exporter = new Exporter();
@ -971,10 +933,8 @@ final class Expectation
/**
* Dynamically calls methods on the class without any arguments
* or creates a new higher order expectation.
*
* @return Expectation|HigherOrderExpectation
*/
public function __get(string $name)
public function __get(string $name): Expectation|OppositeExpectation|Each|HigherOrderExpectation
{
if (!method_exists($this, $name) && !static::hasExtend($name)) {
return new HigherOrderExpectation($this, $this->retrieve($name, $this->value));

View File

@ -22,97 +22,68 @@ use RuntimeException;
*/
final class TestCaseFactory
{
/**
* Holds the test filename.
*
* @readonly
*
* @var string
*/
public $filename;
/**
* Marks this test case as only.
*
* @readonly
*
* @var bool
*/
public $only = false;
/**
* Holds the test description.
*
* If the description is null, means that it
* will be created with the given assertions.
*
* @var string|null
*/
public $description;
public bool $only = false;
/**
* Holds the test closure.
*
* @readonly
*
* @var Closure
*/
public $test;
public Closure $test;
/**
* Holds the dataset, if any.
*
* @var array<Closure|iterable<int|string, mixed>|string>
*/
public $datasets = [];
public array $datasets = [];
/**
* The FQN of the test case class.
*
* @var string
* @var class-string
*/
public $class = TestCase::class;
public string $class = TestCase::class;
/**
* An array of FQN of the class traits.
*
* @var array <int, string>
*/
public $traits = [
public array $traits = [
Concerns\Testable::class,
Concerns\Expectable::class,
];
/**
* Holds the higher order messages
* for the factory that are proxyble.
*
* @var HigherOrderMessageCollection
* Holds the higher order messages for the factory that are proxyble.
*/
public $factoryProxies;
public HigherOrderMessageCollection $factoryProxies;
/**
* Holds the higher order messages that are proxyble.
*
* @var HigherOrderMessageCollection
*/
public $proxies;
public HigherOrderMessageCollection $proxies;
/**
* Holds the higher order messages that are chainable.
*
* @var HigherOrderMessageCollection
*/
public $chains;
public HigherOrderMessageCollection $chains;
/**
* Creates a new anonymous test case pending object.
*/
public function __construct(string $filename, string $description = null, Closure $closure = null)
public function __construct(
public string $filename,
public ?string $description = null,
Closure $closure = null)
{
$this->filename = $filename;
$this->description = $description;
$this->test = $closure ?? function (): void {
$this->test = $closure ?? function (): void {
if (Assert::getCount() === 0) {
self::markTestIncomplete(); // @phpstan-ignore-line
}
@ -146,7 +117,7 @@ final class TestCaseFactory
$chains->chain($this);
/* @phpstan-ignore-next-line */
return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args());
return call_user_func(Closure::bind($factoryTest, $this, $this::class), ...func_get_args());
};
$className = $this->makeClassFromFilename($this->filename);
@ -170,9 +141,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', function ($match): string {
return strtolower($match['drive']);
}, $filename);
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', fn ($match): string => strtolower($match['drive']), $filename);
}
$filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
@ -184,9 +153,7 @@ final class TestCaseFactory
// Strip out any %-encoded octets.
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
// Remove escaped quote sequences (maintain namespace)
$relativePath = str_replace(array_map(function (string $quote): string {
return sprintf('\\%s', $quote);
}, ['\'', '"']), '', $relativePath);
$relativePath = str_replace(array_map(fn (string $quote): string => sprintf('\\%s', $quote), ['\'', '"']), '', $relativePath);
// Limit to A-Z, a-z, 0-9, '_', '-'.
$relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath);
@ -196,9 +163,7 @@ final class TestCaseFactory
}
$hasPrintableTestCaseClassFQN = sprintf('\%s', HasPrintableTestCaseName::class);
$traitsCode = sprintf('use %s;', implode(', ', array_map(function ($trait): string {
return sprintf('\%s', $trait);
}, $this->traits)));
$traitsCode = sprintf('use %s;', implode(', ', array_map(fn ($trait): string => sprintf('\%s', $trait), $this->traits)));
$partsFQN = explode('\\', $classFQN);
$className = array_pop($partsFQN);

View File

@ -18,10 +18,8 @@ use PHPUnit\Framework\TestCase;
* Creates a new expectation.
*
* @param mixed $value the Value
*
* @return Expectation|Extendable
*/
function expect($value = null)
function expect($value = null): Expectation|Extendable
{
if (func_num_args() === 0) {
return new Extendable(Expectation::class);
@ -60,7 +58,7 @@ if (!function_exists('dataset')) {
*
* @param Closure|iterable<int|string, mixed> $dataset
*/
function dataset(string $name, $dataset): void
function dataset(string $name, Closure|iterable $dataset): void
{
Datasets::set($name, $dataset);
}

View File

@ -17,39 +17,17 @@ final class HigherOrderExpectation
use Expectable;
use RetrievesValues;
/**
* @var Expectation
*/
private $original;
private Expectation|Each $expectation;
/**
* @var Expectation|Each
*/
private $expectation;
private bool $opposite = false;
/**
* @var bool
*/
private $opposite = false;
/**
* @var bool
*/
private $shouldReset = false;
/**
* @var string
*/
private $name;
private bool $shouldReset = false;
/**
* Creates a new higher order expectation.
*
* @param mixed $value
*/
public function __construct(Expectation $original, $value)
public function __construct(private Expectation $original, mixed $value)
{
$this->original = $original;
$this->expectation = $this->expect($value);
}
@ -72,7 +50,7 @@ final class HigherOrderExpectation
*
* @return Expectation<TValue>
*/
public function and($value): Expectation
public function and(mixed $value): Expectation
{
return $this->expect($value);
}
@ -118,10 +96,8 @@ final class HigherOrderExpectation
/**
* Retrieve the applicable value based on the current reset condition.
*
* @return mixed
*/
private function getValue()
private function getValue(): mixed
{
return $this->shouldReset ? $this->original->value : $this->expectation->value;
}

View File

@ -16,7 +16,6 @@ use function class_exists;
use DOMDocument;
use DOMElement;
use Exception;
use function get_class;
use function method_exists;
use Pest\Concerns\Testable;
use PHPUnit\Framework\AssertionFailedError;
@ -42,65 +41,50 @@ use function trim;
*/
final class JUnit extends Printer implements TestListener
{
/**
* @var DOMDocument
*/
private $document;
private DOMDocument $document;
private DOMElement $root;
/**
* @var DOMElement
* @var array<int, DOMElement>
*/
private $root;
/**
* @var DOMElement[]
*/
private $testSuites = [];
private array $testSuites = [];
/**
* @var int[]
*/
private $testSuiteTests = [0];
private array $testSuiteTests = [0];
/**
* @var int[]
*/
private $testSuiteAssertions = [0];
private array $testSuiteAssertions = [0];
/**
* @var int[]
*/
private $testSuiteErrors = [0];
private array $testSuiteErrors = [0];
/**
* @var int[]
*/
private $testSuiteWarnings = [0];
private array $testSuiteWarnings = [0];
/**
* @var int[]
*/
private $testSuiteFailures = [0];
private array $testSuiteFailures = [0];
/**
* @var int[]
*/
private $testSuiteSkipped = [0];
private array $testSuiteSkipped = [0];
/**
* @var int[]|float[]
*/
private $testSuiteTimes = [0];
private array $testSuiteTimes = [0];
/**
* @var int
*/
private $testSuiteLevel = 0;
private int $testSuiteLevel = 0;
/**
* @var DOMElement|null
*/
private $currentTestCase;
private ?DOMElement $currentTestCase = null;
public function __construct(string $out)
{
@ -190,7 +174,7 @@ final class JUnit extends Printer implements TestListener
}
$testSuite->setAttribute('file', $fileName);
} catch (ReflectionException $e) {
} catch (ReflectionException) {
// @ignoreException
}
}
@ -313,7 +297,7 @@ final class JUnit extends Printer implements TestListener
$testCase->setAttribute('class', $test->getPrintableTestCaseName());
$testCase->setAttribute('classname', str_replace('\\', '.', $test->getPrintableTestCaseName()));
// @phpstan-ignore-next-line
$testCase->setAttribute('file', $test->__getFileName());
$testCase->setAttribute('file', $test->__getFilename());
}
$this->currentTestCase = $testCase;
@ -409,7 +393,7 @@ final class JUnit extends Printer implements TestListener
if ($t instanceof ExceptionWrapper) {
$fault->setAttribute('type', $t->getClassName());
} else {
$fault->setAttribute('type', get_class($t));
$fault->setAttribute('type', $t::class);
}
$this->currentTestCase->appendChild($fault);

View File

@ -16,9 +16,9 @@ use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\TextUI\DefaultResultPrinter;
use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity as BaseTeamCity;
use function round;
use function str_replace;
use function strlen;
use Throwable;
final class TeamCity extends DefaultResultPrinter
@ -34,22 +34,19 @@ final class TeamCity extends DefaultResultPrinter
private const TEST_STARTED = 'testStarted';
private const TEST_FINISHED = 'testFinished';
/** @var int */
private $flowId;
private ?int $flowId = null;
/** @var bool */
private $isSummaryTestCountPrinted = false;
private bool $isSummaryTestCountPrinted = false;
/** @var \PHPUnit\Util\Log\TeamCity */
private $phpunitTeamCity;
private BaseTeamCity $phpunitTeamCity;
/**
* @param resource|string|null $out
* Creates a new printer instance.
*/
public function __construct($out, bool $verbose, string $colors)
public function __construct(resource|string|null $out, bool $verbose, string $colors)
{
parent::__construct($out, $verbose, $colors);
$this->phpunitTeamCity = new \PHPUnit\Util\Log\TeamCity($out, $verbose, $colors);
$this->phpunitTeamCity = new BaseTeamCity($out, $verbose, $colors);
$this->logo();
}
@ -74,9 +71,7 @@ final class TeamCity extends DefaultResultPrinter
'passed' => ['count' => $this->successfulTestCount($result), 'color' => 'fg-green'],
];
$filteredResults = array_filter($results, function ($item): bool {
return $item['count'] > 0;
});
$filteredResults = array_filter($results, fn ($item): bool => $item['count'] > 0);
foreach ($filteredResults as $key => $info) {
$this->writeWithColor($info['color'], $info['count'] . " $key", false);
@ -203,7 +198,7 @@ final class TeamCity extends DefaultResultPrinter
*/
private static function isPestTestSuite(TestSuite $suite): bool
{
return strncmp($suite->getName(), 'P\\', strlen('P\\')) === 0;
return str_starts_with($suite->getName(), 'P\\');
}
/**

View File

@ -14,17 +14,12 @@ use SebastianBergmann\Exporter\Exporter;
*/
final class OppositeExpectation
{
/**
* @var Expectation
*/
private $original;
/**
* Creates a new opposite expectation.
*/
public function __construct(Expectation $original)
public function __construct(private Expectation $original)
{
$this->original = $original;
// ..
}
/**
@ -37,7 +32,7 @@ final class OppositeExpectation
foreach ($keys as $key) {
try {
$this->original->toHaveKey($key);
} catch (ExpectationFailedException $e) {
} catch (ExpectationFailedException) {
continue;
}
@ -57,7 +52,7 @@ final class OppositeExpectation
try {
/* @phpstan-ignore-next-line */
$this->original->{$name}(...$arguments);
} catch (ExpectationFailedException $e) {
} catch (ExpectationFailedException) {
return $this->original;
}
@ -73,7 +68,7 @@ final class OppositeExpectation
try {
/* @phpstan-ignore-next-line */
$this->original->{$name};
} catch (ExpectationFailedException $e) {
} catch (ExpectationFailedException) {
return $this->original;
}
@ -90,10 +85,8 @@ final class OppositeExpectation
{
$exporter = new Exporter();
$toString = function ($argument) use ($exporter): string {
return $exporter->shortenedExport($argument);
};
$toString = fn ($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(function ($argument) use ($toString): string { return $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 ($argument): string => $toString($argument), $arguments))));
}
}

View File

@ -16,42 +16,24 @@ use Pest\TestSuite;
*/
final class AfterEachCall
{
/**
* Holds the test suite.
*
* @var TestSuite
*/
private $testSuite;
/**
* Holds the filename.
*
* @var string
*/
private $filename;
/**
* Holds the before each closure.
*
* @var Closure
*/
private $closure;
private Closure $closure;
/**
* Holds calls that should be proxied.
*
* @var HigherOrderMessageCollection
*/
private $proxies;
private HigherOrderMessageCollection $proxies;
/**
* Creates a new instance of before each call.
*/
public function __construct(TestSuite $testSuite, string $filename, Closure $closure = null)
{
$this->testSuite = $testSuite;
$this->filename = $filename;
$this->closure = $closure instanceof Closure ? $closure : NullClosure::create();
public function __construct(
private TestSuite $testSuite,
private string $filename, Closure $closure = null
) {
$this->closure = $closure instanceof Closure ? $closure : NullClosure::create();
$this->proxies = new HigherOrderMessageCollection();
}

View File

@ -16,42 +16,25 @@ use Pest\TestSuite;
*/
final class BeforeEachCall
{
/**
* Holds the test suite.
*
* @var TestSuite
*/
private $testSuite;
/**
* Holds the filename.
*
* @var string
*/
private $filename;
/**
* Holds the before each closure.
*
* @var Closure
*/
private $closure;
private \Closure $closure;
/**
* Holds calls that should be proxied.
*
* @var HigherOrderMessageCollection
*/
private $proxies;
private HigherOrderMessageCollection $proxies;
/**
* Creates a new instance of before each call.
*/
public function __construct(TestSuite $testSuite, string $filename, Closure $closure = null)
{
$this->testSuite = $testSuite;
$this->filename = $filename;
$this->closure = $closure instanceof Closure ? $closure : NullClosure::create();
public function __construct(
private TestSuite $testSuite,
private string $filename,
Closure $closure = null
) {
$this->closure = $closure instanceof Closure ? $closure : NullClosure::create();
$this->proxies = new HigherOrderMessageCollection();
}

View File

@ -19,40 +19,30 @@ use SebastianBergmann\Exporter\Exporter;
*/
final class TestCall
{
/**
* Holds the test suite.
*
* @readonly
*
* @var TestSuite
*/
private $testSuite;
/**
* Holds the test case factory.
*
* @readonly
*
* @var TestCaseFactory
*/
private $testCaseFactory;
private TestCaseFactory $testCaseFactory;
/**
* If test call is descriptionLess.
*
* @readonly
*
* @var bool
*/
private $descriptionLess = false;
private bool $descriptionLess = false;
/**
* Creates a new instance of a pending test call.
*/
public function __construct(TestSuite $testSuite, string $filename, string $description = null, Closure $closure = null)
{
public function __construct(
private TestSuite $testSuite,
string $filename,
string $description = null,
Closure $closure = null
) {
$this->testCaseFactory = new TestCaseFactory($filename, $description, $closure);
$this->testSuite = $testSuite;
$this->descriptionLess = $description === null;
}
@ -83,12 +73,12 @@ final class TestCall
*
* @param (callable(): bool)|bool $condition
*/
public function throwsIf($condition, string $exception, string $exceptionMessage = null): TestCall
public function throwsIf(callable|bool $condition, string $exception, string $exceptionMessage = null): TestCall
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line
return $condition; // @phpstan-ignore-line
};
if ($condition()) {
@ -149,10 +139,8 @@ final class TestCall
/**
* Skips the current test.
*
* @param Closure|bool|string $conditionOrMessage
*/
public function skip($conditionOrMessage = true, string $message = ''): TestCall
public function skip(Closure|bool|string $conditionOrMessage = true, string $message = ''): TestCall
{
$condition = is_string($conditionOrMessage)
? NullClosure::create()
@ -160,9 +148,7 @@ final class TestCall
$condition = is_callable($condition)
? $condition
: function () use ($condition) { /* @phpstan-ignore-line */
return $condition;
};
: fn () => $condition;
$message = is_string($conditionOrMessage)
? $conditionOrMessage

View File

@ -24,46 +24,32 @@ final class UsesCall
*
* @var array<int, Closure>
*/
private $hooks = [];
/**
* Holds the class and traits.
*
* @var array<int, string>
*/
private $classAndTraits;
/**
* Holds the base dirname here the uses call was performed.
*
* @var string
*/
private $filename;
private array $hooks = [];
/**
* Holds the targets of the uses.
*
* @var array<int, string>
*/
private $targets;
private array $targets;
/**
* Holds the groups of the uses.
*
* @var array<int, string>
*/
private $groups = [];
private array $groups = [];
/**
* Creates a new instance of a pending test uses.
*
* @param array<int, string> $classAndTraits
*/
public function __construct(string $filename, array $classAndTraits)
{
$this->classAndTraits = $classAndTraits;
$this->filename = $filename;
$this->targets = [$filename];
public function __construct(
private string $filename,
private array $classAndTraits
) {
$this->targets = [$filename];
}
/**
@ -76,14 +62,12 @@ final class UsesCall
$startChar = DIRECTORY_SEPARATOR;
if ('\\' === DIRECTORY_SEPARATOR || preg_match('~\A[A-Z]:(?![^/\\\\])~i', $path) > 0) {
$path = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', function ($match): string {
return strtolower($match['drive']);
}, $path);
$path = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', fn ($match): string => strtolower($match['drive']), $path);
$startChar = strtolower((string) preg_replace('~^([a-z]+:\\\).*$~i', '$1', __DIR__));
}
return 0 === strpos($path, $startChar)
return str_starts_with($path, $startChar)
? $path
: implode(DIRECTORY_SEPARATOR, [
dirname($this->filename),

View File

@ -14,7 +14,7 @@ final class Plugin
*
* @internal
*/
public static $callables = [];
public static array $callables = [];
/**
* Lazy loads an `uses` call on the context of plugins.

View File

@ -29,31 +29,30 @@ final class Coverage implements AddsOutput, HandlesArguments
/**
* Whether should show the coverage or not.
*
* @var bool
*/
public $coverage = false;
public bool $coverage = false;
/**
* The minimum coverage.
*
* @var float
*/
public $coverageMin = 0.0;
public float $coverageMin = 0.0;
/**
* @var OutputInterface
* Creates a new Plugin instance.
*/
private $output;
public function __construct(OutputInterface $output)
public function __construct(private OutputInterface $output)
{
$this->output = $output;
// ..
}
/**
* @param array<int, string> $originals
*
* @return array<int, string>
*/
public function handleArguments(array $originals): array
{
$arguments = array_merge([''], array_values(array_filter($originals, function ($original): bool {
$arguments = [...[''], ...array_values(array_filter($originals, function ($original): bool {
foreach ([self::COVERAGE_OPTION, self::MIN_OPTION] as $option) {
if ($original === sprintf('--%s', $option) || Str::startsWith($original, sprintf('--%s=', $option))) {
return true;
@ -61,7 +60,7 @@ final class Coverage implements AddsOutput, HandlesArguments
}
return false;
})));
}))];
$originals = array_flip($originals);
foreach ($arguments as $argument) {
@ -75,9 +74,9 @@ final class Coverage implements AddsOutput, HandlesArguments
$input = new ArgvInput($arguments, new InputDefinition($inputs));
if ((bool) $input->getOption(self::COVERAGE_OPTION)) {
$this->coverage = true;
$originals[] = '--coverage-php';
$originals[] = \Pest\Support\Coverage::getPath();
$this->coverage = true;
$originals[] = '--coverage-php';
$originals[] = \Pest\Support\Coverage::getPath();
}
if ($input->getOption(self::MIN_OPTION) !== null) {

View File

@ -21,17 +21,10 @@ final class Environment implements HandlesArguments
*/
public const LOCAL = 'local';
/**
* @var \Pest\Plugins\Environment|null
*/
private static $instance;
/**
* The current environment.
*
* @var string|null
*/
private static $name;
private static ?string $name = null;
/**
* Allows to handle custom command line arguments.

View File

@ -28,23 +28,14 @@ final class Init implements HandlesArguments
'ExampleTest.php' => 'tests/ExampleTest.php',
];
/**
* @var OutputInterface
*/
private $output;
/**
* @var TestSuite
*/
private $testSuite;
/**
* Creates a new Plugin instance.
*/
public function __construct(TestSuite $testSuite, OutputInterface $output)
{
$this->testSuite = $testSuite;
$this->output = $output;
public function __construct(
private TestSuite $testSuite,
private OutputInterface $output
) {
// ..
}
public function handleArguments(array $arguments): array

View File

@ -13,17 +13,13 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
final class Version implements HandlesArguments
{
/**
* @var OutputInterface
*/
private $output;
/**
* Creates a new instance of the plugin.
*/
public function __construct(OutputInterface $output)
{
$this->output = $output;
public function __construct(
private OutputInterface $output
) {
// ..
}
public function handleArguments(array $arguments): array

View File

@ -17,7 +17,7 @@ final class AfterAllRepository
/**
* @var array<string, Closure>
*/
private $state = [];
private array $state = [];
/**
* Runs the given closure for each after all.

View File

@ -18,7 +18,7 @@ final class AfterEachRepository
/**
* @var array<string, Closure>
*/
private $state = [];
private array $state = [];
/**
* Sets a after each closure.

View File

@ -17,7 +17,7 @@ final class BeforeAllRepository
/**
* @var array<string, Closure>
*/
private $state = [];
private array $state = [];
/**
* Runs one before all closure, and unsets it from the repository.

View File

@ -16,7 +16,7 @@ final class BeforeEachRepository
/**
* @var array<string, Closure>
*/
private $state = [];
private array $state = [];
/**
* Sets a before each closure.

View File

@ -30,12 +30,12 @@ final class TestRepository
/**
* @var array<string, TestCaseFactory>
*/
private $state = [];
private array $state = [];
/**
* @var array<string, array<int, array<int, string|Closure>>>
*/
private $uses = [];
private array $uses = [];
/**
* Counts the number of test cases.
@ -54,9 +54,7 @@ final class TestRepository
{
$testsWithOnly = $this->testsUsingOnly();
return array_values(array_map(function (TestCaseFactory $factory): string {
return $factory->filename;
}, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state));
return array_values(array_map(fn (TestCaseFactory $factory): string => $factory->filename, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state));
}
/**
@ -64,9 +62,7 @@ final class TestRepository
*/
public function build(TestSuite $testSuite, callable $each): void
{
$startsWith = function (string $target, string $directory): bool {
return Str::startsWith($target, $directory . DIRECTORY_SEPARATOR);
};
$startsWith = fn (string $target, string $directory): bool => Str::startsWith($target, $directory . DIRECTORY_SEPARATOR);
foreach ($this->uses as $path => $uses) {
[$classOrTraits, $groups, $hooks] = $uses;
@ -123,9 +119,7 @@ final class TestRepository
return [];
}
return array_filter($this->state, function ($testFactory): bool {
return $testFactory->only;
});
return array_filter($this->state, fn ($testFactory): bool => $testFactory->only);
}
/**
@ -147,8 +141,8 @@ final class TestRepository
foreach ($paths as $path) {
if (array_key_exists($path, $this->uses)) {
$this->uses[$path] = [
array_merge($this->uses[$path][0], $classOrTraits),
array_merge($this->uses[$path][1], $groups),
[...$this->uses[$path][0], ...$classOrTraits],
[...$this->uses[$path][1], ...$groups],
$this->uses[$path][2] + $hooks, // NOTE: array_merge will destroy numeric indices
];
} else {

View File

@ -17,6 +17,6 @@ final class EnsureConfigurationDefaults implements ConfiguredSubscriber
*/
public function notify(Configured $event): void
{
$configuration = $event->configuration();
// TODO...
}
}

View File

@ -18,7 +18,7 @@ final class EnsureTestsAreLoaded implements LoadedSubscriber
/**
* The current test suite, if any.
*/
private static ?TestSuite $testSuite;
private static ?TestSuite $testSuite = null;
/**
* Runs the subscriber.
@ -31,7 +31,7 @@ final class EnsureTestsAreLoaded implements LoadedSubscriber
$testSuite = \Pest\TestSuite::getInstance();
$testSuite->tests->build($testSuite, function (TestCase $testCase) use (&$testSuites): void {
$testCaseClass = get_class($testCase);
$testCaseClass = $testCase::class;
if (!array_key_exists($testCaseClass, $testSuites)) {
$testSuites[$testCaseClass] = [];
}

View File

@ -15,9 +15,8 @@ final class Arr
{
/**
* @param array<mixed> $array
* @param string|int $key
*/
public static function has(array $array, $key): bool
public static function has(array $array, string|int $key): bool
{
$key = (string) $key;
@ -38,12 +37,11 @@ final class Arr
/**
* @param array<mixed> $array
* @param string|int $key
* @param null $default
*
* @return array|mixed|null
*/
public static function get(array $array, $key, $default = null)
public static function get(array $array, string|int $key, $default = null)
{
$key = (string) $key;
@ -51,7 +49,7 @@ final class Arr
return $array[$key];
}
if (strpos($key, '.') === false) {
if (!str_contains($key, '.')) {
return $array[$key] ?? $default;
}

View File

@ -26,7 +26,6 @@ final class Backtrace
$current = null;
foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) {
if (Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) {
break;
}

View File

@ -18,9 +18,9 @@ final class ChainableClosure
{
return function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($closure, $this, get_class($this)), func_get_args());
call_user_func_array(Closure::bind($closure, $this, $this::class), func_get_args());
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, $this, get_class($this)), func_get_args());
call_user_func_array(Closure::bind($next, $this, $this::class), func_get_args());
};
}

View File

@ -13,15 +13,12 @@ use ReflectionParameter;
*/
final class Container
{
/**
* @var self
*/
private static $instance;
private static ?Container $instance = null;
/**
* @var array<string, mixed>
*/
private $instances = [];
private array $instances = [];
/**
* Gets a new or already existing container.

View File

@ -162,7 +162,7 @@ final class Coverage
$lastKey = count($array) - 1;
if (array_key_exists($lastKey, $array) && strpos($array[$lastKey], '..') !== false) {
if (array_key_exists($lastKey, $array) && str_contains($array[$lastKey], '..')) {
[$from] = explode('..', $array[$lastKey]);
$array[$lastKey] = $line > $from ? sprintf('%s..%s', $from, $line) : sprintf('%s..%s', $line, $from);

View File

@ -8,19 +8,13 @@ use Closure;
final class Extendable
{
/**
* The extendable class.
*
* @var string
*/
private $extendableClass;
/**
* Creates a new extendable instance.
*/
public function __construct(string $extendableClass)
{
$this->extendableClass = $extendableClass;
public function __construct(
private string $extendableClass
) {
// ..
}
/**

View File

@ -6,8 +6,6 @@ namespace Pest\Support;
use Closure;
use Pest\Expectation;
use Pest\PendingObjects\TestCall;
use PHPUnit\Framework\TestCase;
/**
* @internal
@ -15,13 +13,11 @@ use PHPUnit\Framework\TestCase;
final class HigherOrderCallables
{
/**
* @var object
* Creates a new Higher Order Callables instances.
*/
private $target;
public function __construct(object $target)
public function __construct(private object $target)
{
$this->target = $target;
// ..
}
/**
@ -33,7 +29,7 @@ final class HigherOrderCallables
*
* @return Expectation<TValue>
*/
public function expect($value)
public function expect(mixed $value): Expectation
{
return new Expectation($value instanceof Closure ? Reflection::bindCallableWithData($value) : $value);
}
@ -47,17 +43,15 @@ final class HigherOrderCallables
*
* @return Expectation<TValue>
*/
public function and($value)
public function and(mixed $value)
{
return $this->expect($value);
}
/**
* Tap into the test case to perform an action and return the test case.
*
* @return TestCall|TestCase|object
*/
public function tap(callable $callable)
public function tap(callable $callable): object
{
Reflection::bindCallableWithData($callable);

View File

@ -15,68 +15,31 @@ final class HigherOrderMessage
{
public const UNDEFINED_METHOD = 'Method %s does not exist';
/**
* The filename where the function was originally called.
*
* @readonly
*
* @var string
*/
public $filename;
/**
* The line where the function was originally called.
*
* @readonly
*
* @var int
*/
public $line;
/**
* The method or property name to access.
*
* @readonly
*
* @var string
*/
public $name;
/**
* The arguments.
*
* @var array<int, mixed>|null
*
* @readonly
*/
public $arguments;
/**
* An optional condition that will determine if the message will be executed.
*
* @var callable(): bool|null
* @var (callable(): bool)|null
*/
public $condition = null;
public $condition;
/**
* Creates a new higher order message.
*
* @param array<int, mixed>|null $arguments
*/
public function __construct(string $filename, int $line, string $methodName, $arguments)
{
$this->filename = $filename;
$this->line = $line;
$this->name = $methodName;
$this->arguments = $arguments;
public function __construct(
public string $filename,
public int $line,
public string $name,
public ?array $arguments
) {
// ..
}
/**
* Re-throws the given `$throwable` with the good line and filename.
*
* @return mixed
*/
public function call(object $target)
public function call(object $target): mixed
{
/* @phpstan-ignore-next-line */
if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) {
@ -91,7 +54,8 @@ final class HigherOrderMessage
try {
return is_array($this->arguments)
? Reflection::call($target, $this->name, $this->arguments)
: $target->{$this->name}; /* @phpstan-ignore-line */
: $target->{$this->name};
/* @phpstan-ignore-line */
} catch (Throwable $throwable) {
Reflection::setPropertyValue($throwable, 'file', $this->filename);
Reflection::setPropertyValue($throwable, 'line', $this->line);
@ -122,10 +86,8 @@ final class HigherOrderMessage
/**
* Determines whether or not there exists a higher order callable with the message name.
*
* @return bool
*/
private function hasHigherOrderCallable()
private function hasHigherOrderCallable(): bool
{
return in_array($this->name, get_class_methods(HigherOrderCallables::class), true);
}
@ -133,7 +95,7 @@ final class HigherOrderMessage
private static function getUndefinedMethodMessage(object $target, string $methodName): string
{
if (\PHP_MAJOR_VERSION >= 8) {
return sprintf(sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', get_class($target), $methodName)));
return sprintf(sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', $target::class, $methodName)));
}
return sprintf(self::UNDEFINED_METHOD, $methodName);

View File

@ -12,7 +12,7 @@ final class HigherOrderMessageCollection
/**
* @var array<int, HigherOrderMessage>
*/
private $messages = [];
private array $messages = [];
/**
* Adds a new higher order message to the collection.
@ -63,9 +63,7 @@ final class HigherOrderMessageCollection
{
return array_reduce(
$this->messages,
static function (int $total, HigherOrderMessage $message) use ($name): int {
return $total + (int) ($name === $message->name);
},
static fn (int $total, HigherOrderMessage $message): int => $total + (int) ($name === $message->name),
0,
);
}

View File

@ -15,19 +15,13 @@ final class HigherOrderTapProxy
{
private const UNDEFINED_PROPERTY = 'Undefined property: P\\';
/**
* The target being tapped.
*
* @var TestCase
*/
public $target;
/**
* Create a new tap proxy instance.
*/
public function __construct(TestCase $target)
{
$this->target = $target;
public function __construct(
public TestCase $target
) {
// ..
}
/**

View File

@ -193,9 +193,7 @@ final class Reflection
}
$arguments[$parameter->getName()] = implode('|', array_map(
static function (ReflectionNamedType $type): string {
return $type->getName();
},
static fn (ReflectionNamedType $type): string => $type->getName(),
($types instanceof ReflectionNamedType)
? [$types] // NOTE: normalize as list of to handle unions
: $types->getTypes(),

View File

@ -33,7 +33,7 @@ final class Str
*/
public static function startsWith(string $target, string $search): bool
{
return substr($target, 0, strlen($search)) === $search;
return str_starts_with($target, $search);
}
/**

View File

@ -19,71 +19,51 @@ final class TestSuite
{
/**
* Holds the current test case.
*
* @var TestCase|null
*/
public $test;
public ?TestCase $test = null;
/**
* Holds the tests repository.
*
* @var TestRepository
*/
public $tests;
public TestRepository $tests;
/**
* Holds the before each repository.
*
* @var BeforeEachRepository
*/
public $beforeEach;
public BeforeEachRepository $beforeEach;
/**
* Holds the before all repository.
*
* @var BeforeAllRepository
*/
public $beforeAll;
public BeforeAllRepository $beforeAll;
/**
* Holds the after each repository.
*
* @var AfterEachRepository
*/
public $afterEach;
public AfterEachRepository $afterEach;
/**
* Holds the after all repository.
*
* @var AfterAllRepository
*/
public $afterAll;
public AfterAllRepository $afterAll;
/**
* Holds the root path.
*
* @var string
*/
public $rootPath;
/**
* Holds the test path.
*
* @var string
*/
public $testPath;
public string $rootPath;
/**
* Holds an instance of the test suite.
*
* @var TestSuite
*/
private static $instance;
private static ?TestSuite $instance = null;
/**
* Creates a new instance of the test suite.
*/
public function __construct(string $rootPath, string $testPath)
public function __construct(string $rootPath, /**
* Holds the test path.
*/
public string $testPath)
{
$this->beforeAll = new BeforeAllRepository();
$this->beforeEach = new BeforeEachRepository();
@ -92,7 +72,6 @@ final class TestSuite
$this->afterAll = new AfterAllRepository();
$this->rootPath = (string) realpath($rootPath);
$this->testPath = $testPath;
}
/**