Skip to content

Commit

Permalink
@readonly property allows private mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
zonuexe authored and ondrejmirtes committed Sep 27, 2022
1 parent 6335a80 commit ea87e95
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 7 deletions.
15 changes: 14 additions & 1 deletion src/PhpDoc/PhpDocNodeResolver.php
Expand Up @@ -454,7 +454,7 @@ public function resolveIsImpure(PhpDocNode $phpDocNode): bool

public function resolveIsReadOnly(PhpDocNode $phpDocNode): bool
{
foreach (['@readonly', '@psalm-readonly', '@phpstan-readonly', '@psalm-readonly-allow-private-mutation'] as $tagName) {
foreach (['@readonly', '@psalm-readonly', '@phpstan-readonly', '@phpstan-readonly-allow-private-mutation', '@psalm-readonly-allow-private-mutation'] as $tagName) {
$tags = $phpDocNode->getTagsByName($tagName);

if (count($tags) > 0) {
Expand Down Expand Up @@ -505,4 +505,17 @@ private function shouldSkipType(string $tagName, Type $type): bool
return $this->unresolvableTypeHelper->containsUnresolvableType($type);
}

public function resolveAllowPrivateMutation(PhpDocNode $phpDocNode): bool
{
foreach (['@phpstan-readonly-allow-private-mutation', '@phpstan-allow-private-mutation', '@psalm-readonly-allow-private-mutation', '@psalm-allow-private-mutation'] as $tagName) {
$tags = $phpDocNode->getTagsByName($tagName);

if (count($tags) > 0) {
return true;
}
}

return false;
}

}
17 changes: 17 additions & 0 deletions src/PhpDoc/ResolvedPhpDocBlock.php
Expand Up @@ -100,6 +100,8 @@ class ResolvedPhpDocBlock

private ?bool $isImmutable = null;

private ?bool $isAllowedPrivateMutation = null;

private ?bool $hasConsistentConstructor = null;

private ?bool $acceptsNamedArguments = null;
Expand Down Expand Up @@ -162,6 +164,8 @@ public static function createEmpty(): self
$self->isFinal = false;
$self->isPure = null;
$self->isReadOnly = false;
$self->isImmutable = false;
$self->isAllowedPrivateMutation = false;
$self->hasConsistentConstructor = false;
$self->acceptsNamedArguments = true;

Expand Down Expand Up @@ -211,6 +215,8 @@ public function merge(array $parents, array $parentPhpDocBlocks): self
$result->isFinal = $this->isFinal();
$result->isPure = $this->isPure();
$result->isReadOnly = $this->isReadOnly();
$result->isImmutable = $this->isImmutable();
$result->isAllowedPrivateMutation = $this->isAllowedPrivateMutation();
$result->hasConsistentConstructor = $this->hasConsistentConstructor();
$result->acceptsNamedArguments = $acceptsNamedArguments;

Expand Down Expand Up @@ -593,6 +599,17 @@ public function isImmutable(): bool
return $this->isImmutable;
}

public function isAllowedPrivateMutation(): bool
{
if ($this->isAllowedPrivateMutation === null) {
$this->isAllowedPrivateMutation = $this->phpDocNodeResolver->resolveAllowPrivateMutation(
$this->phpDocNode,
);
}

return $this->isAllowedPrivateMutation;
}

/**
* @param array<string|int, VarTag> $varTags
* @param array<int, self> $parents
Expand Down
5 changes: 4 additions & 1 deletion src/Reflection/Php/PhpClassReflectionExtension.php
Expand Up @@ -214,14 +214,15 @@ private function createProperty(
$types[] = $value;
}

return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, false, false, false);
return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, false, false, false, false);
}
}

$deprecatedDescription = null;
$isDeprecated = false;
$isInternal = false;
$isReadOnlyByPhpDoc = $classReflection->isImmutable();
$isAllowedPrivateMutation = false;

if (
$includingAnnotations
Expand Down Expand Up @@ -297,6 +298,7 @@ private function createProperty(
$isDeprecated = $resolvedPhpDoc->isDeprecated();
$isInternal = $resolvedPhpDoc->isInternal();
$isReadOnlyByPhpDoc = $isReadOnlyByPhpDoc || $resolvedPhpDoc->isReadOnly();
$isAllowedPrivateMutation = $resolvedPhpDoc->isAllowedPrivateMutation();
}

if ($phpDocType === null) {
Expand Down Expand Up @@ -361,6 +363,7 @@ private function createProperty(
$isDeprecated,
$isInternal,
$isReadOnlyByPhpDoc,
$isAllowedPrivateMutation,
);
}

Expand Down
6 changes: 6 additions & 0 deletions src/Reflection/Php/PhpPropertyReflection.php
Expand Up @@ -31,6 +31,7 @@ public function __construct(
private bool $isDeprecated,
private bool $isInternal,
private bool $isReadOnlyByPhpDoc,
private bool $isAllowedPrivateMutation,
)
{
}
Expand Down Expand Up @@ -169,6 +170,11 @@ public function isInternal(): TrinaryLogic
return TrinaryLogic::createFromBoolean($this->isInternal);
}

public function isAllowedPrivateMutation(): bool
{
return $this->isAllowedPrivateMutation;
}

public function getNativeReflection(): ReflectionProperty
{
return $this->reflection;
Expand Down
4 changes: 4 additions & 0 deletions src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php
Expand Up @@ -83,6 +83,10 @@ public function processNode(Node $node, Scope $scope): array
continue;
}

if ($nativeReflection->isAllowedPrivateMutation()) {
continue;
}

$errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is assigned outside of the constructor.', $declaringClass->getDisplayName(), $propertyReflection->getName()))->build();
}

Expand Down
Expand Up @@ -32,10 +32,6 @@ public function testRule(): void
'@readonly property ReadonlyPropertyAssignPhpDoc\Foo::$foo is assigned outside of the constructor.',
40,
],
[
'@readonly property ReadonlyPropertyAssignPhpDoc\Foo::$psalm is assigned outside of the constructor.',
41,
],
[
'@readonly property ReadonlyPropertyAssignPhpDoc\Foo::$bar is assigned outside of its declaring class.',
53,
Expand Down
Expand Up @@ -38,7 +38,7 @@ public function __construct(int $foo)
public function setFoo(int $foo): void
{
$this->foo = $foo; // setter - report
$this->psalm = $foo; // setter - report, but Psalm allowed private mutation
$this->psalm = $foo; // do not report -allowed private mutation
}

}
Expand Down

0 comments on commit ea87e95

Please sign in to comment.