diff --git a/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/CallbackTest.php b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/CallbackTest.php new file mode 100644 index 00000000000..8d2b0c4467f --- /dev/null +++ b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/CallbackTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/FixtureCallback'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule_callback.php'; + } +} diff --git a/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/FixtureCallback/callback_can_skip.php.inc b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/FixtureCallback/callback_can_skip.php.inc new file mode 100644 index 00000000000..41c9b0ec737 --- /dev/null +++ b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/FixtureCallback/callback_can_skip.php.inc @@ -0,0 +1,25 @@ + +----- + diff --git a/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/config/configured_rule_callback.php b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/config/configured_rule_callback.php new file mode 100644 index 00000000000..21d153599a3 --- /dev/null +++ b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/config/configured_rule_callback.php @@ -0,0 +1,23 @@ +withConfiguredRule(PrivatizeFinalClassMethodRector::class, [ + PrivatizeFinalClassMethodRector::SHOULD_SKIP_CALLBACK => static function ( + ClassMethod $classMethod, + ClassReflection $classReflection, + ): bool { + if (! str_contains($classReflection->getName(), 'FinalClassWithProtectedMethods')) { + return false; + } + + + return str_contains($classMethod->name->toString(), 'shouldBeProtected'); + }, + ]); diff --git a/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/CallbackTest.php b/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/CallbackTest.php new file mode 100644 index 00000000000..d8577551c5e --- /dev/null +++ b/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/CallbackTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/FixtureCallback'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule_callback.php'; + } +} diff --git a/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/FixtureCallback/callback_can_skip.php.inc b/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/FixtureCallback/callback_can_skip.php.inc new file mode 100644 index 00000000000..4370deb27bf --- /dev/null +++ b/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/FixtureCallback/callback_can_skip.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/config/configured_rule_callback.php b/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/config/configured_rule_callback.php new file mode 100644 index 00000000000..880a7fb281d --- /dev/null +++ b/rules-tests/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector/config/configured_rule_callback.php @@ -0,0 +1,26 @@ +withConfiguredRule(PrivatizeFinalClassPropertyRector::class, [ + PrivatizeFinalClassPropertyRector::SHOULD_SKIP_CALLBACK => static function ( + Property|string $property, + ClassReflection $classReflection, + ): bool { + if (! str_contains($classReflection->getName(), 'FinalClassWithProtectedProperties')) { + return false; + } + + $name = $property instanceof Property + ? (string) $property->props[0]->name + : $property; + + return str_contains($name, 'shouldBeProtected'); + }, + ]); diff --git a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php index 80757ceb7bb..53f2e61765d 100644 --- a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php +++ b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php @@ -9,20 +9,33 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Reflection\ClassReflection; +use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PHPStan\ScopeFetcher; use Rector\Privatization\Guard\OverrideByParentClassGuard; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard; use Rector\Rector\AbstractRector; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\PrivatizeFinalClassMethodRectorTest + * @see \Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\CallbackTest */ -final class PrivatizeFinalClassMethodRector extends AbstractRector +final class PrivatizeFinalClassMethodRector extends AbstractRector implements ConfigurableRectorInterface { + /** + * @api + * @var string + */ + public const SHOULD_SKIP_CALLBACK = 'should_skip_callback'; + + /** + * @var ?callable(ClassMethod, ClassReflection): bool + */ + private $shouldSkipCallback = null; + public function __construct( private readonly ClassMethodVisibilityGuard $classMethodVisibilityGuard, private readonly VisibilityManipulator $visibilityManipulator, @@ -36,8 +49,14 @@ public function getRuleDefinition(): RuleDefinition return new RuleDefinition( 'Change protected class method to private if possible', [ - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' +final class SomeOtherClass +{ + protected function someMethod() + { + } +} final class SomeClass { protected function someMethod() @@ -47,6 +66,12 @@ protected function someMethod() CODE_SAMPLE , <<<'CODE_SAMPLE' +final class SomeOtherClass +{ + protected function someMethod() + { + } +} final class SomeClass { private function someMethod() @@ -54,11 +79,28 @@ private function someMethod() } } CODE_SAMPLE + , + [ + self::SHOULD_SKIP_CALLBACK => static function ( + ClassMethod $classMethod, + ClassReflection $classReflection, + ): bool { + return $classReflection->is('SomeOtherClass'); + }, + ], ), ] ); } + /** + * @param mixed[] $configuration + */ + public function configure(array $configuration): void + { + $this->shouldSkipCallback = $configuration[self::SHOULD_SKIP_CALLBACK] ?? null; + } + /** * @return array> */ @@ -107,6 +149,13 @@ public function refactor(Node $node): ?Node continue; } + if ( + is_callable($this->shouldSkipCallback) + && call_user_func($this->shouldSkipCallback, $classMethod, $classReflection) + ) { + continue; + } + $this->visibilityManipulator->makePrivate($classMethod); $hasChanged = true; } diff --git a/rules/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector.php b/rules/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector.php index eacd9346fda..96706e53b98 100644 --- a/rules/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector.php +++ b/rules/Privatization/Rector/Property/PrivatizeFinalClassPropertyRector.php @@ -9,20 +9,33 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PHPStan\Reflection\ClassReflection; +use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Privatization\Guard\ParentPropertyLookupGuard; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\ValueObject\MethodName; use Rector\ValueObject\Visibility; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector\PrivatizeFinalClassPropertyRectorTest + * @see \Rector\Tests\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector\CallbackTest */ -final class PrivatizeFinalClassPropertyRector extends AbstractRector +final class PrivatizeFinalClassPropertyRector extends AbstractRector implements ConfigurableRectorInterface { + /** + * @api + * @var string + */ + public const SHOULD_SKIP_CALLBACK = 'should_skip_callback'; + + /** + * @var ?callable(Property|string, ClassReflection): bool + */ + private $shouldSkipCallback = null; + public function __construct( private readonly VisibilityManipulator $visibilityManipulator, private readonly ParentPropertyLookupGuard $parentPropertyLookupGuard, @@ -33,8 +46,12 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Change property to private if possible', [ - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' +final class SomeOtherClass +{ + protected $value; +} final class SomeClass { protected $value; @@ -42,15 +59,36 @@ final class SomeClass CODE_SAMPLE , <<<'CODE_SAMPLE' +final class SomeOtherClass +{ + protected $value; +} final class SomeClass { private $value; } CODE_SAMPLE + , + [ + self::SHOULD_SKIP_CALLBACK => static function ( + Property|string $property, + ClassReflection $classReflection, + ): bool { + return $classReflection->is('SomeOtherClass'); + }, + ], ), ]); } + /** + * @param mixed[] $configuration + */ + public function configure(array $configuration): void + { + $this->shouldSkipCallback = $configuration[self::SHOULD_SKIP_CALLBACK] ?? null; + } + /** * @return array> */ @@ -84,6 +122,13 @@ public function refactor(Node $node): ?Node continue; } + if ( + is_callable($this->shouldSkipCallback) + && call_user_func($this->shouldSkipCallback, $property, $classReflection) + ) { + continue; + } + $this->visibilityManipulator->makePrivate($property); $hasChanged = true; } @@ -99,7 +144,16 @@ public function refactor(Node $node): ?Node continue; } - if (! $this->parentPropertyLookupGuard->isLegal((string) $this->getName($param), $classReflection)) { + $property = (string) $this->getName($param); + + if (! $this->parentPropertyLookupGuard->isLegal($property, $classReflection)) { + continue; + } + + if ( + is_callable($this->shouldSkipCallback) + && call_user_func($this->shouldSkipCallback, $property, $classReflection) + ) { continue; }