reflectionResolver = $reflectionResolver; $this->parentClassMethodTypeOverrideGuard = $parentClassMethodTypeOverrideGuard; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Add param type from $param set to typed property', [new CodeSample(<<<'CODE_SAMPLE' final class SomeClass { private int $age; public function setAge($age) { $this->age = $age; } } CODE_SAMPLE , <<<'CODE_SAMPLE' final class SomeClass { private int $age; public function setAge(int $age) { $this->age = $age; } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [Param::class]; } /** * @param Param $node */ public function refactorWithScope(Node $node, Scope $scope) : ?Node { $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); if (!$parentNode instanceof ClassMethod) { return null; } return $this->decorateParamWithType($parentNode, $node, $scope); } public function provideMinPhpVersion() : int { return PhpVersionFeature::TYPED_PROPERTIES; } private function decorateParamWithType(ClassMethod $classMethod, Param $param, Scope $scope) : ?Param { if ($param->type !== null) { return null; } if ($classMethod->isAbstract()) { return null; } if ($this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod)) { return null; } $originalParamType = $this->resolveParamOriginalType($param, $scope); $paramName = $this->getName($param); /** @var Assign[] $assigns */ $assigns = $this->betterNodeFinder->findInstanceOf((array) $classMethod->getStmts(), Assign::class); foreach ($assigns as $assign) { if (!$assign->var instanceof PropertyFetch) { continue; } if (!$this->nodeComparator->areNodesEqual($assign->expr, $param->var)) { continue; } if ($this->hasTypeChangedBeforeAssign($assign, $paramName, $originalParamType)) { return null; } $singlePropertyTypeNode = $this->matchPropertySingleTypeNode($assign->var); if (!$singlePropertyTypeNode instanceof Node) { return null; } $param->type = $singlePropertyTypeNode; return $param; } return null; } /** * @return Identifier|Name|ComplexType|null */ private function matchPropertySingleTypeNode(PropertyFetch $propertyFetch) : ?Node { $phpPropertyReflection = $this->reflectionResolver->resolvePropertyReflectionFromPropertyFetch($propertyFetch); if (!$phpPropertyReflection instanceof PhpPropertyReflection) { return null; } $propertyType = $phpPropertyReflection->getNativeType(); if ($propertyType instanceof MixedType) { return null; } if ($propertyType instanceof UnionType) { return null; } if ($propertyType instanceof NullableType) { return null; } return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType, TypeKind::PROPERTY); } private function hasTypeChangedBeforeAssign(Assign $assign, string $paramName, Type $originalType) : bool { $scope = $assign->getAttribute(AttributeKey::SCOPE); if (!$scope instanceof Scope) { return \false; } if (!$scope->hasVariableType($paramName)->yes()) { return \false; } $currentParamType = $scope->getVariableType($paramName); return !$currentParamType->equals($originalType); } private function resolveParamOriginalType(Param $param, Scope $scope) : Type { $paramName = $this->getName($param); if (!$scope->hasVariableType($paramName)->yes()) { return new MixedType(); } return $scope->getVariableType($paramName); } }