diff --git a/tests/Issues/RepeatedThreeTimes/Fixture/fixture.php.inc b/tests/Issues/RepeatedThreeTimes/Fixture/fixture.php.inc new file mode 100644 index 00000000000..fd64bc884cc --- /dev/null +++ b/tests/Issues/RepeatedThreeTimes/Fixture/fixture.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/tests/Issues/RepeatedThreeTimes/RepeatedThreeTimesTest.php b/tests/Issues/RepeatedThreeTimes/RepeatedThreeTimesTest.php new file mode 100644 index 00000000000..d2e454bc70a --- /dev/null +++ b/tests/Issues/RepeatedThreeTimes/RepeatedThreeTimesTest.php @@ -0,0 +1,34 @@ +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/tests/Issues/RepeatedThreeTimes/Source/NarrowArrayCollectionDocblockRector.php b/tests/Issues/RepeatedThreeTimes/Source/NarrowArrayCollectionDocblockRector.php new file mode 100644 index 00000000000..5e71571fa68 --- /dev/null +++ b/tests/Issues/RepeatedThreeTimes/Source/NarrowArrayCollectionDocblockRector.php @@ -0,0 +1,78 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?ClassMethod + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if ($phpDocInfo === null) { + return null; + } + + $hasChanged = false; + foreach ($phpDocInfo->getParamTagValueNodes() as $paramTagValueNode) { + if (! $paramTagValueNode->type instanceof UnionTypeNode) { + continue; + } + + foreach ($paramTagValueNode->type->types as $key => $unionedTypeNode) { + $unionedType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($unionedTypeNode, $node); + if (! $unionedType->equals(new ObjectType(SomeArrayCollection::class))) { + continue; + } + + $paramTagValueNode->type->types[$key] = new FullyQualifiedIdentifierTypeNode(SomeCollection::class); + $hasChanged = true; + } + } + + if (! $hasChanged) { + return null; + } + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } +} diff --git a/tests/Issues/RepeatedThreeTimes/Source/NarrowParamUnionDocblockRector.php b/tests/Issues/RepeatedThreeTimes/Source/NarrowParamUnionDocblockRector.php new file mode 100644 index 00000000000..e1eb2d6874e --- /dev/null +++ b/tests/Issues/RepeatedThreeTimes/Source/NarrowParamUnionDocblockRector.php @@ -0,0 +1,85 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?ClassMethod + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if ($phpDocInfo === null) { + return null; + } + + $hasChanged = false; + foreach ($phpDocInfo->getParamTagValueNodes() as $paramTagValueNode) { + if (! $paramTagValueNode->type instanceof UnionTypeNode) { + continue; + } + + if (count($paramTagValueNode->type->types) !== 2) { + continue; + } + + [$firstTypeNode, $secondTypeNode] = $paramTagValueNode->type->types; + if (! $firstTypeNode instanceof IdentifierTypeNode || $firstTypeNode->name !== 'array') { + continue; + } + + $secondType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($secondTypeNode, $node); + if (! $secondType->equals(new ObjectType(SomeCollection::class))) { + continue; + } + + $paramTagValueNode->type = $secondTypeNode; + $hasChanged = true; + } + + if (! $hasChanged) { + return null; + } + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } +} diff --git a/tests/Issues/RepeatedThreeTimes/Source/ParamTypeFromDocblockRector.php b/tests/Issues/RepeatedThreeTimes/Source/ParamTypeFromDocblockRector.php new file mode 100644 index 00000000000..9fe75a05539 --- /dev/null +++ b/tests/Issues/RepeatedThreeTimes/Source/ParamTypeFromDocblockRector.php @@ -0,0 +1,69 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?ClassMethod + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if ($phpDocInfo === null) { + return null; + } + + $hasChanged = false; + foreach ($node->params as $param) { + if ($param->type instanceof Node) { + continue; + } + + $paramType = $phpDocInfo->getParamType($this->getName($param)); + if (! $paramType->equals(new ObjectType(SomeCollection::class))) { + continue; + } + + $param->type = new FullyQualified(SomeCollection::class); + $hasChanged = true; + } + + if (! $hasChanged) { + return null; + } + + return $node; + } +} diff --git a/tests/Issues/RepeatedThreeTimes/Source/SomeArrayCollection.php b/tests/Issues/RepeatedThreeTimes/Source/SomeArrayCollection.php new file mode 100644 index 00000000000..566eadf74dd --- /dev/null +++ b/tests/Issues/RepeatedThreeTimes/Source/SomeArrayCollection.php @@ -0,0 +1,9 @@ +withImportNames() + // registered in reverse order of dependency: + // 3rd pass: native param type from @param SomeCollection + // 2nd pass: @param array|SomeCollection to @param SomeCollection + // 1st pass: @param array|SomeArrayCollection to @param array|SomeCollection + ->withRules([ + ParamTypeFromDocblockRector::class, + NarrowParamUnionDocblockRector::class, + NarrowArrayCollectionDocblockRector::class, + ]);