Skip to content

Commit

Permalink
ReadWritePropertiesExtensions are part of ClassPropertiesNode
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 29, 2022
1 parent 59fb0a3 commit 49c27d8
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 79 deletions.
4 changes: 3 additions & 1 deletion src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\NonEmptyArrayType;
Expand Down Expand Up @@ -195,6 +196,7 @@ public function __construct(
private FileHelper $fileHelper,
private TypeSpecifier $typeSpecifier,
private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
private ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider,
private bool $polluteScopeWithLoopInitialAssignments,
private bool $polluteScopeWithAlwaysIterableForeach,
private array $earlyTerminatingMethodCalls,
Expand Down Expand Up @@ -639,7 +641,7 @@ private function processStmtNode(
$this->processAttributeGroups($stmt->attrGroups, $classScope, $classStatementsGatherer);

$this->processStmtNodes($stmt, $stmt->stmts, $classScope, $classStatementsGatherer);
$nodeCallback(new ClassPropertiesNode($stmt, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls()), $classScope);
$nodeCallback(new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensionProvider, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls()), $classScope);
$nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls()), $classScope);
$nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches()), $classScope);
$classReflection->evictPrivateSymbols();
Expand Down
17 changes: 14 additions & 3 deletions src/Node/ClassPropertiesNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use PHPStan\Node\Property\PropertyWrite;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Rules\Properties\ReadWritePropertiesExtension;
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
Expand All @@ -33,7 +34,13 @@ class ClassPropertiesNode extends NodeAbstract implements VirtualNode
* @param array<int, PropertyRead|PropertyWrite> $propertyUsages
* @param array<int, MethodCall> $methodCalls
*/
public function __construct(private ClassLike $class, private array $properties, private array $propertyUsages, private array $methodCalls)
public function __construct(
private ClassLike $class,
private ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider,
private array $properties,
private array $propertyUsages,
private array $methodCalls,
)
{
parent::__construct($class->getAttributes());
}
Expand Down Expand Up @@ -74,13 +81,13 @@ public function getSubNodeNames(): array

/**
* @param string[] $constructors
* @param ReadWritePropertiesExtension[] $extensions
* @param ReadWritePropertiesExtension[]|null $extensions
* @return array{array<string, ClassPropertyNode>, array<array{string, int, ClassPropertyNode}>, array<array{string, int, ClassPropertyNode}>}
*/
public function getUninitializedProperties(
Scope $scope,
array $constructors,
array $extensions,
?array $extensions = null,
): array
{
if (!$this->getClass() instanceof Class_) {
Expand All @@ -105,6 +112,10 @@ public function getUninitializedProperties(
$properties[$property->getName()] = $property;
}

if ($extensions === null) {
$extensions = $this->readWritePropertiesExtensionProvider->getExtensions();
}

foreach (array_keys($properties) as $name) {
foreach ($extensions as $extension) {
if (!$classReflection->hasNativeProperty($name)) {
Expand Down
2 changes: 1 addition & 1 deletion src/Rules/DeadCode/UnusedPrivatePropertyRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public function processNode(Node $node, Scope $scope): array
}
}

[$uninitializedProperties] = $node->getUninitializedProperties($scope, [], $this->extensionProvider->getExtensions());
[$uninitializedProperties] = $node->getUninitializedProperties($scope, []);

$errors = [];
foreach ($properties as $name => $data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class MissingReadOnlyByPhpDocPropertyAssignRule implements Rule

public function __construct(
private ConstructorsHelper $constructorsHelper,
private ReadWritePropertiesExtensionProvider $extensionProvider,
)
{
}
Expand All @@ -35,7 +34,7 @@ public function processNode(Node $node, Scope $scope): array
throw new ShouldNotHappenException();
}
$classReflection = $scope->getClassReflection();
[$properties, $prematureAccess, $additionalAssigns] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection), $this->extensionProvider->getExtensions());
[$properties, $prematureAccess, $additionalAssigns] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection));

$errors = [];
foreach ($properties as $propertyName => $propertyNode) {
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class MissingReadOnlyPropertyAssignRule implements Rule

public function __construct(
private ConstructorsHelper $constructorsHelper,
private ReadWritePropertiesExtensionProvider $extensionProvider,
)
{
}
Expand All @@ -35,7 +34,7 @@ public function processNode(Node $node, Scope $scope): array
throw new ShouldNotHappenException();
}
$classReflection = $scope->getClassReflection();
[$properties, $prematureAccess, $additionalAssigns] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection), $this->extensionProvider->getExtensions());
[$properties, $prematureAccess, $additionalAssigns] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection));

$errors = [];
foreach ($properties as $propertyName => $propertyNode) {
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Properties/UninitializedPropertyRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class UninitializedPropertyRule implements Rule
{

public function __construct(
private ReadWritePropertiesExtensionProvider $extensionProvider,
private ConstructorsHelper $constructorsHelper,
)
{
Expand All @@ -35,7 +34,7 @@ public function processNode(Node $node, Scope $scope): array
throw new ShouldNotHappenException();
}
$classReflection = $scope->getClassReflection();
[$properties, $prematureAccess] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection), $this->extensionProvider->getExtensions());
[$properties, $prematureAccess] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection));

$errors = [];
foreach ($properties as $propertyName => $propertyNode) {
Expand Down
11 changes: 11 additions & 0 deletions src/Testing/RuleTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
use PHPStan\PhpDoc\StubPhpDocProvider;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Rules\Properties\DirectReadWritePropertiesExtensionProvider;
use PHPStan\Rules\Properties\ReadWritePropertiesExtension;
use PHPStan\Rules\Registry as RuleRegistry;
use PHPStan\Rules\Rule;
use PHPStan\Type\FileTypeMapper;
Expand Down Expand Up @@ -50,6 +52,14 @@ protected function getCollectors(): array
return [];
}

/**
* @return ReadWritePropertiesExtension[]
*/
protected function getReadWritePropertiesExtensions(): array
{
return [];
}

protected function getTypeSpecifier(): TypeSpecifier
{
return self::getContainer()->getService('typeSpecifier');
Expand Down Expand Up @@ -78,6 +88,7 @@ private function getAnalyser(): Analyser
self::getContainer()->getByType(FileHelper::class),
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
new DirectReadWritePropertiesExtensionProvider($this->getReadWritePropertiesExtensions()),
$this->shouldPolluteScopeWithLoopInitialAssignments(),
$this->shouldPolluteScopeWithAlwaysIterableForeach(),
[],
Expand Down
2 changes: 2 additions & 0 deletions src/Testing/TypeInferenceTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
use PHPStan\PhpDoc\StubPhpDocProvider;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
use PHPStan\TrinaryLogic;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\VerbosityLevel;
Expand Down Expand Up @@ -53,6 +54,7 @@ public function processFile(
self::getContainer()->getByType(FileHelper::class),
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
true,
true,
$this->getEarlyTerminatingMethodCalls(),
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/AnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use PHPStan\PhpDoc\StubPhpDocProvider;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Rules\AlwaysFailRule;
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
use PHPStan\Rules\Registry as RuleRegistry;
use PHPStan\Testing\PHPStanTestCase;
use PHPStan\Type\FileTypeMapper;
Expand Down Expand Up @@ -485,6 +486,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): Analyser
$fileHelper,
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
false,
true,
[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,37 @@ protected function getRule(): Rule
new ConstructorsHelper([
'MissingReadOnlyPropertyAssignPhpDoc\\TestCase::setUp',
]),
new DirectReadWritePropertiesExtensionProvider([
new class() implements ReadWritePropertiesExtension {
);
}

protected function getReadWritePropertiesExtensions(): array
{
return [
new class() implements ReadWritePropertiesExtension {

public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}
public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}
public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

public function isInitialized(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}
public function isInitialized(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

private function isEntityId(PropertyReflection $property, string $propertyName): bool
{
return $property->getDeclaringClass()->getName() === 'MissingReadOnlyPropertyAssignPhpDoc\\Entity'
&& in_array($propertyName, ['id'], true);
}
private function isEntityId(PropertyReflection $property, string $propertyName): bool
{
return $property->getDeclaringClass()->getName() === 'MissingReadOnlyPropertyAssignPhpDoc\\Entity'
&& in_array($propertyName, ['id'], true);
}

},
]),
);
},
];
}

public function testRule(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,39 @@ protected function getRule(): Rule
new ConstructorsHelper([
'MissingReadOnlyPropertyAssign\\TestCase::setUp',
]),
new DirectReadWritePropertiesExtensionProvider([
new class() implements ReadWritePropertiesExtension {

public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

public function isInitialized(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

private function isEntityId(PropertyReflection $property, string $propertyName): bool
{
return $property->getDeclaringClass()->getName() === 'MissingReadOnlyPropertyAssign\\Entity'
&& in_array($propertyName, ['id'], true);
}

},
]),
);
}

protected function getReadWritePropertiesExtensions(): array
{
return [
new class() implements ReadWritePropertiesExtension {

public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

public function isInitialized(PropertyReflection $property, string $propertyName): bool
{
return $this->isEntityId($property, $propertyName);
}

private function isEntityId(PropertyReflection $property, string $propertyName): bool
{
return $property->getDeclaringClass()->getName() === 'MissingReadOnlyPropertyAssign\\Entity'
&& in_array($propertyName, ['id'], true);
}

},
];
}

public function testRule(): void
{
if (PHP_VERSION_ID < 80100) {
Expand Down
44 changes: 24 additions & 20 deletions tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,6 @@ class UninitializedPropertyRuleTest extends RuleTestCase
protected function getRule(): Rule
{
return new UninitializedPropertyRule(
new DirectReadWritePropertiesExtensionProvider([
new class() implements ReadWritePropertiesExtension {

public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
{
return false;
}

public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
{
return false;
}

public function isInitialized(PropertyReflection $property, string $propertyName): bool
{
return $property->getDeclaringClass()->getName() === 'UninitializedProperty\\TestExtension' && $propertyName === 'inited';
}

},
]),
new ConstructorsHelper(
[
'UninitializedProperty\\TestCase::setUp',
Expand All @@ -44,6 +24,30 @@ public function isInitialized(PropertyReflection $property, string $propertyName
);
}

protected function getReadWritePropertiesExtensions(): array
{
return [
new class() implements ReadWritePropertiesExtension {

public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
{
return false;
}

public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
{
return false;
}

public function isInitialized(PropertyReflection $property, string $propertyName): bool
{
return $property->getDeclaringClass()->getName() === 'UninitializedProperty\\TestExtension' && $propertyName === 'inited';
}

},
];
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/uninitialized-property.php'], [
Expand Down

0 comments on commit 49c27d8

Please sign in to comment.