Passed
Pull Request — master (#30)
by Scott
02:27
created

PhpCsLoader::getErrorsOnLine()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 4
nop 2
dl 0
loc 15
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
    protected function addRangeError($file, $lookup, $message)
208
    {
209
        $line = $message->line;
210
        foreach ($lookup as $limit) {
211
            if ($line >= $limit->getStartLine() && $line <= $limit->getEndLine()) {
212
                $this->invalidRanges[$file][] = [
213
                    'from' => $limit->getStartLine(),
214
                    'to' => $limit->getEndLine(),
215
                    'message' => $message->message,
216
                ];
217
            }
218
        }
219
    }
220
221
    protected function getMessageRanges($error, $fileParser)
222
    {
223
        if ($error == 'Squiz.Commenting.ClassComment') {
224
            return $fileParser->getClassLimits();
225
        }
226
227
        return $fileParser->getFunctionLimits();
228
    }
229
230
    /**
231
     * @param string $file
232
     * @param int $lineNumber
233
     * @return array errors on the line
234
     */
235
    protected function getRangeErrors($file, $lineNumber)
236
    {
237
        $errors = [];
238
239
        if (!empty($this->invalidRanges[$file])) {
240
            foreach ($this->invalidRanges[$file] as $invalidRange) {
241
                $inRange = $lineNumber >= $invalidRange['from'] &&
242
                    $lineNumber <= $invalidRange['to'];
243
                if ($inRange) {
244
                    $errors[] = $invalidRange['message'];
245
                }
246
            }
247
        }
248
249
        return $errors;
250
    }
251
}
252