-
Notifications
You must be signed in to change notification settings - Fork 472
Commit
8.4
or above in cl…
…asses
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Properties; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\ClassPropertyNode; | ||
use PHPStan\Php\PhpVersion; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
|
||
/** | ||
* @implements Rule<ClassPropertyNode> | ||
*/ | ||
final class PropertyInClassRule implements Rule | ||
{ | ||
|
||
public function __construct(private PhpVersion $phpVersion) | ||
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan with result cache (8.3)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.4, ubuntu-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.2, ubuntu-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.3, ubuntu-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan with result cache (8.4)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan with result cache (8.2)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.1, ubuntu-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.0, ubuntu-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan with result cache (8.1)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (7.4, ubuntu-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.1, windows-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.4, windows-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.0, windows-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.3, windows-latest)
Check failure on line 18 in src/Rules/Properties/PropertyInClassRule.php GitHub Actions / PHPStan (8.2, windows-latest)
|
||
{ | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return ClassPropertyNode::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$classReflection = $node->getClassReflection(); | ||
|
||
if (!$classReflection->isClass()) { | ||
return []; | ||
} | ||
|
||
if (!$classReflection->isAbstract() && $node->hasHooks() && $node->isAbstract()) { | ||
return [ | ||
RuleErrorBuilder::message('Classes may not include abstract hooked properties.') | ||
->nonIgnorable() | ||
->identifier('property.abstractHookedInClass') | ||
->build(), | ||
]; | ||
} | ||
|
||
if (!$classReflection->isAbstract() && $node->hasHooks() && !$this->doAllHooksHaveBody($node)) { | ||
return [ | ||
RuleErrorBuilder::message('Classes may not include hooked properties without bodies.') | ||
->nonIgnorable() | ||
->identifier('property.hookedWithoutBodyInClass') | ||
->build(), | ||
]; | ||
} | ||
|
||
if (!$classReflection->isAbstract()) { | ||
return []; | ||
} | ||
|
||
if ($node->hasHooks() && !$node->isAbstract()) { | ||
return [ | ||
RuleErrorBuilder::message('Abstract classes may not include non-abstract hooked properties without bodies.') | ||
->nonIgnorable() | ||
->identifier('property.nonAbstractHookedWithoutBodyInAbstractClass') | ||
->build(), | ||
]; | ||
} | ||
|
||
if ($node->isAbstract() && !$node->hasHooks()) { | ||
return [ | ||
RuleErrorBuilder::message('Only hooked properties may be declared abstract.') | ||
->nonIgnorable() | ||
->identifier('property.nonHookedAbstractInClass') | ||
->build(), | ||
]; | ||
} | ||
|
||
if ($node->isAbstract() && !$this->isAtLeastOneHookBodyEmpty($node)) { | ||
return [ | ||
RuleErrorBuilder::message('Abstract properties must specify at least one abstract hook.') | ||
->nonIgnorable() | ||
->identifier('property.hookedAbstractWithBodies') | ||
->build(), | ||
]; | ||
} | ||
|
||
return []; | ||
} | ||
|
||
private function doAllHooksHaveBody(ClassPropertyNode $node): bool | ||
{ | ||
foreach ($node->getHooks() as $hook) { | ||
if ($hook->body === null) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private function isAtLeastOneHookBodyEmpty(ClassPropertyNode $node): bool | ||
{ | ||
foreach ($node->getHooks() as $hook) { | ||
if ($hook->body === null) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Properties; | ||
|
||
use PHPStan\Php\PhpVersion; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
use const PHP_VERSION_ID; | ||
|
||
/** | ||
* @extends RuleTestCase<PropertyInClassRule> | ||
*/ | ||
class PropertyInClassRuleTest extends RuleTestCase | ||
{ | ||
|
||
private int $phpVersion = PHP_VERSION_ID; | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new PropertyInClassRule(new PhpVersion($this->phpVersion)); | ||
} | ||
|
||
public function testPhp84AndNonAbstractHookedPropertiesInClass(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/non-abstract-hooked-properties-in-class.php'], [ | ||
[ | ||
'Classes may not include hooked properties without bodies.', | ||
7, | ||
], | ||
[ | ||
'Classes may not include hooked properties without bodies.', | ||
9, | ||
], | ||
]); | ||
} | ||
|
||
public function testPhp84AndAbstractHookedPropertiesInClass(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/abstract-hooked-properties-in-class.php'], [ | ||
[ | ||
'Classes may not include abstract hooked properties.', | ||
7, | ||
], | ||
[ | ||
'Classes may not include abstract hooked properties.', | ||
9, | ||
], | ||
]); | ||
} | ||
|
||
public function testPhp84AndNonAbstractHookedPropertiesInAbstractClass(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/non-abstract-hooked-properties-in-abstract-class.php'], [ | ||
[ | ||
'Abstract classes may not include non-abstract hooked properties without bodies.', | ||
7, | ||
], | ||
[ | ||
'Abstract classes may not include non-abstract hooked properties without bodies.', | ||
9, | ||
], | ||
]); | ||
} | ||
|
||
public function testPhp84AndAbstractNonHookedPropertiesInAbstractClass(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/abstract-non-hooked-properties-in-abstract-class.php'], [ | ||
[ | ||
'Only hooked properties may be declared abstract.', | ||
7, | ||
], | ||
[ | ||
'Only hooked properties may be declared abstract.', | ||
9, | ||
], | ||
]); | ||
} | ||
|
||
public function testPhp84AndAbstractHookedPropertiesWithBodies(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/abstract-hooked-properties-with-bodies.php'], [ | ||
[ | ||
'Abstract properties must specify at least one abstract hook.', | ||
7, | ||
], | ||
[ | ||
'Abstract properties must specify at least one abstract hook.', | ||
12, | ||
], | ||
]); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace NonAbstractHookedPropertiesInAbstractClass; | ||
|
||
class AbstractPerson | ||
{ | ||
public abstract string $name { get; set; } | ||
|
||
public abstract string $lastName { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace AbstractHookedPropertiesWithBodies; | ||
|
||
abstract class AbstractPerson | ||
{ | ||
public abstract string $name { | ||
get => $this->name; | ||
set => $this->name = $value; | ||
} | ||
|
||
public abstract string $lastName { | ||
get => $this->lastName; | ||
set => $this->lastName = $value; | ||
} | ||
|
||
public abstract string $middleName { | ||
get => $this->name; | ||
set; | ||
} | ||
|
||
public abstract string $familyName { | ||
get; | ||
set; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace NonAbstractHookedPropertiesInAbstractClass; | ||
|
||
abstract class AbstractPerson | ||
{ | ||
public abstract string $name; | ||
|
||
public abstract string $lastName; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace NonAbstractHookedPropertiesInAbstractClass; | ||
|
||
abstract class AbstractPerson | ||
{ | ||
public string $name { get; set; } | ||
|
||
public string $lastName { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace NonAbstractHookedPropertiesInClass; | ||
|
||
class Person | ||
{ | ||
public string $name { get; set; } | ||
|
||
public string $lastName { get; set; } | ||
} |