PhpCs   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 237
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 84
dl 0
loc 237
rs 9.6
c 0
b 0
f 0
wmc 35

12 Methods

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