Annotator   A
last analyzed

Complexity

Total Complexity 9

Size/Duplication

Total Lines 118
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 29
c 1
b 0
f 0
dl 0
loc 118
ccs 33
cts 33
cp 1
rs 10
wmc 9

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A formatUseStatements() 0 7 2
A formatMethodAnnotations() 0 5 1
A addDocBlock() 0 5 1
A addAnnotations() 0 5 1
A annotate() 0 20 2
A replaceAnnotations() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cerbero\Enum\Services;
6
7
use Cerbero\Enum\Data\MethodAnnotation;
8
use InvalidArgumentException;
9
10
use function Cerbero\Enum\className;
11
12
/**
13
 * The enums annotator.
14
 *
15
 * @template TEnum of \UnitEnum
16
 */
17
class Annotator
18
{
19
    /**
20
     * The regular expression to extract the use statements.
21
     *
22
     * @var string
23
     */
24
    public const RE_USE_STATEMENTS = '~^use(?:[\s\S]+(?=^use))?.+~im';
25
26
    /**
27
     * The regular expression to extract the enum declaring line with potential attributes.
28
     *
29
     * @var string
30
     */
31
    public const RE_ENUM = '~(^(?:#\[[\s\S]+)?^enum.+)~im';
32
33
    /**
34
     * The regular expression to extract the method annotations.
35
     *
36
     * @var string
37
     */
38
    public const RE_METHOD_ANNOTATIONS = '~^ \* @method(?:[\s\S]+(?=@method))?.+~im';
39
40
    /**
41
     * The enum inspector.
42
     *
43
     * @var Inspector<TEnum>
44
     */
45
    protected Inspector $inspector;
46
47
    /**
48
     * Instantiate the class.
49
     *
50
     * @param class-string<TEnum> $enum
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<TEnum> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<TEnum>.
Loading history...
51
     * @throws InvalidArgumentException
52
     */
53 6
    public function __construct(protected string $enum)
54
    {
55 6
        $this->inspector = new Inspector($enum);
56
    }
57
58
    /**
59
     * Annotate the given enum.
60
     */
61 5
    public function annotate(bool $overwrite = false): bool
62
    {
63 5
        if (empty($annotations = $this->inspector->methodAnnotations(! $overwrite))) {
64 1
            return true;
65
        }
66
67 4
        $docBlock = $this->inspector->docBlock();
68 4
        $filename = $this->inspector->filename();
69 4
        $oldContent = (string) file_get_contents($filename);
70 4
        $methodAnnotations = $this->formatMethodAnnotations($annotations);
71 4
        $useStatements = $this->formatUseStatements($this->inspector->useStatements(! $overwrite));
72 4
        $newContent = (string) preg_replace(static::RE_USE_STATEMENTS, $useStatements, $oldContent, 1);
73
74 4
        $newContent = match (true) {
75 4
            empty($docBlock) => $this->addDocBlock($methodAnnotations, $newContent),
76 3
            str_contains($docBlock, '@method') => $this->replaceAnnotations($methodAnnotations, $newContent),
77 1
            default => $this->addAnnotations($methodAnnotations, $newContent, $docBlock),
78 4
        };
79
80 4
        return file_put_contents($filename, $newContent) !== false;
81
    }
82
83
    /**
84
     * Retrieve the formatted method annotations.
85
     *
86
     * @param array<string, MethodAnnotation> $annotations
87
     */
88 4
    protected function formatMethodAnnotations(array $annotations): string
89
    {
90 4
        $mapped = array_map(fn(MethodAnnotation $annotation) => " * {$annotation}", $annotations);
91
92 4
        return implode(PHP_EOL, $mapped);
93
    }
94
95
    /**
96
     * Retrieve the formatted use statements.
97
     *
98
     * @param array<string, class-string> $statements
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, class-string> at position 4 could not be parsed: Unknown type name 'class-string' at position 4 in array<string, class-string>.
Loading history...
99
     */
100 4
    protected function formatUseStatements(array $statements): string
101
    {
102 4
        array_walk($statements, function (string &$namespace, string $alias) {
103 4
            $namespace = "use {$namespace}" . (className($namespace) == $alias ? ';' : " as {$alias};");
104 4
        });
105
106 4
        return implode(PHP_EOL, $statements);
107
    }
108
109
    /**
110
     * Add a docBlock with the given method annotations.
111
     */
112 1
    protected function addDocBlock(string $methodAnnotations, string $content): string
113
    {
114 1
        $replacement = implode(PHP_EOL, ['/**', $methodAnnotations, ' */', '$1']);
115
116 1
        return (string) preg_replace(static::RE_ENUM, $replacement, $content, 1);
117
    }
118
119
    /**
120
     * Replace existing method annotations with the given method annotations.
121
     */
122 2
    protected function replaceAnnotations(string $methodAnnotations, string $content): string
123
    {
124 2
        return (string) preg_replace(static::RE_METHOD_ANNOTATIONS, $methodAnnotations, $content, 1);
125
    }
126
127
    /**
128
     * Add the given method annotations to the provided docBlock.
129
     */
130 1
    protected function addAnnotations(string $methodAnnotations, string $content, string $docBlock): string
131
    {
132 1
        $newDocBlock = str_replace(' */', implode(PHP_EOL, [' *', $methodAnnotations, ' */']), $docBlock);
133
134 1
        return str_replace($docBlock, $newDocBlock, $content);
135
    }
136
}
137