From ed62f66b0152e442d65f67e64a8e5707b08df0cf Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 12 Oct 2026 10:46:48 +0200 Subject: [PATCH] use PreferGenericTemplateOnSameUnionTypeRule from symplify/phpstan-rules Rule extracted to symplify/phpstan-rules 14.18.2; registered in the rector-rules set, which phpstan.neon already includes, so enforcement is preserved. --- composer.json | 2 +- phpstan.neon | 1 - ...eferGenericTemplateOnSameUnionTypeRule.php | 121 ------------------ ...GenericTemplateOnSameUnionTypeRuleTest.php | 32 ----- .../Source/SameUnionParamAndReturn.php | 16 --- .../Source/SkipGenericTemplate.php | 32 ----- 6 files changed, 1 insertion(+), 203 deletions(-) delete mode 100644 utils/phpstan/src/Rule/PreferGenericTemplateOnSameUnionTypeRule.php delete mode 100644 utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/PreferGenericTemplateOnSameUnionTypeRuleTest.php delete mode 100644 utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/Source/SameUnionParamAndReturn.php delete mode 100644 utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/Source/SkipGenericTemplate.php diff --git a/composer.json b/composer.json index bc3a97b1d41..e8d1fb52fd5 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "shipmonk/dead-code-detector": "^1.4", "symfony/process": "^8.1", "symplify/easy-coding-standard": "^13.3.5", - "symplify/phpstan-rules": "^14.18", + "symplify/phpstan-rules": "^14.18.2", "tomasvotruba/class-leak": "^2.3.4", "tomasvotruba/fast-unit": "^0.1", "tomasvotruba/type-coverage": "^2.3.6", diff --git a/phpstan.neon b/phpstan.neon index 2c146381ddb..9d5e1d70a33 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,7 +4,6 @@ includes: rules: - Rector\Utils\PHPStan\Rule\RegisterRelatedPolyfillRectorRule - Rector\Utils\PHPStan\Rule\NoScopeFetchBeforeCheapGuardRule - - Rector\Utils\PHPStan\Rule\PreferGenericTemplateOnSameUnionTypeRule - Rector\Utils\PHPStan\Rule\NarrowReturnToUnionParamRule parameters: diff --git a/utils/phpstan/src/Rule/PreferGenericTemplateOnSameUnionTypeRule.php b/utils/phpstan/src/Rule/PreferGenericTemplateOnSameUnionTypeRule.php deleted file mode 100644 index 32d3b952ece..00000000000 --- a/utils/phpstan/src/Rule/PreferGenericTemplateOnSameUnionTypeRule.php +++ /dev/null @@ -1,121 +0,0 @@ - - * @see \Rector\Utils\PHPStan\Tests\Rule\PreferGenericTemplateOnSameUnionTypeRule\PreferGenericTemplateOnSameUnionTypeRuleTest - */ -final class PreferGenericTemplateOnSameUnionTypeRule implements Rule -{ - private const string ERROR_MESSAGE = 'Param and return share the same class-type union "%s". Add a @template generic to narrow the return type to the passed one.'; - - public function getNodeType(): string - { - return ClassMethod::class; - } - - /** - * @param ClassMethod $node - * @return list - */ - public function processNode(Node $node, Scope $scope): array - { - $docComment = $node->getDocComment(); - if ($docComment instanceof Doc && str_contains($docComment->getText(), '@template')) { - return []; - } - - $returnNames = $this->resolveClassUnionNames($node->returnType); - if ($returnNames === null) { - return []; - } - - foreach ($node->params as $param) { - $paramNames = $this->resolveClassUnionNames($param->type); - if ($paramNames === null) { - continue; - } - - if ($paramNames !== $returnNames) { - continue; - } - - $shortNames = array_map( - static function (string $name): string { - $parts = explode('\\', $name); - return end($parts); - }, - $returnNames - ); - - return [ - RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, implode('|', $shortNames))) - ->identifier('rector.genericTemplateOverSameUnionType') - ->line($node->getStartLine()) - ->build(), - ]; - } - - return []; - } - - /** - * Returns sorted class-type member names (null stripped), or null when the type is not a - * union of 2+ class types - scalars, array and intersections disqualify it. - * - * @return list|null - */ - private function resolveClassUnionNames(null|Identifier|Name|ComplexType $type): ?array - { - if (! $type instanceof UnionType) { - return null; - } - - $names = []; - foreach ($type->types as $memberType) { - if ($memberType instanceof Identifier) { - if ($memberType->toLowerString() === 'null') { - continue; - } - - // built-in scalar/array type, not a class - return null; - } - - if ($memberType instanceof Name) { - $names[] = $memberType->toString(); - continue; - } - - // IntersectionType or anything else - return null; - } - - if (count($names) < 2) { - return null; - } - - $names = array_unique($names); - sort($names); - - return $names; - } -} diff --git a/utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/PreferGenericTemplateOnSameUnionTypeRuleTest.php b/utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/PreferGenericTemplateOnSameUnionTypeRuleTest.php deleted file mode 100644 index 61005a393ef..00000000000 --- a/utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/PreferGenericTemplateOnSameUnionTypeRuleTest.php +++ /dev/null @@ -1,32 +0,0 @@ - - */ -final class PreferGenericTemplateOnSameUnionTypeRuleTest extends RuleTestCase -{ - public function testSameUnionParamAndReturn(): void - { - $expectedErrorMessage = 'Param and return share the same class-type union "ClassMethod|Function_". Add a @template generic to narrow the return type to the passed one.'; - - $this->analyse([__DIR__ . '/Source/SameUnionParamAndReturn.php'], [[$expectedErrorMessage, 12]]); - } - - public function testSkip(): void - { - $this->analyse([__DIR__ . '/Source/SkipGenericTemplate.php'], []); - } - - protected function getRule(): Rule - { - return new PreferGenericTemplateOnSameUnionTypeRule(); - } -} diff --git a/utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/Source/SameUnionParamAndReturn.php b/utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/Source/SameUnionParamAndReturn.php deleted file mode 100644 index 2caca6b02cc..00000000000 --- a/utils/phpstan/tests/Rule/PreferGenericTemplateOnSameUnionTypeRule/Source/SameUnionParamAndReturn.php +++ /dev/null @@ -1,16 +0,0 @@ -