From cf66c3d9e549ce7d663d8edef0c89736e61b5633 Mon Sep 17 00:00:00 2001 From: Thomas Gnandt <thomas.gnandt@mayflower.de> Date: Fri, 17 Mar 2023 09:31:20 +0100 Subject: [PATCH 1/5] detect override of deprecated property --- rules.neon | 6 ++ .../OverrideDeprecatedPropertyRule.php | 79 +++++++++++++++++++ .../OverrideDeprecatedPropertyRuleTest.php | 32 ++++++++ .../data/override-deprecated-property.php | 38 +++++++++ 4 files changed, 155 insertions(+) create mode 100644 src/Rules/Deprecations/OverrideDeprecatedPropertyRule.php create mode 100644 tests/Rules/Deprecations/OverrideDeprecatedPropertyRuleTest.php create mode 100644 tests/Rules/Deprecations/data/override-deprecated-property.php diff --git a/rules.neon b/rules.neon index 704579b..a045d8f 100644 --- a/rules.neon +++ b/rules.neon @@ -15,6 +15,8 @@ services: class: PHPStan\Rules\Deprecations\DefaultDeprecatedScopeResolver tags: - phpstan.deprecations.deprecatedScopeResolver + - + class: PHPStan\Rules\Deprecations\OverrideDeprecatedPropertyRule rules: - PHPStan\Rules\Deprecations\AccessDeprecatedPropertyRule @@ -33,3 +35,7 @@ rules: - PHPStan\Rules\Deprecations\TypeHintDeprecatedInFunctionSignatureRule - PHPStan\Rules\Deprecations\UsageOfDeprecatedCastRule - PHPStan\Rules\Deprecations\UsageOfDeprecatedTraitRule + +conditionalTags: + PHPStan\Rules\Deprecations\OverrideDeprecatedPropertyRule: + phpstan.rules.rule: %featureToggles.bleedingEdge% diff --git a/src/Rules/Deprecations/OverrideDeprecatedPropertyRule.php b/src/Rules/Deprecations/OverrideDeprecatedPropertyRule.php new file mode 100644 index 0000000..e88cbad --- /dev/null +++ b/src/Rules/Deprecations/OverrideDeprecatedPropertyRule.php @@ -0,0 +1,79 @@ +<?php declare(strict_types = 1); + +namespace PHPStan\Rules\Deprecations; + +use PhpParser\Node; +use PhpParser\Node\Stmt\Property; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use function sprintf; + +/** + * @implements Rule<Property> + */ +class OverrideDeprecatedPropertyRule implements Rule +{ + + /** @var DeprecatedScopeHelper */ + private $deprecatedScopeHelper; + + public function __construct(DeprecatedScopeHelper $deprecatedScopeHelper) + { + $this->deprecatedScopeHelper = $deprecatedScopeHelper; + } + + public function getNodeType(): string + { + return Property::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->deprecatedScopeHelper->isScopeDeprecated($scope)) { + return []; + } + + if (!$scope->isInClass()) { + return []; + } + + if ($node->isPrivate()) { + return []; + } + + $class = $scope->getClassReflection(); + + $parents = $class->getParents(); + + $propertyName = (string) $node->props[0]->name; + + $property = $class->getProperty($propertyName, $scope); + + if ($property->isDeprecated()->no()) { + return []; + } + + foreach ($parents as $parent) { + if (!$parent->hasProperty($propertyName)) { + continue; + } + + $parentProperty = $parent->getProperty($propertyName, $scope); + + if (!$parentProperty->isDeprecated()->yes()) { + return []; + } + + return [RuleErrorBuilder::message(sprintf( + 'Class %s overrides deprecated property %s of class %s.', + $class->getName(), + $propertyName, + $parent->getName() + ))->identifier('property.deprecated')->build()]; + } + + return []; + } + +} diff --git a/tests/Rules/Deprecations/OverrideDeprecatedPropertyRuleTest.php b/tests/Rules/Deprecations/OverrideDeprecatedPropertyRuleTest.php new file mode 100644 index 0000000..9664768 --- /dev/null +++ b/tests/Rules/Deprecations/OverrideDeprecatedPropertyRuleTest.php @@ -0,0 +1,32 @@ +<?php declare(strict_types = 1); + +namespace PHPStan\Rules\Deprecations; + +use PHPStan\Rules\Rule; +use PHPStan\Testing\RuleTestCase; + +/** + * @extends RuleTestCase<OverrideDeprecatedPropertyRule> + */ +class OverrideDeprecatedPropertyRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new OverrideDeprecatedPropertyRule(new DeprecatedScopeHelper([new DefaultDeprecatedScopeResolver()])); + } + + public function testDeprecatedPropertyOverride(): void + { + $this->analyse( + [__DIR__ . '/data/override-deprecated-property.php'], + [ + [ + 'Class OverrideDeprecatedProperty\Child overrides deprecated property deprecatedProperty of class OverrideDeprecatedProperty\Ancestor.', + 25, + ], + ] + ); + } + +} diff --git a/tests/Rules/Deprecations/data/override-deprecated-property.php b/tests/Rules/Deprecations/data/override-deprecated-property.php new file mode 100644 index 0000000..fbcc885 --- /dev/null +++ b/tests/Rules/Deprecations/data/override-deprecated-property.php @@ -0,0 +1,38 @@ +<?php + +namespace OverrideDeprecatedProperty; + +class Ancestor +{ + /** + * @deprecated + */ + public $deprecatedProperty; + + /** + * @deprecated + */ + private $privateDeprecatedProperty; + + /** + * @deprecated + */ + public $explicitlyNotDeprecated; +} + +class Child extends Ancestor +{ + public $deprecatedProperty; + + private $privateDeprecatedProperty; + + /** + * @not-deprecated + */ + public $explicitlyNotDeprecated; +} + +class GrandChild extends Child +{ + public $explicitlyNotDeprecated; +} From 0a68ae11d32194cc8c6baa3d75c5500359d0176c Mon Sep 17 00:00:00 2001 From: Thomas Gnandt <thomas.gnandt@mayflower.de> Date: Fri, 31 Mar 2023 11:50:00 +0200 Subject: [PATCH 2/5] detect override of deprecated method --- rules.neon | 4 + .../OverrideDeprecatedMethodRule.php | 84 +++++++++++++++++++ .../OverrideDeprecatedMethodRuleTest.php | 37 ++++++++ .../data/override-deprecated-method.php | 56 +++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 src/Rules/Deprecations/OverrideDeprecatedMethodRule.php create mode 100644 tests/Rules/Deprecations/OverrideDeprecatedMethodRuleTest.php create mode 100644 tests/Rules/Deprecations/data/override-deprecated-method.php diff --git a/rules.neon b/rules.neon index a045d8f..0d7efba 100644 --- a/rules.neon +++ b/rules.neon @@ -17,6 +17,8 @@ services: - phpstan.deprecations.deprecatedScopeResolver - class: PHPStan\Rules\Deprecations\OverrideDeprecatedPropertyRule + - + class: PHPStan\Rules\Deprecations\OverrideDeprecatedMethodRule rules: - PHPStan\Rules\Deprecations\AccessDeprecatedPropertyRule @@ -39,3 +41,5 @@ rules: conditionalTags: PHPStan\Rules\Deprecations\OverrideDeprecatedPropertyRule: phpstan.rules.rule: %featureToggles.bleedingEdge% + PHPStan\Rules\Deprecations\OverrideDeprecatedMethodRule: + phpstan.rules.rule: %featureToggles.bleedingEdge% diff --git a/src/Rules/Deprecations/OverrideDeprecatedMethodRule.php b/src/Rules/Deprecations/OverrideDeprecatedMethodRule.php new file mode 100644 index 0000000..f4d664f --- /dev/null +++ b/src/Rules/Deprecations/OverrideDeprecatedMethodRule.php @@ -0,0 +1,84 @@ +<?php declare(strict_types = 1); + +namespace PHPStan\Rules\Deprecations; + +use PhpParser\Node; +use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use function sprintf; + +/** + * @implements Rule<ClassMethod> + */ +class OverrideDeprecatedMethodRule implements Rule +{ + + /** @var DeprecatedScopeHelper */ + private $deprecatedScopeHelper; + + public function __construct(DeprecatedScopeHelper $deprecatedScopeHelper) + { + $this->deprecatedScopeHelper = $deprecatedScopeHelper; + } + + public function getNodeType(): string + { + return ClassMethod::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->deprecatedScopeHelper->isScopeDeprecated($scope)) { + return []; + } + + if (!$scope->isInClass()) { + return []; + } + + if ($node->isPrivate()) { + return []; + } + + $class = $scope->getClassReflection(); + + $ancestors = $class->getAncestors(); + + $methodName = (string) $node->name; + + $method = $class->getMethod($methodName, $scope); + + if ($method->isDeprecated()->no()) { + return []; + } + + foreach ($ancestors as $ancestor) { + if ($ancestor === $class) { + continue; + } + + if (!$ancestor->hasMethod($methodName)) { + continue; + } + + $ancestorMethod = $ancestor->getMethod($methodName, $scope); + + if (!$ancestorMethod->isDeprecated()->yes()) { + return []; + } + + return [RuleErrorBuilder::message(sprintf( + 'Class %s overrides deprecated method %s of %s %s.', + $class->getName(), + $methodName, + $ancestor->isInterface() ? 'interface' : 'class', + $ancestor->getName() + ))->identifier('method.deprecated')->build()]; + } + + return []; + } + +} diff --git a/tests/Rules/Deprecations/OverrideDeprecatedMethodRuleTest.php b/tests/Rules/Deprecations/OverrideDeprecatedMethodRuleTest.php new file mode 100644 index 0000000..126874c --- /dev/null +++ b/tests/Rules/Deprecations/OverrideDeprecatedMethodRuleTest.php @@ -0,0 +1,37 @@ +<?php declare(strict_types = 1); + +namespace PHPStan\Rules\Deprecations; + +use PHPStan\Rules\Rule; +use PHPStan\Testing\RuleTestCase; + +/** + * @extends RuleTestCase<OverrideDeprecatedMethodRule> + */ +class OverrideDeprecatedMethodRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new OverrideDeprecatedMethodRule(new DeprecatedScopeHelper([new DefaultDeprecatedScopeResolver()])); + } + + public function testDeprecatedMethodOverride(): void + { + $this->analyse( + [__DIR__ . '/data/override-deprecated-method.php'], + [ + [ + 'Class OverrideDeprecatedMethod\Child overrides deprecated method deprecatedMethod of class OverrideDeprecatedMethod\Ancestor.', + 36, + ], + + [ + 'Class OverrideDeprecatedMethod\Child overrides deprecated method deprecatedInInterface of interface OverrideDeprecatedMethod\Deprecated.', + 48, + ], + ] + ); + } + +} diff --git a/tests/Rules/Deprecations/data/override-deprecated-method.php b/tests/Rules/Deprecations/data/override-deprecated-method.php new file mode 100644 index 0000000..635878f --- /dev/null +++ b/tests/Rules/Deprecations/data/override-deprecated-method.php @@ -0,0 +1,56 @@ +<?php + +namespace OverrideDeprecatedMethod; + +class Ancestor +{ + /** + * @deprecated + */ + public function deprecatedMethod(): void + {} + + /** + * @deprecated + */ + private function privateDeprecatedMethod(): void + {} + + /** + * @deprecated + */ + public function explicitlyNotDeprecatedMethod(): void + {} +} + +interface Deprecated +{ + /** + * @deprecated + */ + public function deprecatedInInterface(): void; +} + +class Child extends Ancestor implements Deprecated +{ + public function deprecatedMethod(): void + {} + + private function privateDeprecatedMethod(): void + {} + + /** + * @not-deprecated + */ + public function explicitlyNotDeprecatedMethod(): void + {} + + public function deprecatedInInterface(): void + {} +} + +class GrandChild extends Child +{ + public function explicitlyNotDeprecatedMethod(): void + {} +} From db87881f689896b30462c684018f755a425dfb7f Mon Sep 17 00:00:00 2001 From: Thomas Gnandt <thomas.gnandt@mayflower.de> Date: Fri, 31 Mar 2023 11:58:38 +0200 Subject: [PATCH 3/5] detect override of deprecated constant --- rules.neon | 4 + .../OverrideDeprecatedConstantRule.php | 73 +++++++++++++++++++ .../OverrideDeprecatedConstantRuleTest.php | 32 ++++++++ .../data/override-deprecated-constant.php | 23 ++++++ 4 files changed, 132 insertions(+) create mode 100644 src/Rules/Deprecations/OverrideDeprecatedConstantRule.php create mode 100644 tests/Rules/Deprecations/OverrideDeprecatedConstantRuleTest.php create mode 100644 tests/Rules/Deprecations/data/override-deprecated-constant.php diff --git a/rules.neon b/rules.neon index 0d7efba..2a88088 100644 --- a/rules.neon +++ b/rules.neon @@ -19,6 +19,8 @@ services: class: PHPStan\Rules\Deprecations\OverrideDeprecatedPropertyRule - class: PHPStan\Rules\Deprecations\OverrideDeprecatedMethodRule + - + class: PHPStan\Rules\Deprecations\OverrideDeprecatedConstantRule rules: - PHPStan\Rules\Deprecations\AccessDeprecatedPropertyRule @@ -43,3 +45,5 @@ conditionalTags: phpstan.rules.rule: %featureToggles.bleedingEdge% PHPStan\Rules\Deprecations\OverrideDeprecatedMethodRule: phpstan.rules.rule: %featureToggles.bleedingEdge% + PHPStan\Rules\Deprecations\OverrideDeprecatedConstantRule: + phpstan.rules.rule: %featureToggles.bleedingEdge% diff --git a/src/Rules/Deprecations/OverrideDeprecatedConstantRule.php b/src/Rules/Deprecations/OverrideDeprecatedConstantRule.php new file mode 100644 index 0000000..3627e6a --- /dev/null +++ b/src/Rules/Deprecations/OverrideDeprecatedConstantRule.php @@ -0,0 +1,73 @@ +<?php declare(strict_types = 1); + +namespace PHPStan\Rules\Deprecations; + +use PhpParser\Node; +use PhpParser\Node\Stmt\ClassConst; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use function sprintf; + +/** + * @implements Rule<ClassConst> + */ +class OverrideDeprecatedConstantRule implements Rule +{ + + /** @var DeprecatedScopeHelper */ + private $deprecatedScopeHelper; + + public function __construct(DeprecatedScopeHelper $deprecatedScopeHelper) + { + $this->deprecatedScopeHelper = $deprecatedScopeHelper; + } + + public function getNodeType(): string + { + return ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->deprecatedScopeHelper->isScopeDeprecated($scope)) { + return []; + } + + if (!$scope->isInClass()) { + return []; + } + + if ($node->isPrivate()) { + return []; + } + + $class = $scope->getClassReflection(); + + $parents = $class->getParents(); + + $name = (string) $node->consts[0]->name; + + foreach ($parents as $parent) { + if (!$parent->hasConstant($name)) { + continue; + } + + $parentConst = $parent->getConstant($name); + + if (!$parentConst->isDeprecated()->yes()) { + return []; + } + + return [RuleErrorBuilder::message(sprintf( + 'Class %s overrides deprecated const %s of class %s.', + $class->getName(), + $name, + $parent->getName() + ))->identifier('constant.deprecated')->build()]; + } + + return []; + } + +} diff --git a/tests/Rules/Deprecations/OverrideDeprecatedConstantRuleTest.php b/tests/Rules/Deprecations/OverrideDeprecatedConstantRuleTest.php new file mode 100644 index 0000000..114d965 --- /dev/null +++ b/tests/Rules/Deprecations/OverrideDeprecatedConstantRuleTest.php @@ -0,0 +1,32 @@ +<?php declare(strict_types = 1); + +namespace PHPStan\Rules\Deprecations; + +use PHPStan\Rules\Rule; +use PHPStan\Testing\RuleTestCase; + +/** + * @extends RuleTestCase<OverrideDeprecatedConstantRule> + */ +class OverrideDeprecatedConstantRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new OverrideDeprecatedConstantRule(new DeprecatedScopeHelper([new DefaultDeprecatedScopeResolver()])); + } + + public function testDeprecatedConstantOverride(): void + { + $this->analyse( + [__DIR__ . '/data/override-deprecated-constant.php'], + [ + [ + 'Class OverrideDeprecatedConstant\Child overrides deprecated const DEPRECATED of class OverrideDeprecatedConstant\Ancestor.', + 20, + ], + ] + ); + } + +} diff --git a/tests/Rules/Deprecations/data/override-deprecated-constant.php b/tests/Rules/Deprecations/data/override-deprecated-constant.php new file mode 100644 index 0000000..e69ffb5 --- /dev/null +++ b/tests/Rules/Deprecations/data/override-deprecated-constant.php @@ -0,0 +1,23 @@ +<?php + +namespace OverrideDeprecatedConstant; + +class Ancestor +{ + /** + * @deprecated + */ + public const DEPRECATED = ''; + + /** + * @deprecated + */ + private const PRIVATE_DEPRECATED = ''; +} + +class Child extends Ancestor +{ + public const DEPRECATED = ''; + + private const PRIVATE_DEPRECATED = ''; +} From 2ab527619f689b1d5f28b13be9d6b31c6425cb33 Mon Sep 17 00:00:00 2001 From: Thomas Gnandt <thomas.gnandt@mayflower.de> Date: Mon, 8 Jan 2024 09:43:29 +0100 Subject: [PATCH 4/5] handle deprected methods in traits --- composer.json | 2 +- .../OverrideDeprecatedMethodRule.php | 4 ++++ .../OverrideDeprecatedMethodRuleTest.php | 13 ++++++++++--- .../data/override-deprecated-method.php | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 149508f..11e1ea5 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.3" + "phpstan/phpstan": "^1.10.24" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", diff --git a/src/Rules/Deprecations/OverrideDeprecatedMethodRule.php b/src/Rules/Deprecations/OverrideDeprecatedMethodRule.php index f4d664f..07ed031 100644 --- a/src/Rules/Deprecations/OverrideDeprecatedMethodRule.php +++ b/src/Rules/Deprecations/OverrideDeprecatedMethodRule.php @@ -59,6 +59,10 @@ public function processNode(Node $node, Scope $scope): array continue; } + if ($ancestor->isTrait()) { + continue; + } + if (!$ancestor->hasMethod($methodName)) { continue; } diff --git a/tests/Rules/Deprecations/OverrideDeprecatedMethodRuleTest.php b/tests/Rules/Deprecations/OverrideDeprecatedMethodRuleTest.php index 126874c..bc784f4 100644 --- a/tests/Rules/Deprecations/OverrideDeprecatedMethodRuleTest.php +++ b/tests/Rules/Deprecations/OverrideDeprecatedMethodRuleTest.php @@ -23,12 +23,19 @@ public function testDeprecatedMethodOverride(): void [ [ 'Class OverrideDeprecatedMethod\Child overrides deprecated method deprecatedMethod of class OverrideDeprecatedMethod\Ancestor.', - 36, + 49, ], - [ 'Class OverrideDeprecatedMethod\Child overrides deprecated method deprecatedInInterface of interface OverrideDeprecatedMethod\Deprecated.', - 48, + 61, + ], + [ + 'Class OverrideDeprecatedMethod\Child overrides deprecated method deprecatedInTrait of class OverrideDeprecatedMethod\Ancestor.', + 64, + ], + [ + 'Class OverrideDeprecatedMethod\GrandChild overrides deprecated method deprecatedInChild of class OverrideDeprecatedMethod\Child.', + 73, ], ] ); diff --git a/tests/Rules/Deprecations/data/override-deprecated-method.php b/tests/Rules/Deprecations/data/override-deprecated-method.php index 635878f..7ac3216 100644 --- a/tests/Rules/Deprecations/data/override-deprecated-method.php +++ b/tests/Rules/Deprecations/data/override-deprecated-method.php @@ -2,8 +2,18 @@ namespace OverrideDeprecatedMethod; +trait DeprecationTrait +{ + /** + * @deprecated + */ + public function deprecatedInTrait(): void + {} +} + class Ancestor { + use DeprecationTrait; /** * @deprecated */ @@ -33,6 +43,9 @@ public function deprecatedInInterface(): void; class Child extends Ancestor implements Deprecated { + use DeprecationTrait { + deprecatedInTrait as deprecatedInChild; + } public function deprecatedMethod(): void {} @@ -47,10 +60,16 @@ public function explicitlyNotDeprecatedMethod(): void public function deprecatedInInterface(): void {} + + public function deprecatedInTrait(): void + {} } class GrandChild extends Child { public function explicitlyNotDeprecatedMethod(): void {} + + public function deprecatedInChild(): void + {} } From b924bc469bd4f4fea2265e58aec0d8e3d842d5b6 Mon Sep 17 00:00:00 2001 From: Thomas Gnandt <thomas.gnandt@mayflower.de> Date: Mon, 8 Jan 2024 10:54:38 +0100 Subject: [PATCH 5/5] remove unnecessary null check --- .../Deprecations/InheritanceOfDeprecatedInterfaceRule.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Rules/Deprecations/InheritanceOfDeprecatedInterfaceRule.php b/src/Rules/Deprecations/InheritanceOfDeprecatedInterfaceRule.php index 8acab06..c47fb89 100644 --- a/src/Rules/Deprecations/InheritanceOfDeprecatedInterfaceRule.php +++ b/src/Rules/Deprecations/InheritanceOfDeprecatedInterfaceRule.php @@ -31,10 +31,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if ($node->extends === null) { - return []; - } - $interfaceName = isset($node->namespacedName) ? (string) $node->namespacedName : (string) $node->name;