Passed
Push — master ( 65484c...fedbc6 )
by Satoshi
02:08
created

ExtraPhpDocTagResolver   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 213
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 82
dl 0
loc 213
rs 9.44
c 0
b 0
f 0
wmc 37

12 Methods

Rating   Name   Duplication   Size   Complexity  
A resolve() 0 14 3
C resolveDepsInternalTag() 0 55 12
A collectExtraPhpDocs() 0 4 1
A resolveInternalTag() 0 13 4
A resolveUsesTag() 0 3 1
A notifyError() 0 4 2
A haveTag() 0 19 6
A resolveDependeeTag() 0 7 2
A resolveDependerTag() 0 7 2
A resolveCanOnlyUsedByTag() 0 7 2
A setObserver() 0 3 1
A __construct() 0 4 1
1
<?php
2
declare(strict_types=1);
3
4
namespace DependencyAnalyzer\DependencyGraphBuilder;
5
6
use DependencyAnalyzer\DependencyDumper;
7
use DependencyAnalyzer\DependencyGraph\ExtraPhpDocTagResolver\DepsInternal;
8
use DependencyAnalyzer\DependencyGraph\FullyQualifiedStructuralElementName as FQSEN;
9
use DependencyAnalyzer\Exceptions\InvalidFullyQualifiedStructureElementNameException;
10
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
11
use PHPStan\PhpDocParser\Lexer\Lexer;
12
use PHPStan\PhpDocParser\Parser\PhpDocParser;
13
use PHPStan\PhpDocParser\Parser\TokenIterator;
14
use PHPStan\Reflection\ClassReflection;
15
16
class ExtraPhpDocTagResolver
17
{
18
    const DEPS_INTERNAL = '@deps-internal';
19
    const ONLY_USED_BY_TAGS = '@canOnlyUsedBy';
20
    const DEPENDER_TAGS = '@dependee';
21
    const DEPENDEE_TAGS = '@dependee';
22
23
    /**
24
     * @var Lexer
25
     */
26
    protected $phpDocLexer;
27
28
    /**
29
     * @var PhpDocParser
30
     */
31
    protected $phpDocParser;
32
33
    /**
34
     * @var ObserverInterface
35
     */
36
    protected $observer = null;
37
38
    public function __construct(Lexer $phpDocLexer, PhpDocParser $phpDocParser)
39
    {
40
        $this->phpDocLexer = $phpDocLexer;
41
        $this->phpDocParser = $phpDocParser;
42
    }
43
44
    public function setObserver(ObserverInterface $observer): void
45
    {
46
        $this->observer = $observer;
47
    }
48
49
    protected function notifyError(string $file, string $fqsen, InvalidFullyQualifiedStructureElementNameException $exception)
50
    {
51
        if (!is_null($this->observer)) {
52
            $this->observer->notifyResolvePhpDocError($file, $fqsen, $exception);
53
        }
54
    }
55
56
    public function collectExtraPhpDocs(\ReflectionClass $reflectionClass)
57
    {
58
//        $this->resolveInternalTag($reflectionClass);
59
        $this->resolveDepsInternalTag($reflectionClass);
60
//        $this->resolveUsesTag($reflectionClass);
61
    }
62
63
    /**
64
     * @param \ReflectionClass $reflectionClass
65
     * @return DepsInternal[]
66
     */
67
    public function resolveDepsInternalTag(\ReflectionClass $reflectionClass): array
68
    {
69
        $ret = [];
70
71
        if ($this->haveTag($reflectionClass, self::DEPS_INTERNAL)) {
72
            try {
73
                $ret[] = new DepsInternal(
74
                    FQSEN::createClass($reflectionClass->getName()),
75
                    $this->resolve($reflectionClass->getDocComment(), self::DEPS_INTERNAL)
0 ignored issues
show
Bug introduced by
It seems like $reflectionClass->getDocComment() can also be of type boolean; however, parameter $phpdoc of DependencyAnalyzer\Depen...cTagResolver::resolve() 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

75
                    $this->resolve(/** @scrutinizer ignore-type */ $reflectionClass->getDocComment(), self::DEPS_INTERNAL)
Loading history...
76
                );
77
            } catch (InvalidFullyQualifiedStructureElementNameException $e) {
78
                $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
79
            }
80
        }
81
82
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
83
            if ($this->haveTag($reflectionProperty, self::DEPS_INTERNAL)) {
84
                try {
85
                    $ret[] = new DepsInternal(
86
                        FQSEN::createProperty($reflectionClass->getName(), $reflectionProperty->getName()),
87
                        $this->resolve($reflectionProperty->getDocComment(), self::DEPS_INTERNAL)
88
                    );
89
                } catch (InvalidFullyQualifiedStructureElementNameException $e) {
90
                    $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
91
                }
92
            }
93
        }
94
95
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
96
            if ($this->haveTag($reflectionMethod, self::DEPS_INTERNAL)) {
97
                try {
98
                    $ret[] = new DepsInternal(
99
                        FQSEN::createMethod($reflectionClass->getName(), $reflectionMethod->getName()),
100
                        $this->resolve($reflectionMethod->getDocComment(), self::DEPS_INTERNAL)
101
                    );
102
                } catch (InvalidFullyQualifiedStructureElementNameException $e) {
103
                    $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
104
                }
105
            }
106
        }
107
108
        foreach ($reflectionClass->getReflectionConstants() as $reflectionClassConstant) {
109
            if ($this->haveTag($reflectionClassConstant, self::DEPS_INTERNAL)) {
110
                try {
111
                    $ret[] = new DepsInternal(
112
                        FQSEN::createClassConstant($reflectionClass->getName(), $reflectionClassConstant->getName()),
113
                        $this->resolve($reflectionClassConstant->getDocComment(), self::DEPS_INTERNAL)
114
                    );
115
                } catch (InvalidFullyQualifiedStructureElementNameException $e) {
116
                    $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
117
                }
118
            }
119
        }
120
121
        return $ret;
122
    }
123
124
    protected function resolveInternalTag(\ReflectionClass $reflectionClass)
125
    {
126
        $phpDocs = [];
127
128
        $phpDocs[] = $reflectionClass->getDocComment();
129
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
130
            $phpDocs[] = $reflectionProperty->getDocComment();
131
        }
132
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
133
            $phpDocs[] = $reflectionMethod->getDocComment();
134
        }
135
        foreach ($reflectionClass->getReflectionConstants() as $reflectionClassConstant) {
136
            $phpDocs[] = $reflectionClassConstant->getDocComment();
137
        }
138
139
140
    }
141
142
143
    protected function resolveUsesTag(\ReflectionClass $reflectionClass)
144
    {
145
        $reflectionClass->getDocComment();
146
    }
147
148
    public function resolveCanOnlyUsedByTag(\ReflectionClass $classReflection): array
149
    {
150
        if ($phpdoc = $classReflection->getDocComment()) {
151
            return $this->resolve($phpdoc, self::ONLY_USED_BY_TAGS);
0 ignored issues
show
Bug introduced by
It seems like $phpdoc can also be of type true; however, parameter $phpdoc of DependencyAnalyzer\Depen...cTagResolver::resolve() 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

151
            return $this->resolve(/** @scrutinizer ignore-type */ $phpdoc, self::ONLY_USED_BY_TAGS);
Loading history...
152
        }
153
154
        return [];
155
//        $ret = [];
156
//        if ($phpdoc = $classReflection->getNativeReflection()->getDocComment()) {
157
//            $ret['class_must_have_special_chars_of_method'] = $this->resolve($phpdoc, self::ONLY_USED_BY_TAGS);
158
//        }
159
//
160
//        foreach ($classReflection->getNativeReflection()->getMethods() as $reflectionMethod) {
161
//            $phpdoc = $reflectionMethod->getDocComment();  // /**\n  * Hogefuga
162
//            $ret[$reflectionMethod->getName()] = $this->resolve($phpdoc, self::ONLY_USED_BY_TAGS);
163
//        }
164
//
165
//        return [];
166
    }
167
168
    /**
169
     * @param ClassReflection $classReflection
170
     * @return string[]
171
     */
172
    public function resolveDependerTag(ClassReflection $classReflection): array
173
    {
174
        if ($phpdoc = $classReflection->getNativeReflection()->getDocComment()) {
175
            return $this->resolve($phpdoc, self::DEPENDER_TAGS);
0 ignored issues
show
Bug introduced by
It seems like $phpdoc can also be of type true; however, parameter $phpdoc of DependencyAnalyzer\Depen...cTagResolver::resolve() 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

175
            return $this->resolve(/** @scrutinizer ignore-type */ $phpdoc, self::DEPENDER_TAGS);
Loading history...
176
        }
177
178
        return [];
179
    }
180
181
    /**
182
     * @param ClassReflection $classReflection
183
     * @return string[]
184
     */
185
    public function resolveDependeeTag(ClassReflection $classReflection): array
186
    {
187
        if ($phpdoc = $classReflection->getNativeReflection()->getDocComment()) {
188
            return $this->resolve($phpdoc, self::DEPENDEE_TAGS);
0 ignored issues
show
Bug introduced by
It seems like $phpdoc can also be of type true; however, parameter $phpdoc of DependencyAnalyzer\Depen...cTagResolver::resolve() 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

188
            return $this->resolve(/** @scrutinizer ignore-type */ $phpdoc, self::DEPENDEE_TAGS);
Loading history...
189
        }
190
191
        return [];
192
    }
193
194
    protected function haveTag(\Reflector $reflector, string $tag): bool
195
    {
196
        if (!method_exists($reflector, 'getDocComment') && $reflector->getDocComment() !== false) {
0 ignored issues
show
Bug introduced by
The method getDocComment() does not exist on Reflector. It seems like you code against a sub-type of said class. However, the method does not exist in ReflectionExtension or ReflectionZendExtension or ReflectionParameter. Are you sure you never get one of those? ( Ignorable by Annotation )

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

196
        if (!method_exists($reflector, 'getDocComment') && $reflector->/** @scrutinizer ignore-call */ getDocComment() !== false) {
Loading history...
197
            return false;
198
        } elseif (($docComment = $reflector->getDocComment()) === false) {
0 ignored issues
show
Unused Code introduced by
The assignment to $docComment is dead and can be removed.
Loading history...
199
            return false;
200
    }
201
202
        $tokens = new TokenIterator($this->phpDocLexer->tokenize($reflector->getDocComment()));
203
        $phpDocNode = $this->phpDocParser->parse($tokens);
204
205
        foreach ($phpDocNode->getTagsByName($tag) as $phpDocTagNode) {
206
            /** @var PhpDocTagNode $phpDocTagNode */
207
            if (preg_match('/^' . $tag . '/', $phpDocTagNode->__toString()) === 1) {
208
                return true;
209
            }
210
        }
211
212
        return false;
213
    }
214
215
    protected function resolve(string $phpdoc, string $tag): array
216
    {
217
        $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpdoc));
218
        $phpDocNode = $this->phpDocParser->parse($tokens);
219
220
        $ret = [];
221
        foreach ($phpDocNode->getTagsByName($tag) as $phpDocTagNode) {
222
            /** @var PhpDocTagNode $phpDocTagNode */
223
            if (preg_match('/^' . $tag . '\s+(.+)$/', $phpDocTagNode->__toString(), $matches) === 1) {
224
                $ret[] = $matches[1];
225
            }
226
        };
227
228
        return $ret;
229
    }
230
}
231