diff --git a/src/Analyzer/ClassMethodAnalyzer.php b/src/Analyzer/ClassMethodAnalyzer.php index 7fdc0a7a..58117782 100644 --- a/src/Analyzer/ClassMethodAnalyzer.php +++ b/src/Analyzer/ClassMethodAnalyzer.php @@ -422,20 +422,16 @@ private function isReturnsEqualByNullability(ClassMethod $before, ClassMethod $a */ private function getDocReturnDeclaration(ClassMethod $method) { - if ($method->getDocComment() !== null) { - $lexer = new Lexer(); - $typeParser = new TypeParser(); - $constExprParser = new ConstExprParser(); - $phpDocParser = new PhpDocParser($typeParser, $constExprParser); - - $tokens = $lexer->tokenize((string)$method->getDocComment()); - $tokenIterator = new TokenIterator($tokens); - $phpDocNode = $phpDocParser->parse($tokenIterator); - $tags = $phpDocNode->getTagsByName('@return'); - /** @var PhpDocTagNode $tag */ - $tag = array_shift($tags); + if ( + ($parsedComment = $method->getAttribute('docCommentParsed')) + && isset($parsedComment['return']) + ) { + $result = implode('|', $parsedComment['return']); + + return $result; + } else { + return ' '; } - return isset($tag) ? (string)$tag->value : ' '; } /** diff --git a/src/Scanner/ScannerRegistryFactory.php b/src/Scanner/ScannerRegistryFactory.php index 0cc1ec98..edf5a0f6 100644 --- a/src/Scanner/ScannerRegistryFactory.php +++ b/src/Scanner/ScannerRegistryFactory.php @@ -18,7 +18,7 @@ use Magento\SemanticVersionChecker\Visitor\ApiTraitVisitor; use PhpParser\Lexer\Emulative; use PhpParser\NodeTraverser; -use PhpParser\NodeVisitor\NameResolver; +use Magento\SemanticVersionChecker\Visitor\NameResolver; use PhpParser\Parser\Php7 as Parser; use PHPSemVerChecker\Registry\Registry; use PHPSemVerChecker\Visitor\ClassVisitor; diff --git a/src/Visitor/NameResolver.php b/src/Visitor/NameResolver.php new file mode 100644 index 00000000..52166ca9 --- /dev/null +++ b/src/Visitor/NameResolver.php @@ -0,0 +1,155 @@ +resolveDocBlockParamTypes($node); + } + + return $return; + } + + /** + * @param ClassMethod $node + * @return void + */ + private function resolveDocBlockParamTypes(ClassMethod $node) + { + /** @var PhpDocNode $docNode */ + $docNode = $this->getParsedDocNode($node); + if ($docNode) { + $result = []; + /** @var ParamTagValueNode[] $paramTags */ + $paramTags = $docNode->getParamTagValues(); + /** @var ParamTagValueNode $paramTag */ + foreach ($paramTags as $paramTag) { + $paramNode = [ + 'name' => $paramTag->parameterName ?? '', + 'type' => $this->parseType($paramTag->type), + ]; + $result['params'][] = $paramNode; + } + + /** @var ReturnTagValueNode[] $returnTags */ + $returnTags = $docNode->getReturnTagValues(); + /** @var ReturnTagValueNode $returnTag */ + $returnTag = array_shift($returnTags); + if ($returnTag) { + $result['return'] = $this->parseType($returnTag->type); + } + $node->setAttribute('docCommentParsed', $result); + } + } + + /** + * Parse param or return type into array of resolved types + * + * @param TypeNode $type + * @return array + */ + private function parseType($type) + { + $result = []; + if ($type instanceof UnionTypeNode) { + foreach ($type->types as $typeNode) { + $normalizedType = BuilderHelpers::normalizeType((string)$typeNode); + $resolvedType = $this->resolveType($normalizedType); + $result[] = $resolvedType; + } + } else { + $normalizedType = BuilderHelpers::normalizeType((string)$type); + $resolvedType = $this->resolveType($normalizedType); + $result[] = $resolvedType; + } + + uasort( + $result, + function ($elementOne, $elementTwo) { + return ((string)$elementOne < (string)$elementTwo) ? -1 : 1; + } + ); + + return $result; + } + + /** + * Resolve type from Relative to FQCN + * + * @param $node + * @return Name|Node\NullableType|Node\UnionType + */ + private function resolveType($node) + { + if ($node instanceof Name) { + return $this->resolveClassName($node); + } + if ($node instanceof Node\NullableType) { + $node->type = $this->resolveType($node->type); + return $node; + } + if ($node instanceof Node\UnionType) { + foreach ($node->types as &$type) { + $type = $this->resolveType($type); + } + return $node; + } + return $node; + } + + /** + * Analyses the Method doc block and returns parsed node + * + * @param ClassMethod $method + * @return PhpDocNode|null + */ + private function getParsedDocNode(ClassMethod $method) + { + $docComment = $method->getDocComment(); + if ($docComment !== null) { + $lexer = new Lexer(); + $typeParser = new TypeParser(); + $constExprParser = new ConstExprParser(); + $phpDocParser = new PhpDocParser($typeParser, $constExprParser); + $tokens = $lexer->tokenize((string)$docComment); + $tokenIterator = new TokenIterator($tokens); + $phpDocNode = $phpDocParser->parse($tokenIterator); + + return $phpDocNode; + } + + return null; + } +}