Skip to content

Commit ea0d808

Browse files
authored
[rector] add rule to encourage node passing over assignig in rector rules (#244)
1 parent 103574d commit ea0d808

File tree

7 files changed

+159
-0
lines changed

7 files changed

+159
-0
lines changed

config/rector-rules.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ rules:
44
- Symplify\PHPStanRules\Rules\Rector\NoInstanceOfStaticReflectionRule
55
- Symplify\PHPStanRules\Rules\Rector\NoLeadingBackslashInNameRule
66
- Symplify\PHPStanRules\Rules\Rector\NoClassReflectionStaticReflectionRule
7+
- Symplify\PHPStanRules\Rules\Rector\NoPropertyNodeAssignRule
78

89
services:
910
# $node->getAttribute($1) => Type|null by $1

src/Enum/RuleIdentifier/RectorRuleIdentifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ final class RectorRuleIdentifier
1313
public const PHP_RULE_IMPLEMENTS_MIN_VERSION = 'rector.phpRuleImplementsMinVersion';
1414

1515
public const NO_CLASS_REFLECTION_STATIC_REFLECTION = 'rector.noClassReflectionStaticReflection';
16+
17+
public const NO_PROPERTY_NODE_ASSIGN = 'rector.noPropertyNodeAssign';
1618
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Rules\Rector;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\Assign;
9+
use PhpParser\Node\Expr\PropertyFetch;
10+
use PhpParser\Node\Expr\Variable;
11+
use PHPStan\Analyser\Scope;
12+
use PHPStan\Rules\Rule;
13+
use PHPStan\Rules\RuleErrorBuilder;
14+
use PHPStan\Type\ObjectType;
15+
use Rector\Contract\Rector\RectorInterface;
16+
use Symplify\PHPStanRules\Enum\RuleIdentifier\RectorRuleIdentifier;
17+
18+
/**
19+
* @see \Symplify\PHPStanRules\Tests\Rules\Rector\NoPropertyNodeAssignRule\NoPropertyNodeAssignRuleTest
20+
*
21+
* @implements Rule<Assign>
22+
*/
23+
final class NoPropertyNodeAssignRule implements Rule
24+
{
25+
/**
26+
* @var string
27+
*/
28+
public const ERROR_MESSAGE = 'Avoid assigning a node to property to avoid object juggling, pass it as argument instead';
29+
30+
public function getNodeType(): string
31+
{
32+
return Assign::class;
33+
}
34+
35+
/**
36+
* @param Assign $node
37+
*/
38+
public function processNode(Node $node, Scope $scope): array
39+
{
40+
if (! $scope->isInClass()) {
41+
return [];
42+
}
43+
44+
$classReflection = $scope->getClassReflection();
45+
if (! $classReflection->isSubclassOf(RectorInterface::class)) {
46+
return [];
47+
}
48+
49+
if (! $node->var instanceof PropertyFetch) {
50+
return [];
51+
}
52+
53+
$propertyFetch = $node->var;
54+
if (! $propertyFetch->var instanceof Variable) {
55+
return [];
56+
}
57+
58+
if (! is_string($propertyFetch->var->name)) {
59+
return [];
60+
}
61+
62+
if ($propertyFetch->var->name !== 'this') {
63+
return [];
64+
}
65+
66+
$assignedExprType = $scope->getType($node->expr);
67+
if (! $assignedExprType instanceof ObjectType) {
68+
return [];
69+
}
70+
71+
if (! $assignedExprType->isInstanceOf(Node::class)->yes()) {
72+
return [];
73+
}
74+
75+
$identifierRuleError = RuleErrorBuilder::message(self::ERROR_MESSAGE)
76+
->identifier(RectorRuleIdentifier::NO_PROPERTY_NODE_ASSIGN)
77+
->build();
78+
79+
return [$identifierRuleError];
80+
}
81+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Rector\NoPropertyNodeAssignRule\Fixture;
6+
7+
use PhpParser\Node;
8+
9+
final class SkipNoRectorAssign
10+
{
11+
private ?\PhpParser\Node $localNode = null;
12+
13+
public function refactor(Node $node)
14+
{
15+
$this->localNode = $node;
16+
}
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Rector\NoPropertyNodeAssignRule\Fixture;
6+
7+
use PhpParser\Node;
8+
use Rector\Rector\AbstractRector;
9+
10+
final class SomeNodePropertyAssign extends AbstractRector
11+
{
12+
private ?\PhpParser\Node $localNode = null;
13+
14+
public function getNodeTypes(): array
15+
{
16+
}
17+
18+
public function refactor(Node $node)
19+
{
20+
$this->localNode = $node;
21+
}
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Rector\NoPropertyNodeAssignRule;
6+
7+
use Iterator;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use Symplify\PHPStanRules\Rules\Rector\NoPropertyNodeAssignRule;
12+
13+
final class NoPropertyNodeAssignRuleTest extends RuleTestCase
14+
{
15+
#[DataProvider('provideData')]
16+
public function testRule(string $filePath, array $expectedErrorsWithLines): void
17+
{
18+
$this->analyse([$filePath], $expectedErrorsWithLines);
19+
}
20+
21+
public static function provideData(): Iterator
22+
{
23+
yield [__DIR__ . '/Fixture/SomeNodePropertyAssign.php', [
24+
[NoPropertyNodeAssignRule::ERROR_MESSAGE, 20],
25+
]];
26+
27+
yield [__DIR__ . '/Fixture/SkipNoRectorAssign.php', []];
28+
}
29+
30+
protected function getRule(): Rule
31+
{
32+
return new NoPropertyNodeAssignRule();
33+
}
34+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rules:
2+
- Symplify\PHPStanRules\Rules\Rector\NoPropertyNodeAssignRule

0 commit comments

Comments
 (0)