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

UrlFilters   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 98.15%

Importance

Changes 0
Metric Value
dl 0
loc 251
ccs 106
cts 108
cp 0.9815
rs 8.3157
c 0
b 0
f 0
wmc 43
lcom 1
cbo 8

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
B resolveLink() 0 26 6
A annotation() 0 17 2
A typeLinks() 0 16 3
A annotationDescription() 0 5 2
A description() 0 13 2
A doc() 0 9 1
A resolveInternalAnnotation() 0 11 2
A resolveLinkAndSeeAnnotation() 0 17 4
A highlightPhp() 0 5 2
A highlightValue() 0 4 1
A processReturnAnnotations() 0 6 1
A processThrowsAnnotations() 0 6 1
A getDescriptionFromValue() 0 9 3
A processLicenseAnnotations() 0 5 2
A processLinkAnnotations() 0 9 3
A processSeeAnnotations() 0 13 3
A processUsesAnnotations() 0 11 4

How to fix   Complexity   

Complex Class

Complex classes like UrlFilters often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UrlFilters, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
namespace ApiGen\Templating\Filters;
4
5
use ApiGen\Contracts\Generator\Resolvers\ElementResolverInterface;
6
use ApiGen\Contracts\Generator\SourceCodeHighlighter\SourceCodeHighlighterInterface;
7
use ApiGen\Contracts\Parser\Reflection\ClassReflectionInterface;
8
use ApiGen\Contracts\Parser\Reflection\ElementReflectionInterface;
9
use ApiGen\Contracts\Parser\Reflection\FunctionReflectionInterface;
10
use ApiGen\Event\ProcessDocTextEvent;
11
use ApiGen\Templating\Filters\Helpers\ElementLinkFactory;
12
use ApiGen\Templating\Filters\Helpers\LinkBuilder;
13
use ApiGen\Templating\Filters\Helpers\Strings;
14
use Latte\Runtime\Filters as LatteFilters;
15
use Nette\Utils\Validators;
16
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
17
18
final class UrlFilters extends Filters
19
{
20
    /**
21
     * @var SourceCodeHighlighterInterface
22
     */
23
    private $highlighter;
24
25
    /**
26
     * @var ElementResolverInterface
27
     */
28
    private $elementResolver;
29
30
    /**
31
     * @var LinkBuilder
32
     */
33
    private $linkBuilder;
34
35
    /**
36
     * @var ElementLinkFactory
37
     */
38
    private $elementLinkFactory;
39
40
    /**
41
     * @var EventDispatcherInterface
42
     */
43
    private $eventDispatcher;
44
45 50
    public function __construct(
46
        SourceCodeHighlighterInterface $highlighter,
47
        ElementResolverInterface $elementResolver,
48
        LinkBuilder $linkBuilder,
49
        ElementLinkFactory $elementLinkFactory,
50
        EventDispatcherInterface $eventDispatcher
51
    ) {
52 50
        $this->highlighter = $highlighter;
53 50
        $this->elementResolver = $elementResolver;
54 50
        $this->linkBuilder = $linkBuilder;
55 50
        $this->elementLinkFactory = $elementLinkFactory;
56 50
        $this->eventDispatcher = $eventDispatcher;
57 50
    }
58
59
    /**
60
     * Tries to parse a definition of a class/method/property/constant/function
61
     * and returns the appropriate link if successful.
62
     */
63 23
    public function resolveLink(string $definition, ElementReflectionInterface $reflectionElement): ?string
64
    {
65 23
        if (empty($definition)) {
66
            return null;
67
        }
68
69 23
        $suffix = '';
70 23
        if (substr($definition, -2) === '[]') {
71 2
            $definition = substr($definition, 0, -2);
72 2
            $suffix = '[]';
73
        }
74
75 23
        $element = $this->elementResolver->resolveElement($definition, $reflectionElement, $expectedName);
76 23
        if ($element === null || $element instanceof FunctionReflectionInterface) {
77 15
            return $expectedName;
78
        }
79
80 10
        $classes = [];
81 10
        if ($element->isDeprecated()) {
82 9
            $classes[] = 'deprecated';
83
        }
84
85 10
        $link = $this->elementLinkFactory->createForElement($element, $classes);
0 ignored issues
show
Bug introduced by
It seems like $element defined by $this->elementResolver->...Element, $expectedName) on line 75 can also be of type boolean; however, ApiGen\Templating\Filter...ory::createForElement() does only seem to accept object<ApiGen\Contracts\...entReflectionInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
86
87 10
        return '<code>' . $link . $suffix . '</code>';
88
    }
89
90
    public function annotation(string $value, string $name, ElementReflectionInterface $reflectionElement): string
91
    {
92
        $annotationProcessors = [
93 9
            'return' => $this->processReturnAnnotations($value, $reflectionElement),
94
            'throws' => $this->processThrowsAnnotations($value, $reflectionElement),
95
            'license' => $this->processLicenseAnnotations($value),
96 9
            'link' => $this->processLinkAnnotations($value),
97 9
            'see' => $this->processSeeAnnotations($value, $reflectionElement),
98 9
            'uses' => $this->processUsesAnnotations($value, $reflectionElement),
99 9
        ];
100 9
101 9
        if (isset($annotationProcessors[$name])) {
102
            return $annotationProcessors[$name];
103
        }
104 9
105 9
        return $this->doc($value, $reflectionElement);
106
    }
107
108
    /**
109
     * Returns links for types.
110
     */
111
    public function typeLinks(string $annotation, ElementReflectionInterface $reflectionElement): string
112
    {
113
        $links = [];
114 12
115
        // typehints can not contain spaces
116 12
        // valid typehint is:
117
        // [TYPE[|TYPE[|...]][SPACE[METHOD|PARAM][DESCRIPTION]]
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% 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...
118
        $parts = explode(' ', $annotation);
119
120
        foreach (explode('|', $parts[0]) as $type) {
121 12
            $type = $this->getTypeName($type, false);
122
            $links[] = $this->resolveLink($type, $reflectionElement) ?: LatteFilters::escapeHtml(ltrim($type, '\\'));
123 12
        }
124 12
125 12
        return implode('|', $links);
126
    }
127
128 12
    public function annotationDescription(string $annotation, ElementReflectionInterface $reflectionElement): string
129
    {
130
        $description = trim(strpbrk($annotation, "\n\r\t $")) ?: $annotation;
131 1
        return $this->doc($description, $reflectionElement);
132
    }
133 1
134 1
    public function description(ElementReflectionInterface $element): string
135
    {
136
        $long = $element->getDescription();
137 2
138
        // Merge lines
139 2
        $long = preg_replace_callback('~(?:<(code|pre)>.+?</\1>)|([^<]*)~s', function ($matches) {
140
            return ! empty($matches[2])
141
                ? preg_replace('~\n(?:(\s+\n){2,})+~', ' ', $matches[2])
142
                : $matches[0];
143 2
        }, $long);
144 1
145 2
        return $this->doc($long, $element);
146 2
    }
147
148 2
    public function doc(string $text, ElementReflectionInterface $reflectionElement): string
149
    {
150
        $text = $this->resolveInternalAnnotation($text);
151 8
152
        $processDocTextEvent = new ProcessDocTextEvent($text, $reflectionElement);
153 8
        $this->eventDispatcher->dispatch(ProcessDocTextEvent::class, $processDocTextEvent);
154
155 8
        return $this->resolveLinkAndSeeAnnotation($processDocTextEvent->getText(), $reflectionElement);
156 8
    }
157
158 8
    private function resolveInternalAnnotation(string $text): string
159
    {
160
        $pattern = '~\\{@(\\w+)(?:(?:\\s+((?>(?R)|[^{}]+)*)\\})|\\})~';
161 12
        return preg_replace_callback($pattern, function ($matches) {
162
            if ($matches[1] !== 'internal') {
163 12
                return $matches[0];
164
            }
165 4
166 2
            return '';
167
        }, $text);
168
    }
169 3
170 12
    private function resolveLinkAndSeeAnnotation(string $text, ElementReflectionInterface $reflectionElement): string
171
    {
172
        return preg_replace_callback('~{@(?:link|see)\\s+([^}]+)}~', function ($matches) use ($reflectionElement) {
173
            [$url, $description] = Strings::split($matches[1]);
0 ignored issues
show
Bug introduced by
The variable $url does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $description does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
174
175 15
            $link = $this->resolveLink($matches[1], $reflectionElement);
176 7
            if ($link) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $link of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
177
                return $link;
178 7
            }
179 7
180 1
            if (Validators::isUri($url)) {
181
                return $this->linkBuilder->build($url, $description ?: $url);
182
            }
183 6
184 5
            return $matches[1];
185
        }, $text);
186
    }
187 1
188 15
    public function highlightPhp(string $source, ElementReflectionInterface $reflectionElement): string
189
    {
190
        return $this->resolveLink($this->getTypeName($source), $reflectionElement)
191 2
            ?: $this->highlighter->highlight((string) $source);
192
    }
193 2
194 2
    public function highlightValue(string $definition, ElementReflectionInterface $reflectionElement): string
195
    {
196
        return $this->highlightPhp(preg_replace('~^(?:[ ]{4}|\t)~m', '', $definition), $reflectionElement);
197 1
    }
198
199 1
    private function processReturnAnnotations(string $value, ElementReflectionInterface $reflectionElement): string
200
    {
201
        $description = $this->getDescriptionFromValue($value, $reflectionElement);
202 9
        $typeLinks = $this->typeLinks($value, $reflectionElement);
203
        return $typeLinks . $description;
204 9
    }
205 9
206 9
    private function processThrowsAnnotations(string $value, ElementReflectionInterface $elementReflection): string
207
    {
208
        $description = $this->getDescriptionFromValue($value, $elementReflection);
209 9
        $typeLinks = $this->typeLinks($value, $elementReflection);
210
        return $typeLinks . $description;
211 9
    }
212 9
213 9
    /**
214
     * @param mixed $value
215
     * @param ElementReflectionInterface $elementReflection
216
     */
217
    private function getDescriptionFromValue($value, ElementReflectionInterface $elementReflection): string
218
    {
219
        $description = (string) trim((string) strpbrk($value, "\n\r\t $")) ?: null;
220 9
        if ($description) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $description of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
221
            $description = '<br>' . $this->doc($description, $elementReflection);
222 9
        }
223 9
224 3
        return (string) $description;
225
    }
226
227 9
    private function processLicenseAnnotations(string $value): string
228
    {
229
        [$url, $description] = Strings::split($value);
0 ignored issues
show
Bug introduced by
The variable $url does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $description does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
230 9
        return $this->linkBuilder->build($url, $description ?: $url);
231
    }
232 9
233 9
    private function processLinkAnnotations(string $value): string
234
    {
235
        [$url, $description] = Strings::split($value);
0 ignored issues
show
Bug introduced by
The variable $url does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $description does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
236 9
        if (Validators::isUrl($url)) {
237
            return $this->linkBuilder->build($url, $description ?: $url);
238 9
        }
239 9
240 2
        return '';
241
    }
242
243 7
    private function processSeeAnnotations(string $value, ElementReflectionInterface $reflectionElement): string
244
    {
245
        $doc = [];
246 9
        foreach (preg_split('~\\s*,\\s*~', $value) as $link) {
247
            if ($this->elementResolver->resolveElement($link, $reflectionElement) !== null) {
248 9
                $doc[] = $this->typeLinks($link, $reflectionElement);
249 9
            } else {
250 9
                $doc[] = $this->doc($link, $reflectionElement);
251 5
            }
252
        }
253 4
254
        return implode(', ', $doc);
255
    }
256
257 9
    private function processUsesAnnotations(string $value, ElementReflectionInterface $reflectionElement): ?string
258
    {
259
        [$link, $description] = Strings::split($value);
0 ignored issues
show
Bug introduced by
The variable $link does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $description does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
260 9
        $separator = $reflectionElement instanceof ClassReflectionInterface || ! $description ? ' ' : '<br>';
261
        if ($this->elementResolver->resolveElement($link, $reflectionElement) !== null) {
262 9
            $value = $this->typeLinks($link, $reflectionElement) . $separator . $description;
263 9
            return trim($value);
264 9
        }
265 6
266 6
        return null;
267
    }
268
}
269