Chris@17
|
1 <?php
|
Chris@17
|
2 /**
|
Chris@17
|
3 * Manages reporting of errors and warnings.
|
Chris@17
|
4 *
|
Chris@17
|
5 * @author Greg Sherwood <gsherwood@squiz.net>
|
Chris@17
|
6 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
|
Chris@17
|
7 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
|
Chris@17
|
8 */
|
Chris@17
|
9
|
Chris@17
|
10 namespace PHP_CodeSniffer;
|
Chris@17
|
11
|
Chris@17
|
12 use PHP_CodeSniffer\Reports\Report;
|
Chris@17
|
13 use PHP_CodeSniffer\Files\File;
|
Chris@17
|
14 use PHP_CodeSniffer\Exceptions\RuntimeException;
|
Chris@17
|
15 use PHP_CodeSniffer\Exceptions\DeepExitException;
|
Chris@17
|
16 use PHP_CodeSniffer\Util\Common;
|
Chris@17
|
17
|
Chris@17
|
18 class Reporter
|
Chris@17
|
19 {
|
Chris@17
|
20
|
Chris@17
|
21 /**
|
Chris@17
|
22 * The config data for the run.
|
Chris@17
|
23 *
|
Chris@17
|
24 * @var \PHP_CodeSniffer\Config
|
Chris@17
|
25 */
|
Chris@17
|
26 public $config = null;
|
Chris@17
|
27
|
Chris@17
|
28 /**
|
Chris@17
|
29 * Total number of files that contain errors or warnings.
|
Chris@17
|
30 *
|
Chris@17
|
31 * @var integer
|
Chris@17
|
32 */
|
Chris@17
|
33 public $totalFiles = 0;
|
Chris@17
|
34
|
Chris@17
|
35 /**
|
Chris@17
|
36 * Total number of errors found during the run.
|
Chris@17
|
37 *
|
Chris@17
|
38 * @var integer
|
Chris@17
|
39 */
|
Chris@17
|
40 public $totalErrors = 0;
|
Chris@17
|
41
|
Chris@17
|
42 /**
|
Chris@17
|
43 * Total number of warnings found during the run.
|
Chris@17
|
44 *
|
Chris@17
|
45 * @var integer
|
Chris@17
|
46 */
|
Chris@17
|
47 public $totalWarnings = 0;
|
Chris@17
|
48
|
Chris@17
|
49 /**
|
Chris@17
|
50 * Total number of errors/warnings that can be fixed.
|
Chris@17
|
51 *
|
Chris@17
|
52 * @var integer
|
Chris@17
|
53 */
|
Chris@17
|
54 public $totalFixable = 0;
|
Chris@17
|
55
|
Chris@17
|
56 /**
|
Chris@17
|
57 * Total number of errors/warnings that were fixed.
|
Chris@17
|
58 *
|
Chris@17
|
59 * @var integer
|
Chris@17
|
60 */
|
Chris@17
|
61 public $totalFixed = 0;
|
Chris@17
|
62
|
Chris@17
|
63 /**
|
Chris@17
|
64 * When the PHPCS run started.
|
Chris@17
|
65 *
|
Chris@17
|
66 * @var float
|
Chris@17
|
67 */
|
Chris@17
|
68 public static $startTime = 0;
|
Chris@17
|
69
|
Chris@17
|
70 /**
|
Chris@17
|
71 * A cache of report objects.
|
Chris@17
|
72 *
|
Chris@17
|
73 * @var array
|
Chris@17
|
74 */
|
Chris@17
|
75 private $reports = [];
|
Chris@17
|
76
|
Chris@17
|
77 /**
|
Chris@17
|
78 * A cache of opened temporary files.
|
Chris@17
|
79 *
|
Chris@17
|
80 * @var array
|
Chris@17
|
81 */
|
Chris@17
|
82 private $tmpFiles = [];
|
Chris@17
|
83
|
Chris@17
|
84
|
Chris@17
|
85 /**
|
Chris@17
|
86 * Initialise the reporter.
|
Chris@17
|
87 *
|
Chris@17
|
88 * All reports specified in the config will be created and their
|
Chris@17
|
89 * output file (or a temp file if none is specified) initialised by
|
Chris@17
|
90 * clearing the current contents.
|
Chris@17
|
91 *
|
Chris@17
|
92 * @param \PHP_CodeSniffer\Config $config The config data for the run.
|
Chris@17
|
93 *
|
Chris@17
|
94 * @return void
|
Chris@18
|
95 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If a report is not available.
|
Chris@17
|
96 */
|
Chris@17
|
97 public function __construct(Config $config)
|
Chris@17
|
98 {
|
Chris@17
|
99 $this->config = $config;
|
Chris@17
|
100
|
Chris@17
|
101 foreach ($config->reports as $type => $output) {
|
Chris@17
|
102 if ($output === null) {
|
Chris@17
|
103 $output = $config->reportFile;
|
Chris@17
|
104 }
|
Chris@17
|
105
|
Chris@17
|
106 $reportClassName = '';
|
Chris@17
|
107 if (strpos($type, '.') !== false) {
|
Chris@17
|
108 // This is a path to a custom report class.
|
Chris@17
|
109 $filename = realpath($type);
|
Chris@17
|
110 if ($filename === false) {
|
Chris@17
|
111 $error = "ERROR: Custom report \"$type\" not found".PHP_EOL;
|
Chris@17
|
112 throw new DeepExitException($error, 3);
|
Chris@17
|
113 }
|
Chris@17
|
114
|
Chris@17
|
115 $reportClassName = Autoload::loadFile($filename);
|
Chris@17
|
116 } else if (class_exists('PHP_CodeSniffer\Reports\\'.ucfirst($type)) === true) {
|
Chris@17
|
117 // PHPCS native report.
|
Chris@17
|
118 $reportClassName = 'PHP_CodeSniffer\Reports\\'.ucfirst($type);
|
Chris@17
|
119 } else if (class_exists($type) === true) {
|
Chris@17
|
120 // FQN of a custom report.
|
Chris@17
|
121 $reportClassName = $type;
|
Chris@17
|
122 } else {
|
Chris@17
|
123 // OK, so not a FQN, try and find the report using the registered namespaces.
|
Chris@17
|
124 $registeredNamespaces = Autoload::getSearchPaths();
|
Chris@17
|
125 $trimmedType = ltrim($type, '\\');
|
Chris@17
|
126
|
Chris@17
|
127 foreach ($registeredNamespaces as $nsPrefix) {
|
Chris@17
|
128 if ($nsPrefix === '') {
|
Chris@17
|
129 continue;
|
Chris@17
|
130 }
|
Chris@17
|
131
|
Chris@17
|
132 if (class_exists($nsPrefix.'\\'.$trimmedType) === true) {
|
Chris@17
|
133 $reportClassName = $nsPrefix.'\\'.$trimmedType;
|
Chris@17
|
134 break;
|
Chris@17
|
135 }
|
Chris@17
|
136 }
|
Chris@17
|
137 }//end if
|
Chris@17
|
138
|
Chris@17
|
139 if ($reportClassName === '') {
|
Chris@17
|
140 $error = "ERROR: Class file for report \"$type\" not found".PHP_EOL;
|
Chris@17
|
141 throw new DeepExitException($error, 3);
|
Chris@17
|
142 }
|
Chris@17
|
143
|
Chris@17
|
144 $reportClass = new $reportClassName();
|
Chris@17
|
145 if (false === ($reportClass instanceof Report)) {
|
Chris@17
|
146 throw new RuntimeException('Class "'.$reportClassName.'" must implement the "PHP_CodeSniffer\Report" interface.');
|
Chris@17
|
147 }
|
Chris@17
|
148
|
Chris@17
|
149 $this->reports[$type] = [
|
Chris@17
|
150 'output' => $output,
|
Chris@17
|
151 'class' => $reportClass,
|
Chris@17
|
152 ];
|
Chris@17
|
153
|
Chris@17
|
154 if ($output === null) {
|
Chris@17
|
155 // Using a temp file.
|
Chris@17
|
156 // This needs to be set in the constructor so that all
|
Chris@17
|
157 // child procs use the same report file when running in parallel.
|
Chris@17
|
158 $this->tmpFiles[$type] = tempnam(sys_get_temp_dir(), 'phpcs');
|
Chris@17
|
159 file_put_contents($this->tmpFiles[$type], '');
|
Chris@17
|
160 } else {
|
Chris@17
|
161 file_put_contents($output, '');
|
Chris@17
|
162 }
|
Chris@17
|
163 }//end foreach
|
Chris@17
|
164
|
Chris@17
|
165 }//end __construct()
|
Chris@17
|
166
|
Chris@17
|
167
|
Chris@17
|
168 /**
|
Chris@17
|
169 * Generates and prints final versions of all reports.
|
Chris@17
|
170 *
|
Chris@17
|
171 * Returns TRUE if any of the reports output content to the screen
|
Chris@17
|
172 * or FALSE if all reports were silently printed to a file.
|
Chris@17
|
173 *
|
Chris@17
|
174 * @return bool
|
Chris@17
|
175 */
|
Chris@17
|
176 public function printReports()
|
Chris@17
|
177 {
|
Chris@17
|
178 $toScreen = false;
|
Chris@17
|
179 foreach ($this->reports as $type => $report) {
|
Chris@17
|
180 if ($report['output'] === null) {
|
Chris@17
|
181 $toScreen = true;
|
Chris@17
|
182 }
|
Chris@17
|
183
|
Chris@17
|
184 $this->printReport($type);
|
Chris@17
|
185 }
|
Chris@17
|
186
|
Chris@17
|
187 return $toScreen;
|
Chris@17
|
188
|
Chris@17
|
189 }//end printReports()
|
Chris@17
|
190
|
Chris@17
|
191
|
Chris@17
|
192 /**
|
Chris@17
|
193 * Generates and prints a single final report.
|
Chris@17
|
194 *
|
Chris@17
|
195 * @param string $report The report type to print.
|
Chris@17
|
196 *
|
Chris@17
|
197 * @return void
|
Chris@17
|
198 */
|
Chris@17
|
199 public function printReport($report)
|
Chris@17
|
200 {
|
Chris@17
|
201 $reportClass = $this->reports[$report]['class'];
|
Chris@17
|
202 $reportFile = $this->reports[$report]['output'];
|
Chris@17
|
203
|
Chris@17
|
204 if ($reportFile !== null) {
|
Chris@17
|
205 $filename = $reportFile;
|
Chris@17
|
206 $toScreen = false;
|
Chris@17
|
207 } else {
|
Chris@17
|
208 if (isset($this->tmpFiles[$report]) === true) {
|
Chris@17
|
209 $filename = $this->tmpFiles[$report];
|
Chris@17
|
210 } else {
|
Chris@17
|
211 $filename = null;
|
Chris@17
|
212 }
|
Chris@17
|
213
|
Chris@17
|
214 $toScreen = true;
|
Chris@17
|
215 }
|
Chris@17
|
216
|
Chris@17
|
217 $reportCache = '';
|
Chris@17
|
218 if ($filename !== null) {
|
Chris@17
|
219 $reportCache = file_get_contents($filename);
|
Chris@17
|
220 }
|
Chris@17
|
221
|
Chris@17
|
222 ob_start();
|
Chris@17
|
223 $reportClass->generate(
|
Chris@17
|
224 $reportCache,
|
Chris@17
|
225 $this->totalFiles,
|
Chris@17
|
226 $this->totalErrors,
|
Chris@17
|
227 $this->totalWarnings,
|
Chris@17
|
228 $this->totalFixable,
|
Chris@17
|
229 $this->config->showSources,
|
Chris@17
|
230 $this->config->reportWidth,
|
Chris@17
|
231 $this->config->interactive,
|
Chris@17
|
232 $toScreen
|
Chris@17
|
233 );
|
Chris@17
|
234 $generatedReport = ob_get_contents();
|
Chris@17
|
235 ob_end_clean();
|
Chris@17
|
236
|
Chris@17
|
237 if ($this->config->colors !== true || $reportFile !== null) {
|
Chris@17
|
238 $generatedReport = preg_replace('`\033\[[0-9;]+m`', '', $generatedReport);
|
Chris@17
|
239 }
|
Chris@17
|
240
|
Chris@17
|
241 if ($reportFile !== null) {
|
Chris@17
|
242 if (PHP_CODESNIFFER_VERBOSITY > 0) {
|
Chris@17
|
243 echo $generatedReport;
|
Chris@17
|
244 }
|
Chris@17
|
245
|
Chris@17
|
246 file_put_contents($reportFile, $generatedReport.PHP_EOL);
|
Chris@17
|
247 } else {
|
Chris@17
|
248 echo $generatedReport;
|
Chris@17
|
249 if ($filename !== null && file_exists($filename) === true) {
|
Chris@17
|
250 unlink($filename);
|
Chris@17
|
251 unset($this->tmpFiles[$report]);
|
Chris@17
|
252 }
|
Chris@17
|
253 }
|
Chris@17
|
254
|
Chris@17
|
255 }//end printReport()
|
Chris@17
|
256
|
Chris@17
|
257
|
Chris@17
|
258 /**
|
Chris@17
|
259 * Caches the result of a single processed file for all reports.
|
Chris@17
|
260 *
|
Chris@17
|
261 * The report content that is generated is appended to the output file
|
Chris@17
|
262 * assigned to each report. This content may be an intermediate report format
|
Chris@17
|
263 * and not reflect the final report output.
|
Chris@17
|
264 *
|
Chris@17
|
265 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed.
|
Chris@17
|
266 *
|
Chris@17
|
267 * @return void
|
Chris@17
|
268 */
|
Chris@17
|
269 public function cacheFileReport(File $phpcsFile)
|
Chris@17
|
270 {
|
Chris@17
|
271 if (isset($this->config->reports) === false) {
|
Chris@17
|
272 // This happens during unit testing, or any time someone just wants
|
Chris@17
|
273 // the error data and not the printed report.
|
Chris@17
|
274 return;
|
Chris@17
|
275 }
|
Chris@17
|
276
|
Chris@17
|
277 $reportData = $this->prepareFileReport($phpcsFile);
|
Chris@17
|
278 $errorsShown = false;
|
Chris@17
|
279
|
Chris@17
|
280 foreach ($this->reports as $type => $report) {
|
Chris@17
|
281 $reportClass = $report['class'];
|
Chris@17
|
282
|
Chris@17
|
283 ob_start();
|
Chris@17
|
284 $result = $reportClass->generateFileReport($reportData, $phpcsFile, $this->config->showSources, $this->config->reportWidth);
|
Chris@17
|
285 if ($result === true) {
|
Chris@17
|
286 $errorsShown = true;
|
Chris@17
|
287 }
|
Chris@17
|
288
|
Chris@17
|
289 $generatedReport = ob_get_contents();
|
Chris@17
|
290 ob_end_clean();
|
Chris@17
|
291
|
Chris@17
|
292 if ($report['output'] === null) {
|
Chris@17
|
293 // Using a temp file.
|
Chris@17
|
294 if (isset($this->tmpFiles[$type]) === false) {
|
Chris@17
|
295 // When running in interactive mode, the reporter prints the full
|
Chris@17
|
296 // report many times, which will unlink the temp file. So we need
|
Chris@17
|
297 // to create a new one if it doesn't exist.
|
Chris@17
|
298 $this->tmpFiles[$type] = tempnam(sys_get_temp_dir(), 'phpcs');
|
Chris@17
|
299 file_put_contents($this->tmpFiles[$type], '');
|
Chris@17
|
300 }
|
Chris@17
|
301
|
Chris@17
|
302 file_put_contents($this->tmpFiles[$type], $generatedReport, (FILE_APPEND | LOCK_EX));
|
Chris@17
|
303 } else {
|
Chris@17
|
304 file_put_contents($report['output'], $generatedReport, (FILE_APPEND | LOCK_EX));
|
Chris@17
|
305 }//end if
|
Chris@17
|
306 }//end foreach
|
Chris@17
|
307
|
Chris@17
|
308 if ($errorsShown === true || PHP_CODESNIFFER_CBF === true) {
|
Chris@17
|
309 $this->totalFiles++;
|
Chris@17
|
310 $this->totalErrors += $reportData['errors'];
|
Chris@17
|
311 $this->totalWarnings += $reportData['warnings'];
|
Chris@17
|
312
|
Chris@17
|
313 // When PHPCBF is running, we need to use the fixable error values
|
Chris@17
|
314 // after the report has run and fixed what it can.
|
Chris@17
|
315 if (PHP_CODESNIFFER_CBF === true) {
|
Chris@17
|
316 $this->totalFixable += $phpcsFile->getFixableCount();
|
Chris@17
|
317 $this->totalFixed += $phpcsFile->getFixedCount();
|
Chris@17
|
318 } else {
|
Chris@17
|
319 $this->totalFixable += $reportData['fixable'];
|
Chris@17
|
320 }
|
Chris@17
|
321 }
|
Chris@17
|
322
|
Chris@17
|
323 }//end cacheFileReport()
|
Chris@17
|
324
|
Chris@17
|
325
|
Chris@17
|
326 /**
|
Chris@17
|
327 * Generate summary information to be used during report generation.
|
Chris@17
|
328 *
|
Chris@17
|
329 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed.
|
Chris@17
|
330 *
|
Chris@17
|
331 * @return array
|
Chris@17
|
332 */
|
Chris@17
|
333 public function prepareFileReport(File $phpcsFile)
|
Chris@17
|
334 {
|
Chris@17
|
335 $report = [
|
Chris@17
|
336 'filename' => Common::stripBasepath($phpcsFile->getFilename(), $this->config->basepath),
|
Chris@17
|
337 'errors' => $phpcsFile->getErrorCount(),
|
Chris@17
|
338 'warnings' => $phpcsFile->getWarningCount(),
|
Chris@17
|
339 'fixable' => $phpcsFile->getFixableCount(),
|
Chris@17
|
340 'messages' => [],
|
Chris@17
|
341 ];
|
Chris@17
|
342
|
Chris@17
|
343 if ($report['errors'] === 0 && $report['warnings'] === 0) {
|
Chris@17
|
344 // Prefect score!
|
Chris@17
|
345 return $report;
|
Chris@17
|
346 }
|
Chris@17
|
347
|
Chris@17
|
348 if ($this->config->recordErrors === false) {
|
Chris@17
|
349 $message = 'Errors are not being recorded but this report requires error messages. ';
|
Chris@17
|
350 $message .= 'This report will not show the correct information.';
|
Chris@17
|
351 $report['messages'][1][1] = [
|
Chris@17
|
352 [
|
Chris@17
|
353 'message' => $message,
|
Chris@17
|
354 'source' => 'Internal.RecordErrors',
|
Chris@17
|
355 'severity' => 5,
|
Chris@17
|
356 'fixable' => false,
|
Chris@17
|
357 'type' => 'ERROR',
|
Chris@17
|
358 ],
|
Chris@17
|
359 ];
|
Chris@17
|
360 return $report;
|
Chris@17
|
361 }
|
Chris@17
|
362
|
Chris@17
|
363 $errors = [];
|
Chris@17
|
364
|
Chris@17
|
365 // Merge errors and warnings.
|
Chris@17
|
366 foreach ($phpcsFile->getErrors() as $line => $lineErrors) {
|
Chris@17
|
367 foreach ($lineErrors as $column => $colErrors) {
|
Chris@17
|
368 $newErrors = [];
|
Chris@17
|
369 foreach ($colErrors as $data) {
|
Chris@17
|
370 $newErrors[] = [
|
Chris@17
|
371 'message' => $data['message'],
|
Chris@17
|
372 'source' => $data['source'],
|
Chris@17
|
373 'severity' => $data['severity'],
|
Chris@17
|
374 'fixable' => $data['fixable'],
|
Chris@17
|
375 'type' => 'ERROR',
|
Chris@17
|
376 ];
|
Chris@17
|
377 }
|
Chris@17
|
378
|
Chris@17
|
379 $errors[$line][$column] = $newErrors;
|
Chris@17
|
380 }
|
Chris@17
|
381
|
Chris@17
|
382 ksort($errors[$line]);
|
Chris@17
|
383 }//end foreach
|
Chris@17
|
384
|
Chris@17
|
385 foreach ($phpcsFile->getWarnings() as $line => $lineWarnings) {
|
Chris@17
|
386 foreach ($lineWarnings as $column => $colWarnings) {
|
Chris@17
|
387 $newWarnings = [];
|
Chris@17
|
388 foreach ($colWarnings as $data) {
|
Chris@17
|
389 $newWarnings[] = [
|
Chris@17
|
390 'message' => $data['message'],
|
Chris@17
|
391 'source' => $data['source'],
|
Chris@17
|
392 'severity' => $data['severity'],
|
Chris@17
|
393 'fixable' => $data['fixable'],
|
Chris@17
|
394 'type' => 'WARNING',
|
Chris@17
|
395 ];
|
Chris@17
|
396 }
|
Chris@17
|
397
|
Chris@17
|
398 if (isset($errors[$line]) === false) {
|
Chris@17
|
399 $errors[$line] = [];
|
Chris@17
|
400 }
|
Chris@17
|
401
|
Chris@17
|
402 if (isset($errors[$line][$column]) === true) {
|
Chris@17
|
403 $errors[$line][$column] = array_merge(
|
Chris@17
|
404 $newWarnings,
|
Chris@17
|
405 $errors[$line][$column]
|
Chris@17
|
406 );
|
Chris@17
|
407 } else {
|
Chris@17
|
408 $errors[$line][$column] = $newWarnings;
|
Chris@17
|
409 }
|
Chris@17
|
410 }//end foreach
|
Chris@17
|
411
|
Chris@17
|
412 ksort($errors[$line]);
|
Chris@17
|
413 }//end foreach
|
Chris@17
|
414
|
Chris@17
|
415 ksort($errors);
|
Chris@17
|
416 $report['messages'] = $errors;
|
Chris@17
|
417 return $report;
|
Chris@17
|
418
|
Chris@17
|
419 }//end prepareFileReport()
|
Chris@17
|
420
|
Chris@17
|
421
|
Chris@17
|
422 }//end class
|