Skip to content

Commit aadb409

Browse files
authored
Merge pull request #16 from magento/NovemberInfraSync
Updating To latest Infra SVC code changes
2 parents c236238 + 76d4fd4 commit aadb409

23 files changed

+729
-401
lines changed

src/Analyzer/AbstractCodeAnalyzer.php

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\SemanticVersionChecker\Analyzer;
99

10+
use Magento\SemanticVersionChecker\ClassHierarchy\DependencyGraph;
1011
use Magento\SemanticVersionChecker\Helper\ClassParser;
1112
use PhpParser\Node;
1213
use PhpParser\Node\Stmt\Class_;
@@ -22,59 +23,59 @@ abstract class AbstractCodeAnalyzer implements AnalyzerInterface
2223
* @var null|string
2324
*/
2425
protected $context;
25-
2626
/**
2727
* File path before changes.
2828
*
2929
* @var null|string
3030
*/
3131
protected $fileBefore;
32-
3332
/**
3433
* File path after changes.
3534
*
3635
* @var null|string
3736
*/
3837
protected $fileAfter;
39-
38+
/**
39+
* @var null|DependencyGraph
40+
*/
41+
protected $dependencyGraph;
4042
/**
4143
* @param string $context
4244
* @param string $fileBefore
4345
* @param string $fileAfter
46+
* @param DependencyGraph|null $dependencyGraph
4447
*/
45-
public function __construct($context = null, $fileBefore = null, $fileAfter = null)
46-
{
48+
public function __construct(
49+
$context = null,
50+
$fileBefore = null,
51+
$fileAfter = null,
52+
DependencyGraph $dependencyGraph = null
53+
) {
4754
$this->context = $context;
4855
$this->fileBefore = $fileBefore;
4956
$this->fileAfter = $fileAfter;
57+
$this->dependencyGraph = $dependencyGraph;
5058
}
51-
5259
public function analyze($registryBefore, $registryAfter)
5360
{
5461
$report = new Report();
55-
5662
$beforeNameMap = $this->getNodeNameMap($registryBefore);
5763
$afterNameMap = $this->getNodeNameMap($registryAfter);
58-
5964
$namesBefore = array_keys($beforeNameMap);
6065
$namesAfter = array_keys($afterNameMap);
6166
$added = array_diff($namesAfter, $namesBefore);
6267
$removed = array_diff($namesBefore, $namesAfter);
6368
$toVerify = array_intersect($namesBefore, $namesAfter);
64-
6569
$this->reportAdded($report, $registryAfter, $added);
6670
$this->reportMovedOrRemoved($report, $registryBefore, $registryAfter, $removed);
67-
6871
$this->reportChanged(
6972
$report,
7073
$registryBefore,
7174
$registryAfter,
7275
$toVerify
7376
);
74-
7577
return $report;
7678
}
77-
7879
/**
7980
* Gets the appropriate nodes from the context and maps them to their names
8081
*
@@ -90,7 +91,6 @@ protected function getNodeNameMap($context)
9091
}
9192
return $keyed;
9293
}
93-
9494
/**
9595
* Has the node been moved to parent class.
9696
*
@@ -101,13 +101,11 @@ protected function getNodeNameMap($context)
101101
protected function isMovedToParent($parsedClass, $removedNode)
102102
{
103103
$parentClass = $parsedClass->getParentClass();
104-
105104
if ($removedNode instanceof ClassLike ||
106105
$parentClass === null ||
107106
(property_exists($removedNode, 'flags') && in_array($removedNode->flags, [Class_::MODIFIER_PRIVATE]))) {
108107
return false;
109108
}
110-
111109
$parentNodes = $parentClass->getNodesOfType($this->getNodeClass());
112110
foreach ($parentNodes as $parentNode) {
113111
$parentNodeName = $this->getNodeName($parentNode);
@@ -117,25 +115,21 @@ protected function isMovedToParent($parsedClass, $removedNode)
117115
return true;
118116
}
119117
}
120-
121118
return $this->isMovedToParent($parentClass, $removedNode);
122119
}
123-
124120
/**
125121
* Get the name for a given node of the type analyzed
126122
*
127123
* @param Node $node
128124
* @return string
129125
*/
130126
abstract protected function getNodeName($node);
131-
132127
/**
133128
* The class of the nodes to analyze
134129
*
135130
* @return string
136131
*/
137132
abstract protected function getNodeClass();
138-
139133
/**
140134
* Create and report a NodeAdded operation
141135
*
@@ -146,7 +140,6 @@ abstract protected function getNodeClass();
146140
* @return void
147141
*/
148142
abstract protected function reportAddedNode($report, $fileAfter, $contextAfter, $nodeAfter);
149-
150143
/**
151144
* Create and report a NodeRemoved operation
152145
*
@@ -157,7 +150,6 @@ abstract protected function reportAddedNode($report, $fileAfter, $contextAfter,
157150
* @return void
158151
*/
159152
abstract protected function reportRemovedNode($report, $fileBefore, $contextBefore, $nodeBefore);
160-
161153
/**
162154
* Create and report a NodeMoved operation
163155
*
@@ -171,7 +163,6 @@ protected function reportMovedNode($report, $fileBefore, $contextBefore, $nodeBe
171163
{
172164
// ClassLike nodes do not have Moved operations, so do not enforce implementing this method
173165
}
174-
175166
/**
176167
* Report the list of added nodes
177168
*
@@ -190,7 +181,6 @@ protected function reportAdded($report, $contextAfter, $addedNames)
190181
$this->reportAddedNode($report, $fileAfter, $contextAfter, $node);
191182
}
192183
}
193-
194184
/**
195185
* Report moved or removed nodes
196186
*
@@ -214,7 +204,6 @@ protected function reportMovedOrRemoved($report, $contextBefore, $contextAfter,
214204
}
215205
}
216206
}
217-
218207
/**
219208
* Find changes to nodes that exist in both before and after states and add them to the report
220209
*
@@ -228,7 +217,6 @@ protected function reportChanged($report, $contextBefore, $contextAfter, $toVeri
228217
{
229218
// Not all types have changes beyond add/remove
230219
}
231-
232220
/**
233221
* Get the filename to use in the report.
234222
*
@@ -243,4 +231,4 @@ protected function getFileName($context, $nodeName, $isBefore = true)
243231
{
244232
return $isBefore ? $this->fileBefore : $this->fileAfter;
245233
}
246-
}
234+
}

src/Analyzer/ClassAnalyzer.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\SemanticVersionChecker\Analyzer;
99

10+
use Magento\SemanticVersionChecker\ClassHierarchy\DependencyGraph;
1011
use PhpParser\Node\Stmt\Class_;
1112
use PHPSemVerChecker\Operation\ClassAdded;
1213
use PHPSemVerChecker\Operation\ClassRemoved;
@@ -45,18 +46,6 @@ protected function getNodeClass()
4546
return Class_::class;
4647
}
4748

48-
49-
/**
50-
* Get the class node registry
51-
*
52-
* @param Registry $registry
53-
* @return Class_[]
54-
*/
55-
protected function getNodeNameMap($registry)
56-
{
57-
return $registry->data[static::CONTEXT];
58-
}
59-
6049
/**
6150
* Get the filename from the registry
6251
*
@@ -120,7 +109,7 @@ protected function reportChanged($report, $registryBefore, $registryAfter, $toVe
120109
if ($classBefore !== $classAfter) {
121110
/** @var AnalyzerInterface[] $analyzers */
122111
$analyzers = [
123-
new ClassMethodAnalyzer(static::CONTEXT, $fileBefore, $fileAfter),
112+
new ClassMethodAnalyzer(static::CONTEXT, $fileBefore, $fileAfter, $this->dependencyGraph),
124113
new PropertyAnalyzer(static::CONTEXT, $fileBefore, $fileAfter),
125114
new ClassConstantAnalyzer(static::CONTEXT, $fileBefore, $fileAfter),
126115
new ClassMethodExceptionAnalyzer(static::CONTEXT, $fileBefore, $fileAfter),
@@ -136,4 +125,15 @@ protected function reportChanged($report, $registryBefore, $registryAfter, $toVe
136125
}
137126
}
138127
}
139-
}
128+
129+
/**
130+
* Get the class node registry
131+
*
132+
* @param Registry $registry
133+
* @return Class_[]
134+
*/
135+
protected function getNodeNameMap($registry)
136+
{
137+
return $registry->data[static::CONTEXT];
138+
}
139+
}

src/Analyzer/ClassMethodAnalyzer.php

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515
use Magento\SemanticVersionChecker\Operation\ClassMethodLastParameterRemoved;
1616
use Magento\SemanticVersionChecker\Operation\ClassMethodMoved;
1717
use Magento\SemanticVersionChecker\Operation\ClassMethodOptionalParameterAdded;
18+
use Magento\SemanticVersionChecker\Operation\ClassMethodOverwriteAdded;
19+
use Magento\SemanticVersionChecker\Operation\ClassMethodOverwriteRemoved;
1820
use Magento\SemanticVersionChecker\Operation\ClassMethodParameterTypingChanged;
1921
use Magento\SemanticVersionChecker\Operation\ClassMethodReturnTypingChanged;
2022
use Magento\SemanticVersionChecker\Operation\ExtendableClassConstructorOptionalParameterAdded;
2123
use Magento\SemanticVersionChecker\Operation\Visibility\MethodDecreased as VisibilityMethodDecreased;
2224
use Magento\SemanticVersionChecker\Operation\Visibility\MethodIncreased as VisibilityMethodIncreased;
25+
use PhpParser\Node\NullableType;
2326
use PhpParser\Node\Stmt;
2427
use PhpParser\Node\Stmt\ClassLike;
2528
use PhpParser\Node\Stmt\ClassMethod;
2629
use PHPSemVerChecker\Comparator\Implementation;
27-
use PHPSemVerChecker\Comparator\Type;
2830
use PHPSemVerChecker\Operation\ClassMethodAdded;
2931
use PHPSemVerChecker\Operation\ClassMethodImplementationChanged;
3032
use PHPSemVerChecker\Operation\ClassMethodOperationUnary;
@@ -35,6 +37,12 @@
3537
use PHPSemVerChecker\Operation\ClassMethodParameterTypingRemoved;
3638
use PHPSemVerChecker\Operation\ClassMethodRemoved;
3739
use PHPSemVerChecker\Report\Report;
40+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
41+
use PHPStan\PhpDocParser\Lexer\Lexer;
42+
use PHPStan\PhpDocParser\Parser\ConstExprParser;
43+
use PHPStan\PhpDocParser\Parser\PhpDocParser;
44+
use PHPStan\PhpDocParser\Parser\TokenIterator;
45+
use PHPStan\PhpDocParser\Parser\TypeParser;
3846

3947
/**
4048
* Class method analyzer.
@@ -105,6 +113,27 @@ protected function getNodeClass()
105113
*/
106114
protected function reportAddedNode($report, $fileAfter, $classAfter, $methodAfter)
107115
{
116+
if ($this->dependencyGraph === null) {
117+
$report->add($this->context, new ClassMethodAdded($this->context, $fileAfter, $classAfter, $methodAfter));
118+
return;
119+
}
120+
121+
$class = $this->dependencyGraph->findEntityByName((string) $classAfter->namespacedName);
122+
123+
if ($class !== null) {
124+
foreach ($class->getExtends() as $entity) {
125+
$methods = $entity->getMethodList();
126+
// checks if the method is already exiting in parent class
127+
if (isset($methods[$methodAfter->name])) {
128+
$report->add(
129+
$this->context,
130+
new ClassMethodOverwriteAdded($this->context, $fileAfter, $classAfter, $methodAfter)
131+
);
132+
return;
133+
}
134+
}
135+
}
136+
108137
$report->add($this->context, new ClassMethodAdded($this->context, $fileAfter, $classAfter, $methodAfter));
109138
}
110139

@@ -331,12 +360,76 @@ protected function reportChanged($report, $contextBefore, $contextAfter, $method
331360
*/
332361
private function isReturnTypeChanged(ClassMethod $methodBefore, ClassMethod $methodAfter): bool
333362
{
334-
$hasPHP7ReturnDeclarationChanged = !Type::isSame($methodBefore->returnType, $methodAfter->returnType);
363+
return $this->isDocBlockAnnotationReturnTypeChanged($methodBefore, $methodAfter) || $this->isDeclarationReturnTypeChanged($methodBefore, $methodAfter);
364+
}
365+
366+
/**
367+
* @param ClassMethod $methodBefore
368+
* @param ClassMethod $methodAfter
369+
*
370+
* @return bool
371+
*/
372+
private function isDocBlockAnnotationReturnTypeChanged(ClassMethod $methodBefore, ClassMethod $methodAfter)
373+
{
374+
$returnBefore = $this->getDocReturnDeclaration($methodBefore);
375+
$returnAfter = $this->getDocReturnDeclaration($methodAfter);
335376

336-
$returnBefore = $this->methodDocBlockAnalyzer->getMethodDocDeclarationByTag($methodBefore, $this->methodDocBlockAnalyzer::DOC_RETURN_TAG);
337-
$returnAfter = $this->methodDocBlockAnalyzer->getMethodDocDeclarationByTag($methodAfter, $this->methodDocBlockAnalyzer::DOC_RETURN_TAG);
377+
return $returnBefore !== $returnAfter;
378+
}
338379

339-
return $hasPHP7ReturnDeclarationChanged || $returnBefore !== $returnAfter;
380+
/**
381+
* @param ClassMethod $methodBefore
382+
* @param ClassMethod $methodAfter
383+
*
384+
* @return bool
385+
*/
386+
private function isDeclarationReturnTypeChanged(ClassMethod $methodBefore, ClassMethod $methodAfter)
387+
{
388+
if (!$this->isReturnsEqualByNullability($methodBefore, $methodAfter)) {
389+
return true;
390+
}
391+
$beforeMethodReturnType = $methodBefore->returnType instanceof NullableType ? (string) $methodBefore->returnType->type : (string) $methodBefore->returnType;
392+
$afterMethodReturnType = $methodAfter->returnType instanceof NullableType ? (string) $methodAfter->returnType->type : (string) $methodAfter->returnType;
393+
394+
return $beforeMethodReturnType !== $afterMethodReturnType;
395+
}
396+
397+
/**
398+
* checks if both return types has same nullable status
399+
*
400+
* @param ClassMethod $before
401+
* @param ClassMethod $after
402+
*
403+
* @return bool
404+
*/
405+
private function isReturnsEqualByNullability(ClassMethod $before, ClassMethod $after): bool
406+
{
407+
return ($before instanceof NullableType) === ($after instanceof NullableType);
408+
}
409+
410+
/**
411+
* Analyses the Method doc block and returns the return type declaration
412+
*
413+
* @param ClassMethod $method
414+
*
415+
* @return string
416+
*/
417+
private function getDocReturnDeclaration(ClassMethod $method)
418+
{
419+
if ($method->getDocComment() !== null) {
420+
$lexer = new Lexer();
421+
$typeParser = new TypeParser();
422+
$constExprParser = new ConstExprParser();
423+
$phpDocParser = new PhpDocParser($typeParser, $constExprParser);
424+
425+
$tokens = $lexer->tokenize((string)$method->getDocComment());
426+
$tokenIterator = new TokenIterator($tokens);
427+
$phpDocNode = $phpDocParser->parse($tokenIterator);
428+
$tags = $phpDocNode->getTagsByName('@return');
429+
/** @var PhpDocTagNode $tag */
430+
$tag = array_shift($tags);
431+
}
432+
return isset($tag) ? (string)$tag->value : ' ';
340433
}
341434

342435
/**
@@ -415,4 +508,4 @@ private function analyzeRemainingMethodParams($contextAfter, $methodAfter, $rema
415508

416509
return $data;
417510
}
418-
}
511+
}

0 commit comments

Comments
 (0)