allAssignNodePropertyTypeInferer = $allAssignNodePropertyTypeInferer; $this->propertyTypeDecorator = $propertyTypeDecorator; $this->varTagRemover = $varTagRemover; $this->makePropertyTypedGuard = $makePropertyTypedGuard; $this->reflectionResolver = $reflectionResolver; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->valueResolver = $valueResolver; $this->staticTypeMapper = $staticTypeMapper; } public function configure(array $configuration) : void { $this->inlinePublic = $configuration[self::INLINE_PUBLIC] ?? (bool) \current($configuration); } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Add typed property from assigned types', [new ConfiguredCodeSample(<<<'CODE_SAMPLE' final class SomeClass { private $name; public function run() { $this->name = 'string'; } } CODE_SAMPLE , <<<'CODE_SAMPLE' final class SomeClass { private string|null $name = null; public function run() { $this->name = 'string'; } } CODE_SAMPLE , [\Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::INLINE_PUBLIC => \false])]); } /** * @return array> */ public function getNodeTypes() : array { return [Class_::class]; } public function provideMinPhpVersion() : int { return PhpVersionFeature::TYPED_PROPERTIES; } /** * @param Class_ $node */ public function refactor(Node $node) : ?Node { $hasChanged = \false; $classReflection = null; foreach ($node->getProperties() as $property) { // non-private property can be anything with not inline public configured if (!$property->isPrivate() && !$this->inlinePublic) { continue; } if (!$classReflection instanceof ClassReflection) { $classReflection = $this->reflectionResolver->resolveClassReflection($node); } if (!$classReflection instanceof ClassReflection) { return null; } if (!$this->makePropertyTypedGuard->isLegal($property, $classReflection, $this->inlinePublic)) { continue; } $inferredType = $this->allAssignNodePropertyTypeInferer->inferProperty($property, $classReflection, $this->file); if (!$inferredType instanceof Type) { continue; } if ($inferredType instanceof MixedType) { continue; } $inferredType = $this->decorateTypeWithNullableIfDefaultPropertyNull($property, $inferredType); $typeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($inferredType, TypeKind::PROPERTY); if (!$typeNode instanceof Node) { continue; } $hasChanged = \true; $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); if ($inferredType instanceof UnionType && ($typeNode instanceof NodeUnionType || $typeNode instanceof NullableType)) { $this->propertyTypeDecorator->decoratePropertyUnionType($inferredType, $typeNode, $property, $phpDocInfo, \false); } else { $property->type = $typeNode; } if (!$property->type instanceof Node) { continue; } $this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $property); } if ($hasChanged) { return $node; } return null; } private function decorateTypeWithNullableIfDefaultPropertyNull(Property $property, Type $inferredType) : Type { $defaultExpr = $property->props[0]->default; if (!$defaultExpr instanceof Expr) { return $inferredType; } if (!$this->valueResolver->isNull($defaultExpr)) { return $inferredType; } if (TypeCombinator::containsNull($inferredType)) { return $inferredType; } return TypeCombinator::addNull($inferredType); } }