11#!/usr/bin/env php
22<?php declare (strict_types = 1 );
33
4+ use PhpParser \Comment ;
45use PhpParser \Node ;
56use PhpParser \ParserFactory ;
7+ use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocNode ;
8+ use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocTagNode ;
9+ use PHPStan \PhpDocParser \Ast \PhpDoc \ReturnTagValueNode ;
10+ use PHPStan \PhpDocParser \Lexer \Lexer ;
11+ use PHPStan \PhpDocParser \Parser \PhpDocParser ;
12+ use PHPStan \PhpDocParser \Parser \TokenIterator ;
13+ use PHPStan \PhpDocParser \Parser \ConstExprParser ;
14+ use PHPStan \PhpDocParser \Parser \TypeParser ;
615use Symfony \Component \Console \Application ;
716use Symfony \Component \Console \Command \Command ;
817use Symfony \Component \Console \Input \InputArgument ;
2635 /** @var \PhpParser\PrettyPrinter\Standard */
2736 private $ printer ;
2837
38+ /** @var Lexer */
39+ private $ phpDocLexer ;
40+
41+ /** @var PhpDocParser */
42+ private $ phpDocParser ;
43+
2944 public function __construct (
3045 \PhpParser \Parser $ parser ,
3146 \PhpParser \PrettyPrinter \Standard $ printer
@@ -34,6 +49,11 @@ public function __construct(
3449 parent ::__construct ();
3550 $ this ->parser = $ parser ;
3651 $ this ->printer = $ printer ;
52+ $ this ->phpDocLexer = new Lexer ();
53+
54+ $ constExprParser = new ConstExprParser ();
55+ $ typeParser = new TypeParser ($ constExprParser );
56+ $ this ->phpDocParser = new PhpDocParser ($ typeParser , $ constExprParser );
3757 }
3858
3959 protected function configure (): void
@@ -363,6 +383,20 @@ private function compareFunctions(Node\FunctionLike $old, Node\FunctionLike $new
363383 if ($ old ->getReturnType () === null && $ new ->getReturnType () !== null ) {
364384 if ($ new ->getDocComment () !== null && strpos ($ new ->getDocComment ()->getText (), '@tentative-return-type ' ) !== 0 ) {
365385 $ new ->returnType = null ; // @phpstan-ignore-line
386+ if ($ old ->getDocComment () !== null ) {
387+ $ oldPhpDocNode = $ this ->parseDocComment ($ old ->getDocComment ()->getText ());
388+ $ oldPhpDocReturn = $ this ->findPhpDocReturn ($ oldPhpDocNode );
389+ if ($ oldPhpDocNode !== null ) {
390+ $ newPhpDocNode = $ this ->parseDocComment ($ new ->getDocComment ()->getText ());
391+ $ newPhpDocReturn = $ this ->findPhpDocReturn ($ newPhpDocNode );
392+ if ($ newPhpDocReturn === null ) {
393+ $ children = $ newPhpDocNode ->children ;
394+ $ children [] = new PhpDocTagNode ('@return ' , $ oldPhpDocReturn );
395+ $ newPhpDocNodeWithReturn = new PhpDocNode ($ children );
396+ $ new ->setDocComment (new Comment \Doc ((string ) $ newPhpDocNodeWithReturn ));
397+ }
398+ }
399+ }
366400 }
367401 }
368402 if ($ this ->printType ($ old ->getReturnType ()) !== $ this ->printType ($ new ->getReturnType ())) {
@@ -521,6 +555,25 @@ private function filterClassPhpDocs(Node\Stmt\ClassLike $class): Node\Stmt\Class
521555 return $ class ;
522556 }
523557
558+ private function parseDocComment (string $ docComment ): PhpDocNode
559+ {
560+ $ tokens = new TokenIterator ($ this ->phpDocLexer ->tokenize ($ docComment ));
561+ $ phpDocNode = $ this ->phpDocParser ->parse ($ tokens );
562+ $ tokens ->consumeTokenType (Lexer::TOKEN_END );
563+
564+ return $ phpDocNode ;
565+ }
566+
567+ private function findPhpDocReturn (PhpDocNode $ node ): ?ReturnTagValueNode
568+ {
569+ $ returnTags = $ node ->getReturnTagValues ();
570+ if (count ($ returnTags ) !== 1 ) {
571+ return null ;
572+ }
573+
574+ return $ returnTags [0 ];
575+ }
576+
524577 /**
525578 * @param array<string, string> $classes
526579 * @param array<string, string> $functions
0 commit comments