1010use ReflectionException ;
1111use ReflectionIntersectionType ;
1212use ReflectionMethod ;
13+ use ReflectionNamedType ;
1314use ReflectionType ;
1415use ReflectionUnionType ;
1516use RuntimeException ;
1617use Webmozart \Assert \Assert ;
1718
18- use function array_map ;
19- use function implode ;
20- use function rtrim ;
21-
2219final class MixinGenerator
2320{
2421 /**
2522 * @psalm-var list<string>
2623 *
2724 * @var string[]
2825 */
29- private $ unsupportedMethods = [
26+ private array $ unsupportedMethods = [
3027 'nullOrNotInstanceOf ' , // not supported by psalm (https://github.com/vimeo/psalm/issues/3443)
3128 'allNotInstanceOf ' , // not supported by psalm (https://github.com/vimeo/psalm/issues/3443)
3229 'nullOrNotEmpty ' , // not supported by psalm (https://github.com/vimeo/psalm/issues/3443)
@@ -51,7 +48,7 @@ final class MixinGenerator
5148 *
5249 * @var string[]
5350 */
54- private $ skipMethods = [
51+ private array $ skipMethods = [
5552 'nullOrNull ' , // meaningless
5653 'nullOrNotNull ' , // meaningless
5754 'allNullOrNull ' , // meaningless
@@ -123,7 +120,7 @@ trait Mixin
123120
124121PHP
125122 ,
126- implode ("\n\n" , $ declaredMethods )
123+ \ implode ("\n\n" , $ declaredMethods )
127124 );
128125 }
129126
@@ -224,9 +221,8 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
224221 $ parameters [] = $ parameterReflection ->name ;
225222
226223 if ($ parameterReflection ->isDefaultValueAvailable ()) {
227- /** @var mixed $defaultValue */
228224 $ defaultValue = $ parameterReflection ->getDefaultValue ();
229- Assert::nullOrScalar ($ defaultValue );
225+ $ defaultValue = Assert::nullOrScalar ($ defaultValue );
230226
231227 $ parametersDefaults [$ parameterReflection ->name ] = $ defaultValue ;
232228 }
@@ -250,7 +246,7 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
250246
251247 $ paramsAdded = false ;
252248
253- $ nativeReturnType = reset ($ parameterTypes ) ?? 'mixed ' ;
249+ $ nativeReturnType = $ parameterTypes ? $ parameterTypes [ \array_key_first ($ parameterTypes )] : 'mixed ' ;
254250 $ phpdocReturnType = 'mixed ' ;
255251
256252 $ phpdocLines = [];
@@ -275,10 +271,10 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
275271
276272 foreach ($ values as $ i => $ value ) {
277273 $ parts = $ this ->splitDocLine ($ value );
278- if (('param ' === $ key || 'psalm-param ' === $ key ) && isset ($ parts [1 ]) && $ parts [1 ] === '$ ' .$ parameters [0 ] && 'mixed ' !== $ parts [0 ]) {
274+ if (('param ' === $ key || 'psalm-param ' === $ key ) && isset ($ parts [1 ]) && isset ( $ parameters [ 0 ]) && $ parts [1 ] === '$ ' .$ parameters [0 ] && 'mixed ' !== $ parts [0 ]) {
279275 $ parts [0 ] = $ this ->applyTypeTemplate ($ parts [0 ], $ typeTemplate );
280276
281- $ values [$ i ] = implode (' ' , $ parts );
277+ $ values [$ i ] = \ implode (' ' , $ parts );
282278 }
283279 }
284280
@@ -301,7 +297,7 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
301297 }
302298
303299 if ('param ' === $ key ) {
304- if (!array_key_exists ( 1 , $ parts )) {
300+ if (!isset ( $ parts[ 1 ] )) {
305301 throw new RuntimeException (sprintf ('param key must come with type and variable name in method: %s ' , $ method ->name ));
306302 }
307303
@@ -311,7 +307,7 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
311307
312308 $ comment = sprintf ('@%s %s ' , $ key , $ type );
313309 if (count ($ parts ) >= 2 ) {
314- $ comment .= sprintf (' %s ' , implode (' ' , array_slice ($ parts , 1 )));
310+ $ comment .= sprintf (' %s ' , \ implode (' ' , array_slice ($ parts , 1 )));
315311 }
316312
317313 $ phpdocLines [] = trim ($ comment );
@@ -344,13 +340,15 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
344340 private function reduceParameterType (ReflectionType $ type ): string
345341 {
346342 if ($ type instanceof ReflectionIntersectionType) {
347- return implode ('& ' , array_map ([$ this , 'reduceParameterType ' ], $ type ->getTypes ()));
343+ return \ implode ('& ' , \ array_map ([$ this , 'reduceParameterType ' ], $ type ->getTypes ()));
348344 }
349345
350346 if ($ type instanceof ReflectionUnionType) {
351- return implode ('| ' , array_map ([$ this , 'reduceParameterType ' ], $ type ->getTypes ()));
347+ return \ implode ('| ' , \ array_map ([$ this , 'reduceParameterType ' ], $ type ->getTypes ()));
352348 }
353349
350+ $ type = Assert::isInstanceOf ($ type , ReflectionNamedType::class);
351+
354352 if ($ type ->getName () === 'mixed ' ) {
355353 return $ type ->getName ();
356354 }
@@ -398,7 +396,7 @@ private function findLongestTypeAndName(array $values): array
398396 $ longestType = strlen ($ type );
399397 }
400398
401- if (!array_key_exists ( 1 , $ parts )) {
399+ if (!isset ( $ parts[ 1 ] )) {
402400 continue ;
403401 }
404402
@@ -415,34 +413,36 @@ private function findLongestTypeAndName(array $values): array
415413 * @psalm-param list<string> $parameters
416414 * @psalm-param array<string, scalar|null> $defaults
417415 * @psalm-param list<string> $phpdocLines
418- * @psalm-param callable(string,string):string $body
419416 *
420417 * @param string $name
421418 * @param string[] $parameters
422419 * @param array<string, string> $types
423420 * @param string[] $defaults
424421 * @param array $phpdocLines
425422 * @param int $indent
426- * @param callable $body
423+ * @param callable(string,string): string $body
427424 * @param string $returnType
428425 *
429426 * @return string
430427 */
431428 private function staticMethod (string $ name , array $ parameters , array $ types , array $ defaults , array $ phpdocLines , int $ indent , callable $ body , string $ returnType ): string
432429 {
430+ Assert::notEmpty ($ parameters );
431+
433432 $ indentation = str_repeat (' ' , $ indent );
434433
434+ $ parameterList = \array_map (static fn (string $ parameter ): string => '$ ' .$ parameter , $ parameters );
435+ $ firstParameter = \array_shift ($ parameterList );
436+ $ parameterList = \implode (', ' , $ parameterList );
437+
438+ $ methodBody = \preg_replace ('/(?<=^|\n)(?!\n)/ ' , $ indentation .$ indentation , $ body ($ firstParameter , $ parameterList ));
439+
440+ Assert::stringNotEmpty ($ methodBody );
441+
435442 $ staticFunction = $ this ->phpdoc ($ phpdocLines , $ indent )."\n" ;
436443 $ staticFunction .= $ indentation .'public static function ' .$ name .$ this ->functionParameters ($ parameters , $ types , $ defaults ).": {$ returnType }\n"
437444 .$ indentation ."{ \n" ;
438-
439- $ firstParameter = '$ ' .array_shift ($ parameters );
440- $ parameters = implode (', ' , \array_map (static function (string $ parameter ): string {
441- return '$ ' .$ parameter ;
442- }, $ parameters ));
443-
444- $ staticFunction .= preg_replace ('/(?<=^|\n)(?!\n)/ ' , $ indentation .$ indentation , $ body ($ firstParameter , $ parameters ))."\n" ;
445- $ staticFunction .= $ indentation .'} ' ;
445+ $ staticFunction .= $ methodBody ."\n" .$ indentation ."} " ;
446446
447447 return $ staticFunction ;
448448 }
@@ -496,10 +496,10 @@ private function phpdoc(array $lines, int $indent): string
496496 $ throws = '' ;
497497
498498 foreach ($ lines as $ line ) {
499- if (strpos ($ line , '@throws ' ) === 0 ) {
500- $ throws .= "\n" .$ indentation .rtrim (' * ' .$ line );
499+ if (\str_starts_with ($ line , '@throws ' )) {
500+ $ throws .= "\n" .$ indentation .\ rtrim (' * ' .$ line );
501501 } else {
502- $ phpdoc .= "\n" .$ indentation .rtrim (' * ' .$ line );
502+ $ phpdoc .= "\n" .$ indentation .\ rtrim (' * ' .$ line );
503503 }
504504 }
505505
@@ -538,7 +538,7 @@ private function parseDocComment(string $comment): array
538538 }
539539
540540 /**
541- * @psalm-return array{0: string, 1?: string, 2?: string}
541+ * @psalm-return list{ string, string|null, string|null }
542542 *
543543 * @param string $line
544544 *
@@ -547,15 +547,10 @@ private function parseDocComment(string $comment): array
547547 private function splitDocLine (string $ line ): array
548548 {
549549 if (!preg_match ('~^(.*)\s+(\$\S+)(?:\s+(.*))?$~ ' , $ line , $ matches )) {
550- return [$ line ];
551- }
552-
553- $ parts = [trim ($ matches [1 ]), $ matches [2 ]];
554- if (count ($ matches ) > 3 ) {
555- $ parts [2 ] = $ matches [3 ];
550+ return [$ line , null , null ];
556551 }
557552
558- return $ parts ;
553+ return [ trim ( $ matches [ 1 ]), $ matches [ 2 ], $ matches [ 3 ] ?? null ] ;
559554 }
560555
561556 /**
0 commit comments