Completed
Pull Request — master (#30)
by Scott
02:21
created

PhpStanLoader::getClassReflector()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace exussum12\CoverageChecker;
3
4
use ReflectionFunction;
5
use ReflectionFunctionAbstract;
6
use ReflectionMethod;
7
use Reflector;
8
9
/**
10
 * Class PhpStanLoader
11
 * Used for parsing phpstan standard output
12
 * @package exussum12\CoverageChecker
13
 */
14
class PhpStanLoader implements FileChecker
15
{
16
    protected $lineRegex = '/^\s+(?<lineNumber>[0-9]+)/';
17
18
    protected $file;
19
    protected $relatedRegex = '#(function|method) (?:(?P<class>.*?)::)?(?P<function>.*?)[ \(]#';
20
21
    /**
22
     * @var array
23
     */
24
    protected $invalidLines = [];
25
26
    /**
27
     * @param string $filename the path to the phpstan.txt file
28
     */
29
    public function __construct($filename)
30
    {
31
        $this->file = fopen($filename, 'r');
32
    }
33
34
    /**
35
     * {@inheritdoc}
36
     */
37
    public function parseLines()
38
    {
39
        $filename = '';
40
        $lineNumber = 0;
41
        while (($line = fgets($this->file)) !== false) {
42
            $filename = $this->checkForFilename($line, $filename);
43
            if ($lineNumber = $this->getLineNumber($line, $lineNumber)) {
44
                $error = $this->getMessage($line);
45
                if ($this->isExtendedMessage($line)) {
46
                    $this->appendError($filename, $lineNumber, $error);
47
                    continue;
48
                }
49
                $this->handleRelatedError($filename, $lineNumber, $error);
50
                $this->addError($filename, $lineNumber, $error);
51
            }
52
        }
53
54
        $this->trimLines();
55
56
        return array_keys($this->invalidLines);
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62 View Code Duplication
    public function getErrorsOnLine($file, $lineNumber)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
63
    {
64
        $errors = [];
65
        if (isset($this->invalidLines[$file][$lineNumber])) {
66
            $errors = $this->invalidLines[$file][$lineNumber];
67
        }
68
69
        return $errors;
70
    }
71
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    public function handleNotFoundFile()
77
    {
78
        return true;
79
    }
80
81
    /**
82
     * @param string $line
83
     * @param string $currentFile
84
     * @return string the currentFileName
85
     */
86
    protected function checkForFilename($line, $currentFile)
87
    {
88
        if (strpos($line, " Line ")) {
89
            return trim(str_replace('Line', '', $line));
90
        }
91
        return $currentFile;
92
    }
93
94
    protected function getLineNumber($line, $currentLineNumber)
95
    {
96
        $matches = [];
97
        if (!preg_match($this->lineRegex, $line, $matches)) {
98
            if (preg_match('#^\s{3,}#', $line)) {
99
                return $currentLineNumber;
100
            }
101
102
            return false;
103
        }
104
105
        return $matches['lineNumber'];
106
    }
107
108
    protected function getMessage($line)
109
    {
110
        return trim(preg_replace($this->lineRegex, '', $line));
111
    }
112
113
    protected function isExtendedMessage($line)
114
    {
115
        return preg_match($this->lineRegex, $line) === 0;
116
    }
117
118
    protected function trimLines()
119
    {
120
        array_walk_recursive($this->invalidLines, function (&$item) {
121
            if (is_string($item)) {
122
                $item = trim($item);
123
            }
124
        });
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130
    public static function getDescription()
131
    {
132
        return 'Parses the text output of phpstan';
133
    }
134
135
    protected function handleRelatedError($filename, $line, $error)
136
    {
137
138
        $matches = [];
139
        if (preg_match($this->relatedRegex, $error, $matches)) {
140
            $error = sprintf(
141
                '%s (used %s line %d)',
142
                $error,
143
                $filename,
144
                $line
145
            );
146
147
            $reflection = $this->getReflector($matches);
148
            if ($reflection && ($filename = $reflection->getFileName())) {
149
                $currentLine = $reflection->getStartLine();
150
                while ($currentLine < $reflection->getEndLine()) {
151
                    $this->addError($filename, $currentLine++, $error);
152
                }
153
            }
154
        }
155
    }
156
157
    /**
158
     * @param string $filename
159
     * @param int $lineNumber
160
     * @param string $error
161
     */
162
    protected function addError($filename, $lineNumber, $error)
163
    {
164
        if (!isset($this->invalidLines[$filename][$lineNumber])) {
165
            $this->invalidLines[$filename][$lineNumber] = [];
166
        }
167
        $this->invalidLines[$filename][$lineNumber][] = $error;
168
    }
169
170
    /**
171
     * @param array $matches
172
     * @return ReflectionFunctionAbstract
173
     */
174
    protected function getReflector($matches)
175
    {
176
        if ($matches['class']) {
177
            return $this->getClassReflector($matches);
178
        }
179
180
        return $this->getFunctionReflector($matches);
181
    }
182
183
    private function appendError($filename, $lineNumber, $error)
184
    {
185
        end($this->invalidLines[$filename][$lineNumber]);
186
        $key = key($this->invalidLines[$filename][$lineNumber]);
187
        $this->invalidLines[$filename][$lineNumber][$key] .= ' ' . $error;
188
    }
189
190
    /**
191
     * @param $matches
192
     * @return bool|ReflectionMethod
193
     */
194
    protected function getClassReflector($matches)
195
    {
196
        if (!method_exists($matches['class'], $matches['function'])) {
197
            return false;
198
        }
199
        return new ReflectionMethod(
200
            $matches['class'],
201
            $matches['function']
202
        );
203
    }
204
205
    /**
206
     * @param $matches
207
     * @return bool|ReflectionFunction
208
     */
209
    protected function getFunctionReflector($matches)
210
    {
211
        if (!function_exists($matches['function'])) {
212
            return false;
213
        }
214
        return new ReflectionFunction(
215
            $matches['function']
216
        );
217
    }
218
}
219