From a3765f4fa0ebb16455855ef7009ce359a43988db Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 19 Sep 2023 14:16:08 +0200 Subject: [PATCH] =?UTF-8?q?make=20LongAndDependentComplexRectorRule=20work?= =?UTF-8?q?=20with=20complex=20transitional=20construcotr=20dependencies?= =?UTF-8?q?=C5=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gs --- phpstan.neon | 3 - .../LongAndDependentComplexRectorRule.php | 111 +++++++++++++----- 2 files changed, 81 insertions(+), 33 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index b155dcd5abb..91cb03a4755 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -547,7 +547,6 @@ parameters: message: '#Parameter \#1 \$useType of callable callable\(0\|1\|2\|3, PhpParser\\Node\\Stmt\\UseUse, string\): void expects 0\|1\|2\|3, int given.#' path: rules/CodingStyle/ClassNameImport/UseImportsTraverser.php -<<<<<<< HEAD # rector collectors - '#Creating new PHPStan\\Collectors\\CollectedData is not covered by backward compatibility promise\. The class might change in a minor PHPStan version#' @@ -560,6 +559,4 @@ parameters: - '#Access to an undefined property (.*?)\\Core\\Contract\\PhpParser\\Node\\StmtsAwareInterface\:\:\$stmts#' - '#Property Rector\\Core\\Contract\\PhpParser\\Node\\StmtsAwareInterface\:\:\$stmts \(array\|null\) does not accept array#' -======= - '#Class "Rector\\Utils\\PHPStan\\Rule\\LongAndDependentComplexRectorRule" is missing @see annotation with test case class reference#' ->>>>>>> e4bb45f52e ([Internal] Experiment with internal PHPStan rule to spot heavy Rector rules) diff --git a/utils/PHPStan/Rule/LongAndDependentComplexRectorRule.php b/utils/PHPStan/Rule/LongAndDependentComplexRectorRule.php index 4ead9bcf279..95b49164f22 100644 --- a/utils/PHPStan/Rule/LongAndDependentComplexRectorRule.php +++ b/utils/PHPStan/Rule/LongAndDependentComplexRectorRule.php @@ -4,14 +4,21 @@ namespace Rector\Utils\PHPStan\Rule; +use Nette\Utils\FileSystem; use PhpParser\Node; -use PhpParser\Node\Stmt\ClassLike; -use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Class_; +use PhpParser\NodeFinder; +use PhpParser\Parser; +use PhpParser\ParserFactory; use PHPStan\Analyser\Scope; use PHPStan\Node\InClassNode; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ParameterReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; +use PHPStan\Type\TypeWithClassName; use Rector\Core\Contract\Rector\RectorInterface; -use Rector\Core\ValueObject\MethodName; +use TomasVotruba\CognitiveComplexity\AstCognitiveComplexityAnalyzer; /** * @implements Rule @@ -22,12 +29,20 @@ final class LongAndDependentComplexRectorRule implements Rule /** * @var int */ - private const MAX_CLASS_LINES = 320; + private const ALLOWED_TRANSITIONAL_COMPLEXITY = 120; - /** - * @var int - */ - private const MAX_DEPENDENCY_COUNT = 7; + private Parser $phpParser; + + private NodeFinder $nodeFinder; + + public function __construct( + private AstCognitiveComplexityAnalyzer $astCognitiveComplexityAnalyzer, + ) { + $parserFactory = new ParserFactory(); + $this->phpParser = $parserFactory->create(ParserFactory::PREFER_PHP7); + + $this->nodeFinder = new NodeFinder(); + } public function getNodeType(): string { @@ -45,37 +60,73 @@ public function processNode(Node $node, Scope $scope): array return []; } - $class = $node->getOriginalNode(); - $errorMessages = []; + // not much complex + if (! $classReflection->hasConstructor()) { + return []; + } + + $constructorMethodReflection = $classReflection->getConstructor(); + $parametersAcceptor = ParametersAcceptorSelector::selectSingle($constructorMethodReflection->getVariants()); - $constructorParameterCount = $this->resolveConstructorParameterCount($class); - if ($constructorParameterCount > self::MAX_DEPENDENCY_COUNT) { - $errorMessages[] = sprintf( - 'Class "%s" has too many constructor parameters (%d), consider using value objects', - $classReflection->getName(), - $constructorParameterCount - ); + $originalClassLike = $node->getOriginalNode(); + if (! $originalClassLike instanceof Class_) { + return []; } - $classLineCount = $class->getEndLine() - $class->getStartLine(); - if ($classLineCount > self::MAX_CLASS_LINES) { - $errorMessages[] = sprintf( - 'Class "%s" is too long (%d lines), consider splitting it to smaller classes', - $classReflection->getName(), - $classLineCount - ); + $currentClassLikeComplexity = $this->astCognitiveComplexityAnalyzer->analyzeClassLike($originalClassLike); + $totalTransitionalComplexity = $currentClassLikeComplexity; + + foreach ($parametersAcceptor->getParameters() as $parameterReflection) { + /** @var ParameterReflection $parameterReflection */ + $parameterType = $parameterReflection->getType(); + if (! $parameterType instanceof TypeWithClassName) { + continue; + } + + $parameterClassReflection = $parameterType->getClassReflection(); + if (! $parameterClassReflection instanceof ClassReflection) { + continue; + } + + $dependencyClass = $this->parseClassReflectionToClassNode($parameterClassReflection); + if (! $dependencyClass instanceof Class_) { + continue; + } + + $dependencyComplexity = $this->astCognitiveComplexityAnalyzer->analyzeClassLike($dependencyClass); + $totalTransitionalComplexity += $dependencyComplexity; + } + + if ($totalTransitionalComplexity < self::ALLOWED_TRANSITIONAL_COMPLEXITY) { + return []; } - return $errorMessages; + return [sprintf( + 'Transitional dependency complexity %d is over %d, please consider splitting it up.', + $totalTransitionalComplexity, + self::ALLOWED_TRANSITIONAL_COMPLEXITY + )]; } - private function resolveConstructorParameterCount(ClassLike $classLike): int + private function parseClassReflectionToClassNode(ClassReflection $classReflection): ?Class_ { - $constructorClassMethod = $classLike->getMethod(MethodName::CONSTRUCT); - if (! $constructorClassMethod instanceof ClassMethod) { - return 0; + $fileName = $classReflection->getFileName(); + if (! is_string($fileName)) { + return null; + } + + $fileContents = FileSystem::read($fileName); + + $stmts = $this->phpParser->parse($fileContents); + if ($stmts === null) { + return null; + } + + $dependencyClass = $this->nodeFinder->findFirstInstanceOf($stmts, Class_::class); + if (! $dependencyClass instanceof Class_) { + return null; } - return count($constructorClassMethod->getParams()); + return $dependencyClass; } }