Skip to content

Commit c4eb675

Browse files
authored
Merge pull request #11 from magento-tsg/MC-36802
[Arrows] MC-36802: SVC doesn't catch MINOR change in PATCH release when adding @api to a class
2 parents 9ef11a4 + ea001cc commit c4eb675

File tree

27 files changed

+654
-41
lines changed

27 files changed

+654
-41
lines changed

src/Analyzer/ApiClassAnalyzer.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Magento\SemanticVersionChecker\Analyzer;
11+
12+
/**
13+
* API Class analyzer.
14+
* Performs comparison of API classes and creates reports such as:
15+
* - class added
16+
* - class removed
17+
* Runs method, constant, and property analyzers.
18+
*/
19+
class ApiClassAnalyzer extends ClassAnalyzer
20+
{
21+
/**
22+
* Get the list of content analyzers
23+
*
24+
* @param string $context
25+
* @param string $fileBefore
26+
* @param string $fileAfter
27+
* @return AbstractCodeAnalyzer[]
28+
*/
29+
protected function getContentAnalyzers($context, $fileBefore, $fileAfter)
30+
{
31+
return array_merge(
32+
[new ClassLikeApiAnnotationAnalyzer($context, $fileBefore, $fileAfter, $this->dependencyGraph)],
33+
parent::getContentAnalyzers($context, $fileBefore, $fileAfter)
34+
);
35+
}
36+
}

src/Analyzer/ApiInterfaceAnalyzer.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Magento\SemanticVersionChecker\Analyzer;
11+
12+
/**
13+
* API Interface analyzer.
14+
* Performs comparison of API interfaces and creates reports such as:
15+
* - interface added
16+
* - interface removed
17+
* Runs method analyzer and constant analyzer.
18+
*/
19+
class ApiInterfaceAnalyzer extends InterfaceAnalyzer
20+
{
21+
/**
22+
* Get the list of content analyzers
23+
*
24+
* @param string $context
25+
* @param string $fileBefore
26+
* @param string $fileAfter
27+
* @return AbstractCodeAnalyzer[]
28+
*/
29+
protected function getContentAnalyzers($context, $fileBefore, $fileAfter)
30+
{
31+
return array_merge(
32+
[new ClassLikeApiAnnotationAnalyzer($context, $fileBefore, $fileAfter, $this->dependencyGraph)],
33+
parent::getContentAnalyzers($context, $fileBefore, $fileAfter)
34+
);
35+
}
36+
}

src/Analyzer/ClassAnalyzer.php

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

1010
namespace Magento\SemanticVersionChecker\Analyzer;
1111

12-
use Magento\SemanticVersionChecker\ClassHierarchy\DependencyGraph;
1312
use PhpParser\Node\Stmt\Class_;
1413
use PHPSemVerChecker\Operation\ClassAdded;
1514
use PHPSemVerChecker\Operation\ClassRemoved;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Magento\SemanticVersionChecker\Analyzer;
11+
12+
use Magento\SemanticVersionChecker\ClassHierarchy\DependencyGraph;
13+
use Magento\SemanticVersionChecker\Helper\Node;
14+
use Magento\SemanticVersionChecker\Operation\ClassLikeApiAnnotationAdded;
15+
use Magento\SemanticVersionChecker\Operation\ClassLikeApiAnnotationRemoved;
16+
use PhpParser\Node\Stmt\ClassLike;
17+
use PHPSemVerChecker\Report\Report;
18+
19+
/**
20+
* Class Extends analyzer performs comparison of classes/interfaces/traits and creates reports such as:
21+
* <ul>
22+
* <li><kbd>added</kbd>: @api annotation has been added</li>
23+
* <li><kbd>remove</kbd>: @api annotation has been removed</li>
24+
* <ul>
25+
*/
26+
class ClassLikeApiAnnotationAnalyzer extends AbstractCodeAnalyzer
27+
{
28+
/**
29+
* @param null $context
30+
* @param null $fileBefore
31+
* @param null $fileAfter
32+
* @param DependencyGraph|null $dependencyGraph
33+
*/
34+
public function __construct(
35+
$context = null,
36+
$fileBefore = null,
37+
$fileAfter = null,
38+
DependencyGraph $dependencyGraph = null
39+
) {
40+
parent::__construct($context, $fileBefore, $fileAfter, $dependencyGraph);
41+
$this->nodeHelper = new Node();
42+
}
43+
44+
/**
45+
* @var Node
46+
*/
47+
private $nodeHelper;
48+
49+
/**
50+
* Get the name of a ClassLike node
51+
*
52+
* @param ClassLike $node
53+
* @return string
54+
*/
55+
protected function getNodeName($node)
56+
{
57+
return $node->name->toString();
58+
}
59+
60+
/**
61+
* Use nodes of the ClassLike type for this analyzer
62+
*
63+
* @return string
64+
*/
65+
protected function getNodeClass()
66+
{
67+
return ClassLike::class;
68+
}
69+
70+
/**
71+
* @inheritDoc
72+
*/
73+
protected function reportAddedNode($report, $fileAfter, $contextAfter, $nodeAfter)
74+
{
75+
// implementation not needed
76+
}
77+
78+
/**
79+
* @inheritDoc
80+
*/
81+
protected function reportRemovedNode($report, $fileBefore, $contextBefore, $nodeBefore)
82+
{
83+
// implementation not needed
84+
}
85+
86+
/**
87+
* Find changes to class/interface @api annotation
88+
*
89+
* @param Report $report
90+
* @param ClassLike $contextBefore
91+
* @param ClassLike $contextAfter
92+
* @param string[] $toVerify
93+
* @return void
94+
*/
95+
protected function reportChanged($report, $contextBefore, $contextAfter, $toVerify)
96+
{
97+
$isApiBefore = $this->nodeHelper->isApiNode($contextBefore);
98+
$isApiAfter = $this->nodeHelper->isApiNode($contextAfter);
99+
100+
if (!$isApiBefore && $isApiAfter) {
101+
$operation = new ClassLikeApiAnnotationAdded($contextAfter, $this->fileAfter);
102+
$report->add($this->context, $operation);
103+
} elseif ($isApiBefore && !$isApiAfter) {
104+
$operation = new ClassLikeApiAnnotationRemoved($contextAfter, $this->fileAfter);
105+
$report->add($this->context, $operation);
106+
}
107+
}
108+
}

src/Analyzer/Factory/AnalyzerFactory.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
use Magento\SemanticVersionChecker\Analyzer\Analyzer;
1313
use Magento\SemanticVersionChecker\Analyzer\AnalyzerInterface;
14-
use Magento\SemanticVersionChecker\Analyzer\ClassAnalyzer;
15-
use Magento\SemanticVersionChecker\Analyzer\InterfaceAnalyzer;
14+
use Magento\SemanticVersionChecker\Analyzer\ApiClassAnalyzer;
15+
use Magento\SemanticVersionChecker\Analyzer\ApiInterfaceAnalyzer;
1616
use Magento\SemanticVersionChecker\Analyzer\TraitAnalyzer;
1717
use Magento\SemanticVersionChecker\ClassHierarchy\DependencyGraph;
1818

@@ -21,16 +21,15 @@
2121
*/
2222
class AnalyzerFactory implements AnalyzerFactoryInterface
2323
{
24-
2524
/**
2625
* @param DependencyGraph|null $dependencyGraph
2726
* @return AnalyzerInterface
2827
*/
2928
public function create(DependencyGraph $dependencyGraph = null): AnalyzerInterface
3029
{
3130
$analyzers = [
32-
new ClassAnalyzer(null, null, null, $dependencyGraph),
33-
new InterfaceAnalyzer(null, null, null, $dependencyGraph),
31+
new ApiClassAnalyzer(null, null, null, $dependencyGraph),
32+
new ApiInterfaceAnalyzer(null, null, null, $dependencyGraph),
3433
new TraitAnalyzer(),
3534
];
3635

src/ClassHierarchy/Entity.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public function __construct(string $name, string $type)
124124
*/
125125
public function isApi(): bool
126126
{
127-
return $this->isApi;
127+
return (bool)$this->isApi;
128128
}
129129

130130
/**

src/Helper/Node.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
namespace Magento\SemanticVersionChecker\Helper;
1111

1212
use Magento\SemanticVersionChecker\SemanticVersionChecker;
13+
use PhpParser\Comment\Doc as DocComment;
1314
use PhpParser\Node as PhpNode;
14-
use PhpParser\Node\Stmt\TraitUse;
1515

1616
/**
1717
* Implements a helper that deals with nodes.
@@ -26,9 +26,20 @@ class Node
2626
*/
2727
public function isApiNode(PhpNode $node)
2828
{
29-
$comment = $node->getAttribute('comments');
29+
$comments = $node->getAttribute('comments');
3030

31-
return isset($comment[0])
32-
&& strpos($comment[0]->getText(), SemanticVersionChecker::ANNOTATION_API) !== false;
31+
$result = false;
32+
if (is_array($comments) && !empty($comments)) {
33+
foreach ($comments as $comment) {
34+
if ($comment instanceof DocComment) {
35+
$result = (strpos($comment->getText(), SemanticVersionChecker::ANNOTATION_API) !== false);
36+
if ($result) {
37+
break;
38+
}
39+
}
40+
}
41+
}
42+
43+
return $result;
3344
}
3445
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Magento\SemanticVersionChecker\Operation;
11+
12+
/**
13+
* When @api annotation has been added
14+
*/
15+
class ClassLikeApiAnnotationAdded extends ClassLikeApiAnnotationOperation
16+
{
17+
/**
18+
* @var string
19+
*/
20+
protected $code = 'M0141';
21+
22+
/**
23+
* @var string
24+
*/
25+
protected $reason = '@api annotation has been added.';
26+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Magento\SemanticVersionChecker\Operation;
11+
12+
use PhpParser\Node\Stmt\Class_ as BaseClass;
13+
use PhpParser\Node\Stmt\Interface_ as BaseInterface;
14+
use PhpParser\Node\Stmt\Trait_ as BaseTrait;
15+
use PhpParser\Node\Stmt\ClassLike;
16+
use PHPSemVerChecker\Node\Statement\Class_ as Class_Statement;
17+
use PHPSemVerChecker\Node\Statement\Interface_ as Interface_Statement;
18+
use PHPSemVerChecker\Node\Statement\Trait_ as Trait_Statement;
19+
use PHPSemVerChecker\Operation\Operation;
20+
use PHPSemVerChecker\SemanticVersioning\Level;
21+
22+
/**
23+
* When @api annotation has been added/removed
24+
*/
25+
class ClassLikeApiAnnotationOperation extends Operation
26+
{
27+
/**
28+
* Change level.
29+
*
30+
* @var int
31+
*/
32+
protected $level = Level::MINOR;
33+
34+
/**
35+
* @var ClassLike
36+
*/
37+
protected $classLike;
38+
39+
/**
40+
* @param ClassLike $classLike
41+
* @param string $target
42+
*/
43+
public function __construct(ClassLike $classLike, $target)
44+
{
45+
$this->target = $target;
46+
$this->classLike = $classLike;
47+
}
48+
49+
/**
50+
* @inheritDoc
51+
*/
52+
public function getTarget()
53+
{
54+
$result = '';
55+
56+
if ($this->classLike instanceof BaseClass) {
57+
$result = Class_Statement::getFullyQualifiedName($this->classLike);
58+
} elseif ($this->classLike instanceof BaseInterface) {
59+
$result = Interface_Statement::getFullyQualifiedName($this->classLike);
60+
} elseif ($this->classLike instanceof BaseTrait) {
61+
$result = Trait_Statement::getFullyQualifiedName($this->classLike);
62+
}
63+
64+
return $result;
65+
}
66+
67+
/**
68+
* @inheritDoc
69+
*/
70+
public function getLocation()
71+
{
72+
return $this->target;
73+
}
74+
75+
/**
76+
* @inheritDoc
77+
*/
78+
public function getLine()
79+
{
80+
return 0;
81+
}
82+
83+
/**
84+
* Get level.
85+
*
86+
* @inheritDoc
87+
*/
88+
public function getLevel()
89+
{
90+
return $this->level;
91+
}
92+
}

0 commit comments

Comments
 (0)