henzeb/enumhancer

View on GitHub
src/PHPStan/Constants/Rules/MapperConstantRule.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

namespace Henzeb\Enumhancer\PHPStan\Constants\Rules;

use Henzeb\Enumhancer\Contracts\Mapper;
use Henzeb\Enumhancer\Helpers\EnumImplements;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassConst;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MissingConstantFromReflectionException;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use function strtolower;

/**
 * @phpstan-implements Rule<ClassConst>
 */
class MapperConstantRule implements Rule
{
    public function __construct(
        private ReflectionProvider $reflectionProvider
    ) {
    }

    public function getNodeType(): string
    {
        return ClassConst::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        $constantName = $node->consts[0]->name->name;

        if (!$this->isMapperConstant($constantName)) {
            return [];
        }

        $classReflection = $scope->getClassReflection();

        if (!$classReflection->isEnum()) {
            return [];
        }

        return $this->validate($classReflection, $constantName);
    }

    private function isMapperConstant(string $name): bool
    {
        return str_starts_with(strtolower($name), 'map');
    }

    /**
     * @param ClassReflection|null $class
     * @param string $constantName
     * @return array<int,string>
     * @throws MissingConstantFromReflectionException
     */
    protected function validate(?ClassReflection $class, string $constantName): array
    {
        $implementsMappers = EnumImplements::mappers($class->getName());
        $return = [];

        $isValid = $this->isValidValue($class, $constantName);

        if (!$implementsMappers && $isValid) {
            $return = [
                sprintf(
                    'Enumhancer: `%s` is not going to be used because enum is not implementing `Mappers`',
                    $constantName,
                )
            ];
        }

        if ($implementsMappers && !$isValid) {
            $return = [
                sprintf(
                    'Enumhancer: The specified `%s` map is invalid',
                    $constantName,
                )
            ];
        }

        return $return;
    }

    /**
     * @throws MissingConstantFromReflectionException
     */
    protected function isValidValue(
        ?ClassReflection $class,
        string $constantName
    ): bool {
        $valueType = $class->getConstant($constantName)->getValueType();

        $isValid = $valueType instanceof ConstantArrayType;

        if ($valueType instanceof ConstantStringType) {
            $class = $valueType->getValue();
            $isValid = $valueType->isClassStringType()->yes()
                && $this->reflectionProvider->getClass($class)->isSubclassOf(Mapper::class);
        }

        return $isValid;
    }
}