annotate vendor/squizlabs/php_codesniffer/src/Reporter.php @ 5:12f9dff5fda9 tip

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