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

PhpCsLoader::getErrorsOnLine()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 13
nc 8
nop 2
dl 0
loc 23
rs 6.7272
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
        if (!empty($this->invalidRanges[$file])) {
113
            foreach ($this->invalidRanges[$file] as $invalidRange) {
114
                $inRange = $lineNumber >= $invalidRange['from'] &&
115
                    $lineNumber <= $invalidRange['to'];
116
                if ($inRange) {
117
                    $errors[] = $invalidRange['message'];
118
                }
119
            }
120
        }
121
122
        return $errors;
123
    }
124
125
    /**
126
     * @param string $file
127
     * @param stdClass $message
128
     */
129
    protected function addInvalidLine($file, $message)
130
    {
131
        if (!in_array($message->type, $this->failOnTypes)) {
132
            return;
133
        }
134
        $line = $message->line;
135
136
        if ($error = $this->messageStartsWith($message->source, $this->lookupErrorPrefix)) {
137
            $this->handleLookupError($file, $message, $error);
138
            return;
139
        }
140
141
        if (!isset($this->invalidLines[$file][$line])) {
142
            $this->invalidLines[$file][$line] = [];
143
        }
144
145
        $this->invalidLines[$file][$line][] = $message->message;
146
147
        if (in_array($message->source, $this->wholeFileErrors)) {
148
            $this->invalidFiles[$file][] = $message->message;
149
        }
150
    }
151
152
    /**
153
     * @param $message
154
     * @param array $list
155
     * @return bool|string
156
     */
157
    protected function messageStartsWith($message, array $list)
158
    {
159
        foreach ($list as $item) {
160
            if (strpos($message, $item) === 0) {
161
                return $item;
162
            }
163
        }
164
        return false;
165
    }
166
167
    protected function handleLookupError($file, $message, $error)
168
    {
169
        if ($error == 'Squiz.Commenting.FileComment') {
170
            $this->invalidFiles[$file][] = $message->message;
171
        }
172
        try {
173
            $fileParser = $this->getFileParser($file);
174
            $lookup = $this->getMessageRanges($error, $fileParser);
175
176
            $this->addRangeError($file, $lookup, $message);
177
        } catch (FileNotFound $exception) {
178
            error_log("Can't find file, may have missed an error");
179
        }
180
    }
181
182
    protected function getFileParser($filename)
183
    {
184
        if (!isset($this->parsedFiles[$filename])) {
185
            if (!file_exists($filename)) {
186
                throw new FileNotFound();
187
            }
188
189
            $this->parsedFiles[$filename] = new FileParser(
190
                file_get_contents($filename)
191
            );
192
        }
193
194
        return $this->parsedFiles[$filename];
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function handleNotFoundFile()
201
    {
202
        return true;
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208
    public static function getDescription()
209
    {
210
        return 'Parses the json report format of phpcs, this mode ' .
211
            'only reports errors as violations';
212
    }
213
214
    protected function addRangeError($file, $lookup, $message)
215
    {
216
        $line = $message->line;
217
        foreach ($lookup as $limit) {
218
            if ($line >= $limit->getStartLine() && $line <= $limit->getEndLine()) {
219
                $this->invalidRanges[$file][] = [
220
                    'from' => $limit->getStartLine(),
221
                    'to' => $limit->getEndLine(),
222
                    'message' => $message->message,
223
                ];
224
            }
225
        }
226
    }
227
228
    protected function getMessageRanges($error, $fileParser)
229
    {
230
        if ($error == 'Squiz.Commenting.ClassComment') {
231
            return $fileParser->getClassLimits();
232
        }
233
234
        return $fileParser->getFunctionLimits();
235
    }
236
}
237