diff --git a/rules.neon b/rules.neon index d6a7c9b9..eab49f5a 100644 --- a/rules.neon +++ b/rules.neon @@ -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 diff --git a/src/Internal/DeprecatedScopeCheck.php b/src/Internal/DeprecatedScopeCheck.php new file mode 100644 index 00000000..3cde039f --- /dev/null +++ b/src/Internal/DeprecatedScopeCheck.php @@ -0,0 +1,22 @@ +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(); + } +} diff --git a/src/Rules/Deprecations/AccessDeprecatedConstant.php b/src/Rules/Deprecations/AccessDeprecatedConstant.php index 17e508ee..8783fa32 100644 --- a/src/Rules/Deprecations/AccessDeprecatedConstant.php +++ b/src/Rules/Deprecations/AccessDeprecatedConstant.php @@ -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 @@ -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 []; } diff --git a/src/Rules/Deprecations/SymfonyCmfRouteObjectInterfaceConstantsRule.php b/src/Rules/Deprecations/SymfonyCmfRouteObjectInterfaceConstantsRule.php new file mode 100644 index 00000000..333a8e8a --- /dev/null +++ b/src/Rules/Deprecations/SymfonyCmfRouteObjectInterfaceConstantsRule.php @@ -0,0 +1,63 @@ +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(), + ]; + } +} diff --git a/src/Rules/Deprecations/SymfonyCmfRoutingInClassMethodSignatureRule.php b/src/Rules/Deprecations/SymfonyCmfRoutingInClassMethodSignatureRule.php new file mode 100644 index 00000000..f7bbe32e --- /dev/null +++ b/src/Rules/Deprecations/SymfonyCmfRoutingInClassMethodSignatureRule.php @@ -0,0 +1,121 @@ + 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; + } +} diff --git a/tests/src/Rules/SymfonyCmfRouteObjectInterfaceConstantsRuleTest.php b/tests/src/Rules/SymfonyCmfRouteObjectInterfaceConstantsRuleTest.php new file mode 100644 index 00000000..bc40c0cc --- /dev/null +++ b/tests/src/Rules/SymfonyCmfRouteObjectInterfaceConstantsRuleTest.php @@ -0,0 +1,53 @@ +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' + ], + ] + ); + } + } + +} diff --git a/tests/src/Rules/SymfonyCmfRoutingInClassMethodSignatureRuleTest.php b/tests/src/Rules/SymfonyCmfRoutingInClassMethodSignatureRuleTest.php new file mode 100644 index 00000000..1124b6a8 --- /dev/null +++ b/tests/src/Rules/SymfonyCmfRoutingInClassMethodSignatureRuleTest.php @@ -0,0 +1,53 @@ +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' + ], + ] + ); + } + } + +} diff --git a/tests/src/Rules/data/symfony-cmf-routing.php b/tests/src/Rules/data/symfony-cmf-routing.php new file mode 100644 index 00000000..3d008dcd --- /dev/null +++ b/tests/src/Rules/data/symfony-cmf-routing.php @@ -0,0 +1,33 @@ +