Passed
Push — feature/second-release ( 176745...89fc3a )
by Andrea Marco
02:27
created

Annotator   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 30
c 3
b 0
f 0
dl 0
loc 105
ccs 34
cts 34
cp 1
rs 10
wmc 9

6 Methods

Rating   Name   Duplication   Size   Complexity  
A annotate() 0 26 3
A addDocBlock() 0 5 1
A formatMethodAnnotations() 0 5 1
A replaceAnnotations() 0 3 1
A formatUseStatements() 0 7 2
A addAnnotations() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cerbero\LaravelEnum\Services;
6
7
use Cerbero\LaravelEnum\Concerns\Enumerates;
8
use Cerbero\LaravelEnum\Data\MethodAnnotation;
9
use InvalidArgumentException;
10
11
/**
12
 * The enums annotator.
13
 */
14
final class Annotator
15
{
16
    /**
17
     * The regular expression to extract the use statements.
18
     */
19
    public const RE_USE_STATEMENTS = '~^use(?:[\s\S]+(?=^use))?.+~im';
20
21
    /**
22
     * The regular expression to extract the enum declaring line with potential attributes.
23
     */
24
    public const RE_ENUM = '~(^(?:#\[[\s\S]+)?^enum.+)~im';
25
26
    /**
27
     * The regular expression to extract the method annotations.
28
     */
29
    public const RE_METHOD_ANNOTATIONS = '~^ \* @method(?:[\s\S]+(?=@method))?.+~im';
30
31
    /**
32
     * Annotate the given enum.
33
     *
34
     * @template TEnum
35
     *
36
     * @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...
37
     * @throws InvalidArgumentException
38
     */
39 2
    public function annotate(string $enum, bool $force = false): bool
40
    {
41 2
        $inspector = new Inspector($enum, $force);
42
43 2
        if (! $inspector->uses(Enumerates::class)) {
44 1
            throw new InvalidArgumentException("The enum {$enum} must use the trait " . Enumerates::class);
45
        }
46
47 1
        if (empty($annotations = $inspector->methodAnnotations())) {
48 1
            return true;
49
        }
50
51 1
        $docBlock = $inspector->docBlock();
52 1
        $filename = $inspector->filename();
53 1
        $oldContent = (string) file_get_contents($filename);
54 1
        $methodAnnotations = $this->formatMethodAnnotations($annotations);
55 1
        $useStatements = $this->formatUseStatements($inspector->useStatements());
56 1
        $newContent = (string) preg_replace(self::RE_USE_STATEMENTS, $useStatements, $oldContent, 1);
57
58 1
        $newContent = match (true) {
59 1
            empty($docBlock) => $this->addDocBlock($methodAnnotations, $newContent),
60 1
            str_contains($docBlock, '@method') => $this->replaceAnnotations($methodAnnotations, $newContent),
61 1
            default => $this->addAnnotations($methodAnnotations, $newContent, $docBlock),
62 1
        };
63
64 1
        return file_put_contents($filename, $newContent) !== false;
65
    }
66
67
    /**
68
     * Retrieve the formatted method annotations.
69
     *
70
     * @param array<string, MethodAnnotation> $annotations
71
     */
72 1
    private function formatMethodAnnotations(array $annotations): string
73
    {
74 1
        $mapped = array_map(fn(MethodAnnotation $annotation) => " * {$annotation}", $annotations);
75
76 1
        return implode(PHP_EOL, $mapped);
77
    }
78
79
    /**
80
     * Retrieve the formatted use statements.
81
     *
82
     * @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...
83
     */
84 1
    private function formatUseStatements(array $statements): string
85
    {
86 1
        array_walk($statements, function (string &$namespace, string $alias) {
87 1
            $namespace = "use {$namespace}" . (class_basename($namespace) == $alias ? ';' : " as {$alias};");
88 1
        });
89
90 1
        return implode(PHP_EOL, $statements);
91
    }
92
93
    /**
94
     * Add a docBlock with the given method annotations.
95
     */
96 1
    private function addDocBlock(string $methodAnnotations, string $content): string
97
    {
98 1
        $replacement = implode(PHP_EOL, ['/**', $methodAnnotations, ' */', '$1']);
99
100 1
        return (string) preg_replace(self::RE_ENUM, $replacement, $content, 1);
101
    }
102
103
    /**
104
     * Replace existing method annotations with the given method annotations.
105
     */
106 1
    private function replaceAnnotations(string $methodAnnotations, string $content): string
107
    {
108 1
        return (string) preg_replace(self::RE_METHOD_ANNOTATIONS, $methodAnnotations, $content, 1);
109
    }
110
111
    /**
112
     * Add the given method annotations to the provided docBlock.
113
     */
114 1
    private function addAnnotations(string $methodAnnotations, string $content, string $docBlock): string
115
    {
116 1
        $newDocBlock = str_replace(' */', implode(PHP_EOL, [' *', $methodAnnotations, ' */']), $docBlock);
117
118 1
        return str_replace($docBlock, $newDocBlock, $content);
119
    }
120
}
121