From 11b4944c916b2c7326aaa91838ea1c31615ff27a Mon Sep 17 00:00:00 2001 From: Woody Gilk Date: Mon, 1 Dec 2025 05:16:49 -0600 Subject: [PATCH] Fix static analysis warnings in mixin generator --- bin/src/MixinGenerator.php | 71 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/bin/src/MixinGenerator.php b/bin/src/MixinGenerator.php index 34a53058..4d9591f4 100644 --- a/bin/src/MixinGenerator.php +++ b/bin/src/MixinGenerator.php @@ -10,15 +10,12 @@ use ReflectionException; use ReflectionIntersectionType; use ReflectionMethod; +use ReflectionNamedType; use ReflectionType; use ReflectionUnionType; use RuntimeException; use Webmozart\Assert\Assert; -use function array_map; -use function implode; -use function rtrim; - final class MixinGenerator { /** @@ -26,7 +23,7 @@ final class MixinGenerator * * @var string[] */ - private $unsupportedMethods = [ + private array $unsupportedMethods = [ 'nullOrNotInstanceOf', // not supported by psalm (https://github.com/vimeo/psalm/issues/3443) 'allNotInstanceOf', // not supported by psalm (https://github.com/vimeo/psalm/issues/3443) 'nullOrNotEmpty', // not supported by psalm (https://github.com/vimeo/psalm/issues/3443) @@ -51,7 +48,7 @@ final class MixinGenerator * * @var string[] */ - private $skipMethods = [ + private array $skipMethods = [ 'nullOrNull', // meaningless 'nullOrNotNull', // meaningless 'allNullOrNull', // meaningless @@ -123,7 +120,7 @@ trait Mixin PHP , - implode("\n\n", $declaredMethods) + \implode("\n\n", $declaredMethods) ); } @@ -224,9 +221,8 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate, $parameters[] = $parameterReflection->name; if ($parameterReflection->isDefaultValueAvailable()) { - /** @var mixed $defaultValue */ $defaultValue = $parameterReflection->getDefaultValue(); - Assert::nullOrScalar($defaultValue); + $defaultValue = Assert::nullOrScalar($defaultValue); $parametersDefaults[$parameterReflection->name] = $defaultValue; } @@ -250,7 +246,7 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate, $paramsAdded = false; - $nativeReturnType = reset($parameterTypes) ?? 'mixed'; + $nativeReturnType = $parameterTypes ? $parameterTypes[\array_key_first($parameterTypes)] : 'mixed'; $phpdocReturnType = 'mixed'; $phpdocLines = []; @@ -275,10 +271,10 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate, foreach ($values as $i => $value) { $parts = $this->splitDocLine($value); - if (('param' === $key || 'psalm-param' === $key) && isset($parts[1]) && $parts[1] === '$'.$parameters[0] && 'mixed' !== $parts[0]) { + if (('param' === $key || 'psalm-param' === $key) && isset($parts[1]) && isset($parameters[0]) && $parts[1] === '$'.$parameters[0] && 'mixed' !== $parts[0]) { $parts[0] = $this->applyTypeTemplate($parts[0], $typeTemplate); - $values[$i] = implode(' ', $parts); + $values[$i] = \implode(' ', $parts); } } @@ -301,7 +297,7 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate, } if ('param' === $key) { - if (!array_key_exists(1, $parts)) { + if (!isset($parts[1])) { throw new RuntimeException(sprintf('param key must come with type and variable name in method: %s', $method->name)); } @@ -311,7 +307,7 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate, $comment = sprintf('@%s %s', $key, $type); if (count($parts) >= 2) { - $comment .= sprintf(' %s', implode(' ', array_slice($parts, 1))); + $comment .= sprintf(' %s', \implode(' ', array_slice($parts, 1))); } $phpdocLines[] = trim($comment); @@ -344,13 +340,15 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate, private function reduceParameterType(ReflectionType $type): string { if ($type instanceof ReflectionIntersectionType) { - return implode('&', array_map([$this, 'reduceParameterType'], $type->getTypes())); + return \implode('&', \array_map([$this, 'reduceParameterType'], $type->getTypes())); } if ($type instanceof ReflectionUnionType) { - return implode('|', array_map([$this, 'reduceParameterType'], $type->getTypes())); + return \implode('|', \array_map([$this, 'reduceParameterType'], $type->getTypes())); } + $type = Assert::isInstanceOf($type, ReflectionNamedType::class); + if ($type->getName() === 'mixed') { return $type->getName(); } @@ -398,7 +396,7 @@ private function findLongestTypeAndName(array $values): array $longestType = strlen($type); } - if (!array_key_exists(1, $parts)) { + if (!isset($parts[1])) { continue; } @@ -415,7 +413,6 @@ private function findLongestTypeAndName(array $values): array * @psalm-param list $parameters * @psalm-param array $defaults * @psalm-param list $phpdocLines - * @psalm-param callable(string,string):string $body * * @param string $name * @param string[] $parameters @@ -423,26 +420,29 @@ private function findLongestTypeAndName(array $values): array * @param string[] $defaults * @param array $phpdocLines * @param int $indent - * @param callable $body + * @param callable(string,string): string $body * @param string $returnType * * @return string */ private function staticMethod(string $name, array $parameters, array $types, array $defaults, array $phpdocLines, int $indent, callable $body, string $returnType): string { + Assert::notEmpty($parameters); + $indentation = str_repeat(' ', $indent); + $parameterList = \array_map(static fn (string $parameter): string => '$'.$parameter, $parameters); + $firstParameter = \array_shift($parameterList); + $parameterList = \implode(', ', $parameterList); + + $methodBody = \preg_replace('/(?<=^|\n)(?!\n)/', $indentation.$indentation, $body($firstParameter, $parameterList)); + + Assert::stringNotEmpty($methodBody); + $staticFunction = $this->phpdoc($phpdocLines, $indent)."\n"; $staticFunction .= $indentation.'public static function '.$name.$this->functionParameters($parameters, $types, $defaults).": {$returnType}\n" .$indentation."{\n"; - - $firstParameter = '$'.array_shift($parameters); - $parameters = implode(', ', \array_map(static function (string $parameter): string { - return '$'.$parameter; - }, $parameters)); - - $staticFunction .= preg_replace('/(?<=^|\n)(?!\n)/', $indentation.$indentation, $body($firstParameter, $parameters))."\n"; - $staticFunction .= $indentation.'}'; + $staticFunction .= $methodBody."\n".$indentation."}"; return $staticFunction; } @@ -496,10 +496,10 @@ private function phpdoc(array $lines, int $indent): string $throws = ''; foreach ($lines as $line) { - if (strpos($line, '@throws') === 0) { - $throws .= "\n".$indentation.rtrim(' * '.$line); + if (\str_starts_with($line, '@throws')) { + $throws .= "\n".$indentation.\rtrim(' * '.$line); } else { - $phpdoc .= "\n".$indentation.rtrim(' * '.$line); + $phpdoc .= "\n".$indentation.\rtrim(' * '.$line); } } @@ -538,7 +538,7 @@ private function parseDocComment(string $comment): array } /** - * @psalm-return array{0: string, 1?: string, 2?: string} + * @psalm-return list{string, string|null, string|null} * * @param string $line * @@ -547,15 +547,10 @@ private function parseDocComment(string $comment): array private function splitDocLine(string $line): array { if (!preg_match('~^(.*)\s+(\$\S+)(?:\s+(.*))?$~', $line, $matches)) { - return [$line]; - } - - $parts = [trim($matches[1]), $matches[2]]; - if (count($matches) > 3) { - $parts[2] = $matches[3]; + return [$line, null, null]; } - return $parts; + return [trim($matches[1]), $matches[2], $matches[3] ?? null]; } /**