diff --git a/.github/workflows/php_linter.yaml b/.github/workflows/php_linter.yaml index fd24bb799823..36caea5ddd41 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 de542768a3a5..b0401345db3c 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 3843e3a3ec27..ea6e8e64f2a4 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 708b70a99036..d3a41b990f66 100644 --- a/rector.php +++ b/rector.php @@ -56,6 +56,7 @@ 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 19d2c26f598d..6d5f31fba841 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 @@ 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 cb669e0d3872..de7a6ddba8fc 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 @@ 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 9f47bc8d0511..76ff89f46b20 100644 --- a/rules/php56/tests/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector/AddDefaultValueForUndefinedVariableRectorTest.php +++ b/rules/php56/tests/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector/AddDefaultValueForUndefinedVariableRectorTest.php @@ -12,6 +12,7 @@ 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 bc5541df8008..49a70542bf40 100644 --- a/rules/type-declaration/src/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector.php +++ b/rules/type-declaration/src/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector.php @@ -5,17 +5,22 @@ 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 @@ public function setAge(int $age) */ 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 @@ public function refactor(Node $node): ?Node 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 @@ public function decorateParamWithType(ClassMethod $classMethod, Param $param): v 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 @@ private function matchPropertyWithSingleType(PropertyFetch $propertyFetch): ?Pro 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 000000000000..60b6fd635981 --- /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 000000000000..e6fe5f2068bd --- /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 000000000000..80f5ec3cd407 --- /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 000000000000..36133eb906ab --- /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 000000000000..a1148a10973d --- /dev/null +++ b/rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Source/OutOfControlExternalClass.php @@ -0,0 +1,10 @@ +