Skip to content

Commit b8ad970

Browse files
authored
[Complexity] Skip more specific type via docblock @var on NoJustPropertyAssignRule (#179)
* [Complexity] Skip more specific type via docblock @var on NoJustPropertyAssignRule * cs fix * phpstan fix * final touch: add fixture for equal type * final touch: instanceof check
1 parent c7ed282 commit b8ad970

File tree

5 files changed

+127
-6
lines changed

5 files changed

+127
-6
lines changed

src/Rules/Complexity/NoJustPropertyAssignRule.php

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,111 @@
22

33
namespace Symplify\PHPStanRules\Rules\Complexity;
44

5+
use PhpParser\Comment\Doc;
56
use PhpParser\Node;
7+
use PhpParser\Node\Expr;
68
use PhpParser\Node\Expr\Assign;
79
use PhpParser\Node\Expr\PropertyFetch;
810
use PhpParser\Node\Expr\Variable;
11+
use PhpParser\Node\Stmt\Expression;
912
use PHPStan\Analyser\Scope;
13+
use PHPStan\Reflection\ClassReflection;
1014
use PHPStan\Rules\Rule;
1115
use PHPStan\Rules\RuleError;
1216
use PHPStan\Rules\RuleErrorBuilder;
1317
use Symfony\Component\Form\AbstractType;
1418
use Symplify\PHPStanRules\Enum\RuleIdentifier;
19+
use Symplify\PHPStanRules\PhpDoc\PhpDocResolver;
1520

1621
/**
1722
* @see \Symplify\PHPStanRules\Tests\Rules\Complexity\NoJustPropertyAssignRule\NoJustPropertyAssignRuleTest
1823
*
19-
* @implements Rule<Assign>
24+
* @implements Rule<Expression>
2025
*/
21-
final class NoJustPropertyAssignRule implements Rule
26+
final readonly class NoJustPropertyAssignRule implements Rule
2227
{
2328
/**
2429
* @var string
2530
*/
2631
public const ERROR_MESSAGE = 'Instead of assigning service property to a variable, use the property directly';
2732

33+
public function __construct(
34+
private PhpDocResolver $phpDocResolver
35+
) {
36+
37+
}
38+
2839
public function getNodeType(): string
2940
{
30-
return Assign::class;
41+
return Expression::class;
3142
}
3243

3344
/**
34-
* @param Assign $node
45+
* @param Expression $node
3546
* @return RuleError[]
3647
*/
3748
public function processNode(Node $node, Scope $scope): array
3849
{
39-
if (! $this->isLocalPropertyFetchAssignToVariable($node, $scope)) {
50+
$expr = $node->expr;
51+
52+
if (! $expr instanceof Assign) {
53+
return [];
54+
}
55+
56+
if (! $this->isLocalPropertyFetchAssignToVariable($expr, $scope)) {
4057
return [];
4158
}
4259

4360
if ($this->shouldSkipCurrentClass($scope)) {
4461
return [];
4562
}
4663

64+
if ($this->shoulSkipMoreSpecificTypeByDocblock($scope, $node, $expr)) {
65+
return [];
66+
}
67+
4768
return [RuleErrorBuilder::message(self::ERROR_MESSAGE)
4869
->identifier(RuleIdentifier::NO_JUST_PROPERTY_ASSIGN)
4970
->build()];
5071
}
5172

73+
private function shoulSkipMoreSpecificTypeByDocblock(Scope $scope, Expression $expression, Assign $assign): bool
74+
{
75+
$docComment = $expression->getDocComment();
76+
if (! $docComment instanceof Doc) {
77+
return false;
78+
}
79+
80+
/** @var Variable $variable */
81+
$variable = $assign->var;
82+
$varName = $variable->name;
83+
84+
if ($varName instanceof Expr) {
85+
return false;
86+
}
87+
88+
$classReflection = $scope->getClassReflection();
89+
if (! $classReflection instanceof ClassReflection) {
90+
return false;
91+
}
92+
93+
$resolvedPhpDocBlock = $this->phpDocResolver->resolve($scope, $classReflection, $docComment);
94+
$exprType = $scope->getType($assign->expr);
95+
96+
foreach ($resolvedPhpDocBlock->getVarTags() as $key => $varTag) {
97+
if ($key !== $varName) {
98+
continue;
99+
}
100+
101+
// different type means more specific type on purpose
102+
if (! $varTag->getType()->equals($exprType)) {
103+
return true;
104+
}
105+
}
106+
107+
return false;
108+
}
109+
52110
private function isLocalPropertyFetchAssignToVariable(Assign $assign, Scope $scope): bool
53111
{
54112
if (! $assign->var instanceof Variable) {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Complexity\NoJustPropertyAssignRule\Fixture;
6+
7+
use ArrayIterator;
8+
use Iterator;
9+
10+
final class SkipSpecificVarDoc
11+
{
12+
public Iterator $it;
13+
14+
public function __construct()
15+
{
16+
$this->it = new ArrayIterator([]);
17+
}
18+
19+
public function run()
20+
{
21+
/**
22+
* @var ArrayIterator $it
23+
*/
24+
$it = $this->it;
25+
}
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Complexity\NoJustPropertyAssignRule\Fixture;
6+
7+
use Iterator;
8+
9+
final class WithEqualTypeVarDoc
10+
{
11+
public Iterator $it;
12+
13+
public function run()
14+
{
15+
/**
16+
* @var Iterator $it
17+
*/
18+
$it = $this->it;
19+
}
20+
}

tests/Rules/Complexity/NoJustPropertyAssignRule/NoJustPropertyAssignRuleTest.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,24 @@ public static function provideData(): Iterator
2626
yield [__DIR__ . '/Fixture/ServiceAssign.php', [
2727
[NoJustPropertyAssignRule::ERROR_MESSAGE, 20],
2828
]];
29+
yield [__DIR__ . '/Fixture/WithEqualTypeVarDoc.php', [
30+
[NoJustPropertyAssignRule::ERROR_MESSAGE, 18],
31+
]];
2932

3033
yield [__DIR__ . '/Fixture/SkipScalarAssign.php', []];
34+
yield [__DIR__ . '/Fixture/SkipSpecificVarDoc.php', []];
35+
}
36+
37+
/**
38+
* @return string[]
39+
*/
40+
public static function getAdditionalConfigFiles(): array
41+
{
42+
return [__DIR__ . '/config/configured_rule.neon'];
3143
}
3244

3345
protected function getRule(): Rule
3446
{
35-
return new NoJustPropertyAssignRule();
47+
return self::getContainer()->getByType(NoJustPropertyAssignRule::class);
3648
}
3749
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
includes:
2+
- ../../../../config/included_services.neon
3+
4+
rules:
5+
- Symplify\PHPStanRules\Rules\Complexity\NoJustPropertyAssignRule

0 commit comments

Comments
 (0)