Skip to content

Commit

Permalink
Merge pull request #445 from mglaman/gh440
Browse files Browse the repository at this point in the history
Add SymfonyCmfRouteObjectInterfaceConstantsRule
  • Loading branch information
mglaman authored Jul 13, 2022
2 parents c922084 + 421249f commit a0afcb0
Show file tree
Hide file tree
Showing 8 changed files with 349 additions and 11 deletions.
2 changes: 2 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ rules:
- mglaman\PHPStanDrupal\Rules\Drupal\ModuleLoadInclude
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRouteObjectInterfaceConstantsRule
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule
22 changes: 22 additions & 0 deletions src/Internal/DeprecatedScopeCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Internal;

use PHPStan\Analyser\Scope;

final class DeprecatedScopeCheck
{
public static function inDeprecatedScope(Scope $scope): bool
{
$class = $scope->getClassReflection();
if ($class !== null && $class->isDeprecated()) {
return true;
}
$trait = $scope->getTraitReflection();
if ($trait !== null && $trait->isDeprecated()) {
return true;
}
$function = $scope->getFunction();
return $function !== null && $function->isDeprecated()->yes();
}
}
13 changes: 2 additions & 11 deletions src/Rules/Deprecations/AccessDeprecatedConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace mglaman\PHPStanDrupal\Rules\Deprecations;

use mglaman\PHPStanDrupal\Internal\DeprecatedScopeCheck;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ReflectionProvider;

class AccessDeprecatedConstant implements \PHPStan\Rules\Rule
Expand All @@ -24,16 +24,7 @@ public function getNodeType(): string
public function processNode(Node $node, Scope $scope): array
{
assert($node instanceof Node\Expr\ConstFetch);
$class = $scope->getClassReflection();
if ($class !== null && $class->isDeprecated()) {
return [];
}
$trait = $scope->getTraitReflection();
if ($trait !== null && $trait->isDeprecated()) {
return [];
}
$function = $scope->getFunction();
if ($function instanceof FunctionReflection && $function->isDeprecated()->yes()) {
if (DeprecatedScopeCheck::inDeprecatedScope($scope)) {
return [];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Rules\Deprecations;

use Drupal\Core\Routing\RouteObjectInterface;
use mglaman\PHPStanDrupal\Internal\DeprecatedScopeCheck;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectType;

final class SymfonyCmfRouteObjectInterfaceConstantsRule implements Rule
{

public function getNodeType(): string
{
return Node\Expr\ClassConstFetch::class;
}

public function processNode(Node $node, Scope $scope): array
{
assert($node instanceof Node\Expr\ClassConstFetch);
if (!$node->name instanceof Node\Identifier) {
return [];
}
if (!$node->class instanceof Node\Name) {
return [];
}
$constantName = $node->name->name;
$className = $node->class;
$classType = $scope->resolveTypeByName($className);
if (!$classType->hasConstant($constantName)->yes()) {
return [];
}
if (DeprecatedScopeCheck::inDeprecatedScope($scope)) {
return [];
}
[$major, $minor] = explode('.', \Drupal::VERSION, 3);
if ($major !== '9' && (int) $minor > 1) {
return [];
}
$cmfRouteObjectInterfaceType = new ObjectType(\Symfony\Cmf\Component\Routing\RouteObjectInterface::class);
if (!$classType->isSuperTypeOf($cmfRouteObjectInterfaceType)->yes()) {
return [];
}

$coreRouteObjectInterfaceType = new ObjectType(RouteObjectInterface::class);
if (!$coreRouteObjectInterfaceType->hasConstant($constantName)->yes()) {
return [
RuleErrorBuilder::message(
sprintf('The core dependency symfony-cmf/routing is deprecated and %s::%s is not supported.', $className, $constantName)
)->tip('Change record: https://www.drupal.org/node/3151009')->build(),
];
}

return [
RuleErrorBuilder::message(
sprintf('%s::%s is deprecated and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface::%2$s instead.', $className, $constantName)
)->tip('Change record: https://www.drupal.org/node/3151009')->build(),
];
}
}
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\SymfonyCmfRouteObjectInterfaceConstantsRule;
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;

final class SymfonyCmfRouteObjectInterfaceConstantsRuleTest extends DrupalRuleTestCase
{

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

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'],
[
[
'Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_NAME is deprecated and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface::ROUTE_NAME instead.',
6,
'Change record: https://www.drupal.org/node/3151009'
],
[
'Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_OBJECT is deprecated and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface::ROUTE_OBJECT instead.',
7,
'Change record: https://www.drupal.org/node/3151009'
],
[
'Symfony\Cmf\Component\Routing\RouteObjectInterface::CONTROLLER_NAME is deprecated and removed in Drupal 10. Use \Drupal\Core\Routing\RouteObjectInterface::CONTROLLER_NAME instead.',
8,
'Change record: https://www.drupal.org/node/3151009'
],
[
'The core dependency symfony-cmf/routing is deprecated and Symfony\Cmf\Component\Routing\RouteObjectInterface::TEMPLATE_NAME is not supported.',
9,
'Change record: https://www.drupal.org/node/3151009'
],
]
);
}
}

}
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'
],
]
);
}
}

}
33 changes: 33 additions & 0 deletions tests/src/Rules/data/symfony-cmf-routing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace SymfonyCmfRoutingUsage;

class Foo {
public const NAME = \Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_NAME;
public const OBJECT = \Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_OBJECT;
public const CONTROLLER = \Symfony\Cmf\Component\Routing\RouteObjectInterface::CONTROLLER_NAME;
public const UNMAPPED = \Symfony\Cmf\Component\Routing\RouteObjectInterface::TEMPLATE_NAME;
public function a(\Symfony\Cmf\Component\Routing\RouteObjectInterface $object) {

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

}
public function c(\Symfony\Cmf\Component\Routing\LazyRouteCollection $collection) {

}
}
class Bar {
public const NAME = \Drupal\Core\Routing\RouteObjectInterface::ROUTE_NAME;
public const OBJECT = \Drupal\Core\Routing\RouteObjectInterface::ROUTE_OBJECT;
public const CONTROLLER = \Drupal\Core\Routing\RouteObjectInterface::CONTROLLER_NAME;
public function a(\Drupal\Core\Routing\RouteObjectInterface $object) {

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

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

}
}

0 comments on commit a0afcb0

Please sign in to comment.