Skip to content

Commit a48d567

Browse files
authored
skip non-closure sets in NoServiceSameNameSetClassRule (#216)
1 parent 6de587c commit a48d567

File tree

8 files changed

+127
-32
lines changed

8 files changed

+127
-32
lines changed

src/Helper/NamingHelper.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@
55
namespace Symplify\PHPStanRules\Helper;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\Expr\Variable;
89
use PhpParser\Node\Identifier;
910
use PhpParser\Node\Name;
1011

1112
final class NamingHelper
1213
{
1314
public static function getName(Node $node): ?string
1415
{
16+
if ($node instanceof Variable && is_string($node->name)) {
17+
return $node->name;
18+
}
19+
1520
if ($node instanceof Identifier || $node instanceof Name) {
1621
return $node->toString();
1722
}
@@ -21,11 +26,7 @@ public static function getName(Node $node): ?string
2126

2227
public static function isName(Node $node, string $name): bool
2328
{
24-
if ($node instanceof Identifier || $node instanceof Name) {
25-
return $node->toString() === $name;
26-
}
27-
28-
return false;
29+
return self::getName($node) === $name;
2930
}
3031

3132
/**

src/Rules/Symfony/ConfigClosure/NoServiceSameNameSetClassRule.php

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,82 +7,125 @@
77
use Nette\Utils\Strings;
88
use PhpParser\Node;
99
use PhpParser\Node\Expr\ClassConstFetch;
10+
use PhpParser\Node\Expr\Closure;
1011
use PhpParser\Node\Expr\MethodCall;
12+
use PhpParser\NodeFinder;
1113
use PHPStan\Analyser\Scope;
1214
use PHPStan\Rules\Rule;
1315
use PHPStan\Rules\RuleError;
1416
use PHPStan\Rules\RuleErrorBuilder;
1517
use Symplify\PHPStanRules\Enum\RuleIdentifier\SymfonyRuleIdentifier;
1618
use Symplify\PHPStanRules\Helper\NamingHelper;
19+
use Symplify\PHPStanRules\Symfony\NodeAnalyzer\SymfonyClosureDetector;
1720

1821
/**
19-
* @implements Rule<MethodCall>
22+
* @implements Rule<Closure>
2023
*
2124
* @see \Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\NoServiceSameNameSetClassRuleTest
2225
*/
23-
final class NoServiceSameNameSetClassRule implements Rule
26+
final readonly class NoServiceSameNameSetClassRule implements Rule
2427
{
2528
/**
2629
* @var string
2730
*/
2831
public const ERROR_MESSAGE = 'No need to duplicate service class and name. Use only "$services->set(%s::class)" instead';
2932

33+
private NodeFinder $nodeFinder;
34+
35+
public function __construct()
36+
{
37+
$this->nodeFinder = new NodeFinder();
38+
}
39+
3040
public function getNodeType(): string
3141
{
32-
return MethodCall::class;
42+
return Closure::class;
3343
}
3444

3545
/**
36-
* @param MethodCall $node
46+
* @param Closure $node
3747
* @return RuleError[]
3848
*/
3949
public function processNode(Node $node, Scope $scope): array
4050
{
41-
if ($node->isFirstClassCallable()) {
51+
if (! SymfonyClosureDetector::detect($node)) {
4252
return [];
4353
}
4454

45-
if (! NamingHelper::isName($node->name, 'set')) {
46-
return [];
55+
/** @var MethodCall[] $methodCalls */
56+
$methodCalls = $this->nodeFinder->findInstanceOf($node, MethodCall::class);
57+
58+
$ruleErrors = [];
59+
60+
foreach ($methodCalls as $methodCall) {
61+
if ($methodCall->isFirstClassCallable()) {
62+
continue;
63+
}
64+
65+
if (! NamingHelper::isName($methodCall->var, 'services')) {
66+
continue;
67+
}
68+
69+
if (! NamingHelper::isName($methodCall->name, 'set')) {
70+
continue;
71+
}
72+
73+
$serviceNameValue = $this->matchTwoArgsOfSameClassConstName($methodCall);
74+
if (! is_string($serviceNameValue)) {
75+
continue;
76+
}
77+
78+
if (str_contains($serviceNameValue, '\\')) {
79+
$serviceNameValue = Strings::after($serviceNameValue, '\\', -1);
80+
}
81+
82+
$identifierRuleError = RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $serviceNameValue))
83+
->identifier(SymfonyRuleIdentifier::NO_SERVICE_SAME_NAME_SET_CLASS)
84+
->line($methodCall->getStartLine())
85+
->build();
86+
87+
$ruleErrors[] = $identifierRuleError;
4788
}
4889

49-
if (count($node->getArgs()) !== 2) {
50-
return [];
90+
return $ruleErrors;
91+
}
92+
93+
/**
94+
* We look for:
95+
*
96+
* $services->set(SomeClass::class, SomeClass::class)
97+
*/
98+
private function matchTwoArgsOfSameClassConstName(MethodCall $methodCall): ?string
99+
{
100+
if (count($methodCall->getArgs()) !== 2) {
101+
return null;
51102
}
52103

53-
$serviceName = $node->getArgs()[0]->value;
54-
$serviceType = $node->getArgs()[1]->value;
104+
$serviceName = $methodCall->getArgs()[0]->value;
105+
$serviceType = $methodCall->getArgs()[1]->value;
55106

56107
if (! $serviceName instanceof ClassConstFetch) {
57-
return [];
108+
return null;
58109
}
59110

60111
if (! $serviceType instanceof ClassConstFetch) {
61-
return [];
112+
return null;
62113
}
63114

64115
$serviceNameValue = NamingHelper::getName($serviceName->class);
65116
if (! is_string($serviceNameValue)) {
66-
return [];
117+
return null;
67118
}
68119

69120
$serviceTypeValue = NamingHelper::getName($serviceType->class);
70121
if (! is_string($serviceTypeValue)) {
71-
return [];
122+
return null;
72123
}
73124

74125
if ($serviceNameValue !== $serviceTypeValue) {
75-
return [];
126+
return null;
76127
}
77128

78-
if (str_contains($serviceNameValue, '\\')) {
79-
$serviceNameValue = Strings::after($serviceNameValue, '\\', -1);
80-
}
81-
82-
$identifierRuleError = RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $serviceNameValue))
83-
->identifier(SymfonyRuleIdentifier::NO_SERVICE_SAME_NAME_SET_CLASS)
84-
->build();
85-
86-
return [$identifierRuleError];
129+
return $serviceNameValue;
87130
}
88131
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\Fixture;
4+
5+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
6+
use Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\Source\SomeSetService;
7+
8+
class SkipNonClosureConstantSet
9+
{
10+
private const NAME = 'name';
11+
12+
private const TYPE = 'type';
13+
14+
public function run()
15+
{
16+
$this->set(self::NAME, self::TYPE);
17+
}
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\Fixture;
4+
5+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
6+
use Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\Source\ConstantList;
7+
use Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\Source\SomeSetService;
8+
9+
return function (ContainerConfigurator $container) {
10+
$parameters = $container->parameters();
11+
$parameters->set(ConstantList::NAME, ConstantList::NAME);
12+
};

tests/Rules/Symfony/ConfigClosure/NoServiceSameNameSetClassRule/Fixture/SomeConfigWithInvalidSet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\ServicesExcludedDirectoryMustExistRule\Fixture;
3+
namespace Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\Fixture;
44

55
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
66
use Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\Source\SomeSetService;

tests/Rules/Symfony/ConfigClosure/NoServiceSameNameSetClassRule/NoServiceSameNameSetClassRuleTest.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,21 @@ public static function provideData(): Iterator
2929
11,
3030
],
3131
]];
32+
33+
yield [__DIR__ . '/Fixture/SkipNonClosureConstantSet.php', []];
34+
yield [__DIR__ . '/Fixture/SkipParametersSetConstant.php', []];
35+
}
36+
37+
/**
38+
* @return string[]
39+
*/
40+
public static function getAdditionalConfigFiles(): array
41+
{
42+
return [__DIR__ . '/config/configured_rule.neon'];
3243
}
3344

3445
protected function getRule(): Rule
3546
{
36-
return new NoServiceSameNameSetClassRule();
47+
return self::getContainer()->getByType(NoServiceSameNameSetClassRule::class);
3748
}
3849
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule\Source;
4+
5+
class ConstantList
6+
{
7+
public const NAME = 'name';
8+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rules:
2+
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule

0 commit comments

Comments
 (0)