Passed
Push — feature/second-release ( 3d3b5e...855c58 )
by Andrea Marco
14:24
created

Annotator   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 95
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 26
c 1
b 0
f 0
dl 0
loc 95
rs 10
wmc 7

6 Methods

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