Skip to content

Commit 739b0d6

Browse files
authored
[Arrows] MC-21844: SVC false-positive: short names & FQCN in DockBlock (#51)
1 parent 5663771 commit 739b0d6

File tree

3 files changed

+165
-14
lines changed

3 files changed

+165
-14
lines changed

src/Analyzer/ClassMethodAnalyzer.php

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -422,20 +422,16 @@ private function isReturnsEqualByNullability(ClassMethod $before, ClassMethod $a
422422
*/
423423
private function getDocReturnDeclaration(ClassMethod $method)
424424
{
425-
if ($method->getDocComment() !== null) {
426-
$lexer = new Lexer();
427-
$typeParser = new TypeParser();
428-
$constExprParser = new ConstExprParser();
429-
$phpDocParser = new PhpDocParser($typeParser, $constExprParser);
430-
431-
$tokens = $lexer->tokenize((string)$method->getDocComment());
432-
$tokenIterator = new TokenIterator($tokens);
433-
$phpDocNode = $phpDocParser->parse($tokenIterator);
434-
$tags = $phpDocNode->getTagsByName('@return');
435-
/** @var PhpDocTagNode $tag */
436-
$tag = array_shift($tags);
425+
if (
426+
($parsedComment = $method->getAttribute('docCommentParsed'))
427+
&& isset($parsedComment['return'])
428+
) {
429+
$result = implode('|', $parsedComment['return']);
430+
431+
return $result;
432+
} else {
433+
return ' ';
437434
}
438-
return isset($tag) ? (string)$tag->value : ' ';
439435
}
440436

441437
/**

src/Scanner/ScannerRegistryFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use Magento\SemanticVersionChecker\Visitor\ApiTraitVisitor;
1919
use PhpParser\Lexer\Emulative;
2020
use PhpParser\NodeTraverser;
21-
use PhpParser\NodeVisitor\NameResolver;
21+
use Magento\SemanticVersionChecker\Visitor\NameResolver;
2222
use PhpParser\Parser\Php7 as Parser;
2323
use PHPSemVerChecker\Registry\Registry;
2424
use PHPSemVerChecker\Visitor\ClassVisitor;

src/Visitor/NameResolver.php

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
namespace Magento\SemanticVersionChecker\Visitor;
9+
10+
use PhpParser\Node;
11+
use PhpParser\Node\Name;
12+
use PhpParser\Node\Stmt\ClassMethod;
13+
use PhpParser\NodeVisitor\NameResolver as ParserNameResolver;
14+
use PhpParser\BuilderHelpers;
15+
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
16+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
17+
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
18+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
19+
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
20+
use PHPStan\PhpDocParser\Lexer\Lexer;
21+
use PHPStan\PhpDocParser\Parser\ConstExprParser;
22+
use PHPStan\PhpDocParser\Parser\PhpDocParser;
23+
use PHPStan\PhpDocParser\Parser\TokenIterator;
24+
use PHPStan\PhpDocParser\Parser\TypeParser;
25+
26+
/**
27+
* Extended Name Resolver that parse and resolve also docblock hintings
28+
*/
29+
class NameResolver extends ParserNameResolver
30+
{
31+
/**
32+
* @inheritDoc
33+
*/
34+
public function enterNode(Node $node)
35+
{
36+
$return = parent::enterNode($node);
37+
38+
if ($node instanceof ClassMethod) {
39+
$this->resolveDocBlockParamTypes($node);
40+
}
41+
42+
return $return;
43+
}
44+
45+
/**
46+
* @param ClassMethod $node
47+
* @return void
48+
*/
49+
private function resolveDocBlockParamTypes(ClassMethod $node)
50+
{
51+
/** @var PhpDocNode $docNode */
52+
$docNode = $this->getParsedDocNode($node);
53+
if ($docNode) {
54+
$result = [];
55+
/** @var ParamTagValueNode[] $paramTags */
56+
$paramTags = $docNode->getParamTagValues();
57+
/** @var ParamTagValueNode $paramTag */
58+
foreach ($paramTags as $paramTag) {
59+
$paramNode = [
60+
'name' => $paramTag->parameterName ?? '',
61+
'type' => $this->parseType($paramTag->type),
62+
];
63+
$result['params'][] = $paramNode;
64+
}
65+
66+
/** @var ReturnTagValueNode[] $returnTags */
67+
$returnTags = $docNode->getReturnTagValues();
68+
/** @var ReturnTagValueNode $returnTag */
69+
$returnTag = array_shift($returnTags);
70+
if ($returnTag) {
71+
$result['return'] = $this->parseType($returnTag->type);
72+
}
73+
$node->setAttribute('docCommentParsed', $result);
74+
}
75+
}
76+
77+
/**
78+
* Parse param or return type into array of resolved types
79+
*
80+
* @param TypeNode $type
81+
* @return array
82+
*/
83+
private function parseType($type)
84+
{
85+
$result = [];
86+
if ($type instanceof UnionTypeNode) {
87+
foreach ($type->types as $typeNode) {
88+
$normalizedType = BuilderHelpers::normalizeType((string)$typeNode);
89+
$resolvedType = $this->resolveType($normalizedType);
90+
$result[] = $resolvedType;
91+
}
92+
} else {
93+
$normalizedType = BuilderHelpers::normalizeType((string)$type);
94+
$resolvedType = $this->resolveType($normalizedType);
95+
$result[] = $resolvedType;
96+
}
97+
98+
uasort(
99+
$result,
100+
function ($elementOne, $elementTwo) {
101+
return ((string)$elementOne < (string)$elementTwo) ? -1 : 1;
102+
}
103+
);
104+
105+
return $result;
106+
}
107+
108+
/**
109+
* Resolve type from Relative to FQCN
110+
*
111+
* @param $node
112+
* @return Name|Node\NullableType|Node\UnionType
113+
*/
114+
private function resolveType($node)
115+
{
116+
if ($node instanceof Name) {
117+
return $this->resolveClassName($node);
118+
}
119+
if ($node instanceof Node\NullableType) {
120+
$node->type = $this->resolveType($node->type);
121+
return $node;
122+
}
123+
if ($node instanceof Node\UnionType) {
124+
foreach ($node->types as &$type) {
125+
$type = $this->resolveType($type);
126+
}
127+
return $node;
128+
}
129+
return $node;
130+
}
131+
132+
/**
133+
* Analyses the Method doc block and returns parsed node
134+
*
135+
* @param ClassMethod $method
136+
* @return PhpDocNode|null
137+
*/
138+
private function getParsedDocNode(ClassMethod $method)
139+
{
140+
$docComment = $method->getDocComment();
141+
if ($docComment !== null) {
142+
$lexer = new Lexer();
143+
$typeParser = new TypeParser();
144+
$constExprParser = new ConstExprParser();
145+
$phpDocParser = new PhpDocParser($typeParser, $constExprParser);
146+
$tokens = $lexer->tokenize((string)$docComment);
147+
$tokenIterator = new TokenIterator($tokens);
148+
$phpDocNode = $phpDocParser->parse($tokenIterator);
149+
150+
return $phpDocNode;
151+
}
152+
153+
return null;
154+
}
155+
}

0 commit comments

Comments
 (0)