reflectionProvider = $reflectionProvider; $this->staticTypeMapper = $staticTypeMapper; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Add type to constants', [new CodeSample(<<<'CODE_SAMPLE' final class SomeClass { public const TYPE = 'some_type'; } CODE_SAMPLE , <<<'CODE_SAMPLE' final class SomeClass { public const string TYPE = 'some_type'; } CODE_SAMPLE )]); } public function getNodeTypes() : array { return [Class_::class]; } /** * @param Class_ $node */ public function refactor(Node $node) : ?Class_ { $className = $this->getName($node); if (!\is_string($className)) { return null; } if ($node->isAbstract()) { return null; } $classConsts = $node->getConstants(); if ($classConsts === []) { return null; } $parentClassReflections = $this->getParentReflections($className); $hasChanged = \false; foreach ($classConsts as $classConst) { $valueType = null; // If a type is set, skip if ($classConst->type !== null) { continue; } foreach ($classConst->consts as $constNode) { if ($this->isConstGuardedByParents($constNode, $parentClassReflections)) { continue; } if ($this->canBeInherited($classConst, $node)) { continue; } $valueType = $this->findValueType($constNode->value); } if (!($valueType ?? null) instanceof Identifier) { continue; } $classConst->type = $valueType; $hasChanged = \true; } if (!$hasChanged) { return null; } return $node; } public function provideMinPhpVersion() : int { return PhpVersionFeature::TYPED_CLASS_CONSTANTS; } /** * @param ClassReflection[] $parentClassReflections */ public function isConstGuardedByParents(Const_ $const, array $parentClassReflections) : bool { $constantName = $this->getName($const); foreach ($parentClassReflections as $parentClassReflection) { if ($parentClassReflection->hasConstant($constantName)) { return \true; } } return \false; } private function findValueType(Expr $expr) : ?Identifier { if ($expr instanceof UnaryPlus || $expr instanceof UnaryMinus) { return $this->findValueType($expr->expr); } if ($expr instanceof String_) { return new Identifier('string'); } if ($expr instanceof LNumber) { return new Identifier('int'); } if ($expr instanceof DNumber) { return new Identifier('float'); } if ($expr instanceof ConstFetch) { if ($expr->name->toLowerString() === 'null') { return new Identifier('null'); } $type = $this->nodeTypeResolver->getNativeType($expr); $nodeType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PROPERTY); if (!$nodeType instanceof Identifier) { return null; } return $nodeType; } if ($expr instanceof Array_) { return new Identifier('array'); } if ($expr instanceof Concat) { return new Identifier('string'); } return null; } /** * @return ClassReflection[] */ private function getParentReflections(string $className) : array { if (!$this->reflectionProvider->hasClass($className)) { return []; } $currentClassReflection = $this->reflectionProvider->getClass($className); return \array_filter($currentClassReflection->getAncestors(), static function (ClassReflection $classReflection) use($currentClassReflection) : bool { return $currentClassReflection !== $classReflection; }); } private function canBeInherited(ClassConst $classConst, Class_ $class) : bool { return !$class->isFinal() && !$classConst->isPrivate() && !$classConst->isFinal(); } }