Skip to content

Commit 421249f

Browse files
committed
Add SymfonyCmfRoutingInClassMethodSignatureRule
1 parent 78d8fa5 commit 421249f

File tree

4 files changed

+177
-2
lines changed

4 files changed

+177
-2
lines changed

rules.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ rules:
1616
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
1717
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
1818
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRouteObjectInterfaceConstantsRule
19+
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Rules\Deprecations;
4+
5+
use mglaman\PHPStanDrupal\Internal\DeprecatedScopeCheck;
6+
use PhpParser\Node;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Node\InClassMethodNode;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Reflection\ParametersAcceptorSelector;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use PHPStan\Type\ObjectType;
14+
15+
final class SymfonyCmfRoutingInClassMethodSignatureRule implements Rule
16+
{
17+
18+
public function getNodeType(): string
19+
{
20+
return InClassMethodNode::class;
21+
}
22+
23+
public function processNode(Node $node, Scope $scope): array
24+
{
25+
assert($node instanceof InClassMethodNode);
26+
if (DeprecatedScopeCheck::inDeprecatedScope($scope)) {
27+
return [];
28+
}
29+
[$major, $minor] = explode('.', \Drupal::VERSION, 3);
30+
if ($major !== '9' && (int) $minor > 1) {
31+
return [];
32+
}
33+
$method = $scope->getFunction();
34+
if (!$method instanceof MethodReflection) {
35+
throw new \PHPStan\ShouldNotHappenException();
36+
}
37+
38+
$cmfRouteObjectInterfaceType = new ObjectType(\Symfony\Cmf\Component\Routing\RouteObjectInterface::class);
39+
$cmfRouteProviderInterfaceType = new ObjectType(\Symfony\Cmf\Component\Routing\RouteProviderInterface::class);
40+
$cmfLazyRouteCollectionType = new ObjectType(\Symfony\Cmf\Component\Routing\LazyRouteCollection::class);
41+
42+
$methodSignature = ParametersAcceptorSelector::selectSingle($method->getVariants());
43+
44+
$errors = [];
45+
$errorMessage = 'Parameter $%s of method %s() uses deprecated %s and removed in Drupal 10. Use %s instead.';
46+
foreach ($methodSignature->getParameters() as $parameter) {
47+
foreach ($parameter->getType()->getReferencedClasses() as $referencedClass) {
48+
$referencedClassType = new ObjectType($referencedClass);
49+
if ($referencedClassType->isSuperTypeOf($cmfRouteObjectInterfaceType)->yes()) {
50+
$errors[] = RuleErrorBuilder::message(
51+
sprintf(
52+
$errorMessage,
53+
$parameter->getName(),
54+
$method->getName(),
55+
$referencedClass,
56+
'\Drupal\Core\Routing\RouteObjectInterface'
57+
)
58+
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
59+
} elseif ($referencedClassType->isSuperTypeOf($cmfRouteProviderInterfaceType)->yes()) {
60+
$errors[] = RuleErrorBuilder::message(
61+
sprintf(
62+
$errorMessage,
63+
$parameter->getName(),
64+
$method->getName(),
65+
$referencedClass,
66+
'\Drupal\Core\Routing\RouteProviderInterface'
67+
)
68+
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
69+
} elseif ($referencedClassType->isSuperTypeOf($cmfLazyRouteCollectionType)->yes()) {
70+
$errors[] = RuleErrorBuilder::message(
71+
sprintf(
72+
$errorMessage,
73+
$parameter->getName(),
74+
$method->getName(),
75+
$referencedClass,
76+
'\Drupal\Core\Routing\LazyRouteCollection'
77+
)
78+
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
79+
}
80+
}
81+
}
82+
83+
$errorMessage = 'Return type of method %s::%s() has typehint with deprecated %s and is removed in Drupal 10. Use %s instead.';
84+
$returnClasses = $methodSignature->getReturnType()->getReferencedClasses();
85+
foreach ($returnClasses as $returnClass) {
86+
$returnType = new ObjectType($returnClass);
87+
if ($returnType->isSuperTypeOf($cmfRouteObjectInterfaceType)->yes()) {
88+
$errors[] = RuleErrorBuilder::message(
89+
sprintf(
90+
$errorMessage,
91+
$method->getDeclaringClass()->getName(),
92+
$method->getName(),
93+
$returnClass,
94+
'\Drupal\Core\Routing\RouteObjectInterface'
95+
)
96+
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
97+
} elseif ($returnType->isSuperTypeOf($cmfRouteProviderInterfaceType)->yes()) {
98+
$errors[] = RuleErrorBuilder::message(
99+
sprintf(
100+
$errorMessage,
101+
$method->getDeclaringClass()->getName(),
102+
$method->getName(),
103+
$returnClass,
104+
'\Drupal\Core\Routing\RouteProviderInterface'
105+
)
106+
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
107+
} elseif ($returnType->isSuperTypeOf($cmfLazyRouteCollectionType)->yes()) {
108+
$errors[] = RuleErrorBuilder::message(
109+
sprintf(
110+
$errorMessage,
111+
$method->getDeclaringClass()->getName(),
112+
$method->getName(),
113+
$returnClass,
114+
'\Drupal\Core\Routing\LazyRouteCollection'
115+
)
116+
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
117+
}
118+
}
119+
return $errors;
120+
}
121+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace mglaman\PHPStanDrupal\Tests\Rules;
5+
6+
use mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule;
7+
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
8+
9+
final class SymfonyCmfRoutingInClassMethodSignatureRuleTest extends DrupalRuleTestCase
10+
{
11+
12+
protected function getRule(): \PHPStan\Rules\Rule
13+
{
14+
return new SymfonyCmfRoutingInClassMethodSignatureRule();
15+
}
16+
17+
public function testRule(): void
18+
{
19+
[$version] = explode('.', \Drupal::VERSION, 2);
20+
if ($version === '8') {
21+
$this->analyse([__DIR__.'/data/symfony-cmf-routing.php'], []);
22+
} elseif ($version === '10') {
23+
self::markTestSkipped('Not tested on 10.x.x');
24+
} else {
25+
$this->analyse(
26+
[__DIR__.'/data/symfony-cmf-routing.php'],
27+
[
28+
[
29+
'Parameter $object of method a() uses deprecated Symfony\Cmf\Component\Routing\RouteObjectInterface and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface instead.',
30+
10,
31+
'Change record: https://www.drupal.org/node/3151009'
32+
],
33+
[
34+
'Parameter $provider of method b() uses deprecated Symfony\Cmf\Component\Routing\RouteProviderInterface and removed in Drupal 10. Use \Drupal\Core\Routing\RouteProviderInterface instead.',
35+
13,
36+
'Change record: https://www.drupal.org/node/3151009'
37+
],
38+
[
39+
'Return type of method SymfonyCmfRoutingUsage\Foo::b() has typehint with deprecated Symfony\Cmf\Component\Routing\RouteObjectInterface and is removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface instead.',
40+
13,
41+
'Change record: https://www.drupal.org/node/3151009'
42+
],
43+
[
44+
'Parameter $collection of method c() uses deprecated Symfony\Cmf\Component\Routing\LazyRouteCollection and removed in Drupal 10. Use \Drupal\Core\Routing\LazyRouteCollection instead.',
45+
16,
46+
'Change record: https://www.drupal.org/node/3151009'
47+
],
48+
]
49+
);
50+
}
51+
}
52+
53+
}

tests/src/Rules/data/symfony-cmf-routing.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Foo {
1010
public function a(\Symfony\Cmf\Component\Routing\RouteObjectInterface $object) {
1111

1212
}
13-
public function b(\Symfony\Cmf\Component\Routing\RouteProviderInterface $provider) {
13+
public function b(\Symfony\Cmf\Component\Routing\RouteProviderInterface $provider): \Symfony\Cmf\Component\Routing\RouteObjectInterface {
1414

1515
}
1616
public function c(\Symfony\Cmf\Component\Routing\LazyRouteCollection $collection) {
@@ -24,7 +24,7 @@ class Bar {
2424
public function a(\Drupal\Core\Routing\RouteObjectInterface $object) {
2525

2626
}
27-
public function b(\Drupal\Core\Routing\RouteProviderInterface $provider) {
27+
public function b(\Drupal\Core\Routing\RouteProviderInterface $provider): \Drupal\Core\Routing\RouteObjectInterface {
2828

2929
}
3030
public function c(\Drupal\Core\Routing\LazyRouteCollection $collection) {

0 commit comments

Comments
 (0)