Skip to content

Commit

Permalink
Add SymfonyCmfRoutingInClassMethodSignatureRule
Browse files Browse the repository at this point in the history
  • Loading branch information
mglaman committed Jul 13, 2022
1 parent 78d8fa5 commit 421249f
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 2 deletions.
1 change: 1 addition & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ rules:
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRouteObjectInterfaceConstantsRule
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule
121 changes: 121 additions & 0 deletions src/Rules/Deprecations/SymfonyCmfRoutingInClassMethodSignatureRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Rules\Deprecations;

use mglaman\PHPStanDrupal\Internal\DeprecatedScopeCheck;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassMethodNode;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectType;

final class SymfonyCmfRoutingInClassMethodSignatureRule implements Rule
{

public function getNodeType(): string
{
return InClassMethodNode::class;
}

public function processNode(Node $node, Scope $scope): array
{
assert($node instanceof InClassMethodNode);
if (DeprecatedScopeCheck::inDeprecatedScope($scope)) {
return [];
}
[$major, $minor] = explode('.', \Drupal::VERSION, 3);
if ($major !== '9' && (int) $minor > 1) {
return [];
}
$method = $scope->getFunction();
if (!$method instanceof MethodReflection) {
throw new \PHPStan\ShouldNotHappenException();
}

$cmfRouteObjectInterfaceType = new ObjectType(\Symfony\Cmf\Component\Routing\RouteObjectInterface::class);
$cmfRouteProviderInterfaceType = new ObjectType(\Symfony\Cmf\Component\Routing\RouteProviderInterface::class);
$cmfLazyRouteCollectionType = new ObjectType(\Symfony\Cmf\Component\Routing\LazyRouteCollection::class);

$methodSignature = ParametersAcceptorSelector::selectSingle($method->getVariants());

$errors = [];
$errorMessage = 'Parameter $%s of method %s() uses deprecated %s and removed in Drupal 10. Use %s instead.';
foreach ($methodSignature->getParameters() as $parameter) {
foreach ($parameter->getType()->getReferencedClasses() as $referencedClass) {
$referencedClassType = new ObjectType($referencedClass);
if ($referencedClassType->isSuperTypeOf($cmfRouteObjectInterfaceType)->yes()) {
$errors[] = RuleErrorBuilder::message(
sprintf(
$errorMessage,
$parameter->getName(),
$method->getName(),
$referencedClass,
'\Drupal\Core\Routing\RouteObjectInterface'
)
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
} elseif ($referencedClassType->isSuperTypeOf($cmfRouteProviderInterfaceType)->yes()) {
$errors[] = RuleErrorBuilder::message(
sprintf(
$errorMessage,
$parameter->getName(),
$method->getName(),
$referencedClass,
'\Drupal\Core\Routing\RouteProviderInterface'
)
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
} elseif ($referencedClassType->isSuperTypeOf($cmfLazyRouteCollectionType)->yes()) {
$errors[] = RuleErrorBuilder::message(
sprintf(
$errorMessage,
$parameter->getName(),
$method->getName(),
$referencedClass,
'\Drupal\Core\Routing\LazyRouteCollection'
)
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
}
}
}

$errorMessage = 'Return type of method %s::%s() has typehint with deprecated %s and is removed in Drupal 10. Use %s instead.';
$returnClasses = $methodSignature->getReturnType()->getReferencedClasses();
foreach ($returnClasses as $returnClass) {
$returnType = new ObjectType($returnClass);
if ($returnType->isSuperTypeOf($cmfRouteObjectInterfaceType)->yes()) {
$errors[] = RuleErrorBuilder::message(
sprintf(
$errorMessage,
$method->getDeclaringClass()->getName(),
$method->getName(),
$returnClass,
'\Drupal\Core\Routing\RouteObjectInterface'
)
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
} elseif ($returnType->isSuperTypeOf($cmfRouteProviderInterfaceType)->yes()) {
$errors[] = RuleErrorBuilder::message(
sprintf(
$errorMessage,
$method->getDeclaringClass()->getName(),
$method->getName(),
$returnClass,
'\Drupal\Core\Routing\RouteProviderInterface'
)
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
} elseif ($returnType->isSuperTypeOf($cmfLazyRouteCollectionType)->yes()) {
$errors[] = RuleErrorBuilder::message(
sprintf(
$errorMessage,
$method->getDeclaringClass()->getName(),
$method->getName(),
$returnClass,
'\Drupal\Core\Routing\LazyRouteCollection'
)
)->tip('Change record: https://www.drupal.org/node/3151009')->build();
}
}
return $errors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Tests\Rules;

use mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule;
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;

final class SymfonyCmfRoutingInClassMethodSignatureRuleTest extends DrupalRuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
return new SymfonyCmfRoutingInClassMethodSignatureRule();
}

public function testRule(): void
{
[$version] = explode('.', \Drupal::VERSION, 2);
if ($version === '8') {
$this->analyse([__DIR__.'/data/symfony-cmf-routing.php'], []);
} elseif ($version === '10') {
self::markTestSkipped('Not tested on 10.x.x');
} else {
$this->analyse(
[__DIR__.'/data/symfony-cmf-routing.php'],
[
[
'Parameter $object of method a() uses deprecated Symfony\Cmf\Component\Routing\RouteObjectInterface and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface instead.',
10,
'Change record: https://www.drupal.org/node/3151009'
],
[
'Parameter $provider of method b() uses deprecated Symfony\Cmf\Component\Routing\RouteProviderInterface and removed in Drupal 10. Use \Drupal\Core\Routing\RouteProviderInterface instead.',
13,
'Change record: https://www.drupal.org/node/3151009'
],
[
'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.',
13,
'Change record: https://www.drupal.org/node/3151009'
],
[
'Parameter $collection of method c() uses deprecated Symfony\Cmf\Component\Routing\LazyRouteCollection and removed in Drupal 10. Use \Drupal\Core\Routing\LazyRouteCollection instead.',
16,
'Change record: https://www.drupal.org/node/3151009'
],
]
);
}
}

}
4 changes: 2 additions & 2 deletions tests/src/Rules/data/symfony-cmf-routing.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Foo {
public function a(\Symfony\Cmf\Component\Routing\RouteObjectInterface $object) {

}
public function b(\Symfony\Cmf\Component\Routing\RouteProviderInterface $provider) {
public function b(\Symfony\Cmf\Component\Routing\RouteProviderInterface $provider): \Symfony\Cmf\Component\Routing\RouteObjectInterface {

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

}
public function b(\Drupal\Core\Routing\RouteProviderInterface $provider) {
public function b(\Drupal\Core\Routing\RouteProviderInterface $provider): \Drupal\Core\Routing\RouteObjectInterface {

}
public function c(\Drupal\Core\Routing\LazyRouteCollection $collection) {
Expand Down

0 comments on commit 421249f

Please sign in to comment.