Passed
Push — main ( ca6308...1322d6 )
by Yunfeng
58s queued 12s
created

EnumPhpdocCommand::writeDocComment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 25
rs 9.7333
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace BiiiiiigMonster\LaravelEnum\Commands;
4
5
use BiiiiiigMonster\LaravelEnum\ClassVisitor;
6
use BiiiiiigMonster\LaravelEnum\Concerns\EnumTraits;
0 ignored issues
show
Bug introduced by
The type BiiiiiigMonster\LaravelEnum\Concerns\EnumTraits was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use BiiiiiigMonster\LaravelEnum\Concerns\Meta;
8
use BiiiiiigMonster\LaravelEnum\Reader;
9
use Illuminate\Console\Command;
10
use Illuminate\Contracts\Filesystem\FileNotFoundException;
11
use Illuminate\Filesystem\Filesystem;
12
use Illuminate\Support\Arr;
13
use InvalidArgumentException;
14
use Laminas\Code\Generator\DocBlock\Tag\MethodTag;
15
use Laminas\Code\Generator\DocBlock\Tag\TagInterface;
16
use Laminas\Code\Generator\DocBlockGenerator;
17
use Laminas\Code\Reflection\DocBlockReflection;
18
use ReflectionEnum;
0 ignored issues
show
Bug introduced by
The type ReflectionEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use ReflectionEnumUnitCase;
0 ignored issues
show
Bug introduced by
The type ReflectionEnumUnitCase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use ReflectionException;
21
use ReflectionIntersectionType;
0 ignored issues
show
Bug introduced by
The type ReflectionIntersectionType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use ReflectionMethod;
23
use ReflectionNamedType;
24
use ReflectionUnionType;
25
use Symfony\Component\Console\Attribute\AsCommand;
26
use Symfony\Component\Finder\Finder;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Finder\Finder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
use UnitEnum;
0 ignored issues
show
Bug introduced by
The type UnitEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
29
#[AsCommand(name: 'enum:phpdoc')]
30
class EnumPhpdocCommand extends Command
31
{
32
    protected $signature = 'enum:phpdoc
33
                            {enum?* : The enum class to generate PHPDoc for}
34
                            {--folder=* : The folder to scan for enums to generate PHPDoc}';
35
36
    protected $description = 'Generate PHP DocBlock of static case method and meta method for enum classes';
37
38
    protected Filesystem $filesystem;
39
40
    /**
41
     * @throws ReflectionException|FileNotFoundException
42
     */
43
    public function handle(Filesystem $filesystem): void
44
    {
45
        $this->filesystem = $filesystem;
46
47
        if ($classNames = (array) $this->argument('enum')) {
48
            foreach ($classNames as $className) {
49
                /** @var class-string $className */
50
                if (! enum_exists($className)) {
0 ignored issues
show
Bug introduced by
The function enum_exists was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

50
                if (! /** @scrutinizer ignore-call */ enum_exists($className)) {
Loading history...
51
                    throw new InvalidArgumentException(
52
                        sprintf('The given class must be an instance of %s: %s', UnitEnum::class, $className)
53
                    );
54
                }
55
56
                if (! in_array(EnumTraits::class, class_uses_recursive($className))) {
57
                    throw new InvalidArgumentException(
58
                        sprintf('The given class must be use trait of %s: %s', EnumTraits::class, $className)
59
                    );
60
                }
61
                $this->phpdoc($className);
62
            }
63
64
            return;
65
        }
66
67
        foreach ($this->getClassFinder() as $file) {
68
            new Reader($file, $classVisitor = new ClassVisitor());
69
            $className = $classVisitor->getName();
70
            if (enum_exists($className) && in_array(EnumTraits::class, class_uses_recursive($className))) {
71
                $this->phpdoc($className);
72
            }
73
        }
74
    }
75
76
    /**
77
     * @throws ReflectionException|FileNotFoundException
78
     */
79
    protected function phpdoc(string $className): void
80
    {
81
        $reflection = new ReflectionEnum($className);
82
83
        $this->writeDocComment($reflection, $this->getDocBlock($reflection));
84
    }
85
86
    protected function getDocBlock(ReflectionEnum $reflection): DocBlockGenerator
87
    {
88
        $docBlock = $reflection->getDocComment()
89
            ? DocBlockGenerator::fromReflection(new DocBlockReflection($reflection))
90
            : new DocBlockGenerator();
91
92
        $retainedTags = collect($docBlock->getTags())
0 ignored issues
show
Bug introduced by
$docBlock->getTags() of type Laminas\Code\Generator\DocBlock\Tag\TagInterface[] is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

92
        $retainedTags = collect(/** @scrutinizer ignore-type */ $docBlock->getTags())
Loading history...
93
            ->reject(fn (TagInterface $tag) => $tag instanceof MethodTag)
94
            ->all();
95
96
        $caseTags = [];
97
        $enumBackingType = 'string';
98
        $rft = $reflection->getBackingType();
99
        if ($rft instanceof ReflectionNamedType) {
100
            $enumBackingType = $rft->getName();
101
        }
102
103
        $metaTags = collect($reflection->getCases())
104
            ->flatMap(function (ReflectionEnumUnitCase $reflectionEnumUnitCase) use (&$caseTags, $enumBackingType) {
105
                $case = $reflectionEnumUnitCase->getValue();
106
                $caseTags[] = new MethodTag($case->name, [$enumBackingType], isStatic: true);
107
108
                return array_map(function (Meta $meta) {
109
                    $rfm = new ReflectionMethod($meta, 'transform');
110
                    $types = [];
111
                    $rft = $rfm->getReturnType();
112
                    if ($rft instanceof ReflectionNamedType) {
113
                        $types[] = $rft->getName();
114
                    } elseif ($rft instanceof ReflectionUnionType) {
115
                        $types = Arr::map($rft->getTypes(), fn (ReflectionNamedType $type) => $type->getName());
116
                    } elseif ($rft instanceof ReflectionIntersectionType) {
117
                        $types[] = collect($rft->getTypes())->map(fn (ReflectionNamedType $type) => $type->getName())->implode('&');
0 ignored issues
show
Bug introduced by
The method getTypes() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionUnionType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

117
                        $types[] = collect($rft->/** @scrutinizer ignore-call */ getTypes())->map(fn (ReflectionNamedType $type) => $type->getName())->implode('&');
Loading history...
118
                    }
119
120
                    return [$meta::method() => $types];
121
                }, $case->metas());
122
            })
123
            ->collapse()
124
            ->map(fn (array $types, string $methodName) => new MethodTag($methodName, $types)) // @phpstan-ignore-line
125
            ->values()
126
            ->all();
127
128
        return new DocBlockGenerator(
129
            $docBlock->getShortDescription(),
130
            $docBlock->getLongDescription(),
131
            array_merge($retainedTags, $caseTags, $metaTags)
132
        );
133
    }
134
135
    /**
136
     * @throws FileNotFoundException
137
     */
138
    protected function writeDocComment(ReflectionEnum $reflection, DocBlockGenerator $docBlock): void
139
    {
140
        $fileName = (string) $reflection->getFileName();
141
        $shortName = $reflection->getShortName();
142
        $classDeclaration = "enum $shortName";
143
        $contents = $this->filesystem->get($fileName);
144
145
        // Remove existing docblock
146
        $contents = (string) preg_replace(
147
            sprintf('#([\n]?\/\*(?:[^*]|\n|(?:\*(?:[^\/]|\n)))*\*\/)?[\n]?%s#ms', preg_quote($classDeclaration)),
148
            "\n".$classDeclaration,
149
            $contents
150
        );
151
152
        $classDeclarationOffset = (int) strpos($contents, $classDeclaration);
153
        // Make sure we don't replace too much
154
        $contents = substr_replace(
155
            $contents,
156
            sprintf('%s%s', $docBlock->generate(), $classDeclaration),
157
            $classDeclarationOffset,
158
            strlen($classDeclaration)
159
        );
160
161
        $this->filesystem->put($fileName, $contents);
0 ignored issues
show
Bug introduced by
It seems like $contents can also be of type array; however, parameter $contents of Illuminate\Filesystem\Filesystem::put() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

161
        $this->filesystem->put($fileName, /** @scrutinizer ignore-type */ $contents);
Loading history...
162
        $this->info("Wrote new phpDocBlock to {$fileName}.");
163
    }
164
165
    protected function getClassFinder(): Finder
166
    {
167
        $scanPaths = $this->option('folder') ?? app_path('Enums');
168
169
        return Finder::create()->files()->in((array) $scanPaths)->name('*.php');
170
    }
171
}
172