GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#837)
by E
07:31 queued 05:03
created

ElementResolver::resolveIfParsed()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0092

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 2
dl 0
loc 21
ccs 11
cts 12
cp 0.9167
crap 4.0092
rs 9.0534
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace ApiGen\Generator\Resolvers;
4
5
use ApiGen\Contracts\Generator\Resolvers\ElementResolverInterface;
6
use ApiGen\Contracts\Parser\ParserStorageInterface;
7
use ApiGen\Contracts\Parser\Reflection\Behavior\InClassInterface;
8
use ApiGen\Contracts\Parser\Reflection\ClassReflectionInterface;
9
use ApiGen\Contracts\Parser\Reflection\ConstantReflectionInterface;
10
use ApiGen\Contracts\Parser\Reflection\ElementReflectionInterface;
11
use ApiGen\Contracts\Parser\Reflection\FunctionReflectionInterface;
12
use ApiGen\Contracts\Parser\Reflection\MethodReflectionInterface;
13
use ApiGen\Contracts\Parser\Reflection\ParameterReflectionInterface;
14
use ApiGen\Contracts\Parser\Reflection\PropertyReflectionInterface;
15
use ApiGen\Parser\Reflection\ReflectionClass;
16
use TokenReflection\Resolver;
17
18
final class ElementResolver implements ElementResolverInterface
19
{
20
    /**
21
     * @var int[]
22
     */
23
    private $simpleTypes = [
24
        'boolean' => 1,
25
        'integer' => 1,
26
        'float' => 1,
27
        'string' => 1,
28
        'array' => 1,
29
        'object' => 1,
30
        'resource' => 1,
31
        'callback' => 1,
32
        'callable' => 1,
33
        'NULL' => 1,
34
        'false' => 1,
35
        'true' => 1,
36
        'mixed' => 1
37
    ];
38
39
    /**
40
     * @var ParserStorageInterface
41
     */
42
    private $parserStorage;
43
44 87
    public function __construct(ParserStorageInterface $parserStorage)
45
    {
46 87
        $this->parserStorage = $parserStorage;
47 87
    }
48
49 36
    public function getClass(string $name, string $namespace = ''): ?ClassReflectionInterface
50
    {
51 36
        $parsedClasses = $this->parserStorage->getClasses();
52
53 36
        $class = $this->findElementByNameAndNamespace($parsedClasses, $name, $namespace);
54 36
        if ($class && $class->isDocumented()) {
55 14
            return $class;
56
        }
57
58 23
        return null;
59
    }
60
61 19
    public function getConstant(string $name, string $namespace = ''): ?ConstantReflectionInterface
62
    {
63 19
        $parsedConstants = $this->parserStorage->getConstants();
64
65
        /** @var ReflectionClass $constant */
66 19
        $constant = $this->findElementByNameAndNamespace($parsedConstants, $name, $namespace);
67 19
        if ($constant && $constant->isDocumented()) {
68 1
            return $constant;
69
        }
70
71 18
        return null;
72
    }
73
74
    /**
75
     * @return FunctionReflectionInterface|MethodReflectionInterface|null
76
     */
77 20
    public function getFunction(string $name, string $namespace = '')
78
    {
79 20
        $parsedFunctions = $this->parserStorage->getFunctions();
80 20
        $function = $this->findElementByNameAndNamespace($parsedFunctions, $name, $namespace);
81 20
        if ($function && $function->isDocumented()) {
82 3
            return $function;
83
        }
84
85 17
        return null;
86
    }
87
88
    /**
89
     * @param string $definition
90
     * @param object|string $reflectionElement
91
     * @param string|null $expectedName
92
     * @return ClassReflectionInterface|ConstantReflectionInterface|FunctionReflectionInterface|MethodReflectionInterface|PropertyReflectionInterface|null
93
     */
94 29
    public function resolveElement(string $definition, $reflectionElement, ?string &$expectedName = null)
95
    {
96 29
        if ($this->isSimpleType($definition)) {
97 4
            return null;
98
        }
99
100
        // @todo hotfix
101 27
        if (class_exists($definition, false)) {
102 1
            return $reflectionElement;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $reflectionElement; of type object|string is incompatible with the return type declared by the interface ApiGen\Contracts\Generat...terface::resolveElement of type ApiGen\Contracts\Parser\...ectionInterface|boolean as it can also be of type string which is not included in this return type.
Loading history...
103
        }
104
105 26
        $originalContext = $reflectionElement;
106 26
        $reflectionElement = $this->correctContextForParameterOrClassMember($reflectionElement);
0 ignored issues
show
Documentation introduced by
$reflectionElement is of type object|string, but the function expects a object<ApiGen\Contracts\...ionReflectionInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
107
108 26
        if ($reflectionElement === null) {
109 1
            return null;
110
        }
111
112
        // self, static, $this references
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
113 25
        if ($definition === 'self' || $definition === 'static' || $definition === '$this') {
114 3
            return $reflectionElement instanceof ClassReflectionInterface ? $reflectionElement : null;
115
        }
116
117 22
        $definitionBase = substr($definition, 0, strcspn($definition, '\\:'));
118
        $namespaceAliases = $reflectionElement->getNamespaceAliases();
119 22
        $className = Resolver::resolveClassFQN($definition, $namespaceAliases, $reflectionElement->getNamespaceName());
120
121 22
        $resolved = $this->resolveIfParsed($definition, $reflectionElement);
122 22
        if ($resolved) {
123 8
            return $resolved;
124
        }
125
126 15
        if (! empty($definitionBase) && isset($namespaceAliases[$definitionBase]) && $definition !== $className) {
127
            // Aliased class
128
            $expectedName = $className;
129
130
            if (strpos($className, ':') === false) {
131
                return $this->getClass($className, $reflectionElement->getNamespaceName());
132
            } else {
133
                $definition = $className;
134
            }
135
        }
136
137 15
        $position = $this->getPositionFromDefinition($definition);
138 15
        if ($reflectionElement instanceof ClassReflectionInterface && $position) {
139
            $reflectionElement = $this->resolveContextForClassProperty($definition, $reflectionElement, $position);
140
            $definition = substr($definition, $position + 2);
141
        } elseif ($originalContext instanceof ParameterReflectionInterface) {
142
            return null;
143
        }
144
145 15
        if (! $this->isContextUsable($reflectionElement)) {
146
            return null;
147
        }
148
149 15
        return $this->resolveIfInContext($definition, $reflectionElement);
150
    }
151
152
    /**
153
     * @param ClassReflectionInterface|ParameterReflectionInterface|FunctionReflectionInterface $reflectionElement
154
     * @return ClassReflectionInterface|FunctionReflectionInterface
155
     */
156 29
    private function correctContextForParameterOrClassMember($reflectionElement)
157
    {
158 29
        if ($reflectionElement instanceof ParameterReflectionInterface
159 2
            && $reflectionElement->getDeclaringClassName() === ''
160
        ) {
161 1
            return $this->getFunction($reflectionElement->getDeclaringFunctionName());
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getFunction($refl...claringFunctionName()); of type ApiGen\Contracts\Parser\...eflectionInterface|null adds the type ApiGen\Contracts\Parser\...thodReflectionInterface to the return on line 161 which is incompatible with the return type documented by ApiGen\Generator\Resolve...rParameterOrClassMember of type ApiGen\Contracts\Parser\...tionReflectionInterface.
Loading history...
162
        }
163
164 28
        if ($reflectionElement instanceof InClassInterface) {
165 2
            return $this->getClass($reflectionElement->getDeclaringClassName());
166
        }
167
168 26
        return $reflectionElement;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $reflectionElement; of type ApiGen\Contracts\Parser\...tionReflectionInterface is incompatible with the return type documented by ApiGen\Generator\Resolve...rParameterOrClassMember of type ApiGen\Contracts\Parser\...tionReflectionInterface as it can also be of type ApiGen\Contracts\Parser\...eterReflectionInterface which is not included in this return type.
Loading history...
169
    }
170
171 4
    private function resolveContextForSelfProperty(
172
        string $definition,
173
        int $pos,
174
        ElementReflectionInterface $reflectionElement
175
    ): ?ClassReflectionInterface {
176 4
        $class = $this->getClass(substr($definition, 0, $pos), $reflectionElement->getNamespaceName());
177 4
        if ($class === null) {
178 2
            $fqnName = Resolver::resolveClassFQN(
179 2
                substr($definition, 0, $pos),
180 2
                $reflectionElement->getNamespaceAliases(),
181 2
                $reflectionElement->getNamespaceName()
182
            );
183 2
            $class = $this->getClass($fqnName);
184
        }
185
186 4
        return $class;
187
    }
188
189 30
    private function isSimpleType(string $definition): bool
190
    {
191 30
        return empty($definition) || isset($this->simpleTypes[$definition]);
192
    }
193
194
    /**
195
     * @return ClassReflectionInterface|ConstantReflectionInterface|FunctionReflectionInterface|null
196
     */
197 24
    private function resolveIfParsed(string $definition, ElementReflectionInterface $reflectionElement)
198
    {
199 24
        $definition = $this->removeEndBrackets($definition);
200
201 24
        $class = $class = $this->getClass($definition, $reflectionElement->getNamespaceName());
202 24
        if ($class) {
203 8
            return $class;
204
        }
205
206 17
        $constant = $this->getConstant($definition, $reflectionElement->getNamespaceName());
207 17
        if ($constant) {
208
            return $constant;
209
        }
210
211 17
        $function = $this->getFunction($definition, $reflectionElement->getNamespaceName());
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getFunction($defi...t->getNamespaceName()); of type ApiGen\Contracts\Parser\...eflectionInterface|null adds the type ApiGen\Contracts\Parser\...thodReflectionInterface to the return on line 213 which is incompatible with the return type documented by ApiGen\Generator\Resolve...solver::resolveIfParsed of type ApiGen\Contracts\Parser\...eflectionInterface|null.
Loading history...
212 17
        if ($function) {
213 1
            return $function;
214
        }
215
216 16
        return null;
217
    }
218
219
    /**
220
     * @return ConstantReflectionInterface|MethodReflectionInterface|PropertyReflectionInterface|null
221
     */
222 19
    private function resolveIfInContext(string $definition, ClassReflectionInterface $context)
223
    {
224 19
        $definition = $this->removeEndBrackets($definition);
225 19
        $definition = $this->removeStartDollar($definition);
226
227 19
        if ($context->hasProperty($definition)) {
228 1
            return $context->getProperty($definition);
229 18
        } elseif ($context->hasMethod($definition)) {
230 2
            return $context->getMethod($definition);
231 16
        } elseif ($context->hasConstant($definition)) {
232 1
            return $context->getConstant($definition);
233
        }
234
235 15
        return null;
236
    }
237
238 28
    private function removeEndBrackets(string $definition): string
239
    {
240 28
        if (substr($definition, -2) === '()') {
241 1
            return substr($definition, 0, -2);
242
        }
243
244 27
        return $definition;
245
    }
246
247 19
    private function removeStartDollar(string $definition): string
248
    {
249 19
        if ($definition[0] === '$') {
250 2
            return substr($definition, 1);
251
        }
252
253 17
        return $definition;
254
    }
255
256 3
    private function resolveContextForClassProperty(
257
        string $definition,
258
        ClassReflectionInterface $reflectionClass,
259
        int $position
260
    ): ?ClassReflectionInterface {
261
        // Class::something or Class->something
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
262 3
        $parentClassName = $reflectionClass->getParentClassName();
263
264 3
        if (strpos($definition, 'parent::') === 0 && $parentClassName) {
265 1
            return $this->getClass($parentClassName);
266
        }
267
268 2
        if (strpos($definition, 'self::') !== 0) {
269 1
            return $this->resolveContextForSelfProperty($definition, $position, $reflectionClass);
270
        }
271
272 1
        return $reflectionClass;
273
    }
274
275
    /**
276
     * @param object|string|null $reflectionElement
277
     */
278 17
    private function isContextUsable($reflectionElement): bool
279
    {
280 17
        if ($reflectionElement === null || $reflectionElement instanceof ConstantReflectionInterface
281
            || $reflectionElement instanceof FunctionReflectionInterface
282
        ) {
283 1
            return false;
284
        }
285
286 16
        return true;
287
    }
288
289
    /**
290
     * @param mixed[] $elements
291
     * @param string $name
292
     * @param string $namespace
293
     * @return mixed|ElementReflectionInterface
294
     */
295 45
    private function findElementByNameAndNamespace(array $elements, string $name, string $namespace)
296
    {
297 45
        $namespacedName = $namespace . '\\' . $name;
298 45
        if (isset($elements[$namespacedName])) {
299 3
            return $elements[$namespacedName];
300
        }
301
302 43
        $shortName = ltrim($name, '\\');
303 43
        if (isset($elements[$shortName])) {
304 20
            return $elements[$shortName];
305
        }
306
307 25
        return null;
308
    }
309
310 15
    private function getPositionFromDefinition(string $definition): int
311
    {
312 15
        $pos = strpos($definition, '::');
313 15
        if ($pos) {
314
            return $pos;
315
        }
316
317 15
        $pos = strpos($definition, '->');
318 15
        if ($pos) {
319
            return $pos;
320
        }
321
322 15
        return 0;
323
    }
324
}
325