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

PhpCsLoader::getMessageRanges()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace exussum12\CoverageChecker;
3
4
use InvalidArgumentException;
5
use PhpParser\Error;
6
use PhpParser\ParserFactory;
7
use exussum12\CoverageChecker\Exceptions\FileNotFound;
8
use stdClass;
9
10
/**
11
 * Class PhpCsLoader
12
 * Used for reading json output from phpcs
13
 * @package exussum12\CoverageChecker
14
 */
15
class PhpCsLoader implements FileChecker
16
{
17
    /**
18
     * @var stdClass
19
     */
20
    protected $json;
21
    /**
22
     * @var array
23
     */
24
    protected $invalidLines;
25
26
    /**
27
     * @var array
28
     */
29
30
    protected $failOnTypes = [
31
        'ERROR',
32
    ];
33
34
    protected $lookupErrorPrefix = [
35
        'Squiz.Commenting.FileComment',
36
        'Squiz.Commenting.ClassComment',
37
        'Squiz.Commenting.FunctionComment',
38
    ];
39
40
    /**
41
     * @var array
42
     */
43
    protected $wholeFileErrors = [
44
        'PSR1.Files.SideEffects.FoundWithSymbols',
45
        'Generic.Files.LineEndings.InvalidEOLChar',
46
    ];
47
48
49
    /**
50
     * @var array
51
     */
52
    protected $invalidFiles = [];
53
54
    /**
55
     * @var array
56
     */
57
    protected $invalidRanges = [];
58
59
60
    /**
61
     * @var FileParser[]
62
     */
63
    protected $parsedFiles = [];
64
65
    /**
66
     * PhpCsLoader constructor.
67
     * @param string $filePath the file path to the json output from phpcs
68
     */
69 View Code Duplication
    public function __construct($filePath)
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...
70
    {
71
        $this->json = json_decode(file_get_contents($filePath));
72
        if (json_last_error() !== JSON_ERROR_NONE) {
73
            throw new InvalidArgumentException(
74
                "Can't Parse phpcs json - " . json_last_error_msg()
75
            );
76
        }
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function parseLines()
83
    {
84
        $this->invalidLines = [];
85
        foreach ($this->json->files as $fileName => $file) {
86
            foreach ($file->messages as $message) {
87
                $this->addInvalidLine($fileName, $message);
88
            }
89
        }
90
91
        return array_unique(array_merge(
92
            array_keys($this->invalidLines),
93
            array_keys($this->invalidFiles),
94
            array_keys($this->invalidRanges)
95
        ));
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function getErrorsOnLine($file, $lineNumber)
102
    {
103
        $errors = [];
104
        if (!empty($this->invalidFiles[$file])) {
105
            $errors = $this->invalidFiles[$file];
106
        }
107
108
        if (!empty($this->invalidLines[$file][$lineNumber])) {
109
             $errors = array_merge($errors, $this->invalidLines[$file][$lineNumber]);
110
        }
111
112
        $errors = array_merge($errors, $this->getRangeErrors($file, $lineNumber));
113
114
        return $errors;
115
    }
116
117
    /**
118
     * @param string $file
119
     * @param stdClass $message
120
     */
121
    protected function addInvalidLine($file, $message)
122
    {
123
        if (!in_array($message->type, $this->failOnTypes)) {
124
            return;
125
        }
126
127
        $line = $message->line;
128
129
        if ($error = $this->messageStartsWith($message->source, $this->lookupErrorPrefix)) {
130
            $this->handleLookupError($file, $message, $error);
131
            return;
132
        }
133
134
        if (!isset($this->invalidLines[$file][$line])) {
135
            $this->invalidLines[$file][$line] = [];
136
        }
137
138
        $this->invalidLines[$file][$line][] = $message->message;
139
140
        if (in_array($message->source, $this->wholeFileErrors)) {
141
            $this->invalidFiles[$file][] = $message->message;
142
        }
143
    }
144
145
    /**
146
     * @param $message
147
     * @param array $list
148
     * @return bool|string
149
     */
150
    protected function messageStartsWith($message, array $list)
151
    {
152
        foreach ($list as $item) {
153
            if (strpos($message, $item) === 0) {
154
                return $item;
155
            }
156
        }
157
        return false;
158
    }
159
160
    protected function handleLookupError($file, $message, $error)
161
    {
162
        if ($error == 'Squiz.Commenting.FileComment') {
163
            $this->invalidFiles[$file][] = $message->message;
164
        }
165
        try {
166
            $fileParser = $this->getFileParser($file);
167
            $lookup = $this->getMessageRanges($error, $fileParser);
168
169
            $this->addRangeError($file, $lookup, $message);
170
        } catch (FileNotFound $exception) {
171
            error_log("Can't find file, may have missed an error");
172
        }
173
    }
174
175
    protected function getFileParser($filename)
176
    {
177
        if (!isset($this->parsedFiles[$filename])) {
178
            if (!file_exists($filename)) {
179
                throw new FileNotFound();
180
            }
181
182
            $this->parsedFiles[$filename] = new FileParser(
183
                file_get_contents($filename)
184
            );
185
        }
186
187
        return $this->parsedFiles[$filename];
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193
    public function handleNotFoundFile()
194
    {
195
        return true;
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public static function getDescription()
202
    {
203
        return 'Parses the json report format of phpcs, this mode ' .
204
            'only reports errors as violations';
205
    }
206
207
    /**
208
     * @param string $file
209
     * @param CodeLimits[] $lookup
210
     * @param stdClass $message
211
     */
212
    protected function addRangeError($file, $lookup, $message)
213
    {
214
        $line = $message->line;
215
        foreach ($lookup as $limit) {
216
            if ($line >= $limit->getStartLine() && $line <= $limit->getEndLine()) {
217
                $this->invalidRanges[$file][] = [
218
                    'from' => $limit->getStartLine(),
219
                    'to' => $limit->getEndLine(),
220
                    'message' => $message->message,
221
                ];
222
            }
223
        }
224
    }
225
226
    /**
227
     * @param string $error
228
     * @param FileParser $fileParser
229
     * @return mixed
230
     */
231
    protected function getMessageRanges($error, $fileParser)
232
    {
233
        if ($error == 'Squiz.Commenting.ClassComment') {
234
            return $fileParser->getClassLimits();
235
        }
236
237
        return $fileParser->getFunctionLimits();
238
    }
239
240
    /**
241
     * @param string $file
242
     * @param int $lineNumber
243
     * @return array errors on the line
244
     */
245
    protected function getRangeErrors($file, $lineNumber)
246
    {
247
        $errors = [];
248
249
        if (!empty($this->invalidRanges[$file])) {
250
            foreach ($this->invalidRanges[$file] as $invalidRange) {
251
                $inRange = $lineNumber >= $invalidRange['from'] &&
252
                    $lineNumber <= $invalidRange['to'];
253
                if ($inRange) {
254
                    $errors[] = $invalidRange['message'];
255
                }
256
            }
257
        }
258
259
        return $errors;
260
    }
261
}
262