Skip to content

Commit

Permalink
Refactor property rules using virtual ClassPropertyNode
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Oct 28, 2020
1 parent 2d564e6 commit 84f1b65
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 84 deletions.
13 changes: 13 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
use PHPStan\Node\ClassConstantsNode;
use PHPStan\Node\ClassMethodsNode;
use PHPStan\Node\ClassPropertiesNode;
use PHPStan\Node\ClassPropertyNode;
use PHPStan\Node\ClassStatementsGatherer;
use PHPStan\Node\ClosureReturnStatementsNode;
use PHPStan\Node\ExecutionEndNode;
Expand Down Expand Up @@ -586,6 +587,18 @@ private function processStmtNode(
$hasYield = false;
foreach ($stmt->props as $prop) {
$this->processStmtNode($prop, $scope, $nodeCallback);
$docComment = $stmt->getDocComment();
$nodeCallback(
new ClassPropertyNode(
$prop->name->toString(),
$stmt->flags,
$stmt->type,
$prop->default,
$docComment !== null ? $docComment->getText() : null,
$prop
),
$scope
);
}

if ($stmt->type !== null) {
Expand Down
19 changes: 8 additions & 11 deletions src/Node/ClassPropertiesNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Property;
use PhpParser\NodeAbstract;
use PHPStan\Analyser\Scope;
use PHPStan\Node\Method\MethodCall;
Expand All @@ -24,7 +23,7 @@ class ClassPropertiesNode extends NodeAbstract implements VirtualNode

private ClassLike $class;

/** @var Property[] */
/** @var ClassPropertyNode[] */
private array $properties;

/** @var array<int, PropertyRead|PropertyWrite> */
Expand All @@ -35,7 +34,7 @@ class ClassPropertiesNode extends NodeAbstract implements VirtualNode

/**
* @param ClassLike $class
* @param Property[] $properties
* @param ClassPropertyNode[] $properties
* @param array<int, PropertyRead|PropertyWrite> $propertyUsages
* @param array<int, MethodCall> $methodCalls
*/
Expand All @@ -54,7 +53,7 @@ public function getClass(): ClassLike
}

/**
* @return Property[]
* @return ClassPropertyNode[]
*/
public function getProperties(): array
{
Expand Down Expand Up @@ -85,7 +84,7 @@ public function getSubNodeNames(): array
/**
* @param string[] $constructors
* @param ReadWritePropertiesExtension[] $extensions
* @return array{array<string, Property>, array<array{string, int}>}
* @return array{array<string, ClassPropertyNode>, array<array{string, int}>}
*/
public function getUninitializedProperties(
Scope $scope,
Expand All @@ -106,15 +105,13 @@ public function getUninitializedProperties(
if ($property->isStatic()) {
continue;
}
if ($property->type === null) {
if ($property->getNativeType() === null) {
continue;
}
foreach ($property->props as $prop) {
if ($prop->default !== null) {
continue;
}
$properties[$prop->name->toString()] = $property;
if ($property->getDefault() !== null) {
continue;
}
$properties[$property->getName()] = $property;
}

foreach (array_keys($properties) as $name) {
Expand Down
113 changes: 113 additions & 0 deletions src/Node/ClassPropertyNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php declare(strict_types = 1);

namespace PHPStan\Node;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\UnionType;
use PhpParser\NodeAbstract;

class ClassPropertyNode extends NodeAbstract implements VirtualNode
{

private string $name;

private int $flags;

/** @var Identifier|Name|NullableType|UnionType|null */
private $type;

private ?Expr $default;

private ?string $phpDoc;

/**
* @param int $flags
* @param Identifier|Name|NullableType|UnionType|null $type
* @param string $name
* @param Expr|null $default
*/
public function __construct(
string $name,
int $flags,
$type,
?Expr $default,
?string $phpDoc,
Node $originalNode
)
{
parent::__construct($originalNode->getAttributes());
$this->name = $name;
$this->flags = $flags;
$this->type = $type;
$this->default = $default;
$this->phpDoc = $phpDoc;
}

public function getName(): string
{
return $this->name;
}

public function getFlags(): int
{
return $this->flags;
}

public function getDefault(): ?Expr
{
return $this->default;
}

public function getPhpDoc(): ?string
{
return $this->phpDoc;
}

public function isPublic(): bool
{
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0;
}

public function isProtected(): bool
{
return (bool) ($this->flags & Class_::MODIFIER_PROTECTED);
}

public function isPrivate(): bool
{
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
}

public function isStatic(): bool
{
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
}

/**
* @return Identifier|Name|NullableType|UnionType|null
*/
public function getNativeType()
{
return $this->type;
}

public function getType(): string
{
return 'PHPStan_Node_ClassPropertyNode';
}

/**
* @return string[]
*/
public function getSubNodeNames(): array
{
return [];
}

}
6 changes: 3 additions & 3 deletions src/Node/ClassStatementsGatherer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ClassStatementsGatherer
/** @var callable(\PhpParser\Node $node, Scope $scope): void */
private $nodeCallback;

/** @var \PhpParser\Node\Stmt\Property[] */
/** @var ClassPropertyNode[] */
private array $properties = [];

/** @var \PhpParser\Node\Stmt\ClassMethod[] */
Expand Down Expand Up @@ -55,7 +55,7 @@ public function __construct(
}

/**
* @return \PhpParser\Node\Stmt\Property[]
* @return ClassPropertyNode[]
*/
public function getProperties(): array
{
Expand Down Expand Up @@ -117,7 +117,7 @@ private function gatherNodes(\PhpParser\Node $node, Scope $scope): void
if ($scope->getClassReflection()->getName() !== $this->classReflection->getName()) {
return;
}
if ($node instanceof \PhpParser\Node\Stmt\Property && !$scope->isInTrait()) {
if ($node instanceof ClassPropertyNode && !$scope->isInTrait()) {
$this->properties[] = $node;
return;
}
Expand Down
56 changes: 27 additions & 29 deletions src/Rules/DeadCode/UnusedPrivatePropertyRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ public function processNode(Node $node, Scope $scope): array

$alwaysRead = false;
$alwaysWritten = false;
if ($property->getDocComment() !== null) {
$text = $property->getDocComment()->getText();
if ($property->getPhpDoc() !== null) {
$text = $property->getPhpDoc();
foreach ($this->alwaysReadTags as $tag) {
if (strpos($text, $tag) === false) {
continue;
Expand All @@ -93,38 +93,36 @@ public function processNode(Node $node, Scope $scope): array
}
}

foreach ($property->props as $propertyProperty) {
$propertyName = $propertyProperty->name->toString();
if (!$alwaysRead || !$alwaysWritten) {
if (!$classReflection->hasNativeProperty($propertyName)) {
continue;
}

$propertyReflection = $classReflection->getNativeProperty($propertyName);
$propertyName = $property->getName();
if (!$alwaysRead || !$alwaysWritten) {
if (!$classReflection->hasNativeProperty($propertyName)) {
continue;
}

foreach ($this->extensionProvider->getExtensions() as $extension) {
if ($alwaysRead && $alwaysWritten) {
break;
}
if (!$alwaysRead && $extension->isAlwaysRead($propertyReflection, $propertyName)) {
$alwaysRead = true;
}
if ($alwaysWritten || !$extension->isAlwaysWritten($propertyReflection, $propertyName)) {
continue;
}
$propertyReflection = $classReflection->getNativeProperty($propertyName);

$alwaysWritten = true;
foreach ($this->extensionProvider->getExtensions() as $extension) {
if ($alwaysRead && $alwaysWritten) {
break;
}
if (!$alwaysRead && $extension->isAlwaysRead($propertyReflection, $propertyName)) {
$alwaysRead = true;
}
if ($alwaysWritten || !$extension->isAlwaysWritten($propertyReflection, $propertyName)) {
continue;
}
}

$read = $alwaysRead;
$written = $alwaysWritten || $propertyProperty->default !== null;
$properties[$propertyName] = [
'read' => $read,
'written' => $written,
'node' => $property,
];
$alwaysWritten = true;
}
}

$read = $alwaysRead;
$written = $alwaysWritten || $property->getDefault() !== null;
$properties[$propertyName] = [
'read' => $read,
'written' => $written,
'node' => $property,
];
}

foreach ($node->getPropertyUsages() as $usage) {
Expand Down
7 changes: 4 additions & 3 deletions src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\ClassPropertyNode;
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
Expand All @@ -12,7 +13,7 @@
use PHPStan\Type\VerbosityLevel;

/**
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\PropertyProperty>
* @implements \PHPStan\Rules\Rule<\PHPStan\Node\ClassPropertyNode>
*/
class IncompatiblePropertyPhpDocTypeRule implements Rule
{
Expand All @@ -26,7 +27,7 @@ public function __construct(GenericObjectTypeCheck $genericObjectTypeCheck)

public function getNodeType(): string
{
return Node\Stmt\PropertyProperty::class;
return ClassPropertyNode::class;
}

public function processNode(Node $node, Scope $scope): array
Expand All @@ -35,7 +36,7 @@ public function processNode(Node $node, Scope $scope): array
throw new \PHPStan\ShouldNotHappenException();
}

$propertyName = $node->name->toString();
$propertyName = $node->getName();
$propertyReflection = $scope->getClassReflection()->getNativeProperty($propertyName);

if (!$propertyReflection->hasPhpDoc()) {
Expand Down

0 comments on commit 84f1b65

Please sign in to comment.