diff --git a/bin/check-before-after-same-fixtures.php b/bin/check-before-after-same-fixtures.php index 5b4822a6bce..7eb7bb257d9 100644 --- a/bin/check-before-after-same-fixtures.php +++ b/bin/check-before-after-same-fixtures.php @@ -41,7 +41,7 @@ public function run(array $testDirectories): int } if ($invalidFixturePaths === []) { - $this->symfonyStyle->success('All fixtures are valid'); + $this->symfonyStyle->success(sprintf('All %d fixtures are valid', count($fixtureFiles))); return Command::SUCCESS; } diff --git a/config/set/php85.php b/config/set/php85.php index a3125b37f88..37449b68b95 100644 --- a/config/set/php85.php +++ b/config/set/php85.php @@ -15,7 +15,9 @@ use Rector\Renaming\ValueObject\RenameClassAndConstFetch; return static function (RectorConfig $rectorConfig): void { - $rectorConfig->rules([ArrayFirstLastRector::class, RemoveFinfoBufferContextArgRector::class, NullDebugInfoReturnRector::class]); + $rectorConfig->rules( + [ArrayFirstLastRector::class, RemoveFinfoBufferContextArgRector::class, NullDebugInfoReturnRector::class] + ); $rectorConfig->ruleWithConfiguration( RemoveFuncCallArgRector::class, diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/magic_setup_test_assign.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/magic_setup_test_assign.php.inc new file mode 100644 index 00000000000..b168c2536e0 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/magic_setup_test_assign.php.inc @@ -0,0 +1,40 @@ +someType = $this->create('string'); + } +} + +?> +----- +someType = $this->create('string'); + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/skip_no_docblock.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/skip_no_docblock.php.inc new file mode 100644 index 00000000000..7e9a8fc62e5 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/skip_no_docblock.php.inc @@ -0,0 +1,16 @@ +someType = $this->create('string'); + } +} diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/skip_public_property.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/skip_public_property.php.inc new file mode 100644 index 00000000000..744ed5fd27d --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Fixture/skip_public_property.php.inc @@ -0,0 +1,19 @@ +someType = $this->create('string'); + } +} diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Source/SomeDocblockType.php b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Source/SomeDocblockType.php new file mode 100644 index 00000000000..411046e54a9 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/Source/SomeDocblockType.php @@ -0,0 +1,9 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/rule_config.php'; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/config/rule_config.php b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/config/rule_config.php new file mode 100644 index 00000000000..88c338e2c6a --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector/config/rule_config.php @@ -0,0 +1,11 @@ +withRules([TypedPropertyFromDocblockSetUpDefinedRector::class]) + ->withPhpVersion(PhpVersionFeature::TYPED_PROPERTIES); diff --git a/rules/Php85/Rector/ClassMethod/NullDebugInfoReturnRector.php b/rules/Php85/Rector/ClassMethod/NullDebugInfoReturnRector.php index e5ed9d260fa..7f42e4d59d8 100644 --- a/rules/Php85/Rector/ClassMethod/NullDebugInfoReturnRector.php +++ b/rules/Php85/Rector/ClassMethod/NullDebugInfoReturnRector.php @@ -75,7 +75,10 @@ public function refactor(Node $node): ?Node } $hasChanged = \false; - $this->traverseNodesWithCallable((array) $node->stmts, function (Node $node) use (&$hasChanged): int|Return_|null { + + $this->traverseNodesWithCallable((array) $node->stmts, function (Node $node) use ( + &$hasChanged + ): int|Return_|null { if ($node instanceof Class_ || $node instanceof Function_ || $node instanceof Closure) { return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; } diff --git a/rules/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector.php b/rules/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector.php new file mode 100644 index 00000000000..8e50b591ff6 --- /dev/null +++ b/rules/TypeDeclaration/Rector/Class_/TypedPropertyFromDocblockSetUpDefinedRector.php @@ -0,0 +1,172 @@ +doctrine = $this->container('doctrine.orm.entity_manager'); + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; + +class SomeClass extends TestCase +{ + private \Doctrine\ORM\EntityManagerInterface $doctrine; + + protected function setUp(): void + { + $this->doctrine = $this->container('doctrine.orm.entity_manager'); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + // nothing useful here + $setUpClassMethod = $node->getMethod(MethodName::SET_UP); + if (! $setUpClassMethod instanceof ClassMethod) { + return null; + } + + $hasChanged = false; + + foreach ($node->getProperties() as $property) { + // already known type + if ($property->type instanceof Node) { + continue; + } + + // some magic might be going on + if ($property->isStatic()) { + continue; + } + + if (! $property->isPrivate()) { + continue; + } + + // exactly one property + if (count($property->props) !== 1) { + continue; + } + + $propertyName = $property->props[0]->name->toString(); + if (! $this->constructorAssignDetector->isPropertyAssigned($node, $propertyName)) { + continue; + } + + $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property); + if (! $propertyPhpDocInfo instanceof PhpDocInfo) { + continue; + } + + $varType = $propertyPhpDocInfo->getVarType(); + if ($varType instanceof MixedType) { + continue; + } + + $nativePropertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode( + $varType, + TypeKind::PROPERTY + ); + if (! $nativePropertyTypeNode instanceof Node) { + continue; + } + + // remove var tag + $this->removeVarTag($propertyPhpDocInfo, $property); + + $property->type = $nativePropertyTypeNode; + $hasChanged = true; + } + + if ($hasChanged) { + return $node; + } + + return null; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::TYPED_PROPERTIES; + } + + private function removeVarTag(PhpDocInfo $propertyPhpDocInfo, Property $property): void + { + $propertyPhpDocInfo->removeByType(VarTagValueNode::class); + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property); + } +} diff --git a/src/Config/Level/TypeDeclarationLevel.php b/src/Config/Level/TypeDeclarationLevel.php index 1ac9994995b..d1f27f9c087 100644 --- a/src/Config/Level/TypeDeclarationLevel.php +++ b/src/Config/Level/TypeDeclarationLevel.php @@ -13,6 +13,7 @@ use Rector\TypeDeclaration\Rector\Class_\PropertyTypeFromStrictSetterGetterRector; use Rector\TypeDeclaration\Rector\Class_\ReturnTypeFromStrictTernaryRector; use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector; +use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromDocblockSetUpDefinedRector; use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromJMSSerializerAttributeTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeBasedOnPHPUnitDataProviderRector; @@ -142,5 +143,7 @@ final class TypeDeclarationLevel // possibly based on docblocks, but also helpful, intentionally last AddArrayFunctionClosureParamTypeRector::class, + + TypedPropertyFromDocblockSetUpDefinedRector::class, ]; } diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index dcdbe245a2f..ec67f7703e1 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -768,12 +768,12 @@ final class PhpVersionFeature * @var int */ public const ARRAY_ANY = PhpVersion::PHP_84; - + /** * @see https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_context_parameter_for_finfo_buffer * @var int */ - public const DEPRECATE_FINFO_BUFFER_CONTEXT = PhpVersion::PHP_85; + public const DEPRECATE_FINFO_BUFFER_CONTEXT = PhpVersion::PHP_85; /** * @see https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_debuginfo_returning_null