Skip to content

Commit d907252

Browse files
committed
Extracted Registry::compare logic into several classes.
Renamed ClassMethodParameterMismatch to ClassMethodParameterChanged. Bump composer branch-alias to 0.2-dev. Added various fixtures to tests/fixtures. Initial code for Interface and Trait. [#10] Adding methods to an interface should generate a MAJOR
1 parent 3087dad commit d907252

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2164
-864
lines changed

composer.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,22 @@
2626
"phpunit/php-file-iterator": "~1.3"
2727
},
2828
"require-dev": {
29-
"phpunit/phpunit": "~4"
29+
"phpunit/phpunit": "~4",
30+
"mockery/mockery": "~0.9"
3031
},
3132
"autoload": {
3233
"psr-4": {
3334
"PHPSemVerChecker\\": "src/PHPSemVerChecker"
3435
}
3536
},
37+
"autoload-dev": {
38+
"psr-4": {
39+
"PHPSemVerChecker\\Test\\": "tests/PHPSemVerChecker"
40+
}
41+
},
3642
"extra": {
3743
"branch-alias": {
38-
"dev-master": "0.1-dev"
44+
"dev-master": "0.2-dev"
3945
}
4046
}
4147
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace PHPSemVerChecker\Analyzer;
4+
5+
use PHPSemVerChecker\Registry\Registry;
6+
use PHPSemVerChecker\Report\Report;
7+
8+
class Analyzer
9+
{
10+
/**
11+
* Compare with a destination registry (what the new source code is like).
12+
*
13+
* @param \PHPSemVerChecker\Registry\Registry $registryBefore
14+
* @param \PHPSemVerChecker\Registry\Registry $registryAfter
15+
* @return \PHPSemVerChecker\Report\Report
16+
*/
17+
public function analyze(Registry $registryBefore, Registry $registryAfter)
18+
{
19+
$finalReport = new Report();
20+
21+
$analyzers = [
22+
new FunctionAnalyzer(),
23+
new ClassAnalyzer(),
24+
new InterfaceAnalyzer(),
25+
new TraitAnalyzer(),
26+
];
27+
28+
foreach ($analyzers as $analyzer) {
29+
$report = $analyzer->analyze($registryBefore, $registryAfter);
30+
$finalReport->merge($report);
31+
}
32+
33+
return $finalReport;
34+
}
35+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace PHPSemVerChecker\Analyzer;
4+
5+
use PHPSemVerChecker\Operation\ClassAdded;
6+
use PHPSemVerChecker\Operation\ClassRemoved;
7+
use PHPSemVerChecker\Registry\Registry;
8+
use PHPSemVerChecker\Report\Report;
9+
use PHPSemVerChecker\SemanticVersioning\Level;
10+
11+
class ClassAnalyzer
12+
{
13+
protected $context = 'class';
14+
15+
public function analyze(Registry $registryBefore, Registry $registryAfter)
16+
{
17+
$report = new Report();
18+
19+
$keysBefore = array_keys($registryBefore->data['class']);
20+
$keysAfter = array_keys($registryAfter->data['class']);
21+
$added = array_diff($keysAfter, $keysBefore);
22+
$removed = array_diff($keysBefore, $keysAfter);
23+
$toVerify = array_intersect($keysBefore, $keysAfter);
24+
25+
foreach ($removed as $key) {
26+
$fileBefore = $registryBefore->mapping['class'][$key];
27+
$classBefore = $registryBefore->data['class'][$key];
28+
29+
$data = new ClassRemoved($fileBefore, $classBefore);
30+
$report->addClass($data, Level::MAJOR);
31+
}
32+
33+
foreach ($toVerify as $key) {
34+
$fileBefore = $registryBefore->mapping['class'][$key];
35+
/** @var \PhpParser\Node\Stmt\Class_ $classBefore */
36+
$classBefore = $registryBefore->data['class'][$key];
37+
$fileAfter = $registryAfter->mapping['class'][$key];
38+
/** @var \PhpParser\Node\Stmt\Class_ $classBefore */
39+
$classAfter = $registryAfter->data['class'][$key];
40+
41+
// TODO: Verify this comparison works properly <[email protected]>
42+
// Leave non-strict comparison here
43+
if ($classBefore != $classAfter) {
44+
$analyzer = new ClassMethodAnalyzer($fileBefore, $fileAfter);
45+
$classMethodReport = $analyzer->analyze($classBefore, $classAfter);
46+
$report->merge($classMethodReport);
47+
}
48+
}
49+
50+
foreach ($added as $key) {
51+
$fileAfter = $registryAfter->mapping['class'][$key];
52+
$classAfter = $registryAfter->data['class'][$key];
53+
54+
$data = new ClassAdded($fileAfter, $classAfter);
55+
$report->addClass($data, Level::MINOR);
56+
}
57+
58+
return $report;
59+
}
60+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
namespace PHPSemVerChecker\Analyzer;
4+
5+
use PhpParser\Node\Stmt\Class_;
6+
use PhpParser\Node\Stmt\ClassMethod;
7+
use PHPSemVerChecker\Comparator\Signature;
8+
use PHPSemVerChecker\Operation\ClassMethodAdded;
9+
use PHPSemVerChecker\Operation\ClassMethodImplementationChanged;
10+
use PHPSemVerChecker\Operation\ClassMethodParameterChanged;
11+
use PHPSemVerChecker\Operation\ClassMethodRemoved;
12+
use PHPSemVerChecker\Operation\Unknown;
13+
use PHPSemVerChecker\Report\Report;
14+
use PHPSemVerChecker\SemanticVersioning\Level;
15+
16+
class ClassMethodAnalyzer
17+
{
18+
protected $context = 'method';
19+
20+
protected $fileBefore;
21+
protected $fileAfter;
22+
23+
/**
24+
* @param string $fileBefore
25+
* @param string $fileAfter
26+
*/
27+
public function __construct($fileBefore = null, $fileAfter = null)
28+
{
29+
$this->fileBefore = $fileBefore;
30+
$this->fileAfter = $fileAfter;
31+
}
32+
33+
public function analyze(Class_ $classBefore, Class_ $classAfter)
34+
{
35+
$report = new Report();
36+
37+
$methodsBefore = $classBefore->getMethods();
38+
$methodsAfter = $classAfter->getMethods();
39+
40+
$methodsBeforeKeyed = [];
41+
foreach ($methodsBefore as $method) {
42+
$methodsBeforeKeyed[$method->name] = $method;
43+
}
44+
45+
$methodsAfterKeyed = [];
46+
foreach ($methodsAfter as $method) {
47+
$methodsAfterKeyed[$method->name] = $method;
48+
}
49+
50+
$methodsBeforeKeyed = array_filter($methodsBeforeKeyed, function (ClassMethod $method) {
51+
return $method->isPublic();
52+
});
53+
54+
$methodsAfterKeyed = array_filter($methodsAfterKeyed, function (ClassMethod $method) {
55+
return $method->isPublic();
56+
});
57+
58+
$methodNamesBefore = array_keys($methodsBeforeKeyed);
59+
$methodNamesAfter = array_keys($methodsAfterKeyed);
60+
$methodsAdded = array_diff($methodNamesAfter, $methodNamesBefore);
61+
$methodsRemoved = array_diff($methodNamesBefore, $methodNamesAfter);
62+
$methodsToVerify = array_intersect($methodNamesBefore, $methodNamesAfter);
63+
64+
// Here we only care about public methods as they are the only part of the API we care about
65+
66+
// Removed methods can either be implemented in parent classes or not exist anymore
67+
foreach ($methodsRemoved as $method) {
68+
$methodBefore = $methodsBeforeKeyed[$method];
69+
$data = new ClassMethodRemoved($this->fileBefore, $classBefore, $methodBefore);
70+
$report->addClassMethod($data, Level::MAJOR);
71+
}
72+
73+
foreach ($methodsToVerify as $method) {
74+
/** @var \PhpParser\Node\Stmt\ClassMethod $methodBefore */
75+
$methodBefore = $methodsBeforeKeyed[$method];
76+
/** @var \PhpParser\Node\Stmt\ClassMethod $methodAfter */
77+
$methodAfter = $methodsAfterKeyed[$method];
78+
79+
// Leave non-strict comparison here
80+
if ($methodBefore != $methodAfter) {
81+
$paramsBefore = $methodBefore->params;
82+
$paramsAfter = $methodAfter->params;
83+
// Signature
84+
85+
if ( ! Signature::isSameTypehints($paramsBefore, $paramsAfter)) {
86+
$data = new ClassMethodParameterChanged($this->fileBefore, $classBefore, $methodBefore, $this->fileAfter, $classAfter, $methodAfter);
87+
$report->addClassMethod($data, Level::MAJOR);
88+
continue;
89+
}
90+
91+
if ( ! Signature::isSameVariables($paramsBefore, $paramsAfter)) {
92+
$data = new ClassMethodParameterChanged($this->fileBefore, $classBefore, $methodBefore, $this->fileAfter, $classAfter, $methodAfter);
93+
$report->addClassMethod($data, Level::PATCH);
94+
continue;
95+
}
96+
97+
// Different length (considering params with defaults)
98+
99+
// Difference in source code
100+
if ($methodBefore->stmts != $methodAfter->stmts) {
101+
$data = new ClassMethodImplementationChanged($this->fileBefore, $classBefore, $methodBefore, $this->fileAfter, $classAfter, $methodAfter);
102+
$report->addClassMethod($data, Level::PATCH);
103+
continue;
104+
}
105+
106+
// Unable to match an issue, but there is one...
107+
$data = new Unknown($this->fileBefore, $this->fileAfter);
108+
$report->addClassMethod($data, Level::MAJOR);
109+
}
110+
}
111+
112+
// Added methods implies MINOR BUMP
113+
foreach ($methodsAdded as $method) {
114+
$methodAfter = $methodsAfterKeyed[$method];
115+
$data = new ClassMethodAdded($this->fileAfter, $classAfter, $methodAfter);
116+
$report->addClassMethod($data, Level::MINOR);
117+
}
118+
119+
return $report;
120+
}
121+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
namespace PHPSemVerChecker\Analyzer;
4+
5+
use PHPSemVerChecker\Comparator\Signature;
6+
use PHPSemVerChecker\Operation\FunctionAdded;
7+
use PHPSemVerChecker\Operation\FunctionImplementationChanged;
8+
use PHPSemVerChecker\Operation\FunctionParameterChanged;
9+
use PHPSemVerChecker\Operation\FunctionRemoved;
10+
use PHPSemVerChecker\Operation\Unknown;
11+
use PHPSemVerChecker\Registry\Registry;
12+
use PHPSemVerChecker\Report\Report;
13+
use PHPSemVerChecker\SemanticVersioning\Level;
14+
15+
class FunctionAnalyzer
16+
{
17+
protected $context = 'function';
18+
19+
/**
20+
* @param \PHPSemVerChecker\Registry\Registry $registryBefore
21+
* @param \PHPSemVerChecker\Registry\Registry $registryAfter
22+
* @return \PHPSemVerChecker\Report\Report
23+
*/
24+
public function analyze(Registry $registryBefore, Registry $registryAfter)
25+
{
26+
$report = new Report();
27+
28+
$keysBefore = array_keys($registryBefore->data['function']);
29+
$keysAfter = array_keys($registryAfter->data['function']);
30+
$added = array_diff($keysAfter, $keysBefore);
31+
$removed = array_diff($keysBefore, $keysAfter);
32+
$toVerify = array_intersect($keysBefore, $keysAfter);
33+
34+
foreach ($removed as $key) {
35+
$fileBefore = $registryBefore->mapping['function'][$key];
36+
$functionBefore = $registryBefore->data['function'][$key];
37+
38+
$data = new FunctionRemoved($fileBefore, $functionBefore);
39+
$report->addFunction($data, Level::MAJOR);
40+
}
41+
42+
foreach ($toVerify as $key) {
43+
$fileBefore = $registryBefore->mapping['function'][$key];
44+
/** @var Function_ $functionBefore */
45+
$functionBefore = $registryBefore->data['function'][$key];
46+
$fileAfter = $registryAfter->mapping['function'][$key];
47+
$functionAfter = $registryAfter->data['function'][$key];
48+
49+
// TODO: Verify this comparison works properly <[email protected]>
50+
// Leave non-strict comparison here
51+
if ($functionBefore != $functionAfter) {
52+
$paramsBefore = $functionBefore->params;
53+
$paramsAfter = $functionAfter->params;
54+
// Signature
55+
56+
if ( ! Signature::isSameTypehints($paramsBefore, $paramsAfter)) {
57+
$data = new FunctionParameterChanged($fileBefore, $functionBefore, $fileAfter, $functionAfter);
58+
$report->addFunction($data, Level::MAJOR);
59+
continue;
60+
}
61+
62+
if ( ! Signature::isSameVariables($paramsBefore, $paramsAfter)) {
63+
$data = new FunctionParameterChanged($fileBefore, $functionBefore, $fileAfter, $functionAfter);
64+
$report->addFunction($data, Level::PATCH);
65+
continue;
66+
}
67+
68+
// Different length (considering params with defaults)
69+
70+
// Difference in source code
71+
if ($functionBefore->stmts != $functionAfter->stmts) {
72+
$data = new FunctionImplementationChanged($fileBefore, $functionBefore, $fileAfter, $functionAfter);
73+
$report->addFunction($data, Level::PATCH);
74+
continue;
75+
}
76+
77+
// Unable to match an issue, but there is one...
78+
$data = new Unknown($fileBefore, $fileAfter);
79+
$report->addFunction($data, Level::MAJOR);
80+
}
81+
}
82+
83+
foreach ($added as $key) {
84+
$fileAfter = $registryAfter->mapping['function'][$key];
85+
$functionAfter = $registryAfter->data['function'][$key];
86+
87+
$data = new FunctionAdded($fileAfter, $functionAfter);
88+
$report->addFunction($data, Level::MINOR);
89+
}
90+
91+
return $report;
92+
}
93+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace PHPSemVerChecker\Analyzer;
4+
5+
use PHPSemVerChecker\Operation\InterfaceAdded;
6+
use PHPSemVerChecker\Operation\InterfaceRemoved;
7+
use PHPSemVerChecker\Registry\Registry;
8+
use PHPSemVerChecker\Report\Report;
9+
use PHPSemVerChecker\SemanticVersioning\Level;
10+
11+
class InterfaceAnalyzer
12+
{
13+
protected $context = 'interface';
14+
15+
public function analyze(Registry $registryBefore, Registry $registryAfter)
16+
{
17+
$report = new Report();
18+
19+
$keysBefore = array_keys($registryBefore->data['interface']);
20+
$keysAfter = array_keys($registryAfter->data['interface']);
21+
$added = array_diff($keysAfter, $keysBefore);
22+
$removed = array_diff($keysBefore, $keysAfter);
23+
$toVerify = array_intersect($keysBefore, $keysAfter);
24+
25+
foreach ($removed as $key) {
26+
$fileBefore = $registryBefore->mapping['interface'][$key];
27+
$interfaceBefore = $registryBefore->data['interface'][$key];
28+
29+
$data = new InterfaceRemoved($fileBefore, $interfaceBefore);
30+
$report->addInterface($data, Level::MAJOR);
31+
}
32+
33+
// TODO: Verify similar interface methods <[email protected]>
34+
35+
foreach ($added as $key) {
36+
$fileAfter = $registryAfter->mapping['interface'][$key];
37+
$interfaceAfter = $registryAfter->data['interface'][$key];
38+
39+
$data = new InterfaceAdded($fileAfter, $interfaceAfter);
40+
$report->addInterface($data, Level::MAJOR);
41+
}
42+
43+
return $report;
44+
}
45+
}

0 commit comments

Comments
 (0)