Skip to content

Commit 4e77291

Browse files
committed
[#5] Semantic versioning on class/trait properties
1 parent bb31951 commit 4e77291

15 files changed

+419
-19
lines changed

docs/Ruleset.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,26 @@ Code | Level | Rule
1717
V005 | MAJOR | Class removed
1818
V006 | MAJOR | Class public method removed
1919
V007 | MAJOR | Class protected method removed
20-
V008 | MAJOR | *Class public property removed*
21-
V009 | MAJOR | *Class protected property removed*
20+
V008 | MAJOR | Class public property removed
21+
V009 | MAJOR | Class protected property removed
2222
V010 | MAJOR | Class public method parameter changed
2323
V011 | MAJOR | Class protected method parameter changed
2424
V012 | MAJOR | *New public constructor (does not match supertype)*
2525
V013 | MAJOR | *New protected constructor (does not match supertype)*
2626
V015 | MAJOR | Class public method added
2727
V016 | MAJOR | Class protected method added
28+
V019 | MAJOR | Class public property added
29+
V020 | MAJOR | Class protected property added
2830
V014 | MINOR | Class added
2931
V017 | MINOR | *Final class public method added*
3032
V018 | MINOR | *Final class protected method added*
31-
V019 | MINOR | *Class public property added*
32-
V020 | MINOR | *Class protected property added*
3333
V021 | MINOR | *Final class protected method parameter changed*
3434
V022 | PATCH | *Final class protected method removed*
3535
V023 | PATCH | [Final] Class public class method implementation changed
3636
V024 | PATCH | [Final] Class protected class method implementation changed
3737
V025 | PATCH | [Final] Class private class method implementation changed
38-
V026 | PATCH | *Class private property added*
39-
V027 | PATCH | *Class private property removed*
38+
V026 | PATCH | Class private property added
39+
V027 | PATCH | Class private property removed
4040
V028 | PATCH | Class private method added
4141
V029 | PATCH | Class private method removed
4242
V030 | PATCH | *Final class protected method added*
@@ -63,18 +63,18 @@ Code | Level | Rule
6363
V037 | MAJOR | Trait removed
6464
V038 | MAJOR | Trait public method removed
6565
V039 | MAJOR | Trait protected method removed
66-
V040 | MAJOR | *Trait public property removed*
67-
V041 | MAJOR | *Trait protected property removed*
66+
V040 | MAJOR | Trait public property removed
67+
V041 | MAJOR | Trait protected property removed
6868
V042 | MAJOR | Trait public method parameter changed
6969
V043 | MAJOR | Trait protected method parameter changed
7070
V044 | MAJOR | *New public constructor (does not match supertype)*
7171
V045 | MAJOR | *New protected constructor (does not match supertype)*
7272
V047 | MAJOR | Trait public method added
7373
V048 | MAJOR | Trait protected method added
74-
V049 | MAJOR | *Trait public property added*
75-
V050 | MAJOR | *Trait protected property added*
76-
V055 | MAJOR | *Trait private property added*
77-
V056 | MAJOR | *Trait private property removed*
74+
V049 | MAJOR | Trait public property added
75+
V050 | MAJOR | Trait protected property added
76+
V055 | MAJOR | Trait private property added
77+
V056 | MAJOR | Trait private property removed
7878
V057 | MAJOR | Trait private method added
7979
V058 | MAJOR | Trait private method removed
8080
V046 | MINOR | Trait added
@@ -89,4 +89,4 @@ V066 | PATCH | Trait private method parameter name changed
8989

9090
# To classify
9191

92-
* Method visibility changed
92+
* Method visibility changed

src/PHPSemVerChecker/Analyzer/ClassAnalyzer.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,15 @@ public function analyze(Registry $registryBefore, Registry $registryAfter)
4040

4141
// Leave non-strict comparison here
4242
if ($classBefore != $classAfter) {
43-
$analyzer = new ClassMethodAnalyzer('class', $fileBefore, $fileAfter);
44-
$classMethodReport = $analyzer->analyze($classBefore, $classAfter);
45-
$report->merge($classMethodReport);
43+
$analyzers = [
44+
new ClassMethodAnalyzer('class', $fileBefore, $fileAfter),
45+
new PropertyAnalyzer('class', $fileBefore, $fileAfter),
46+
];
47+
48+
foreach ($analyzers as $analyzer) {
49+
$internalReport = $analyzer->analyze($classBefore, $classAfter);
50+
$report->merge($internalReport);
51+
}
4652
}
4753
}
4854

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
namespace PHPSemVerChecker\Analyzer;
4+
5+
use PhpParser\Node\Stmt;
6+
use PhpParser\Node\Stmt\Property;
7+
use PHPSemVerChecker\Operation\PropertyAdded;
8+
use PHPSemVerChecker\Operation\PropertyRemoved;
9+
use PHPSemVerChecker\Report\Report;
10+
11+
class PropertyAnalyzer
12+
{
13+
/**
14+
* @var string
15+
*/
16+
protected $context;
17+
/**
18+
* @var null|string
19+
*/
20+
protected $fileBefore;
21+
/**
22+
* @var null|string
23+
*/
24+
protected $fileAfter;
25+
26+
/**
27+
* @param string $context
28+
* @param string $fileBefore
29+
* @param string $fileAfter
30+
*/
31+
public function __construct($context, $fileBefore = null, $fileAfter = null)
32+
{
33+
$this->context = $context;
34+
$this->fileBefore = $fileBefore;
35+
$this->fileAfter = $fileAfter;
36+
}
37+
38+
public function analyze(Stmt $contextBefore, Stmt $contextAfter)
39+
{
40+
$report = new Report();
41+
42+
$propertiesBefore = $this->getProperties($contextBefore);
43+
$propertiesAfter = $this->getProperties($contextAfter);
44+
45+
$propertiesBeforeKeyed = [];
46+
foreach ($propertiesBefore as $property) {
47+
$propertiesBeforeKeyed[$this->getName($property)] = $property;
48+
}
49+
50+
$propertiesAfterKeyed = [];
51+
foreach ($propertiesAfter as $property) {
52+
$propertiesAfterKeyed[$this->getName($property)] = $property;
53+
}
54+
55+
$propertyNamesBefore = array_keys($propertiesBeforeKeyed);
56+
$propertyNamesAfter = array_keys($propertiesAfterKeyed);
57+
$propertiesAdded = array_diff($propertyNamesAfter, $propertyNamesBefore);
58+
$propertiesRemoved = array_diff($propertyNamesBefore, $propertyNamesAfter);
59+
$propertiesToVerify = array_intersect($propertyNamesBefore, $propertyNamesAfter);
60+
61+
foreach ($propertiesRemoved as $property) {
62+
$propertyBefore = $propertiesBeforeKeyed[$property];
63+
$data = new PropertyRemoved($this->context, $this->fileBefore, $contextBefore, $propertyBefore);
64+
$report->add($this->context, $data);
65+
}
66+
67+
foreach ($propertiesAdded as $property) {
68+
$propertyAfter = $propertiesAfterKeyed[$property];
69+
$data = new PropertyAdded($this->context, $this->fileAfter, $contextAfter, $propertyAfter);
70+
$report->add($this->context, $data);
71+
}
72+
73+
return $report;
74+
}
75+
76+
protected function getProperties(Stmt $context)
77+
{
78+
$properties = [];
79+
foreach ($context->stmts as $stmt) {
80+
if ($stmt instanceof Property) {
81+
$properties[] = $stmt;
82+
}
83+
}
84+
return $properties;
85+
}
86+
87+
protected function getName(Property $property)
88+
{
89+
return $property->props[0]->name;
90+
}
91+
}

src/PHPSemVerChecker/Analyzer/TraitAnalyzer.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,15 @@ public function analyze(Registry $registryBefore, Registry $registryAfter)
4040

4141
// Leave non-strict comparison here
4242
if ($traitBefore != $traitAfter) {
43-
$analyzer = new ClassMethodAnalyzer('trait', $fileBefore, $fileAfter);
44-
$traitMethodReport = $analyzer->analyze($traitBefore, $traitAfter);
45-
$report->merge($traitMethodReport);
43+
$analyzers = [
44+
new ClassMethodAnalyzer('trait', $fileBefore, $fileAfter),
45+
new PropertyAnalyzer('trait', $fileBefore, $fileAfter),
46+
];
47+
48+
foreach ($analyzers as $analyzer) {
49+
$internalReport = $analyzer->analyze($traitBefore, $traitAfter);
50+
$report->merge($internalReport);
51+
}
4652
}
4753
}
4854

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace PHPSemVerChecker\Operation;
4+
5+
use PhpParser\Node\Stmt;
6+
use PhpParser\Node\Stmt\ClassMethod;
7+
use PHPSemVerChecker\SemanticVersioning\Level;
8+
9+
class PropertyAdded extends PropertyOperation {
10+
/**
11+
* @var array
12+
*/
13+
protected $code = [
14+
'class' => ['V019', 'V020', 'V026'],
15+
'trait' => ['V049', 'V050', 'V055'],
16+
];
17+
/**
18+
* @var int
19+
*/
20+
protected $level = [
21+
'class' => [Level::MAJOR, Level::MAJOR, Level::PATCH],
22+
'trait' => [Level::MAJOR, Level::MAJOR, Level::MAJOR],
23+
];
24+
/**
25+
* @var string
26+
*/
27+
protected $reason = 'Property has been added.';
28+
/**
29+
* @var string
30+
*/
31+
protected $fileAfter;
32+
/**
33+
* @var \PhpParser\Node\Stmt
34+
*/
35+
protected $contextAfter;
36+
/**
37+
* @var \PhpParser\Node\Stmt\ClassMethod
38+
*/
39+
protected $propertyAfter;
40+
41+
/**
42+
* @param string $context
43+
* @param string $fileAfter
44+
* @param \PhpParser\Node\Stmt $contextAfter
45+
* @param \PhpParser\Node\Stmt\ClassMethod $propertyAfter
46+
*/
47+
public function __construct($context, $fileAfter, Stmt $contextAfter, Stmt\Property $propertyAfter)
48+
{
49+
$this->context = $context;
50+
$this->visibility = $this->getVisibility($propertyAfter);
51+
$this->fileAfter = $fileAfter;
52+
$this->contextAfter = $contextAfter;
53+
$this->propertyAfter = $propertyAfter;
54+
}
55+
56+
/**
57+
* @return string
58+
*/
59+
public function getLocation()
60+
{
61+
return $this->fileAfter;
62+
}
63+
64+
/**
65+
* @return int
66+
*/
67+
public function getLine()
68+
{
69+
return $this->propertyAfter->getLine();
70+
}
71+
72+
/**
73+
* @return string
74+
*/
75+
public function getTarget()
76+
{
77+
$fqcn = $this->contextAfter->name;
78+
if ($this->contextAfter->namespacedName) {
79+
$fqcn = $this->contextAfter->namespacedName->toString();
80+
}
81+
return $fqcn . '::$' . $this->propertyAfter->props[0]->name;
82+
}
83+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace PHPSemVerChecker\Operation;
4+
5+
use PhpParser\Node\Stmt\Class_;
6+
use PhpParser\Node\Stmt\Property;
7+
8+
abstract class PropertyOperation extends Operation
9+
{
10+
/**
11+
* @var string
12+
*/
13+
protected $context;
14+
/**
15+
* @var int
16+
*/
17+
protected $visibility;
18+
19+
public function getCode()
20+
{
21+
$visiblityMapping = $this->getVisibilityMapping();
22+
return $this->code[$this->context][$visiblityMapping[$this->visibility]];
23+
}
24+
25+
public function getLevel()
26+
{
27+
$visiblityMapping = $this->getVisibilityMapping();
28+
return $this->level[$this->context][$visiblityMapping[$this->visibility]];
29+
}
30+
31+
protected function getVisibilityMapping()
32+
{
33+
return [
34+
Class_::MODIFIER_PUBLIC => 0,
35+
Class_::MODIFIER_PROTECTED => 1,
36+
Class_::MODIFIER_PRIVATE => 2,
37+
];
38+
}
39+
40+
protected function getVisibility(Property $classMethod)
41+
{
42+
if ($classMethod->isPublic()) {
43+
return Class_::MODIFIER_PUBLIC;
44+
} elseif ($classMethod->isProtected()) {
45+
return Class_::MODIFIER_PROTECTED;
46+
} else {
47+
return Class_::MODIFIER_PRIVATE;
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)