Skip to content

Commit d64ecd2

Browse files
authored
[rector] add PreferDirectIsNameRule (#245)
1 parent ea0d808 commit d64ecd2

File tree

7 files changed

+172
-0
lines changed

7 files changed

+172
-0
lines changed

config/rector-rules.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ rules:
55
- Symplify\PHPStanRules\Rules\Rector\NoLeadingBackslashInNameRule
66
- Symplify\PHPStanRules\Rules\Rector\NoClassReflectionStaticReflectionRule
77
- Symplify\PHPStanRules\Rules\Rector\NoPropertyNodeAssignRule
8+
- Symplify\PHPStanRules\Rules\Rector\PreferDirectIsNameRule
89

910
services:
1011
# $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
@@ -15,4 +15,6 @@ final class RectorRuleIdentifier
1515
public const NO_CLASS_REFLECTION_STATIC_REFLECTION = 'rector.noClassReflectionStaticReflection';
1616

1717
public const NO_PROPERTY_NODE_ASSIGN = 'rector.noPropertyNodeAssign';
18+
19+
public const PREFER_DIRECT_IS_NAME = 'rector.preferDirectIsName';
1820
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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\MethodCall;
9+
use PhpParser\Node\Expr\PropertyFetch;
10+
use PhpParser\Node\Identifier;
11+
use PHPStan\Analyser\Scope;
12+
use PHPStan\Rules\Rule;
13+
use PHPStan\Rules\RuleErrorBuilder;
14+
use Rector\Rector\AbstractRector;
15+
use Symplify\PHPStanRules\Enum\RuleIdentifier\RectorRuleIdentifier;
16+
17+
/**
18+
* @implements Rule<MethodCall>
19+
*
20+
* @đee \Symplify\PHPStanRules\Tests\Rules\Rector\PreferDirectIsNameRule\PreferDirectIsNameRuleTest
21+
*/
22+
final class PreferDirectIsNameRule implements Rule
23+
{
24+
/**
25+
* @var string
26+
*/
27+
public const ERROR_MESSAGE = 'Use direct $this->isName() instead of fetching NodeNameResolver service';
28+
29+
public function getNodeType(): string
30+
{
31+
return MethodCall::class;
32+
}
33+
34+
/**
35+
* @param MethodCall $node
36+
*/
37+
public function processNode(Node $node, Scope $scope): array
38+
{
39+
if ($node->isFirstClassCallable()) {
40+
return [];
41+
}
42+
43+
if (! $node->name instanceof Identifier) {
44+
return [];
45+
}
46+
47+
if (! in_array($node->name, ['isName', 'isNames', 'getName'])) {
48+
return [];
49+
}
50+
51+
if ($this->shouldSkipClassReflection($scope)) {
52+
return [];
53+
}
54+
55+
if (! $node->var instanceof PropertyFetch) {
56+
return [];
57+
}
58+
59+
$identifierRuleError = RuleErrorBuilder::message(self::ERROR_MESSAGE)
60+
->identifier(RectorRuleIdentifier::PREFER_DIRECT_IS_NAME)
61+
->build();
62+
63+
return [$identifierRuleError];
64+
}
65+
66+
private function shouldSkipClassReflection(Scope $scope): bool
67+
{
68+
if (! $scope->isInClass()) {
69+
return true;
70+
}
71+
72+
$classReflection = $scope->getClassReflection();
73+
74+
// skip self
75+
if ($classReflection->getName() === AbstractRector::class) {
76+
return true;
77+
}
78+
79+
// check rector rules only
80+
if (! $classReflection->is(AbstractRector::class)) {
81+
return true;
82+
}
83+
84+
// check child Rectors only
85+
return $classReflection->isAbstract();
86+
}
87+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Rector\PreferDirectIsNameRule\Fixture;
6+
7+
use PhpParser\Node;
8+
use Rector\Rector\AbstractRector;
9+
10+
final class NonDirectIsName extends AbstractRector
11+
{
12+
public function getNodeTypes(): array
13+
{
14+
return [];
15+
}
16+
17+
public function refactor(Node $node)
18+
{
19+
$isName = $this->nodeNameResolver->isName($node, 'test');
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Rector\PreferDirectIsNameRule\Fixture;
6+
7+
use PhpParser\Node;
8+
use Rector\Rector\AbstractRector;
9+
10+
final class SkipDirectIsName extends AbstractRector
11+
{
12+
public function getNodeTypes(): array
13+
{
14+
return [];
15+
}
16+
17+
public function refactor(Node $node)
18+
{
19+
$isName = $this->isName($node, 'test');
20+
}
21+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Rector\PreferDirectIsNameRule;
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\PreferDirectIsNameRule;
12+
13+
final class PreferDirectIsNameRuleTest 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/SkipDirectIsName.php', []];
24+
25+
yield [__DIR__ . '/Fixture/NonDirectIsName.php', [
26+
[
27+
PreferDirectIsNameRule::ERROR_MESSAGE,
28+
19,
29+
],
30+
]];
31+
}
32+
33+
protected function getRule(): Rule
34+
{
35+
return new PreferDirectIsNameRule();
36+
}
37+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
rules:
2+
- Symplify\PHPStanRules\Rules\Rector\PreferDirectIsNameRule
3+

0 commit comments

Comments
 (0)