From 1d80ef3df649bb3072f190729fe7f5f1d524e1cb Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 15 Feb 2021 20:31:53 +0100 Subject: [PATCH] [TypeDeclaration] Add external types to ParamTypeFromStrictTypedPropertyRector (#5560) --- .github/workflows/php_linter.yaml | 2 +- composer.json | 2 +- phpstan.neon | 3 + rector.php | 1 + .../CompleteDynamicPropertiesRectorTest.php | 1 + .../RemoveDefaultArgumentValueRectorTest.php | 1 + ...ultValueForUndefinedVariableRectorTest.php | 1 + ...ParamTypeFromStrictTypedPropertyRector.php | 47 +++++++++---- .../src/Reflection/ReflectionTypeResolver.php | 67 +++++++++++++++++++ .../Fixture/even_constructor.php.inc | 31 +++++++++ .../Fixture/external_type.php.inc | 37 ++++++++++ .../Fixture/vendor_external_type.php.inc | 31 +++++++++ .../Source/OutOfControlExternalClass.php | 10 +++ .../CompleteVarDocTypePropertyRectorTest.php | 2 +- .../Fixture/callable_type.php.inc | 2 +- .../Fixture/symfony_console_command.php.inc | 2 +- src/Rector/AbstractTemporaryRector.php | 10 +-- 17 files changed, 228 insertions(+), 22 deletions(-) create mode 100644 rules/type-declaration/src/Reflection/ReflectionTypeResolver.php create mode 100644 rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/even_constructor.php.inc create mode 100644 rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/external_type.php.inc create mode 100644 rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/vendor_external_type.php.inc create mode 100644 rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Source/OutOfControlExternalClass.php diff --git a/.github/workflows/php_linter.yaml b/.github/workflows/php_linter.yaml index fd24bb79982..36caea5ddd4 100644 --- a/.github/workflows/php_linter.yaml +++ b/.github/workflows/php_linter.yaml @@ -11,7 +11,7 @@ jobs: actions: - php_version: 7.3 - run: vendor/bin/parallel-lint src bin/rector config tests packages rules --colors --exclude packages/rector-generator/templates --exclude rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source --exclude rules/autodiscovery/tests/Rector/FileNode/MoveInterfacesToContractNamespaceDirectoryRector/Expected --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithNativeProps.php --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithNativePropsPhp80.php --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithTypedPropertyTypes.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/Source/ExpectedSomeClassCopyEvent.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedFileManagerUploadEvent.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedDuplicatedEventParamsUploadEvent.php + run: vendor/bin/parallel-lint src bin/rector config tests packages rules --colors --exclude packages/rector-generator/templates --exclude rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source --exclude rules/autodiscovery/tests/Rector/FileNode/MoveInterfacesToContractNamespaceDirectoryRector/Expected --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithNativeProps.php --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithNativePropsPhp80.php --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithTypedPropertyTypes.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/Source/ExpectedSomeClassCopyEvent.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedFileManagerUploadEvent.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedDuplicatedEventParamsUploadEvent.php --exclude rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Source/OutOfControlExternalClass.php - php_version: 8.0 diff --git a/composer.json b/composer.json index de542768a3a..b0401345db3 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "nette/utils": "^3.2", "nikic/php-parser": "^4.10.4", "phpstan/phpdoc-parser": "^0.4.9", - "phpstan/phpstan": "^0.12.69, <0.12.70", + "phpstan/phpstan": "^0.12.69", "phpstan/phpstan-phpunit": "^0.12.17", "psr/simple-cache": "^1.0", "sebastian/diff": "^4.0.4", diff --git a/phpstan.neon b/phpstan.neon index 3843e3a3ec2..ea6e8e64f2a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -541,3 +541,6 @@ parameters: # known values - '#Method Rector\\Testing\\Finder\\RectorsFinder\:\:findClassesInDirectoriesByName\(\) should return array but returns array#' - '#Content of method "collectVariableFromAssign\(\)" is duplicated with method "collectVariableFromAssign\(\)" in "Rector\\NetteToSymfony\\NodeAnalyzer\\ClassMethodRenderAnalyzer" class\. Use unique content or abstract service instead#' + - '#Property PhpParser\\Node\\Param\:\:\$type \(PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null\) does not accept PhpParser\\Node#' + - '#Binary operation "\." between array\|string\|false and (.*?) results in an error#' + - '#Parameter \#3 \.\.\.\$rest of function array_uintersect expects array, Closure\(PhpParser\\Node\\Param, PhpParser\\Node\\Param\)\: int given#' diff --git a/rector.php b/rector.php index 708b70a9903..d3a41b990f6 100644 --- a/rector.php +++ b/rector.php @@ -56,6 +56,7 @@ return static function (ContainerConfigurator $containerConfigurator): void { SetList::PHP_72, SetList::PHP_73, SetList::EARLY_RETURN, + SetList::TYPE_DECLARATION_STRICT, ]); $parameters->set(Option::PATHS, [ diff --git a/rules/code-quality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php b/rules/code-quality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php index 19d2c26f598..6d5f31fba84 100644 --- a/rules/code-quality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php +++ b/rules/code-quality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php @@ -11,6 +11,7 @@ use Symplify\SmartFileSystem\SmartFileInfo; final class CompleteDynamicPropertiesRectorTest extends AbstractRectorTestCase { /** + * @requires PHP 8.0 * @dataProvider provideData() */ public function test(SmartFileInfo $fileInfo): void diff --git a/rules/dead-code/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/RemoveDefaultArgumentValueRectorTest.php b/rules/dead-code/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/RemoveDefaultArgumentValueRectorTest.php index cb669e0d387..de7a6ddba8f 100644 --- a/rules/dead-code/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/RemoveDefaultArgumentValueRectorTest.php +++ b/rules/dead-code/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/RemoveDefaultArgumentValueRectorTest.php @@ -12,6 +12,7 @@ use Symplify\SmartFileSystem\SmartFileInfo; final class RemoveDefaultArgumentValueRectorTest extends AbstractRectorTestCase { /** + * @requires PHP 8.0 * @dataProvider provideData() */ public function test(SmartFileInfo $fileInfo): void diff --git a/rules/php56/tests/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector/AddDefaultValueForUndefinedVariableRectorTest.php b/rules/php56/tests/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector/AddDefaultValueForUndefinedVariableRectorTest.php index 9f47bc8d051..76ff89f46b2 100644 --- a/rules/php56/tests/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector/AddDefaultValueForUndefinedVariableRectorTest.php +++ b/rules/php56/tests/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector/AddDefaultValueForUndefinedVariableRectorTest.php @@ -12,6 +12,7 @@ use Symplify\SmartFileSystem\SmartFileInfo; final class AddDefaultValueForUndefinedVariableRectorTest extends AbstractRectorTestCase { /** + * @requires PHP 8.0 * @dataProvider provideData() */ public function test(SmartFileInfo $fileInfo): void diff --git a/rules/type-declaration/src/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector.php b/rules/type-declaration/src/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector.php index bc5541df800..49a70542bf4 100644 --- a/rules/type-declaration/src/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector.php +++ b/rules/type-declaration/src/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector.php @@ -5,17 +5,22 @@ declare(strict_types=1); namespace Rector\TypeDeclaration\Rector\ClassMethod; use PhpParser\Node; +use PhpParser\Node\Expr\ArrowFunction; use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\FunctionLike; use PhpParser\Node\NullableType; use PhpParser\Node\Param; use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; use PhpParser\Node\UnionType; use PhpParser\NodeTraverser; use PHPStan\Type\Type; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersionFeature; +use Rector\TypeDeclaration\Reflection\ReflectionTypeResolver; use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -30,9 +35,17 @@ final class ParamTypeFromStrictTypedPropertyRector extends AbstractRector */ private $simpleCallableNodeTraverser; - public function __construct(SimpleCallableNodeTraverser $simpleCallableNodeTraverser) - { + /** + * @var ReflectionTypeResolver + */ + private $reflectionTypeResolver; + + public function __construct( + SimpleCallableNodeTraverser $simpleCallableNodeTraverser, + ReflectionTypeResolver $reflectionTypeResolver + ) { $this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser; + $this->reflectionTypeResolver = $reflectionTypeResolver; } public function getRuleDefinition(): RuleDefinition @@ -73,11 +86,11 @@ CODE_SAMPLE */ public function getNodeTypes(): array { - return [ClassMethod::class]; + return [ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class]; } /** - * @param ClassMethod $node + * @param ClassMethod|Function_|Closure|ArrowFunction $node */ public function refactor(Node $node): ?Node { @@ -92,13 +105,16 @@ CODE_SAMPLE return $node; } - public function decorateParamWithType(ClassMethod $classMethod, Param $param): void + /** + * @param ClassMethod|Function_|Closure|ArrowFunction $functionLike + */ + public function decorateParamWithType(FunctionLike $functionLike, Param $param): void { if ($param->type !== null) { return; } - $this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function ( + $this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), function ( Node $node ) use ($param): ?int { if (! $node instanceof Assign) { @@ -113,22 +129,29 @@ CODE_SAMPLE return null; } - $property = $this->matchPropertyWithSingleType($node->var); - if (! $property instanceof Property) { + $singlePropertyTypeNode = $this->matchPropertySingleTypeNode($node->var); + if (! $singlePropertyTypeNode instanceof Node) { return null; } - $param->type = $property->type; + $this->rectorChangeCollector->notifyNodeFileInfo($node); + $param->type = $singlePropertyTypeNode; return NodeTraverser::STOP_TRAVERSAL; }); } - private function matchPropertyWithSingleType(PropertyFetch $propertyFetch): ?Property + private function matchPropertySingleTypeNode(PropertyFetch $propertyFetch): ?Node { $property = $this->nodeRepository->findPropertyByPropertyFetch($propertyFetch); if (! $property instanceof Property) { - return null; + // code from /vendor + $propertyFetchType = $this->reflectionTypeResolver->resolvePropertyFetchType($propertyFetch); + if (! $propertyFetchType instanceof Type) { + return null; + } + + return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyFetchType); } if ($property->type === null) { @@ -144,6 +167,6 @@ CODE_SAMPLE return null; } - return $property; + return $property->type; } } diff --git a/rules/type-declaration/src/Reflection/ReflectionTypeResolver.php b/rules/type-declaration/src/Reflection/ReflectionTypeResolver.php new file mode 100644 index 00000000000..60b6fd63598 --- /dev/null +++ b/rules/type-declaration/src/Reflection/ReflectionTypeResolver.php @@ -0,0 +1,67 @@ +nodeTypeResolver = $nodeTypeResolver; + $this->reflectionProvider = $reflectionProvider; + $this->nodeNameResolver = $nodeNameResolver; + } + + public function resolvePropertyFetchType(PropertyFetch $propertyFetch): ?Type + { + $objectType = $this->nodeTypeResolver->resolve($propertyFetch->var); + if (! $objectType instanceof TypeWithClassName) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($objectType->getClassName()); + $propertyName = $this->nodeNameResolver->getName($propertyFetch); + if ($propertyName === null) { + return null; + } + + if ($classReflection->hasProperty($propertyName)) { + $propertyFetchScope = $propertyFetch->getAttribute(AttributeKey::SCOPE); + $propertyReflection = $classReflection->getProperty($propertyName, $propertyFetchScope); + + if ($propertyReflection instanceof PhpPropertyReflection) { + return $propertyReflection->getNativeType(); + } + } + + return null; + } +} diff --git a/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/even_constructor.php.inc b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/even_constructor.php.inc new file mode 100644 index 00000000000..e6fe5f2068b --- /dev/null +++ b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/even_constructor.php.inc @@ -0,0 +1,31 @@ +age = $age; + } +} + +?> +----- +age = $age; + } +} + +?> diff --git a/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/external_type.php.inc b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/external_type.php.inc new file mode 100644 index 00000000000..80f5ec3cd40 --- /dev/null +++ b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/external_type.php.inc @@ -0,0 +1,37 @@ +age = $age; + } +} + +final class ExternalClass +{ + public int $age; +} + +?> +----- +age = $age; + } +} + +final class ExternalClass +{ + public int $age; +} + +?> diff --git a/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/vendor_external_type.php.inc b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/vendor_external_type.php.inc new file mode 100644 index 00000000000..36133eb906a --- /dev/null +++ b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Fixture/vendor_external_type.php.inc @@ -0,0 +1,31 @@ +name = $age; + } +} + +?> +----- +name = $age; + } +} + +?> diff --git a/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Source/OutOfControlExternalClass.php b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Source/OutOfControlExternalClass.php new file mode 100644 index 00000000000..a1148a10973 --- /dev/null +++ b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Source/OutOfControlExternalClass.php @@ -0,0 +1,10 @@ +