Merge branch 'master' into better-dataset-support

# Conflicts:
#	src/Concerns/Testable.php
This commit is contained in:
luke
2021-11-27 19:28:39 +00:00
52 changed files with 929 additions and 1521 deletions

View File

@ -11,6 +11,8 @@ final class Arr
{
/**
* Checks if the given array has the given key.
*
* @param array<array-key, mixed> $array
*/
public static function has(array $array, string|int $key): bool
{
@ -33,6 +35,8 @@ final class Arr
/**
* Gets the given key value.
*
* @param array<array-key, mixed> $array
*/
public static function get(array $array, string|int $key, mixed $default = null): mixed
{

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Pest\Support;
use Closure;
use Pest\Exceptions\ShouldNotHappen;
/**
* @internal
@ -17,10 +18,12 @@ final class ChainableClosure
public static function from(Closure $closure, Closure $next): Closure
{
return function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */
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, $this::class), func_get_args());
if (!is_object($this)) { // @phpstan-ignore-line
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());
};
}
@ -30,10 +33,8 @@ final class ChainableClosure
public static function fromStatic(Closure $closure, Closure $next): Closure
{
return static function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($closure, null, self::class), func_get_args());
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, null, self::class), func_get_args());
\Pest\Support\Closure::bind($closure, null, self::class)(...func_get_args());
\Pest\Support\Closure::bind($next, null, self::class)(...func_get_args());
};
}
}

36
src/Support/Closure.php Normal file
View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Pest\Support;
use Closure as BaseClosure;
use Pest\Exceptions\ShouldNotHappen;
/**
* @internal
*/
final class Closure
{
/**
* Binds the given closure to the given "this".
*
* @return BaseClosure|never
*
* @throws ShouldNotHappen
*/
public static function bind(BaseClosure|null $closure, ?object $newThis, object|string|null $newScope = 'static'): BaseClosure
{
if ($closure == null) {
throw ShouldNotHappen::fromMessage('Could not bind null closure.');
}
$closure = BaseClosure::bind($closure, $newThis, $newScope);
if ($closure == false) {
throw ShouldNotHappen::fromMessage('Could not bind closure.');
}
return $closure;
}
}

View File

@ -35,16 +35,16 @@ final class Container
/**
* Gets a dependency from the container.
*
* @return object
* @param class-string $id
*
* @return mixed
*/
public function get(string $id)
{
if (array_key_exists($id, $this->instances)) {
return $this->instances[$id];
if (!array_key_exists($id, $this->instances)) {
$this->instances[$id] = $this->build($id);
}
$this->instances[$id] = $this->build($id);
return $this->instances[$id];
}
@ -60,10 +60,11 @@ final class Container
/**
* Tries to build the given instance.
*
* @param class-string $id
*/
private function build(string $id): object
{
/** @phpstan-ignore-next-line */
$reflectionClass = new ReflectionClass($id);
if ($reflectionClass->isInstantiable()) {
@ -84,6 +85,7 @@ final class Container
}
}
//@phpstan-ignore-next-line
return $this->get($candidate);
},
$constructor->getParameters()

View File

@ -50,6 +50,8 @@ final class ExceptionTrace
$property = new ReflectionProperty($t, 'serializableTrace');
$property->setAccessible(true);
/** @var array<int, array<string, string>> $trace */
$trace = $property->getValue($t);
$cleanedTrace = [];

View File

@ -25,13 +25,16 @@ final class HigherOrderCallables
*
* Create a new expectation. Callable values will be executed prior to returning the new expectation.
*
* @param callable|TValue $value
* @param (Closure():TValue)|TValue $value
*
* @return Expectation<TValue>
*/
public function expect(mixed $value): Expectation
{
return new Expectation($value instanceof Closure ? Reflection::bindCallableWithData($value) : $value);
/** @var TValue $value */
$value = $value instanceof Closure ? Reflection::bindCallableWithData($value) : $value;
return new Expectation($value);
}
/**

View File

@ -18,14 +18,14 @@ final class HigherOrderMessage
/**
* An optional condition that will determine if the message will be executed.
*
* @var (callable(): bool)|null
* @var (Closure(): bool)|null
*/
public $condition;
public ?Closure $condition = null;
/**
* Creates a new higher order message.
*
* @param array<int, mixed>|null $arguments
* @param array<int, mixed> $arguments
*/
public function __construct(
public string $filename,
@ -41,7 +41,6 @@ final class HigherOrderMessage
*/
public function call(object $target): mixed
{
/* @phpstan-ignore-next-line */
if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) {
return $target;
}
@ -54,8 +53,7 @@ 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);
@ -79,7 +77,7 @@ final class HigherOrderMessage
*/
public function when(callable $condition): self
{
$this->condition = $condition;
$this->condition = Closure::fromCallable($condition);
return $this;
}

View File

@ -19,7 +19,7 @@ final class HigherOrderMessageCollection
*
* @param array<int, mixed>|null $arguments
*/
public function add(string $filename, int $line, string $name, array $arguments = null): void
public function add(string $filename, int $line, string $name, ?array $arguments): void
{
$this->messages[] = new HigherOrderMessage($filename, $line, $name, $arguments);
}
@ -29,7 +29,7 @@ final class HigherOrderMessageCollection
*
* @param array<int, mixed>|null $arguments
*/
public function addWhen(callable $condition, string $filename, int $line, string $name, array $arguments = null): void
public function addWhen(callable $condition, string $filename, int $line, string $name, ?array $arguments): void
{
$this->messages[] = (new HigherOrderMessage($filename, $line, $name, $arguments))->when($condition);
}
@ -40,6 +40,7 @@ final class HigherOrderMessageCollection
public function chain(object $target): void
{
foreach ($this->messages as $message) {
//@phpstan-ignore-next-line
$target = $message->call($target) ?? $target;
}
}

View File

@ -13,7 +13,7 @@ use Throwable;
*/
final class HigherOrderTapProxy
{
private const UNDEFINED_PROPERTY = 'Undefined property: P\\';
private const UNDEFINED_PROPERTY = 'Undefined property: P\\'; // @phpstan-ignore-line
/**
* Create a new tap proxy instance.
@ -31,8 +31,7 @@ final class HigherOrderTapProxy
*/
public function __set(string $property, $value): void
{
// @phpstan-ignore-next-line
$this->target->{$property} = $value;
$this->target->{$property} = $value; // @phpstan-ignore-line
}
/**
@ -43,9 +42,8 @@ final class HigherOrderTapProxy
public function __get(string $property)
{
try {
// @phpstan-ignore-next-line
return $this->target->{$property};
} catch (Throwable $throwable) {
return $this->target->{$property}; // @phpstan-ignore-line
} catch (Throwable $throwable) { // @phpstan-ignore-line
Reflection::setPropertyValue($throwable, 'file', Backtrace::file());
Reflection::setPropertyValue($throwable, 'line', Backtrace::line());

View File

@ -48,4 +48,14 @@ final class Str
return substr($target, -$length) === $search;
}
/**
* Makes the given string evaluable by an `eval`.
*/
public static function evaluable(string $code): string
{
$code = str_replace(' ', '_', $code);
return (string) preg_replace('/[^A-Z_a-z0-9\\\\]/', '', $code);
}
}