Passed
Pull Request — master (#47)
by Pascal
14:53
created

DocBlock::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 18
rs 10
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of PHPUnit.
7
 *
8
 * (c) Sebastian Bergmann <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
namespace FriendsOfPhpSpec\PhpSpec\CodeCoverage\Annotation;
14
15
use function array_map;
16
use function array_merge;
17
use function array_slice;
18
use function array_values;
19
use function count;
20
use function file;
21
use function preg_match;
22
use function preg_match_all;
23
use function strtolower;
24
use function substr;
25
use ReflectionClass;
26
use ReflectionFunctionAbstract;
27
use ReflectionMethod;
28
use Reflector;
29
30
/**
31
 * This is an abstraction around a PHPUnit-specific docBlock,
32
 * allowing us to ask meaningful questions about a specific
33
 * reflection symbol.
34
 *
35
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
36
 */
37
final class DocBlock
38
{
39
    /** @var string */
40
    private $docComment;
41
42
    /** @var bool */
43
    private $isMethod;
44
45
    /** @var array<string, array<int, string>> pre-parsed annotations indexed by name and occurrence index */
46
    private $symbolAnnotations;
47
48
    /** @var int */
49
    private $startLine;
50
51
    /** @var int */
52
    private $endLine;
53
54
    /** @var string */
55
    private $fileName;
56
57
    /** @var string */
58
    private $name;
59
60
    /**
61
     * @var string
62
     *
63
     * @psalm-var class-string
64
     */
65
    private $className;
66
67
    public static function ofClass(ReflectionClass $class): self
68
    {
69
        $className = $class->getName();
70
71
        return new self(
72
            (string) $class->getDocComment(),
73
            false,
74
            self::extractAnnotationsFromReflector($class),
75
            $class->getStartLine(),
76
            $class->getEndLine(),
77
            $class->getFileName(),
78
            $className,
79
            $className
80
        );
81
    }
82
83
    /**
84
     * @psalm-param class-string $classNameInHierarchy
85
     */
86
    public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy): self
87
    {
88
        return new self(
89
            (string) $method->getDocComment(),
90
            true,
91
            self::extractAnnotationsFromReflector($method),
92
            $method->getStartLine(),
93
            $method->getEndLine(),
94
            $method->getFileName(),
95
            $method->getName(),
96
            $classNameInHierarchy
97
        );
98
    }
99
100
    /**
101
     * Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized.
102
     *
103
     * @param string $docComment
104
     * @param bool $isMethod
105
     * @param array<string, array<int, string>> $symbolAnnotations
106
     * @param int $startLine
107
     * @param int $endLine
108
     * @param string $fileName
109
     * @param string $name
110
     * @param string $className
111
     */
112
    private function __construct(
113
        string $docComment,
114
        bool $isMethod,
115
        array $symbolAnnotations,
116
        int $startLine,
117
        int $endLine,
118
        string $fileName,
119
        string $name,
120
        string $className
121
    ) {
122
        $this->docComment        = $docComment;
123
        $this->isMethod          = $isMethod;
124
        $this->symbolAnnotations = $symbolAnnotations;
125
        $this->startLine         = $startLine;
126
        $this->endLine           = $endLine;
127
        $this->fileName          = $fileName;
128
        $this->name              = $name;
129
        $this->className         = $className;
130
    }
131
132
    /**
133
     * @psalm-return array<string, array{line: int, value: string}>
134
     */
135
    public function getInlineAnnotations(): array
136
    {
137
        $code        = file($this->fileName);
138
        $lineNumber  = $this->startLine;
139
        $startLine   = $this->startLine - 1;
140
        $endLine     = $this->endLine - 1;
141
        $codeLines   = array_slice($code, $startLine, $endLine - $startLine + 1);
142
        $annotations = [];
143
144
        foreach ($codeLines as $line) {
145
            if (preg_match('#/\*\*?\s*@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?\*/$#m', $line, $matches)) {
146
                $annotations[strtolower($matches['name'])] = [
147
                    'line'  => $lineNumber,
148
                    'value' => $matches['value'],
149
                ];
150
            }
151
152
            $lineNumber++;
153
        }
154
155
        return $annotations;
156
    }
157
158
    public function symbolAnnotations(): array
159
    {
160
        return $this->symbolAnnotations;
161
    }
162
163
    /**
164
     * @param string $docBlock
165
     * @return array<string, array<int, string>>
166
     */
167
    private static function parseDocBlock(string $docBlock): array
168
    {
169
        // Strip away the docblock header and footer to ease parsing of one line annotations
170
        $docBlock    = (string) substr($docBlock, 3, -2);
171
        $annotations = [];
172
173
        if (preg_match_all('/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m', $docBlock, $matches)) {
174
            $numMatches = count($matches[0]);
175
176
            for ($i = 0; $i < $numMatches; $i++) {
177
                $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i];
178
            }
179
        }
180
181
        return $annotations;
182
    }
183
184
    /** 
185
     * @param ReflectionClass|ReflectionFunctionAbstract $reflector 
186
     * @return array
187
     */
188
    private static function extractAnnotationsFromReflector(Reflector $reflector): array
189
    {
190
        $annotations = [];
191
192
        if ($reflector instanceof ReflectionClass) {
193
            $annotations = array_merge(
194
                $annotations,
195
                ...array_map(
196
                    static function (ReflectionClass $trait): array {
197
                        return self::parseDocBlock((string) $trait->getDocComment());
198
                    },
199
                    array_values($reflector->getTraits())
200
                )
201
            );
202
        }
203
204
        return array_merge(
205
            $annotations,
206
            self::parseDocBlock((string) $reflector->getDocComment())
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

206
            self::parseDocBlock((string) $reflector->/** @scrutinizer ignore-call */ getDocComment())
Loading history...
207
        );
208
    }
209
}
210