Skip to content

Commit a0afcb0

Browse files
authored
Merge pull request #445 from mglaman/gh440
Add SymfonyCmfRouteObjectInterfaceConstantsRule
2 parents c922084 + 421249f commit a0afcb0

8 files changed

+349
-11
lines changed

rules.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ rules:
1515
- mglaman\PHPStanDrupal\Rules\Drupal\ModuleLoadInclude
1616
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
1717
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
18+
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRouteObjectInterfaceConstantsRule
19+
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Internal;
4+
5+
use PHPStan\Analyser\Scope;
6+
7+
final class DeprecatedScopeCheck
8+
{
9+
public static function inDeprecatedScope(Scope $scope): bool
10+
{
11+
$class = $scope->getClassReflection();
12+
if ($class !== null && $class->isDeprecated()) {
13+
return true;
14+
}
15+
$trait = $scope->getTraitReflection();
16+
if ($trait !== null && $trait->isDeprecated()) {
17+
return true;
18+
}
19+
$function = $scope->getFunction();
20+
return $function !== null && $function->isDeprecated()->yes();
21+
}
22+
}

src/Rules/Deprecations/AccessDeprecatedConstant.php

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
namespace mglaman\PHPStanDrupal\Rules\Deprecations;
44

5+
use mglaman\PHPStanDrupal\Internal\DeprecatedScopeCheck;
56
use PhpParser\Node;
67
use PHPStan\Analyser\Scope;
7-
use PHPStan\Reflection\FunctionReflection;
88
use PHPStan\Reflection\ReflectionProvider;
99

1010
class AccessDeprecatedConstant implements \PHPStan\Rules\Rule
@@ -24,16 +24,7 @@ public function getNodeType(): string
2424
public function processNode(Node $node, Scope $scope): array
2525
{
2626
assert($node instanceof Node\Expr\ConstFetch);
27-
$class = $scope->getClassReflection();
28-
if ($class !== null && $class->isDeprecated()) {
29-
return [];
30-
}
31-
$trait = $scope->getTraitReflection();
32-
if ($trait !== null && $trait->isDeprecated()) {
33-
return [];
34-
}
35-
$function = $scope->getFunction();
36-
if ($function instanceof FunctionReflection && $function->isDeprecated()->yes()) {
27+
if (DeprecatedScopeCheck::inDeprecatedScope($scope)) {
3728
return [];
3829
}
3930

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Rules\Deprecations;
4+
5+
use Drupal\Core\Routing\RouteObjectInterface;
6+
use mglaman\PHPStanDrupal\Internal\DeprecatedScopeCheck;
7+
use PhpParser\Node;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Type\ObjectType;
12+
13+
final class SymfonyCmfRouteObjectInterfaceConstantsRule implements Rule
14+
{
15+
16+
public function getNodeType(): string
17+
{
18+
return Node\Expr\ClassConstFetch::class;
19+
}
20+
21+
public function processNode(Node $node, Scope $scope): array
22+
{
23+
assert($node instanceof Node\Expr\ClassConstFetch);
24+
if (!$node->name instanceof Node\Identifier) {
25+
return [];
26+
}
27+
if (!$node->class instanceof Node\Name) {
28+
return [];
29+
}
30+
$constantName = $node->name->name;
31+
$className = $node->class;
32+
$classType = $scope->resolveTypeByName($className);
33+
if (!$classType->hasConstant($constantName)->yes()) {
34+
return [];
35+
}
36+
if (DeprecatedScopeCheck::inDeprecatedScope($scope)) {
37+
return [];
38+
}
39+
[$major, $minor] = explode('.', \Drupal::VERSION, 3);
40+
if ($major !== '9' && (int) $minor > 1) {
41+
return [];
42+
}
43+
$cmfRouteObjectInterfaceType = new ObjectType(\Symfony\Cmf\Component\Routing\RouteObjectInterface::class);
44+
if (!$classType->isSuperTypeOf($cmfRouteObjectInterfaceType)->yes()) {
45+
return [];
46+
}
47+
48+
$coreRouteObjectInterfaceType = new ObjectType(RouteObjectInterface::class);
49+
if (!$coreRouteObjectInterfaceType->hasConstant($constantName)->yes()) {
50+
return [
51+
RuleErrorBuilder::message(
52+
sprintf('The core dependency symfony-cmf/routing is deprecated and %s::%s is not supported.', $className, $constantName)
53+
)->tip('Change record: https://www.drupal.org/node/3151009')->build(),
54+
];
55+
}
56+
57+
return [
58+
RuleErrorBuilder::message(
59+
sprintf('%s::%s is deprecated and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface::%2$s instead.', $className, $constantName)
60+
)->tip('Change record: https://www.drupal.org/node/3151009')->build(),
61+
];
62+
}
63+
}
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\SymfonyCmfRouteObjectInterfaceConstantsRule;
7+
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
8+
9+
final class SymfonyCmfRouteObjectInterfaceConstantsRuleTest extends DrupalRuleTestCase
10+
{
11+
12+
protected function getRule(): \PHPStan\Rules\Rule
13+
{
14+
return new SymfonyCmfRouteObjectInterfaceConstantsRule();
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+
'Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_NAME is deprecated and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface::ROUTE_NAME instead.',
30+
6,
31+
'Change record: https://www.drupal.org/node/3151009'
32+
],
33+
[
34+
'Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_OBJECT is deprecated and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface::ROUTE_OBJECT instead.',
35+
7,
36+
'Change record: https://www.drupal.org/node/3151009'
37+
],
38+
[
39+
'Symfony\Cmf\Component\Routing\RouteObjectInterface::CONTROLLER_NAME is deprecated and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface::CONTROLLER_NAME instead.',
40+
8,
41+
'Change record: https://www.drupal.org/node/3151009'
42+
],
43+
[
44+
'The core dependency symfony-cmf/routing is deprecated and Symfony\Cmf\Component\Routing\RouteObjectInterface::TEMPLATE_NAME is not supported.',
45+
9,
46+
'Change record: https://www.drupal.org/node/3151009'
47+
],
48+
]
49+
);
50+
}
51+
}
52+
53+
}
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+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace SymfonyCmfRoutingUsage;
4+
5+
class Foo {
6+
public const NAME = \Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_NAME;
7+
public const OBJECT = \Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_OBJECT;
8+
public const CONTROLLER = \Symfony\Cmf\Component\Routing\RouteObjectInterface::CONTROLLER_NAME;
9+
public const UNMAPPED = \Symfony\Cmf\Component\Routing\RouteObjectInterface::TEMPLATE_NAME;
10+
public function a(\Symfony\Cmf\Component\Routing\RouteObjectInterface $object) {
11+
12+
}
13+
public function b(\Symfony\Cmf\Component\Routing\RouteProviderInterface $provider): \Symfony\Cmf\Component\Routing\RouteObjectInterface {
14+
15+
}
16+
public function c(\Symfony\Cmf\Component\Routing\LazyRouteCollection $collection) {
17+
18+
}
19+
}
20+
class Bar {
21+
public const NAME = \Drupal\Core\Routing\RouteObjectInterface::ROUTE_NAME;
22+
public const OBJECT = \Drupal\Core\Routing\RouteObjectInterface::ROUTE_OBJECT;
23+
public const CONTROLLER = \Drupal\Core\Routing\RouteObjectInterface::CONTROLLER_NAME;
24+
public function a(\Drupal\Core\Routing\RouteObjectInterface $object) {
25+
26+
}
27+
public function b(\Drupal\Core\Routing\RouteProviderInterface $provider): \Drupal\Core\Routing\RouteObjectInterface {
28+
29+
}
30+
public function c(\Drupal\Core\Routing\LazyRouteCollection $collection) {
31+
32+
}
33+
}

0 commit comments

Comments
 (0)