annotate vendor/squizlabs/php_codesniffer/src/Reporter.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
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