Skip to content

Commit

Permalink
[Strict] Handle may be unitialized property on DisallowedEmptyRuleFix…
Browse files Browse the repository at this point in the history
…erRector (#5409)

* [Strict] Handle may be unitialized property on DisallowedEmptyRuleFixerRector

* fixed 🎉
  • Loading branch information
samsonasik committed Jan 1, 2024
1 parent a64a344 commit 9f93708
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 3 deletions.
@@ -0,0 +1,41 @@
<?php

namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;

final class MayUnitializedPropertyNoDefaultValue
{
public array $items;

public function isEmpty()
{
return empty($this->items);
}

public function isNotEmpty()
{
return ! empty($this->items);
}
}

?>
-----
<?php

namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;

final class MayUnitializedPropertyNoDefaultValue
{
public array $items;

public function isEmpty()
{
return !isset($this->items) || $this->items === [];
}

public function isNotEmpty()
{
return isset($this->items) && $this->items !== [];
}
}

?>
@@ -0,0 +1,51 @@
<?php

namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;

final class PropertyWithAssignInConstructor
{
public array $items;

public function __construct()
{
$this->items = [];
}

public function isEmpty()
{
return empty($this->items);
}

public function isNotEmpty()
{
return ! empty($this->items);
}
}

?>
-----
<?php

namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;

final class PropertyWithAssignInConstructor
{
public array $items;

public function __construct()
{
$this->items = [];
}

public function isEmpty()
{
return $this->items === [];
}

public function isNotEmpty()
{
return $this->items !== [];
}
}

?>
@@ -0,0 +1,41 @@
<?php

namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;

final class PropertyWithDefaultValue
{
public array $items = [];

public function isEmpty()
{
return empty($this->items);
}

public function isNotEmpty()
{
return ! empty($this->items);
}
}

?>
-----
<?php

namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;

final class PropertyWithDefaultValue
{
public array $items = [];

public function isEmpty()
{
return $this->items === [];
}

public function isNotEmpty()
{
return $this->items !== [];
}
}

?>
69 changes: 69 additions & 0 deletions rules/Strict/NodeAnalyzer/UnitializedPropertyAnalyzer.php
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Rector\Strict\NodeAnalyzer;

use PhpParser\Node\Expr;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\ThisType;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\PhpParser\AstResolver;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector;

final class UnitializedPropertyAnalyzer
{
public function __construct(
private readonly AstResolver $astResolver,
private readonly NodeTypeResolver $nodeTypeResolver,
private readonly ConstructorAssignDetector $constructorAssignDetector,
private readonly NodeNameResolver $nodeNameResolver
) {
}

public function isUnitialized(Expr $expr): bool
{
if (! $expr instanceof PropertyFetch && ! $expr instanceof StaticPropertyFetch) {
return false;
}

$varType = $this->nodeTypeResolver->getType($expr->var);

if ($varType instanceof ThisType) {
$varType = $varType->getStaticObjectType();
}

if (! $varType instanceof TypeWithClassName) {
return false;
}

$className = $varType->getClassName();
$classLike = $this->astResolver->resolveClassFromName($className);

if (! $classLike instanceof ClassLike) {
return false;
}

$propertyName = (string) $this->nodeNameResolver->getName($expr);
$property = $classLike->getProperty($propertyName);

if (! $property instanceof Property) {
return false;
}

if (count($property->props) !== 1) {
return false;
}

if ($property->props[0]->default instanceof Expr) {
return false;
}

return ! $this->constructorAssignDetector->isPropertyAssigned($classLike, $propertyName);
}
}
21 changes: 18 additions & 3 deletions rules/Strict/Rector/Empty_/DisallowedEmptyRuleFixerRector.php
Expand Up @@ -8,12 +8,14 @@
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\BooleanNot;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\Isset_;
use PHPStan\Analyser\Scope;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\NodeAnalyzer\ExprAnalyzer;
use Rector\Strict\NodeAnalyzer\UnitializedPropertyAnalyzer;
use Rector\Strict\NodeFactory\ExactCompareFactory;
use Rector\Strict\Rector\AbstractFalsyScalarRuleFixerRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
Expand All @@ -26,7 +28,8 @@ final class DisallowedEmptyRuleFixerRector extends AbstractFalsyScalarRuleFixerR
{
public function __construct(
private readonly ExactCompareFactory $exactCompareFactory,
private readonly ExprAnalyzer $exprAnalyzer
private readonly ExprAnalyzer $exprAnalyzer,
private readonly UnitializedPropertyAnalyzer $unitializedPropertyAnalyzer
) {
}

Expand Down Expand Up @@ -106,11 +109,17 @@ private function refactorBooleanNot(BooleanNot $booleanNot, Scope $scope): Expr|

$emptyExprType = $scope->getNativeType($empty->expr);

return $this->exactCompareFactory->createNotIdenticalFalsyCompare(
$result = $this->exactCompareFactory->createNotIdenticalFalsyCompare(
$emptyExprType,
$empty->expr,
$this->treatAsNonEmpty
);

if ($this->unitializedPropertyAnalyzer->isUnitialized($empty->expr)) {
return new BooleanAnd(new Isset_([$empty->expr]), $result);
}

return $result;
}

private function refactorEmpty(Empty_ $empty, Scope $scope, bool $treatAsNonEmpty): Expr|null
Expand All @@ -120,7 +129,13 @@ private function refactorEmpty(Empty_ $empty, Scope $scope, bool $treatAsNonEmpt
}

$exprType = $scope->getNativeType($empty->expr);
return $this->exactCompareFactory->createIdenticalFalsyCompare($exprType, $empty->expr, $treatAsNonEmpty);
$result = $this->exactCompareFactory->createIdenticalFalsyCompare($exprType, $empty->expr, $treatAsNonEmpty);

if ($this->unitializedPropertyAnalyzer->isUnitialized($empty->expr)) {
return new BooleanOr(new BooleanNot(new Isset_([$empty->expr])), $result);
}

return $result;
}

private function createDimFetchBooleanAnd(ArrayDimFetch $arrayDimFetch): ?BooleanAnd
Expand Down

0 comments on commit 9f93708

Please sign in to comment.