Failed Conditions
Pull Request — master (#126)
by
unknown
05:13
created

Mixin   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 5
dl 0
loc 173
ccs 0
cts 132
cp 0
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A generate() 0 24 3
A parseDocComment() 0 17 5
A splitDocLine() 0 8 2
B createWrapper() 0 47 9
A createNullOr() 0 4 1
A createAll() 0 4 1
A getSupportedMethods() 0 32 1
A createFile() 0 7 1
A createNamespace() 0 7 1
A createInterface() 0 4 1
A isPublicMethod() 0 6 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Webmozart\Assert\Generator;
5
6
use InvalidArgumentException;
7
use Nette\PhpGenerator\ClassType;
8
use Nette\PhpGenerator\PhpFile;
9
use Nette\PhpGenerator\PhpNamespace;
10
use Nette\PhpGenerator\PsrPrinter;
11
use ReflectionClass;
12
use ReflectionMethod;
13
use Webmozart\Assert\Assert;
14
15
final class Mixin
16
{
17
    public function generate(): string
18
    {
19
        $assert = new ReflectionClass(Assert::class);
20
        $staticMethods = $assert->getMethods(ReflectionMethod::IS_STATIC);
21
22
        $supportedMethods = $this->getSupportedMethods($assert);
23
24
        $file = $this->createFile();
25
        $namespace = $this->createNamespace($file, $assert);
26
        $mixin = $this->createInterface($namespace);
27
28
        foreach ($staticMethods as $method) {
29
            if (!$this->isPublicMethod($method)) {
30
                continue;
31
            }
32
33
            $this->createNullOr($method, $supportedMethods, $mixin);
34
            $this->createAll($method, $supportedMethods, $mixin);
35
        }
36
37
        $printer = new PsrPrinter();
38
39
        return $printer->printFile($file);
40
    }
41
42
    private function parseDocComment(string $comment): array
43
    {
44
        $lines = explode("\n", $comment);
45
46
        $result = [];
47
48
        foreach ($lines as $line) {
49
            if (preg_match('~^\*\s+@(\S+)(\s+.*)?$~', trim($line), $matches)) {
50
                if (count($matches) === 2 || $matches[2] === null) {
51
                    $matches[2] = '';
52
                }
53
                $result[$matches[1]][] = trim($matches[2]);
54
            }
55
        }
56
57
        return $result;
58
    }
59
60
    private function splitDocLine(string $line): array
61
    {
62
        if (!preg_match('~^(.*)\s+(\$\S+)$~', $line, $matches)) {
63
            return [$line];
64
        }
65
66
        return [trim($matches[1]), $matches[2]];
67
    }
68
69
    private function createWrapper(ReflectionMethod $method, array $supportedMethods, ClassType $mixin, $methodNameTemplate, $typeTemplate)
70
    {
71
        $newMethodName = sprintf($methodNameTemplate, ucfirst($method->name));
72
73
        if (!in_array($newMethodName, $supportedMethods, true)) {
74
            return;
75
        }
76
77
        $comment = $method->getDocComment();
78
79
        $parsedComment = $this->parseDocComment($comment);
80
81
        if (!in_array('psalm-assert', array_keys($parsedComment))) {
82
            return;
83
        }
84
85
        $mixinMethod = $mixin->addMethod($newMethodName)
86
            ->setStatic();
87
88
        $parameters = $method->getParameters();
89
90
        foreach ($parameters as $parameter) {
91
            $mixinParameter = $mixinMethod->addParameter($parameter->name);
92
93
            if ($parameter->isDefaultValueAvailable()) {
94
                $mixinParameter->setDefaultValue($parameter->getDefaultValue());
95
            }
96
        }
97
98
        foreach ($parsedComment as $key => $values) {
99
            foreach ($values as $value) {
100
                $parts = $this->splitDocLine($value);
101
                $type = $parts[0];
102
103
                if ($key === 'psalm-assert') {
104
                    $type = sprintf($typeTemplate, $type);
105
                }
106
107
                $comment = sprintf('@%s %s', $key, $type);
108
                if (count($parts) >= 2) {
109
                    $comment .= sprintf(' %s', implode(' ', array_slice($parts, 1)));
110
                }
111
112
                $mixinMethod->addComment($comment);
113
            }
114
        }
115
    }
116
117
    private function createNullOr(ReflectionMethod $method, array $supportedMethods, ClassType $mixin)
118
    {
119
        $this->createWrapper($method, $supportedMethods, $mixin, 'nullOr%s', 'null|%s');
120
    }
121
122
    private function createAll(ReflectionMethod $method, array $supportedMethods, ClassType $mixin)
123
    {
124
        $this->createWrapper($method, $supportedMethods, $mixin, 'all%s', 'array<array-key,%s>');
125
    }
126
127
    private function getSupportedMethods(ReflectionClass $class): array
128
    {
129
        $comment = $class->getDocComment();
130
131
        preg_match_all('~@method static void ([^(]+)~', $comment, $matches);
132
133
        $temporaryUnsupported = [
134
            'nullOrNotInstanceOf',
135
            'allNotInstanceOf',
136
            'nullOrNotEmpty',
137
            'allNotEmpty',
138
            'allNotNull',
139
            'nullOrSame',
140
            'allSame',
141
            'nullOrUnicodeLetters',
142
            'allUnicodeLetters',
143
            'nullOrStringNotEmpty',
144
            'allStringNotEmpty',
145
            'nullOrIsNotA',
146
            'allIsNotA',
147
            'nullOrNotFalse',
148
            'allNotFalse',
149
            'nullOrUpper',
150
            'allUpper',
151
            'nullOrIsNonEmptyList',
152
            'allIsNonEmptyList',
153
            'nullOrIsNonEmptyMap',
154
            'allIsNonEmptyMap',
155
        ];
156
157
        return array_diff($matches[1], $temporaryUnsupported);
158
    }
159
160
    private function createFile(): PhpFile
161
    {
162
        $file = new PhpFile();
163
        $file->addComment('Automatically generated');
164
165
        return $file;
166
    }
167
168
    private function createNamespace(PhpFile $file, ReflectionClass $class): PhpNamespace
169
    {
170
        $namespace = $file->addNamespace($class->getNamespaceName());
171
        $namespace->addUse(InvalidArgumentException::class);
172
173
        return $namespace;
174
    }
175
176
    private function createInterface(PhpNamespace $namespace): ClassType
177
    {
178
        return $namespace->addInterface('Mixin');
179
    }
180
181
    private function isPublicMethod(ReflectionMethod $method): bool
182
    {
183
        $modifiers = $method->getModifiers();
184
185
        return ($modifiers & ReflectionMethod::IS_PUBLIC) !== 0;
186
    }
187
}
188