From 81c3c3f7d5bc3c83a2f527c5f7363fb03ec2edfd Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:39:19 +0200 Subject: [PATCH 1/9] Configure psalm & fix basics --- composer.json | 12 ++++++++++-- psalm.xml | 28 ++++++++++++++++++++++++++++ src/Analyzer/Extractor/Nemaspace.php | 2 +- src/Class/Mocker.php | 4 ++-- src/Expectation.php | 3 +++ src/Expector/Expectation.php | 4 ++-- src/Expector/MockExpector.php | 2 +- 7 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 psalm.xml diff --git a/composer.json b/composer.json index 9b9e05d..bdd1ae3 100644 --- a/composer.json +++ b/composer.json @@ -16,16 +16,24 @@ "Tests\\": "tests" } }, + "require": { + "php": "^8.4" + }, "require-dev": { - "php": "^8.4", "symfony/var-dumper": "^7.4", "phpunit/phpunit": "^11.5", "friendsofphp/php-cs-fixer": "^3.92", - "exan/pudocumenter": "^0.1.7" + "exan/pudocumenter": "^0.1.7", + "vimeo/psalm": "^6.16" }, "scripts": { "test": "phpunit tests", + "psalm": "psalm --no-cache", + + "psalm-fix": "psalm --no-cache --alter --issues=all", + + "csf": "php-cs-fixer fix --using-cache=no --allow-risky=yes", "cs": "php-cs-fixer fix --using-cache=no --allow-risky=yes --dry-run" } diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..34e83dc --- /dev/null +++ b/psalm.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Analyzer/Extractor/Nemaspace.php b/src/Analyzer/Extractor/Nemaspace.php index 4e34354..b46ed81 100644 --- a/src/Analyzer/Extractor/Nemaspace.php +++ b/src/Analyzer/Extractor/Nemaspace.php @@ -15,7 +15,7 @@ class Nemaspace { public function __construct( - private readonly TokenEmitter $tokenEmitter, + readonly TokenEmitter $tokenEmitter, private ?array &$namespace, ) { $this->tokenEmitter->on(TokenFilter::ofType(T_NAMESPACE), function (array $token): void { diff --git a/src/Class/Mocker.php b/src/Class/Mocker.php index 14360fe..9326485 100644 --- a/src/Class/Mocker.php +++ b/src/Class/Mocker.php @@ -16,7 +16,7 @@ class Mocker { public private(set) array $interfaces = []; - public private(set) string $extends; + public private(set) ?string $extends = null; public function getCode(): string { @@ -37,7 +37,7 @@ public function getCode(): string $creator = 'return new class '; - if (isset($this->extends)) { + if ($this->extends !== null) { $parent = str_contains($this->extends, '@anonymous') ? get_parent_class($this->extends) : $this->extends; diff --git a/src/Expectation.php b/src/Expectation.php index 590ffe5..f105055 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -75,6 +75,9 @@ public function dd(): never dd($data); } + /** + * @psalm-suppress ForbiddenCode + */ var_dump($this->calls); die(); } diff --git a/src/Expector/Expectation.php b/src/Expector/Expectation.php index e334846..403a5f6 100644 --- a/src/Expector/Expectation.php +++ b/src/Expector/Expectation.php @@ -15,7 +15,7 @@ class Expectation { use FiltersMethodArgs; - public private(set) array $with; + public private(set) ?array $with = null; private MockedClassInterface $mock; @@ -44,7 +44,7 @@ public function matches(DetailedMethodCall $call): bool return false; } - if (isset($this->with) && empty( + if ($this->with !== null && empty( $this->filterArgs([$call->call->args], $this->with) )) { return false; diff --git a/src/Expector/MockExpector.php b/src/Expector/MockExpector.php index ef2f2f8..a94aca5 100644 --- a/src/Expector/MockExpector.php +++ b/src/Expector/MockExpector.php @@ -93,7 +93,7 @@ private function fail(int $i): void ); - MoockAssert::assert(false, true, sprintf($message)); + MoockAssert::assert(false, true, $message); } private function getFunctionNameAt(int $i): string From cf09e6b43c0468a9c91638849c21b53a590be31c Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:05:58 +0200 Subject: [PATCH 2/9] Apply more fixes --- composer.json | 2 +- psalm.xml | 1 + src/Analyzer/Extractor/Nemaspace.php | 2 +- src/Analyzer/TokenEmitter.php | 19 ++++++++++++++++++- src/Analyzer/TokenFilter.php | 11 ++++++++++- src/Analyzer/Utilize.php | 18 ++++++++++++++++-- src/Args/Arr.php | 6 ++++++ src/Args/Date.php | 6 ++++++ src/Args/Number.php | 9 +++++++++ src/Args/Str.php | 6 ++++++ src/Expectation.php | 3 +++ src/Expector/MockExpector.php | 2 ++ src/FiltersMethodArgs.php | 13 ++++++++++++- src/Methods/Mocker.php | 21 ++++++++++++++------- src/Mock.php | 6 ++++++ src/Properties/Mocker.php | 2 ++ 16 files changed, 113 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index bdd1ae3..4d304d7 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "psalm": "psalm --no-cache", - "psalm-fix": "psalm --no-cache --alter --issues=all", + "psalm-fix": "psalm --no-cache --alter --issues=MissingReturnType,MissingClosureReturnType,InvalidReturnType,InvalidNullableReturnType,InvalidFalsableReturnType,MissingParamType,MissingPropertyType,MismatchingDocblockParamType,MismatchingDocblockReturnType,LessSpecificReturnType,PossiblyUndefinedVariable,PossiblyUndefinedGlobalVariable,UnusedMethod,PossiblyUnusedMethod,UnusedProperty,PossiblyUnusedProperty,UnusedVariable,UnnecessaryVarAnnotation,ParamNameMismatch", "csf": "php-cs-fixer fix --using-cache=no --allow-risky=yes", diff --git a/psalm.xml b/psalm.xml index 34e83dc..e5ff0b4 100644 --- a/psalm.xml +++ b/psalm.xml @@ -24,5 +24,6 @@ + diff --git a/src/Analyzer/Extractor/Nemaspace.php b/src/Analyzer/Extractor/Nemaspace.php index b46ed81..63fa8ae 100644 --- a/src/Analyzer/Extractor/Nemaspace.php +++ b/src/Analyzer/Extractor/Nemaspace.php @@ -15,7 +15,7 @@ class Nemaspace { public function __construct( - readonly TokenEmitter $tokenEmitter, + public readonly TokenEmitter $tokenEmitter, private ?array &$namespace, ) { $this->tokenEmitter->on(TokenFilter::ofType(T_NAMESPACE), function (array $token): void { diff --git a/src/Analyzer/TokenEmitter.php b/src/Analyzer/TokenEmitter.php index cc0b5de..022f48a 100644 --- a/src/Analyzer/TokenEmitter.php +++ b/src/Analyzer/TokenEmitter.php @@ -10,10 +10,13 @@ class TokenEmitter { /** - * @var array + * @var array */ private array $handlers = []; + /** + * @psalm-return int<100000, 999999> + */ public function on(Closure $match, Closure $handler): int { $id = $this->getId(); @@ -22,6 +25,9 @@ public function on(Closure $match, Closure $handler): int return $id; } + /** + * @psalm-return int<100000, 999999> + */ public function onLineChange(Closure $handler): int { $line = 0; @@ -38,6 +44,9 @@ public function onLineChange(Closure $handler): int }); } + /** + * @psalm-return int<100000, 999999> + */ public function all(Closure $handler): int { $id = $this->getId(); @@ -52,6 +61,11 @@ public function once(Closure $match, Closure $handler): void $this->handlers[$id] = ['match' => $match, 'handler' => $handler, 'once' => true]; } + /** + * @return int[] + * + * @psalm-return list{int<100000, 999999>, int<100000, 999999>} + */ public function counter(Closure $up, Closure $down, int &$value): array { return [ @@ -71,6 +85,9 @@ public function remove(int ...$ids): void } } + /** + * @psalm-return int<100000, 999999> + */ private function getId(): int { do { diff --git a/src/Analyzer/TokenFilter.php b/src/Analyzer/TokenFilter.php index 8f73043..e8b675c 100644 --- a/src/Analyzer/TokenFilter.php +++ b/src/Analyzer/TokenFilter.php @@ -9,16 +9,25 @@ /** @internal */ class TokenFilter { - public static function eq(mixed $value): Closure + /** + * @psalm-return Closure(array|string):bool + */ + public static function eq(string $value): Closure { return fn (array|string $token) => $token === $value; } + /** + * @psalm-return Closure(array|string):bool + */ public static function ofType(int $type): Closure { return fn (array|string $token) => is_array($token) && $token[0] === $type; } + /** + * @psalm-return Closure(array|string):bool + */ public static function any(Closure ...$closures): Closure { return function (array|string $token) use ($closures) { diff --git a/src/Analyzer/Utilize.php b/src/Analyzer/Utilize.php index 9191215..901f2c0 100644 --- a/src/Analyzer/Utilize.php +++ b/src/Analyzer/Utilize.php @@ -49,6 +49,10 @@ public static function fromTokens(?string $declaringNamespace, array $uses): sta /** * @param array $line + * + * @return array[] + * + * @psalm-return array, array> */ private static function parseLine($line): array { @@ -61,7 +65,10 @@ private static function parseLine($line): array /** * @param array $tokens - * @return array> + * + * @return ((int|string)[]|string)[][] + * + * @psalm-return array, list> */ private static function individualUses($tokens): array { @@ -116,6 +123,10 @@ private static function parseIndividualUse(array $use): array /** * @param array $tokens + * + * @return string[] + * + * @psalm-return array */ private static function parseGroupUse(array $tokens): array { @@ -152,7 +163,10 @@ private static function parseGroupUse(array $tokens): array /** * @param array $tokens - * @return array> + * + * @return ((int|string)[]|string)[][] + * + * @psalm-return array, list> */ private static function splitGroupUse(array $tokens): array { diff --git a/src/Args/Arr.php b/src/Args/Arr.php index 99b8bb8..b59f167 100644 --- a/src/Args/Arr.php +++ b/src/Args/Arr.php @@ -8,11 +8,17 @@ class Arr { + /** + * @psalm-return Closure(array):bool + */ public static function count(int $expectedCount): Closure { return fn (array $actual): bool => count($actual) === $expectedCount; } + /** + * @psalm-return Closure(array):bool + */ public static function partial(array $expectation): Closure { $validator = function (array $actual, array $expectation) use (&$validator): bool { diff --git a/src/Args/Date.php b/src/Args/Date.php index 7ac3ba7..9f2a11d 100644 --- a/src/Args/Date.php +++ b/src/Args/Date.php @@ -9,11 +9,17 @@ class Date { + /** + * @psalm-return Closure(DateTimeInterface):bool + */ public static function before(DateTimeInterface $beforeDate): Closure { return fn (DateTimeInterface $date): bool => $date < $beforeDate; } + /** + * @psalm-return Closure(DateTimeInterface):bool + */ public static function after(DateTimeInterface $beforeDate): Closure { return fn (DateTimeInterface $date): bool => $date > $beforeDate; diff --git a/src/Args/Number.php b/src/Args/Number.php index 7c40198..397c257 100644 --- a/src/Args/Number.php +++ b/src/Args/Number.php @@ -8,16 +8,25 @@ class Number { + /** + * @psalm-return Closure(float|int):bool + */ public static function lt(int|float $lessThan): Closure { return fn (int|float $actual): bool => $actual < $lessThan; } + /** + * @psalm-return Closure(float|int):bool + */ public static function gt(int|float $greaterThan): Closure { return fn (int|float $actual): bool => $actual > $greaterThan; } + /** + * @psalm-return Closure(float|int):bool + */ public static function range(int|float $min, int|float $max): Closure { return fn (int|float $actual): bool => $actual >= $min && $actual <= $max; diff --git a/src/Args/Str.php b/src/Args/Str.php index 9f1f5b4..3a1edc6 100644 --- a/src/Args/Str.php +++ b/src/Args/Str.php @@ -8,11 +8,17 @@ class Str { + /** + * @psalm-return Closure(string):bool + */ public static function length(int $expectedLength): Closure { return fn (string $actual): bool => strlen($actual) === $expectedLength; } + /** + * @psalm-return Closure(string):bool + */ public static function contains(string $needle): Closure { return fn (string $actual): bool => str_contains(strtolower($actual), strtolower($needle)); diff --git a/src/Expectation.php b/src/Expectation.php index f105055..5be1f40 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -35,6 +35,9 @@ public function with(mixed ...$expectedArg): Expectation return new Expectation($this->classMock, $this->methodName, $filteredCalls, $this->expectation); } + /** + * @psalm-return int<0, max> + */ private function callsAmount(): int { return count($this->calls); diff --git a/src/Expector/MockExpector.php b/src/Expector/MockExpector.php index a94aca5..f872158 100644 --- a/src/Expector/MockExpector.php +++ b/src/Expector/MockExpector.php @@ -104,6 +104,8 @@ private function getFunctionNameAt(int $i): string /** * @return DetailedMethodCall[] + * + * @psalm-return list */ private function getActualCalls(): array { diff --git a/src/FiltersMethodArgs.php b/src/FiltersMethodArgs.php index e3e8785..3eb62ee 100644 --- a/src/FiltersMethodArgs.php +++ b/src/FiltersMethodArgs.php @@ -9,6 +9,9 @@ trait FiltersMethodArgs { + /** + * @psalm-return array, mixed> + */ private function filterArgs(array $inputs, array $filters): array { if (empty($inputs)) { @@ -37,7 +40,15 @@ private function filterArgs(array $inputs, array $filters): array return $inputs; } - private function convertArgsToDictionary(array $inputs, array $filters) + /** + * @template TArray of array + * + * @param non-empty-list $inputs + * @param list $filters + * + * @return array, mixed> + */ + private function convertArgsToDictionary(array $inputs, array $filters): array { $argKeys = array_keys($inputs[0]); diff --git a/src/Methods/Mocker.php b/src/Methods/Mocker.php index da5bc7e..1658919 100644 --- a/src/Methods/Mocker.php +++ b/src/Methods/Mocker.php @@ -28,11 +28,18 @@ public function __construct( ) { /** @var ReflectionMethod[] */ $allMethods = array_merge( - ...array_map(function (string $interface): array { - $ref = new ReflectionClass($interface); - - return $ref->getMethods(ReflectionMethod::IS_PUBLIC); - }, $this->interfaces), + ...array_map(/** + * @return \ReflectionMethod[] + * + * @psalm-return list + */ + function (string $interface): array { + $ref = new ReflectionClass($interface); + + return $ref->getMethods(ReflectionMethod::IS_PUBLIC); + }, + $this->interfaces + ), ); $methodNames = []; @@ -173,8 +180,8 @@ private function extractDefaultParamSignature(ReflectionParameter $parameter, Re return $token; } - $argIsColon = fn (int $i) => isset($arg[$i]) && $arg[$i] === ':'; - $argIsWhitespace = fn (int $i) => isset($arg[$i]) && is_array($arg[$i]) && $arg[$i][0] === T_WHITESPACE; + $argIsColon = fn (int $i): bool => isset($arg[$i]) && $arg[$i] === ':'; + $argIsWhitespace = fn (int $i): bool => isset($arg[$i]) && is_array($arg[$i]) && $arg[$i][0] === T_WHITESPACE; if ( $token[0] === T_STRING diff --git a/src/Mock.php b/src/Mock.php index 336cb57..f895a9e 100644 --- a/src/Mock.php +++ b/src/Mock.php @@ -10,6 +10,9 @@ use Exan\Moock\Expector\MockExpector; use ReflectionFunction; +/** + * @psalm-suppress InvalidReturnStatement,InvalidReturnType + */ class Mock { /** @@ -108,6 +111,9 @@ public static function verify(Closure $expectation): void $mockExpector->validate($expectation); } + /** + * @psalm-return Closure(...mixed):mixed + */ public static function fn(): Closure { $mock = Mock::interface(MockFnInterface::class); diff --git a/src/Properties/Mocker.php b/src/Properties/Mocker.php index f3ade7e..4cef9c8 100644 --- a/src/Properties/Mocker.php +++ b/src/Properties/Mocker.php @@ -94,6 +94,8 @@ private function getFormattedProperty(ReflectionProperty $property): string /** * @return ReflectionProperty[] + * + * @psalm-return array, ReflectionProperty> */ private function getPropertiesToMock(): array { From 03f937b36c60be425faa2fb2b83d107855248da7 Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:14:26 +0200 Subject: [PATCH 3/9] Fix remaining errors --- src/Analyzer/Utilize.php | 9 +++++++-- src/Methods/Mocker.php | 23 +++++++++++------------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Analyzer/Utilize.php b/src/Analyzer/Utilize.php index 901f2c0..30d52ab 100644 --- a/src/Analyzer/Utilize.php +++ b/src/Analyzer/Utilize.php @@ -110,11 +110,16 @@ private static function parseIndividualUse(array $use): array } if (count($use) === 5 && is_array($use[2]) && $use[2][1] === 'as') { - // use Some\Potentially\Namespaced\Class as Alias + // use Some\Class as Alias + + /** @var array{0: int, 1: string, 2: int} */ $trueImport = array_shift($use); + /** @var array{0: int, 1: string, 2: int} */ $alias = array_pop($use); - return [$alias[1] => $trueImport[1]]; + return [ + $alias[1] => $trueImport[1], + ]; } // use Some\Potentially\Namespaced\{Class, Or\Other\Class} diff --git a/src/Methods/Mocker.php b/src/Methods/Mocker.php index 1658919..d7662c7 100644 --- a/src/Methods/Mocker.php +++ b/src/Methods/Mocker.php @@ -26,22 +26,21 @@ class Mocker public function __construct( public readonly array $interfaces, ) { - /** @var ReflectionMethod[] */ - $allMethods = array_merge( - ...array_map(/** - * @return \ReflectionMethod[] - * - * @psalm-return list + /** @var list> */ + $allMethodsPerInterface = array_map( + /** + * @return list */ - function (string $interface): array { - $ref = new ReflectionClass($interface); + function (string $interface): array { + $ref = new ReflectionClass($interface); - return $ref->getMethods(ReflectionMethod::IS_PUBLIC); - }, - $this->interfaces - ), + return $ref->getMethods(ReflectionMethod::IS_PUBLIC); + }, + $this->interfaces ); + $allMethods = array_merge(...$allMethodsPerInterface); + $methodNames = []; $methodsToMock = []; From 79ec95380af44226cc69796c0fc426b9111b30ab Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:37:38 +0200 Subject: [PATCH 4/9] Fix remaining issues --- src/Analyzer/Utilize.php | 6 +++--- src/Class/Mocker.php | 1 + src/FiltersMethodArgs.php | 5 +++-- src/Formatting/Variables.php | 7 ++++--- src/Methods/Mocker.php | 12 +++++++++++- src/Mock.php | 6 ++++-- src/MockMethod.php | 2 +- src/Properties/Mocker.php | 4 ++++ 8 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/Analyzer/Utilize.php b/src/Analyzer/Utilize.php index 30d52ab..4b40a2a 100644 --- a/src/Analyzer/Utilize.php +++ b/src/Analyzer/Utilize.php @@ -40,11 +40,11 @@ private function format(string $fullyQualifiedClass): string * @param string $declaringNamespace * @param array> $uses */ - public static function fromTokens(?string $declaringNamespace, array $uses): static + public static function fromTokens(?string $declaringNamespace, array $uses): self { $convertedUses = array_merge(...array_map(static::parseLine(...), $uses)); - return new static($declaringNamespace, array_merge(...$convertedUses)); + return new self($declaringNamespace, array_merge(...$convertedUses)); } /** @@ -148,7 +148,7 @@ private static function parseGroupUse(array $tokens): array $imports = static::splitGroupUse($tokens); return array_merge(...array_map(function (array $tokens) use ($namespacePrefix) { - while (count($tokens) && is_array($tokens) && $tokens[0][0] === T_WHITESPACE) { + while (count($tokens) && is_array($tokens[0]) && $tokens[0][0] === T_WHITESPACE) { array_shift($tokens); } diff --git a/src/Class/Mocker.php b/src/Class/Mocker.php index 9326485..7d4b92f 100644 --- a/src/Class/Mocker.php +++ b/src/Class/Mocker.php @@ -16,6 +16,7 @@ class Mocker { public private(set) array $interfaces = []; + /** @var null|class-string */ public private(set) ?string $extends = null; public function getCode(): string diff --git a/src/FiltersMethodArgs.php b/src/FiltersMethodArgs.php index 3eb62ee..80a0031 100644 --- a/src/FiltersMethodArgs.php +++ b/src/FiltersMethodArgs.php @@ -5,6 +5,7 @@ namespace Exan\Moock; use Closure; +use Exan\Moock\Dto\MethodCall; use RuntimeException; trait FiltersMethodArgs @@ -31,8 +32,8 @@ private function filterArgs(array $inputs, array $filters): array } $validator = $valueOrValidator instanceof Closure - ? fn ($call): bool => $valueOrValidator($call[$name]) - : fn ($call): bool => $call[$name] === $valueOrValidator; + ? fn (array $call): bool => $valueOrValidator($call[$name]) + : fn (array $call): bool => $call[$name] === $valueOrValidator; $inputs = array_filter($inputs, $validator); } diff --git a/src/Formatting/Variables.php b/src/Formatting/Variables.php index be68303..555a03c 100644 --- a/src/Formatting/Variables.php +++ b/src/Formatting/Variables.php @@ -7,6 +7,7 @@ use ReflectionClass; use ReflectionIntersectionType; use ReflectionNamedType; +use ReflectionType; use ReflectionUnionType; /** @@ -43,7 +44,7 @@ private function formatValue(mixed $value): string : (string) $value; } - private function getTypeSignature(ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null $type, ?ReflectionClass $declaringClass = null): string + private function getTypeSignature(ReflectionType|null $type, ?ReflectionClass $declaringClass = null): string { if ($type === null) { return ''; @@ -59,9 +60,9 @@ private function getTypeSignature(ReflectionNamedType|ReflectionUnionType|Reflec } $types[] = $signature; - } else { + } elseif ($type instanceof ReflectionUnionType || $type instanceof ReflectionIntersectionType) { $types = array_map( - fn (ReflectionNamedType $subType) => $this->isSemiBuiltIn($subType) ? $subType->getName() : '\\' . $subType->getName(), + fn (ReflectionType $subType): string => $this->getTypeSignature($subType, $declaringClass), $type->getTypes(), ); } diff --git a/src/Methods/Mocker.php b/src/Methods/Mocker.php index d7662c7..f1545da 100644 --- a/src/Methods/Mocker.php +++ b/src/Methods/Mocker.php @@ -22,7 +22,7 @@ class Mocker public readonly array $methods; - /** @param string[] $interfaces */ + /** @param class-string[] $interfaces */ public function __construct( public readonly array $interfaces, ) { @@ -152,6 +152,11 @@ private function extractDefaultParamSignature(ReflectionParameter $parameter, Re } $fileContents = file_get_contents($class->getFileName()); + if ($fileContents === false) { + throw new RuntimeException( + sprintf('Unable to load source file for class %s', $class->getFileName()) + ); + } if ($class->isAnonymous()) { $fullName = explode(':', $class->getName()); @@ -169,6 +174,11 @@ private function extractDefaultParamSignature(ReflectionParameter $parameter, Re ); $arg = $yanked->args; + if ($arg === null) { + throw new RuntimeException( + sprintf('Unable to retrieve constructor args for %s::%s()', $class->getName(), $method->getName()) + ); + } array_shift($arg); // new array_shift($arg); // (whitespace) diff --git a/src/Mock.php b/src/Mock.php index f895a9e..4a385b7 100644 --- a/src/Mock.php +++ b/src/Mock.php @@ -65,8 +65,10 @@ private static function anonymousClass(string $class): mixed $mocker = new ClassMocker(); - foreach ($implements as $interface) { - $mocker->addInterface($interface); + if ($implements !== false) { + foreach ($implements as $interface) { + $mocker->addInterface($interface); + } } return static::codeToMock($mocker->extends($class)->getCode()); diff --git a/src/MockMethod.php b/src/MockMethod.php index ace1185..c2f6a42 100644 --- a/src/MockMethod.php +++ b/src/MockMethod.php @@ -55,7 +55,7 @@ public function void(): static public function returns(mixed $returnValue): static { - $this->classMock->__replace($this->methodName, fn () => $returnValue); + $this->classMock->__replace($this->methodName, fn (): mixed => $returnValue); return $this; } diff --git a/src/Properties/Mocker.php b/src/Properties/Mocker.php index 4cef9c8..8b34cbc 100644 --- a/src/Properties/Mocker.php +++ b/src/Properties/Mocker.php @@ -16,8 +16,12 @@ class Mocker { use FormatsVariables; + /** @var null|class-string */ public readonly ?string $parent; + /** + * @param class-string $toMock + */ public function __construct( public readonly string $toMock, ) { From 7c219deb3a00f371383bb23f5ecc4f96062937c3 Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:39:22 +0200 Subject: [PATCH 5/9] Rename prop --- src/Analyzer/Dto/Yanked.php | 2 +- src/Methods/Mocker.php | 2 +- tests/Analyzer/YankerTest.php | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Analyzer/Dto/Yanked.php b/src/Analyzer/Dto/Yanked.php index 0e43a77..0f6ccc8 100644 --- a/src/Analyzer/Dto/Yanked.php +++ b/src/Analyzer/Dto/Yanked.php @@ -9,6 +9,6 @@ public function __construct( public ?array $namespace, public array $uses, - public ?array $args, + public ?array $arg, ) {} } diff --git a/src/Methods/Mocker.php b/src/Methods/Mocker.php index f1545da..f214c5f 100644 --- a/src/Methods/Mocker.php +++ b/src/Methods/Mocker.php @@ -173,7 +173,7 @@ private function extractDefaultParamSignature(ReflectionParameter $parameter, Re $yanked->uses ); - $arg = $yanked->args; + $arg = $yanked->arg; if ($arg === null) { throw new RuntimeException( sprintf('Unable to retrieve constructor args for %s::%s()', $class->getName(), $method->getName()) diff --git a/tests/Analyzer/YankerTest.php b/tests/Analyzer/YankerTest.php index 6478e70..c64e709 100644 --- a/tests/Analyzer/YankerTest.php +++ b/tests/Analyzer/YankerTest.php @@ -90,7 +90,7 @@ public function myMethod(string \$arg = 'not-test'): void T_CONSTANT_ENCAPSED_STRING, "'test'", 5, - ]], $yanked->args); + ]], $yanked->arg); } #[Test] @@ -110,7 +110,7 @@ public function myMethod(string \$other = 'something', string \$arg = new Someth } PHP, ['MyClass', 'myMethod', '$arg']); - $this->assertEquals('new Something(new OtherThing())', $this->implode($yanked->args)); + $this->assertEquals('new Something(new OtherThing())', $this->implode($yanked->arg)); } #[Test] @@ -130,7 +130,7 @@ public function myMethod(string \$other = 'something', string \$arg = new Someth } PHP, ['MyClass', 'myMethod', '$arg']); - $this->assertEquals('new Something(\'class MyClass\')', $this->implode($yanked->args)); + $this->assertEquals('new Something(\'class MyClass\')', $this->implode($yanked->arg)); } #[Test] @@ -149,7 +149,7 @@ public function myMethod(string \$other = 'something', string \$arg = new Someth }; PHP, ['3$0', 'myMethod', '$arg']); - $this->assertEquals('new Something(\'class MyClass\')', $this->implode($yanked->args)); + $this->assertEquals('new Something(\'class MyClass\')', $this->implode($yanked->arg)); } #[Test] @@ -173,7 +173,7 @@ public function it_extracts_args_from_the_correct_anonymous_class_on_single_line T_CONSTANT_ENCAPSED_STRING, '"expected"', 1, - ]], $yanked->args); + ]], $yanked->arg); } #[Test] @@ -205,6 +205,6 @@ public function myMethod(string \$arg = 'expected'): void T_CONSTANT_ENCAPSED_STRING, "'expected'", 13, - ]], $yanked->args); + ]], $yanked->arg); } } From b2f8676f2137d4349f4124c46be1cc287f76aa5c Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:46:42 +0200 Subject: [PATCH 6/9] Add CI --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a27a55a..cbd6545 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,3 +9,5 @@ on: jobs: build: uses: Exanlv/ci/.github/workflows/php-unit-tests.yml@main + psalm: + uses: Exanlv/ci/.github/workflows/psalm-l1.yml From d570f2eb684a059019b676ef7ae789a2cf637cd4 Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:48:32 +0200 Subject: [PATCH 7/9] Trigger CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbd6545..5346712 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,5 +9,5 @@ on: jobs: build: uses: Exanlv/ci/.github/workflows/php-unit-tests.yml@main - psalm: + psalm-verify: uses: Exanlv/ci/.github/workflows/psalm-l1.yml From 6c5d8c9acaa2abd23024704141bcaf5f32aec437 Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:50:30 +0200 Subject: [PATCH 8/9] Trigger CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5346712..cbd6545 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,5 +9,5 @@ on: jobs: build: uses: Exanlv/ci/.github/workflows/php-unit-tests.yml@main - psalm-verify: + psalm: uses: Exanlv/ci/.github/workflows/psalm-l1.yml From 61c39b2bab642851456eb36d358d690ec32c6bdc Mon Sep 17 00:00:00 2001 From: Exanlv <51094537+Exanlv@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:51:19 +0200 Subject: [PATCH 9/9] Specify version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbd6545..84da169 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,4 +10,4 @@ jobs: build: uses: Exanlv/ci/.github/workflows/php-unit-tests.yml@main psalm: - uses: Exanlv/ci/.github/workflows/psalm-l1.yml + uses: Exanlv/ci/.github/workflows/psalm-l1.yml@main