diff --git a/config/set/type-declaration-docblocks.php b/config/set/type-declaration-docblocks.php index 07d0b81210c..c50cb08831d 100644 --- a/config/set/type-declaration-docblocks.php +++ b/config/set/type-declaration-docblocks.php @@ -10,6 +10,7 @@ use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDataProviderRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDimFetchAccessRector; +use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForArrayDimAssignedObjectRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector; @@ -32,6 +33,7 @@ AddReturnArrayDocblockBasedOnArrayMapRector::class, AddReturnDocblockForScalarArrayFromAssignsRector::class, DocblockReturnArrayFromDirectArrayInstanceRector::class, + AddReturnDocblockForArrayDimAssignedObjectRector::class, // tests AddParamArrayDocblockFromDataProviderRector::class, diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/AddReturnDocblockForArrayDimAssignedObjectRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/AddReturnDocblockForArrayDimAssignedObjectRectorTest.php new file mode 100644 index 00000000000..e8a62bef8a6 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/AddReturnDocblockForArrayDimAssignedObjectRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/assign_dim_fetch.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/assign_dim_fetch.php.inc new file mode 100644 index 00000000000..8e2a3b9727d --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/assign_dim_fetch.php.inc @@ -0,0 +1,44 @@ + +----- + diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/include_method_dim_fetch.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/include_method_dim_fetch.php.inc new file mode 100644 index 00000000000..b7920906451 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/include_method_dim_fetch.php.inc @@ -0,0 +1,54 @@ +createSomeItem(); + } + + return $items; + } + + private function createSomeItem(): SomeItem + { + return new SomeItem(); + } +} + +?> +----- +createSomeItem(); + } + + return $items; + } + + private function createSomeItem(): SomeItem + { + return new SomeItem(); + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/skip_default_assign.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/skip_default_assign.php.inc new file mode 100644 index 00000000000..8ac6894c774 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/skip_default_assign.php.inc @@ -0,0 +1,18 @@ +createSomeItem(); + } + + return $items; + } + + /** + * @return FakeItem + */ + private function createSomeItem() + { + return new SomeItem(); + } +} diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/skip_property_fetch_in_case_of_another_assign.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/skip_property_fetch_in_case_of_another_assign.php.inc new file mode 100644 index 00000000000..4acdc03f75f --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Fixture/skip_property_fetch_in_case_of_another_assign.php.inc @@ -0,0 +1,18 @@ +items = []; + foreach ($keys as $key) { + $this->items[] = new SomeItem($key); + } + + return $this->items; + } +} diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Source/SomeItem.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Source/SomeItem.php new file mode 100644 index 00000000000..bbdd333ddbd --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector/Source/SomeItem.php @@ -0,0 +1,7 @@ +withRules([AddReturnDocblockForArrayDimAssignedObjectRector::class]); diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/mixed_from_unserialize.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/mixed_from_unserialize.php.inc index 42f536c8628..fefaa792253 100644 --- a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/mixed_from_unserialize.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/mixed_from_unserialize.php.inc @@ -37,4 +37,4 @@ class MixedFromUnserialize } } -?> \ No newline at end of file +?> diff --git a/rules/Privatization/Guard/ParentPropertyLookupGuard.php b/rules/Privatization/Guard/ParentPropertyLookupGuard.php index dc93446a0c8..d45a508d8ca 100644 --- a/rules/Privatization/Guard/ParentPropertyLookupGuard.php +++ b/rules/Privatization/Guard/ParentPropertyLookupGuard.php @@ -128,7 +128,11 @@ private function isFoundInMethodStmts(array $stmts, string $propertyName, string private function isGuardedByParents(array $parentClassReflections, string $propertyName, string $className): bool { foreach ($parentClassReflections as $parentClassReflection) { - if ($parentClassReflection->hasInstanceProperty($propertyName) || $parentClassReflection->hasStaticProperty($propertyName)) { + if ($parentClassReflection->hasInstanceProperty($propertyName)) { + return false; + } + + if ($parentClassReflection->hasStaticProperty($propertyName)) { return false; } diff --git a/rules/TypeDeclaration/NodeAnalyzer/ReturnTypeAnalyzer/StrictReturnNewAnalyzer.php b/rules/TypeDeclaration/NodeAnalyzer/ReturnTypeAnalyzer/StrictReturnNewAnalyzer.php index a3aff70f2a2..1d8166cbf07 100644 --- a/rules/TypeDeclaration/NodeAnalyzer/ReturnTypeAnalyzer/StrictReturnNewAnalyzer.php +++ b/rules/TypeDeclaration/NodeAnalyzer/ReturnTypeAnalyzer/StrictReturnNewAnalyzer.php @@ -4,7 +4,6 @@ namespace Rector\TypeDeclaration\NodeAnalyzer\ReturnTypeAnalyzer; -use PHPStan\Type\ObjectWithoutClassType; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\New_; @@ -14,6 +13,7 @@ use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Function_; use PHPStan\Type\ObjectType; +use PHPStan\Type\ObjectWithoutClassType; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\PhpParser\Node\BetterNodeFinder; diff --git a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector.php new file mode 100644 index 00000000000..44045adb972 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForArrayDimAssignedObjectRector.php @@ -0,0 +1,228 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class]; + } + + /** + * @param ClassMethod|Function_ $node + */ + public function refactor(Node $node): ?Node + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + $returnType = $phpDocInfo->getReturnType(); + + if (! $returnType instanceof MixedType || $returnType->isExplicitMixed()) { + return null; + } + + // definitely not an array return + if ($node->returnType instanceof Node && ! $this->isName($node->returnType, 'array')) { + return null; + } + + $onlyReturnWithExpr = $this->returnNodeFinder->findOnlyReturnWithExpr($node); + if (! $onlyReturnWithExpr instanceof Return_ || ! $onlyReturnWithExpr->expr instanceof Variable) { + return null; + } + + // is expr only used to array dim assign? + + $returnedType = $this->getType($onlyReturnWithExpr->expr); + + $returnedVariableName = $this->getName($onlyReturnWithExpr->expr); + if (! is_string($returnedVariableName)) { + return null; + } + + $isVariableExclusivelyArrayDimAssigned = true; + + $this->traverseNodesWithCallable((array) $node->stmts, function ($node) use ( + $returnedVariableName, + &$isVariableExclusivelyArrayDimAssigned + ): ?int { + if ($node instanceof Assign) { + if ($node->var instanceof ArrayDimFetch) { + $arrayDimFetch = $node->var; + + if (! $arrayDimFetch->var instanceof Variable) { + $isVariableExclusivelyArrayDimAssigned = false; + return null; + } + + if ($this->isName($arrayDimFetch->var, $returnedVariableName)) { + if ($arrayDimFetch->dim instanceof Expr) { + $isVariableExclusivelyArrayDimAssigned = false; + } + + $assignedType = $this->getType($node->expr); + if (! $assignedType instanceof ObjectType) { + $isVariableExclusivelyArrayDimAssigned = false; + } + + if ($assignedType instanceof NonExistingObjectType) { + $isVariableExclusivelyArrayDimAssigned = false; + } + + // ignore lower value + return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } + } + + if ($node->var instanceof Variable && $this->isName( + $node->var, + $returnedVariableName + ) && $node->expr instanceof Array_) { + if ($node->expr->items === []) { + // ignore empty array assignment + return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } + + $isVariableExclusivelyArrayDimAssigned = false; + } + } + + if ($node instanceof Return_ && $node->expr instanceof Variable) { + if ($this->isName($node->expr, $returnedVariableName)) { + // ignore lower value + return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } + + $isVariableExclusivelyArrayDimAssigned = false; + } + + if ($node instanceof Variable && $this->isName($node, $returnedVariableName)) { + $isVariableExclusivelyArrayDimAssigned = false; + } + + return null; + }); + + if ($isVariableExclusivelyArrayDimAssigned === false) { + return null; + } + + $arrayObjectType = $this->matchArrayObjectType($returnedType); + if (! $arrayObjectType instanceof ObjectType) { + return null; + } + + //$arrayReturnDocType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($arrayObjectType); + + $objectTypeArrayType = new ArrayType(new MixedType(), $arrayObjectType); + $hasChanged = $this->phpDocTypeChanger->changeReturnType($node, $phpDocInfo, $objectTypeArrayType); + if (! $hasChanged) { + return null; + } + + return $node; + } + + private function matchArrayObjectType(Type $returnedType): ?Type + { + if ($returnedType instanceof IntersectionType) { + foreach ($returnedType->getTypes() as $intersectionedType) { + if ($intersectionedType instanceof AccessoryArrayListType) { + continue; + } + + if ($intersectionedType instanceof ArrayType && $intersectionedType->getItemType() instanceof ObjectType) { + return $intersectionedType->getItemType(); + } + + return null; + } + } + + return null; + } +} diff --git a/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php b/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php index e3067597f3b..bc3dd9ef682 100644 --- a/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php +++ b/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php @@ -30,8 +30,10 @@ public function __construct( ) { } - public function generalize(ConstantArrayType $constantArrayType, bool $isFresh = true): GenericTypeNode|ArrayShapeNode - { + public function generalize( + ConstantArrayType $constantArrayType, + bool $isFresh = true + ): GenericTypeNode|ArrayShapeNode { if ($isFresh) { $this->currentNesting = 0; } else { @@ -64,8 +66,10 @@ public function generalize(ConstantArrayType $constantArrayType, bool $isFresh = return $this->createArrayGenericTypeNode($genericKeyType, $genericItemType); } - private function createArrayGenericTypeNode(Type $keyType, Type|GenericTypeNode|ArrayShapeNode $itemType): GenericTypeNode - { + private function createArrayGenericTypeNode( + Type $keyType, + Type|GenericTypeNode|ArrayShapeNode $itemType + ): GenericTypeNode { $keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType); if ($itemType instanceof Type) {