Passed
Pull Request — master (#26)
by Scott
02:25
created

PhpStanLoader::parseLines()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 4
nop 0
dl 0
loc 21
rs 9.0534
c 0
b 0
f 0
1
<?php
2
namespace exussum12\CoverageChecker;
3
4
use ReflectionFunction;
5
use ReflectionMethod;
6
use Reflector;
7
8
/**
9
 * Class PhpStanLoader
10
 * Used for parsing phpstan standard output
11
 * @package exussum12\CoverageChecker
12
 */
13
class PhpStanLoader implements FileChecker
14
{
15
    protected $lineRegex = '/^\s+(?<lineNumber>[0-9]+)/';
16
17
    protected $file;
18
    protected $relatedRegex = '#(function|method) (?:(?P<class>.*?)::)?(?P<function>.*?)[ \(]#';
19
20
    /**
21
     * @var array
22
     */
23
    protected $invalidLines = [];
24
25
    /**
26
     * @param string $filename the path to the phpstan.txt file
27
     */
28
    public function __construct($filename)
29
    {
30
        $this->file = fopen($filename, 'r');
31
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36
    public function parseLines()
37
    {
38
        $filename = '';
39
        $lineNumber = 0;
40
        while (($line = fgets($this->file)) !== false) {
41
            $filename = $this->checkForFileName($line, $filename);
42
            if ($lineNumber = $this->getLineNumber($line, $lineNumber)) {
43
                $error = $this->getMessage($line);
44
                if ($this->isExtendedMessage($line)) {
45
                    $this->appendError($filename, $lineNumber, $error);
46
                    continue;
47
                }
48
                $this->handleRelatedError($filename, $lineNumber, $error);
49
                $this->addError($filename, $lineNumber, $error);
50
            }
51
        }
52
53
        $this->trimLines();
54
55
        return array_keys($this->invalidLines);
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61 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...
62
    {
63
        $errors = [];
64
        if (isset($this->invalidLines[$file][$lineNumber])) {
65
            $errors = $this->invalidLines[$file][$lineNumber];
66
        }
67
68
        return $errors;
69
    }
70
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function handleNotFoundFile()
76
    {
77
        return true;
78
    }
79
80
    /**
81
     * @param string $line
82
     * @param string $currentFile
83
     * @return string the currentFileName
84
     */
85
    protected function checkForFilename($line, $currentFile)
86
    {
87
        if (strpos($line, " Line ")) {
88
            return trim(str_replace('Line', '', $line));
89
        }
90
        return $currentFile;
91
    }
92
93
    protected function getLineNumber($line, $currentLineNumber)
94
    {
95
        $matches = [];
96
        if (!preg_match($this->lineRegex, $line, $matches)) {
97
            if (preg_match('#^\s{3,}#', $line)) {
98
                return $currentLineNumber;
99
            }
100
101
            return false;
102
        }
103
104
        return $matches['lineNumber'];
105
    }
106
107
    protected function getMessage($line)
108
    {
109
        return trim(preg_replace($this->lineRegex, '', $line));
110
    }
111
112
    protected function isExtendedMessage($line)
113
    {
114
        return preg_match($this->lineRegex, $line) === 0;
115
    }
116
117
    protected function trimLines()
118
    {
119
        array_walk_recursive($this->invalidLines, function (&$item) {
120
            if (is_string($item)) {
121
                $item = trim($item);
122
            }
123
        });
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public static function getDescription()
130
    {
131
        return 'Parses the text output of phpstan';
132
    }
133
134
    protected function handleRelatedError($filename, $line, $error)
135
    {
136
137
        $matches = [];
138
        if (preg_match($this->relatedRegex, $error, $matches)) {
139
            $error = sprintf(
140
                '%s (used %s line %d)',
141
                $error,
142
                $filename,
143
                $line
144
            );
145
146
            $reflection = $this->getReflector($matches);
147
            if ($reflection && ($filename = $reflection->getFileName())) {
148
                $currentLine = $reflection->getStartLine();
149
                while ($currentLine < $reflection->getEndLine()) {
150
                    $this->addError($filename, $currentLine++, $error);
151
                }
152
            }
153
        }
154
    }
155
156
    /**
157
     * @param $filename
158
     * @param $lineNumber
159
     * @param $error
160
     */
161
    protected function addError($filename, $lineNumber, $error)
162
    {
163
        if (!isset($this->invalidLines[$filename][$lineNumber])) {
164
            $this->invalidLines[$filename][$lineNumber] = [];
165
        }
166
        $this->invalidLines[$filename][$lineNumber][] = $error;
167
    }
168
169
    /**
170
     * @param $matches
171
     * @return Reflector
172
     */
173
    protected function getReflector($matches)
174
    {
175
        if ($matches['class']) {
176
            return new ReflectionMethod(
177
                $matches['class'],
178
                $matches['function']
179
            );
180
        }
181
182
        return new ReflectionFunction(
183
            $matches['function']
184
        );
185
    }
186
187
    private function appendError($filename, $lineNumber, $error)
188
    {
189
        end($this->invalidLines[$filename][$lineNumber]);
190
        $key = key($this->invalidLines[$filename][$lineNumber]);
191
        $this->invalidLines[$filename][$lineNumber][$key] .= ' ' . $error;
192
    }
193
}
194