diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/DisableReturnAbstractOrInterfaceTest.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/DisableReturnAbstractOrInterfaceTest.php new file mode 100644 index 00000000000..7de2cba4981 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/DisableReturnAbstractOrInterfaceTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/FixtureSkipReturnAbstractOrInterface'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/disable_return_abstract_or_interface_configured_rule.php'; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/FixtureSkipReturnAbstractOrInterface/skip_abstract_class.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/FixtureSkipReturnAbstractOrInterface/skip_abstract_class.php.inc new file mode 100644 index 00000000000..19d3903eba2 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/FixtureSkipReturnAbstractOrInterface/skip_abstract_class.php.inc @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/FixtureSkipReturnAbstractOrInterface/skip_return_interface.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/FixtureSkipReturnAbstractOrInterface/skip_return_interface.php.inc new file mode 100644 index 00000000000..285a861e52e --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/FixtureSkipReturnAbstractOrInterface/skip_return_interface.php.inc @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/config/disable_return_abstract_or_interface_configured_rule.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/config/disable_return_abstract_or_interface_configured_rule.php new file mode 100644 index 00000000000..188493cdc09 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/config/disable_return_abstract_or_interface_configured_rule.php @@ -0,0 +1,12 @@ +ruleWithConfiguration(NarrowObjectReturnTypeRector::class, [ + NarrowObjectReturnTypeRector::IS_ALLOW_ABSTRACT_AND_INTERFACE => false, + ]); +}; diff --git a/rules/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector.php b/rules/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector.php index 3bae5bfd486..1de745d7812 100644 --- a/rules/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector.php +++ b/rules/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector.php @@ -87,6 +87,7 @@ public function refactor(Node $node): array|Expr|null|int if (! $node->var instanceof ArrayDimFetch) { return null; } + return $this->createExplicitMethodCall($node->var, 'set', $node->expr); } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index f19253e10a0..75ab5d9708b 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -18,13 +18,14 @@ use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\ValueObject\Type\FullyQualifiedIdentifierTypeNode; use Rector\Comments\NodeDocBlock\DocBlockUpdater; +use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\NodeTypeResolver\TypeComparator\TypeComparator; use Rector\PhpParser\AstResolver; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\StaticTypeMapper\StaticTypeMapper; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** @@ -32,8 +33,15 @@ * * @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\NarrowObjectReturnTypeRectorTest */ -final class NarrowObjectReturnTypeRector extends AbstractRector +final class NarrowObjectReturnTypeRector extends AbstractRector implements ConfigurableRectorInterface { + /** + * @var string + */ + public const IS_ALLOW_ABSTRACT_AND_INTERFACE = 'is_allow_abstract_and_interface'; + + private bool $isAllowAbstractAndInterface = true; + public function __construct( private readonly BetterNodeFinder $betterNodeFinder, private readonly ReflectionResolver $reflectionResolver, @@ -50,7 +58,7 @@ public function getRuleDefinition(): RuleDefinition return new RuleDefinition( 'Narrows return type from generic object or parent class to specific class in final classes/methods', [ - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' final class TalkFactory extends AbstractFactory { @@ -70,8 +78,12 @@ protected function build(): ConferenceTalk } } CODE_SAMPLE + , + [ + self::IS_ALLOW_ABSTRACT_AND_INTERFACE => true, + ] ), - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' final class TalkFactory { @@ -91,6 +103,10 @@ public function createConferenceTalk(): ConferenceTalk } } CODE_SAMPLE + , + [ + self::IS_ALLOW_ABSTRACT_AND_INTERFACE => true, + ] ), ], ); @@ -120,6 +136,10 @@ public function refactor(Node $node): ?Node return null; } + if (! $this->isAllowedReturnsAbstractOrInterface($returnType->toString())) { + return null; + } + if (! $classReflection->isFinalByKeyword() && ! $node->isFinal()) { return null; } @@ -159,6 +179,34 @@ public function refactor(Node $node): ?Node return $node; } + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + $this->isAllowAbstractAndInterface = $configuration[self::IS_ALLOW_ABSTRACT_AND_INTERFACE] ?? true; + } + + private function isAllowedReturnsAbstractOrInterface(string $actualReturnClass): bool + { + if ($this->isAllowAbstractAndInterface) { + return true; + } + + $actualObjectType = new ObjectType($actualReturnClass); + $classReflection = $actualObjectType->getClassReflection(); + + if (! $classReflection instanceof ClassReflection) { + return false; + } + + if ($classReflection->isInterface()) { + return false; + } + + return ! $classReflection->isAbstract(); + } + private function updateDocblock(ClassMethod $classMethod, string $actualReturnClass): void { $phpDocInfo = $this->phpDocInfoFactory->createFromNode($classMethod);