Passed
Push — master ( 697d7b...50775c )
by Satoshi
02:58
created

ExtraPhpDocTagResolver::resolvePackageTag()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace DependencyAnalyzer\DependencyGraphBuilder;
5
6
use DependencyAnalyzer\DependencyDumper;
7
use DependencyAnalyzer\DependencyGraph\ExtraPhpDocTags\DepsInternal;
8
use DependencyAnalyzer\DependencyGraph\ExtraPhpDocTags\Internal;
9
use DependencyAnalyzer\DependencyGraph\FullyQualifiedStructuralElementName as FQSEN;
10
use DependencyAnalyzer\Exceptions\InvalidFullyQualifiedStructureElementNameException;
11
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
12
use PHPStan\PhpDocParser\Lexer\Lexer;
13
use PHPStan\PhpDocParser\Parser\PhpDocParser;
14
use PHPStan\PhpDocParser\Parser\TokenIterator;
15
use PHPStan\Reflection\ClassReflection;
16
17
class ExtraPhpDocTagResolver
18
{
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
    /**
57
     * @param \ReflectionClass $reflectionClass
58
     * @return DepsInternal[]
59
     */
60
    public function resolveDepsInternalTag(\ReflectionClass $reflectionClass): array
61
    {
62
        $ret = [];
63
64
        foreach ($this->collectReflectionsHavingTag($reflectionClass, DepsInternal::getTagName()) as $reflection) {
65
            try {
66
                $ret[] = new DepsInternal(
67
                    FQSEN::createFromReflection($reflection),
68
                    $this->resolve($reflection->getDocComment(), DepsInternal::getTagName())
69
                );
70
            } catch (InvalidFullyQualifiedStructureElementNameException $e) {
71
                $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
72
            }
73
        }
74
75
        return $ret;
76
    }
77
78
    /**
79
     * @param \ReflectionClass $reflectionClass
80
     * @return Internal[]
81
     */
82
    public function resolveInternalTag(\ReflectionClass $reflectionClass): array
83
    {
84
        $ret = [];
85
        $package = $this->resolvePackageTag($reflectionClass) ??
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->resolvePackageTag($reflectionClass) targeting DependencyAnalyzer\Depen...er::resolvePackageTag() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
86
            FQSEN::createFromReflection($reflectionClass)->getFullyQualifiedNamespaceName()->toString();
87
88
        foreach ($this->collectReflectionsHavingTag($reflectionClass, Internal::getTagName()) as $reflection) {
89
            try {
90
                $ret[] = new Internal(FQSEN::createFromReflection($reflection), $package);
91
            } catch (InvalidFullyQualifiedStructureElementNameException $e) {
92
                $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
93
            }
94
        }
95
96
        return $ret;
97
    }
98
99
    public function resolvePackageTag(\ReflectionClass $reflectionClass): ?string
100
    {
101
        if ($this->haveTag($reflectionClass, '@package')) {
102
            try {
103
                $ret[] = new DepsInternal(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$ret was never initialized. Although not strictly required by PHP, it is generally a good practice to add $ret = array(); before regardless.
Loading history...
104
                    FQSEN::createClass($reflectionClass->getName()),
105
                    $this->resolve($reflectionClass->getDocComment(), '@package')
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

105
                    $this->resolve(/** @scrutinizer ignore-type */ $reflectionClass->getDocComment(), '@package')
Loading history...
106
                );
107
            } catch (InvalidFullyQualifiedStructureElementNameException $e) {
108
                $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
109
            }
110
        }
111
112
        return null;
113
    }
114
115
    protected function collectReflectionsHavingTag(\ReflectionClass $reflectionClass, string $tagName): array
116
    {
117
        $ret = [];
118
119
        if ($this->haveTag($reflectionClass, $tagName)) {
120
            $ret[] = $reflectionClass;
121
        }
122
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
123
            if ($this->haveTag($reflectionProperty, $tagName)) {
124
                $ret[] = $reflectionProperty;
125
            }
126
        }
127
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
128
            if ($this->haveTag($reflectionMethod, DepsInternal::getTagName())) {
129
                $ret[] = $reflectionMethod;
130
            }
131
        }
132
        foreach ($reflectionClass->getReflectionConstants() as $reflectionClassConstant) {
133
            if ($this->haveTag($reflectionClassConstant, DepsInternal::getTagName())) {
134
                $ret[] = $reflectionClassConstant;
135
            }
136
        }
137
138
        return $ret;
139
    }
140
141
142
//    protected function resolveUsesTag(\ReflectionClass $reflectionClass)
143
//    {
144
//        $reflectionClass->getDocComment();
145
//    }
146
147
    /**
148
     * @param \ReflectionClass $reflectionClass
149
     * @return DepsInternal[]
150
     */
151
    public function resolveCanOnlyUsedByTag(\ReflectionClass $reflectionClass): array
152
    {
153
        $ret = [];
154
        if ($this->haveTag($reflectionClass, self::ONLY_USED_BY_TAGS)) {
155
            try {
156
                $ret[] = new DepsInternal(
157
                    FQSEN::createClass($reflectionClass->getName()),
158
                    $this->resolve($reflectionClass->getDocComment(), self::ONLY_USED_BY_TAGS)
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

158
                    $this->resolve(/** @scrutinizer ignore-type */ $reflectionClass->getDocComment(), self::ONLY_USED_BY_TAGS)
Loading history...
159
                );
160
            } catch (InvalidFullyQualifiedStructureElementNameException $e) {
161
                $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
162
            }
163
        }
164
165
        return $ret;
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