Skip to content

Commit

Permalink
Implement property name as an expression in TypesAssignedToProperties…
Browse files Browse the repository at this point in the history
…Rule
  • Loading branch information
ondrejmirtes committed Oct 18, 2020
1 parent 36ba006 commit fd66714
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 4 deletions.
9 changes: 9 additions & 0 deletions src/Rules/Properties/FoundPropertyReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ class FoundPropertyReflection implements PropertyReflection

private PropertyReflection $originalPropertyReflection;

private string $propertyName;

private Type $readableType;

private Type $writableType;

public function __construct(
PropertyReflection $originalPropertyReflection,
string $propertyName,
Type $readableType,
Type $writableType
)
{
$this->originalPropertyReflection = $originalPropertyReflection;
$this->propertyName = $propertyName;
$this->readableType = $readableType;
$this->writableType = $writableType;
}
Expand All @@ -34,6 +38,11 @@ public function getDeclaringClass(): ClassReflection
return $this->originalPropertyReflection->getDeclaringClass();
}

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

public function isStatic(): bool
{
return $this->originalPropertyReflection->isStatic();
Expand Down
9 changes: 9 additions & 0 deletions src/Rules/Properties/PropertyDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
class PropertyDescriptor
{

public function describePropertyByName(PropertyReflection $property, string $propertyName): string
{
if (!$property->isStatic()) {
return sprintf('Property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName);
}

return sprintf('Static property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName);
}

/**
* @param \PHPStan\Reflection\PropertyReflection $property
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch
Expand Down
64 changes: 64 additions & 0 deletions src/Rules/Properties/PropertyReflectionFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,79 @@

namespace PHPStan\Rules\Properties;

use PhpParser\Node\VarLikeIdentifier;
use PHPStan\Analyser\Scope;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StaticType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\TypeUtils;

class PropertyReflectionFinder
{

/**
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch
* @param \PHPStan\Analyser\Scope $scope
* @return FoundPropertyReflection[]
*/
public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): array
{
if ($propertyFetch instanceof \PhpParser\Node\Expr\PropertyFetch) {
if ($propertyFetch->name instanceof \PhpParser\Node\Identifier) {
$names = [$propertyFetch->name->name];
} else {
$names = array_map(static function (ConstantStringType $name): string {
return $name->getValue();
}, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name)));
}

$reflections = [];
$propertyHolderType = $scope->getType($propertyFetch->var);
$fetchedOnThis = $propertyHolderType instanceof ThisType && $scope->isInClass();
foreach ($names as $name) {
$reflection = $this->findPropertyReflection($propertyHolderType, $name, $scope, $fetchedOnThis);
if ($reflection === null) {
continue;
}

$reflections[] = $reflection;
}

return $reflections;
}

if ($propertyFetch->class instanceof \PhpParser\Node\Name) {
$propertyHolderType = new ObjectType($scope->resolveName($propertyFetch->class));
} else {
$propertyHolderType = $scope->getType($propertyFetch->class);
}

$fetchedOnThis = $propertyHolderType instanceof ThisType && $scope->isInClass();

if ($propertyFetch->name instanceof VarLikeIdentifier) {
$names = [$propertyFetch->name->name];
} else {
$names = array_map(static function (ConstantStringType $name): string {
return $name->getValue();
}, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name)));
}

$reflections = [];
foreach ($names as $name) {
$reflection = $this->findPropertyReflection($propertyHolderType, $name, $scope, $fetchedOnThis);
if ($reflection === null) {
continue;
}

$reflections[] = $reflection;
}

return $reflections;
}

/**
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch
* @param \PHPStan\Analyser\Scope $scope
Expand Down Expand Up @@ -68,6 +131,7 @@ private function findPropertyReflection(Type $propertyHolderType, string $proper

return new FoundPropertyReflection(
$originalProperty,
$propertyName,
$readableType,
$writableType
);
Expand Down
30 changes: 26 additions & 4 deletions src/Rules/Properties/TypesAssignedToPropertiesRule.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\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\VerbosityLevel;
Expand Down Expand Up @@ -54,11 +55,32 @@ public function processNode(Node $node, Scope $scope): array

/** @var \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch */
$propertyFetch = $node->var;
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $scope);
if ($propertyReflection === null) {
return [];
$propertyReflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($propertyFetch, $scope);

$errors = [];
foreach ($propertyReflections as $propertyReflection) {
$errors = array_merge($errors, $this->processSingleProperty(
$scope,
$propertyReflection,
$node
));
}

return $errors;
}

/**
* @param Scope $scope
* @param FoundPropertyReflection $propertyReflection
* @param Node\Expr $node
* @return RuleError[]
*/
private function processSingleProperty(
Scope $scope,
FoundPropertyReflection $propertyReflection,
Node\Expr $node
): array
{
$propertyType = $propertyReflection->getWritableType();

if ($node instanceof Node\Expr\Assign) {
Expand All @@ -67,7 +89,7 @@ public function processNode(Node $node, Scope $scope): array
$assignedValueType = $scope->getType($node);
}
if (!$this->ruleLevelHelper->accepts($propertyType, $assignedValueType, $scope->isDeclareStrictTypes())) {
$propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $propertyFetch);
$propertyDescription = $this->propertyDescriptor->describePropertyByName($propertyReflection, $propertyReflection->getName());
$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType);

return [
Expand Down

0 comments on commit fd66714

Please sign in to comment.