Skip to content

Commit 751a5ce

Browse files
authored
Merge pull request #52 from magento-tsg/MC-25111
[Arrows] MC-25111: SVC false-positive: method return typing changed
2 parents 739b0d6 + e6a8046 commit 751a5ce

File tree

26 files changed

+577
-193
lines changed

26 files changed

+577
-193
lines changed

composer.lock

Lines changed: 118 additions & 171 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Analyzer/ClassAnalyzer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace Magento\SemanticVersionChecker\Analyzer;
1111

12+
use Magento\SemanticVersionChecker\ClassHierarchy\DependencyGraph;
1213
use PhpParser\Node\Stmt\Class_;
1314
use PHPSemVerChecker\Operation\ClassAdded;
1415
use PHPSemVerChecker\Operation\ClassRemoved;

src/Analyzer/ClassMethodAnalyzer.php

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
use Magento\SemanticVersionChecker\Operation\Visibility\MethodDecreased as VisibilityMethodDecreased;
2525
use Magento\SemanticVersionChecker\Operation\Visibility\MethodIncreased as VisibilityMethodIncreased;
2626
use PhpParser\Node\NullableType;
27-
use PhpParser\Node\Stmt;
27+
use PhpParser\Node\Name;
28+
use PhpParser\Node\Stmt\Class_;
2829
use PhpParser\Node\Stmt\ClassLike;
2930
use PhpParser\Node\Stmt\ClassMethod;
3031
use PHPSemVerChecker\Comparator\Implementation;
@@ -38,12 +39,6 @@
3839
use PHPSemVerChecker\Operation\ClassMethodParameterTypingRemoved;
3940
use PHPSemVerChecker\Operation\ClassMethodRemoved;
4041
use PHPSemVerChecker\Report\Report;
41-
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
42-
use PHPStan\PhpDocParser\Lexer\Lexer;
43-
use PHPStan\PhpDocParser\Parser\ConstExprParser;
44-
use PHPStan\PhpDocParser\Parser\PhpDocParser;
45-
use PHPStan\PhpDocParser\Parser\TokenIterator;
46-
use PHPStan\PhpDocParser\Parser\TypeParser;
4742

4843
/**
4944
* Class method analyzer.
@@ -429,9 +424,55 @@ private function getDocReturnDeclaration(ClassMethod $method)
429424
$result = implode('|', $parsedComment['return']);
430425

431426
return $result;
432-
} else {
433-
return ' ';
427+
} elseif ($this->dependencyGraph !== null) {
428+
/** @var Class_ $methodClass */
429+
$methodClass = $method->getAttribute('parent');
430+
if ($methodClass) {
431+
$ancestors = [];
432+
if (!empty($methodClass->extends)) {
433+
$ancestors = $this->addAncestorsToArray($ancestors, $methodClass->extends);
434+
}
435+
if (!empty($methodClass->implements)) {
436+
$ancestors = $this->addAncestorsToArray($ancestors, $methodClass->implements);
437+
}
438+
/** @var Name $ancestor */
439+
foreach ($ancestors as $ancestor) {
440+
$ancestorClass = $this->dependencyGraph->findEntityByName($ancestor->toString());
441+
if ($ancestorClass) {
442+
foreach ($ancestorClass->getMethodList() as $methodItem) {
443+
if ($method->name->toString() == $methodItem->name->toString()) {
444+
$result = $this->getDocReturnDeclaration($methodItem);
445+
if (!empty(trim($result))) {
446+
return $result;
447+
}
448+
}
449+
}
450+
}
451+
}
452+
}
453+
}
454+
455+
return ' ';
456+
}
457+
458+
/**
459+
* Add ancestors to array
460+
*
461+
* @param array $ancestors
462+
* @param array|Name $toAdd
463+
* @return array
464+
*/
465+
private function addAncestorsToArray(array $ancestors, $toAdd)
466+
{
467+
if (!empty($toAdd)) {
468+
if (is_array($toAdd)) {
469+
$ancestors = array_merge($ancestors, $toAdd);
470+
} else {
471+
$ancestors[] = $toAdd;
472+
}
434473
}
474+
475+
return $ancestors;
435476
}
436477

437478
/**

src/Analyzer/Factory/AnalyzerFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function create(DependencyGraph $dependencyGraph = null): AnalyzerInterfa
3030
{
3131
$analyzers = [
3232
new ClassAnalyzer(null, null, null, $dependencyGraph),
33-
new InterfaceAnalyzer(),
33+
new InterfaceAnalyzer(null, null, null, $dependencyGraph),
3434
new TraitAnalyzer(),
3535
];
3636

src/Analyzer/Factory/NonApiAnalyzerFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class NonApiAnalyzerFactory implements AnalyzerFactoryInterface
2828
public function create(DependencyGraph $dependencyGraph = null): AnalyzerInterface
2929
{
3030
$analyzers = [
31-
new ClassAnalyzer(),
32-
new InterfaceAnalyzer(),
31+
new ClassAnalyzer(null, null, null, $dependencyGraph),
32+
new InterfaceAnalyzer(null, null, null, $dependencyGraph),
3333
new TraitAnalyzer(),
3434
];
3535

src/Analyzer/InterfaceAnalyzer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ protected function reportChanged($report, $registryBefore, $registryAfter, $toVe
139139
protected function getContentAnalyzers($context, $fileBefore, $fileAfter)
140140
{
141141
return [
142-
new ClassMethodAnalyzer($context, $fileBefore, $fileAfter),
142+
new ClassMethodAnalyzer($context, $fileBefore, $fileAfter, $this->dependencyGraph),
143143
new ClassConstantAnalyzer($context, $fileBefore, $fileAfter),
144144
new ClassMethodExceptionAnalyzer($context, $fileBefore, $fileAfter),
145145
new InterfaceExtendsAnalyzer($context, $fileBefore, $fileAfter)

src/Analyzer/MethodDocBlockAnalyzer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
* - method param typehint moved from in-line to doc block
3636
* - method return typehint moved from doc block to in-line
3737
* - method return typehint moved from in-line to doc block
38+
*
39+
* TODO: this class should be rewritten using new possibility added by
40+
* Magento\SemanticVersionChecker\Visitor\NameResolver
41+
* Now all information (and resolved typed) about DocBlock params and return type exists in
42+
* method node 'docCommentParsed' attribute
3843
*/
3944
class MethodDocBlockAnalyzer
4045
{

src/ClassHierarchy/StaticAnalyzerFactory.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
use Magento\SemanticVersionChecker\Helper\Node as NodeHelper;
1313
use PhpParser\NodeTraverser;
14-
use PhpParser\NodeVisitor\NameResolver;
14+
use Magento\SemanticVersionChecker\Visitor\NameResolver;
15+
use PhpParser\NodeVisitor\ParentConnectingVisitor;
1516
use PhpParser\ParserFactory;
1617

1718
/**
@@ -31,6 +32,7 @@ public function create(): StaticAnalyzer
3132
);
3233
$nodeTraverser = new NodeTraverser();
3334

35+
$nodeTraverser->addVisitor(new ParentConnectingVisitor());
3436
$nodeTraverser->addVisitor(new NameResolver());
3537

3638
return new StaticAnalyzer($parser, $dependencyInspectionVisitor, $nodeTraverser);

src/Scanner/ScannerRegistryFactory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use PhpParser\Lexer\Emulative;
2020
use PhpParser\NodeTraverser;
2121
use Magento\SemanticVersionChecker\Visitor\NameResolver;
22+
use PhpParser\NodeVisitor\ParentConnectingVisitor;
2223
use PhpParser\Parser\Php7 as Parser;
2324
use PHPSemVerChecker\Registry\Registry;
2425
use PHPSemVerChecker\Visitor\ClassVisitor;
@@ -38,6 +39,7 @@ private function buildFullScanner()
3839
$traverser = new NodeTraverser();
3940
$apiVisitors = [
4041
new NameResolver(),
42+
new ParentConnectingVisitor(),
4143
new ClassVisitor($registry),
4244
new InterfaceVisitor($registry),
4345
new FunctionVisitor($registry),
@@ -59,6 +61,7 @@ private function buildApiScanner(DependencyGraph $dependencyGraph = null)
5961
$nodeHelper = new NodeHelper();
6062
$apiVisitors = [
6163
new NameResolver(),
64+
new ParentConnectingVisitor(),
6265
new ApiClassVisitor($registry, $nodeHelper, $dependencyGraph),
6366
new ApiInterfaceVisitor($registry, $nodeHelper, $dependencyGraph),
6467
new ApiTraitVisitor($registry, $nodeHelper, $dependencyGraph),

src/Visitor/NameResolver.php

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PhpParser\Node;
1111
use PhpParser\Node\Name;
1212
use PhpParser\Node\Stmt\ClassMethod;
13+
use PhpParser\NodeAbstract;
1314
use PhpParser\NodeVisitor\NameResolver as ParserNameResolver;
1415
use PhpParser\BuilderHelpers;
1516
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
@@ -24,10 +25,29 @@
2425
use PHPStan\PhpDocParser\Parser\TypeParser;
2526

2627
/**
27-
* Extended Name Resolver that parse and resolve also docblock hintings
28+
* Extended Name Resolver that parse and resolve also docblock params and return type hinting.
2829
*/
2930
class NameResolver extends ParserNameResolver
3031
{
32+
/**
33+
* Internal types that should not be resolved for docblock
34+
*
35+
* @var string[]
36+
*/
37+
private $internalTypes = [
38+
'string',
39+
'integer',
40+
'float',
41+
'double',
42+
'boolean',
43+
'bool',
44+
'array',
45+
'object',
46+
'null',
47+
'resource',
48+
'$this',
49+
];
50+
3151
/**
3252
* @inheritDoc
3353
*/
@@ -85,14 +105,10 @@ private function parseType($type)
85105
$result = [];
86106
if ($type instanceof UnionTypeNode) {
87107
foreach ($type->types as $typeNode) {
88-
$normalizedType = BuilderHelpers::normalizeType((string)$typeNode);
89-
$resolvedType = $this->resolveType($normalizedType);
90-
$result[] = $resolvedType;
108+
$result[] = $this->normalizeAndResolve($typeNode);
91109
}
92110
} else {
93-
$normalizedType = BuilderHelpers::normalizeType((string)$type);
94-
$resolvedType = $this->resolveType($normalizedType);
95-
$result[] = $resolvedType;
111+
$result[] = $this->normalizeAndResolve($type);
96112
}
97113

98114
uasort(
@@ -105,11 +121,28 @@ function ($elementOne, $elementTwo) {
105121
return $result;
106122
}
107123

124+
/**
125+
* @param TypeNode $type
126+
* @return NodeAbstract
127+
*/
128+
private function normalizeAndResolve($type)
129+
{
130+
$normalizedType = BuilderHelpers::normalizeType((string)$type);
131+
132+
if (in_array(strtolower((string)$type), $this->internalTypes)) {
133+
$resolvedType = $normalizedType;
134+
} else {
135+
$resolvedType = $this->resolveType($normalizedType);
136+
}
137+
138+
return $resolvedType;
139+
}
140+
108141
/**
109142
* Resolve type from Relative to FQCN
110143
*
111144
* @param $node
112-
* @return Name|Node\NullableType|Node\UnionType
145+
* @return NodeAbstract
113146
*/
114147
private function resolveType($node)
115148
{

0 commit comments

Comments
 (0)