Skip to content

Commit 9e284ca

Browse files
committed
[rector] add PreferDirectIsNameRule
1 parent ea0d808 commit 9e284ca

File tree

7 files changed

+170
-1
lines changed

7 files changed

+170
-1
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
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
16+
/**
17+
* @implements Rule<MethodCall>
18+
*
19+
* @đee \Symplify\PHPStanRules\Tests\Rules\Rector\PreferDirectIsNameRule\PreferDirectIsNameRuleTest
20+
*/
21+
final class PreferDirectIsNameRule implements Rule
22+
{
23+
/**
24+
* @var string
25+
*/
26+
public const ERROR_MESSAGE = 'Use direct $this->isName() instead of fetching NodeNameResolver service';
27+
28+
public function getNodeType(): string
29+
{
30+
return MethodCall::class;
31+
}
32+
33+
/**
34+
* @param MethodCall $node
35+
*/
36+
public function processNode(Node $node, Scope $scope): array
37+
{
38+
if ($node->isFirstClassCallable()) {
39+
return [];
40+
}
41+
42+
if (! $scope->isInClass()) {
43+
return [];
44+
}
45+
46+
if (! $node->name instanceof Identifier) {
47+
return [];
48+
}
49+
50+
if (! in_array($node->name, ['isName', 'isNames', 'getName'])) {
51+
return [];
52+
}
53+
54+
if ($this->shouldSkipClassReflection($scope)) {
55+
return [];
56+
}
57+
58+
if (! $node->var instanceof PropertyFetch) {
59+
return [];
60+
}
61+
62+
$identifierRuleError = RuleErrorBuilder::message(self::ERROR_MESSAGE)
63+
->identifier('rector.preferDirectIsName')
64+
->build();
65+
66+
return [$identifierRuleError];
67+
}
68+
69+
private function shouldSkipClassReflection(Scope $scope): bool
70+
{
71+
$classReflection = $scope->getClassReflection();
72+
73+
// skip self
74+
if ($classReflection->getName() === AbstractRector::class) {
75+
return true;
76+
}
77+
78+
// check rector rules only
79+
if (! $classReflection->is(AbstractRector::class)) {
80+
return true;
81+
}
82+
83+
// check child Rectors only
84+
return $classReflection->isAbstract();
85+
}
86+
}

tests/Rules/Rector/PhpUpgradeImplementsMinPhpVersionInterfaceRule/PhpUpgradeImplementsMinPhpVersionInterfaceRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public static function provideData(): Iterator
2626
[
2727
sprintf(
2828
PhpUpgradeImplementsMinPhpVersionInterfaceRule::ERROR_MESSAGE,
29-
'Rector\Php80\Rector\Class_\SomePhpFeatureRector',
29+
'Rector\Php80\Rector\Class_\NonDirectIsName',
3030
),
3131
7,
3232
],
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)