1<?php 2/** 3 * A class to manage reporting. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Gabriele Santini <gsantini@sqli.com> 10 * @author Greg Sherwood <gsherwood@squiz.net> 11 * @copyright 2009-2014 SQLI <www.sqli.com> 12 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 13 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 14 * @link http://pear.php.net/package/PHP_CodeSniffer 15 */ 16 17/** 18 * A class to manage reporting. 19 * 20 * @category PHP 21 * @package PHP_CodeSniffer 22 * @author Gabriele Santini <gsantini@sqli.com> 23 * @author Greg Sherwood <gsherwood@squiz.net> 24 * @copyright 2009-2014 SQLI <www.sqli.com> 25 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 26 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 27 * @version Release: @package_version@ 28 * @link http://pear.php.net/package/PHP_CodeSniffer 29 */ 30class PHP_CodeSniffer_Reporting 31{ 32 33 /** 34 * Total number of files that contain errors or warnings. 35 * 36 * @var int 37 */ 38 public $totalFiles = 0; 39 40 /** 41 * Total number of errors found during the run. 42 * 43 * @var int 44 */ 45 public $totalErrors = 0; 46 47 /** 48 * Total number of warnings found during the run. 49 * 50 * @var int 51 */ 52 public $totalWarnings = 0; 53 54 /** 55 * Total number of errors/warnings that can be fixed. 56 * 57 * @var int 58 */ 59 public $totalFixable = 0; 60 61 /** 62 * When the PHPCS run started. 63 * 64 * @var float 65 */ 66 public static $startTime = 0; 67 68 /** 69 * A list of reports that have written partial report output. 70 * 71 * @var array 72 */ 73 private $_cachedReports = array(); 74 75 /** 76 * A cache of report objects. 77 * 78 * @var array 79 */ 80 private $_reports = array(); 81 82 /** 83 * A cache of opened tmp files. 84 * 85 * @var array 86 */ 87 private $_tmpFiles = array(); 88 89 90 /** 91 * Produce the appropriate report object based on $type parameter. 92 * 93 * @param string $type The type of the report. 94 * 95 * @return PHP_CodeSniffer_Report 96 * @throws PHP_CodeSniffer_Exception If report is not available. 97 */ 98 public function factory($type) 99 { 100 $type = ucfirst($type); 101 if (isset($this->_reports[$type]) === true) { 102 return $this->_reports[$type]; 103 } 104 105 if (strpos($type, '.') !== false) { 106 // This is a path to a custom report class. 107 $filename = realpath($type); 108 if ($filename === false) { 109 echo 'ERROR: Custom report "'.$type.'" not found'.PHP_EOL; 110 exit(2); 111 } 112 113 $reportClassName = 'PHP_CodeSniffer_Reports_'.basename($filename); 114 $reportClassName = substr($reportClassName, 0, strpos($reportClassName, '.')); 115 include_once $filename; 116 } else { 117 $filename = $type.'.php'; 118 $reportClassName = 'PHP_CodeSniffer_Reports_'.$type; 119 if (class_exists($reportClassName, true) === false) { 120 echo 'ERROR: Report type "'.$type.'" not found'.PHP_EOL; 121 exit(2); 122 } 123 }//end if 124 125 $reportClass = new $reportClassName(); 126 if (false === ($reportClass instanceof PHP_CodeSniffer_Report)) { 127 throw new PHP_CodeSniffer_Exception('Class "'.$reportClassName.'" must implement the "PHP_CodeSniffer_Report" interface.'); 128 } 129 130 $this->_reports[$type] = $reportClass; 131 return $this->_reports[$type]; 132 133 }//end factory() 134 135 136 /** 137 * Actually generates the report. 138 * 139 * @param PHP_CodeSniffer_File $phpcsFile The file that has been processed. 140 * @param array $cliValues An array of command line arguments. 141 * 142 * @return void 143 */ 144 public function cacheFileReport(PHP_CodeSniffer_File $phpcsFile, array $cliValues) 145 { 146 if (isset($cliValues['reports']) === false) { 147 // This happens during unit testing, or any time someone just wants 148 // the error data and not the printed report. 149 return; 150 } 151 152 $reportData = $this->prepareFileReport($phpcsFile); 153 $errorsShown = false; 154 155 foreach ($cliValues['reports'] as $report => $output) { 156 $reportClass = $this->factory($report); 157 $report = get_class($reportClass); 158 159 ob_start(); 160 $result = $reportClass->generateFileReport($reportData, $phpcsFile, $cliValues['showSources'], $cliValues['reportWidth']); 161 if ($result === true) { 162 $errorsShown = true; 163 } 164 165 $generatedReport = ob_get_contents(); 166 ob_end_clean(); 167 168 if ($output === null && $cliValues['reportFile'] !== null) { 169 $output = $cliValues['reportFile']; 170 } 171 172 if ($output === null) { 173 // Using a temp file. 174 if (isset($this->_tmpFiles[$report]) === false) { 175 if (function_exists('sys_get_temp_dir') === true) { 176 // This is needed for HHVM support, but only available from 5.2.1. 177 $this->_tmpFiles[$report] = fopen(tempnam(sys_get_temp_dir(), 'phpcs'), 'w'); 178 } else { 179 $this->_tmpFiles[$report] = tmpfile(); 180 } 181 } 182 183 fwrite($this->_tmpFiles[$report], $generatedReport); 184 } else { 185 $flags = FILE_APPEND; 186 if (isset($this->_cachedReports[$report]) === false) { 187 $this->_cachedReports[$report] = true; 188 $flags = null; 189 } 190 191 file_put_contents($output, $generatedReport, $flags); 192 }//end if 193 }//end foreach 194 195 if ($errorsShown === true) { 196 $this->totalFiles++; 197 $this->totalErrors += $reportData['errors']; 198 $this->totalWarnings += $reportData['warnings']; 199 $this->totalFixable += $reportData['fixable']; 200 } 201 202 }//end cacheFileReport() 203 204 205 /** 206 * Generates and prints a final report. 207 * 208 * Returns an array with the number of errors and the number of 209 * warnings, in the form ['errors' => int, 'warnings' => int]. 210 * 211 * @param string $report Report type. 212 * @param boolean $showSources Show sources? 213 * @param array $cliValues An array of command line arguments. 214 * @param string $reportFile Report file to generate. 215 * @param integer $reportWidth Report max width. 216 * 217 * @return int[] 218 */ 219 public function printReport( 220 $report, 221 $showSources, 222 array $cliValues, 223 $reportFile='', 224 $reportWidth=80 225 ) { 226 $reportClass = $this->factory($report); 227 $report = get_class($reportClass); 228 229 if ($reportFile !== null) { 230 $filename = $reportFile; 231 $toScreen = false; 232 233 if (file_exists($filename) === true 234 && isset($this->_cachedReports[$report]) === true 235 ) { 236 $reportCache = file_get_contents($filename); 237 } else { 238 $reportCache = ''; 239 } 240 } else { 241 if (isset($this->_tmpFiles[$report]) === true) { 242 $data = stream_get_meta_data($this->_tmpFiles[$report]); 243 $filename = $data['uri']; 244 $reportCache = file_get_contents($filename); 245 fclose($this->_tmpFiles[$report]); 246 } else { 247 $reportCache = ''; 248 $filename = null; 249 } 250 251 $toScreen = true; 252 }//end if 253 254 ob_start(); 255 $reportClass->generate( 256 $reportCache, 257 $this->totalFiles, 258 $this->totalErrors, 259 $this->totalWarnings, 260 $this->totalFixable, 261 $showSources, 262 $reportWidth, 263 $toScreen 264 ); 265 $generatedReport = ob_get_contents(); 266 ob_end_clean(); 267 268 if ($cliValues['colors'] !== true || $reportFile !== null) { 269 $generatedReport = preg_replace('`\033\[[0-9]+m`', '', $generatedReport); 270 } 271 272 if ($reportFile !== null) { 273 if (PHP_CODESNIFFER_VERBOSITY > 0) { 274 echo $generatedReport; 275 } 276 277 file_put_contents($reportFile, $generatedReport.PHP_EOL); 278 } else { 279 echo $generatedReport; 280 if ($filename !== null && file_exists($filename) === true) { 281 unlink($filename); 282 } 283 } 284 285 return array( 286 'errors' => $this->totalErrors, 287 'warnings' => $this->totalWarnings, 288 ); 289 290 }//end printReport() 291 292 293 /** 294 * Pre-process and package violations for all files. 295 * 296 * Used by error reports to get a packaged list of all errors in each file. 297 * 298 * @param PHP_CodeSniffer_File $phpcsFile The file that has been processed. 299 * 300 * @return array 301 */ 302 public function prepareFileReport(PHP_CodeSniffer_File $phpcsFile) 303 { 304 $report = array( 305 'filename' => $phpcsFile->getFilename(), 306 'errors' => $phpcsFile->getErrorCount(), 307 'warnings' => $phpcsFile->getWarningCount(), 308 'fixable' => $phpcsFile->getFixableCount(), 309 'messages' => array(), 310 ); 311 312 if ($report['errors'] === 0 && $report['warnings'] === 0) { 313 // Prefect score! 314 return $report; 315 } 316 317 $errors = array(); 318 319 // Merge errors and warnings. 320 foreach ($phpcsFile->getErrors() as $line => $lineErrors) { 321 if (is_array($lineErrors) === false) { 322 continue; 323 } 324 325 foreach ($lineErrors as $column => $colErrors) { 326 $newErrors = array(); 327 foreach ($colErrors as $data) { 328 $newErrors[] = array( 329 'message' => $data['message'], 330 'source' => $data['source'], 331 'severity' => $data['severity'], 332 'fixable' => $data['fixable'], 333 'type' => 'ERROR', 334 ); 335 }//end foreach 336 337 $errors[$line][$column] = $newErrors; 338 }//end foreach 339 340 ksort($errors[$line]); 341 }//end foreach 342 343 foreach ($phpcsFile->getWarnings() as $line => $lineWarnings) { 344 if (is_array($lineWarnings) === false) { 345 continue; 346 } 347 348 foreach ($lineWarnings as $column => $colWarnings) { 349 $newWarnings = array(); 350 foreach ($colWarnings as $data) { 351 $newWarnings[] = array( 352 'message' => $data['message'], 353 'source' => $data['source'], 354 'severity' => $data['severity'], 355 'fixable' => $data['fixable'], 356 'type' => 'WARNING', 357 ); 358 }//end foreach 359 360 if (isset($errors[$line]) === false) { 361 $errors[$line] = array(); 362 } 363 364 if (isset($errors[$line][$column]) === true) { 365 $errors[$line][$column] = array_merge( 366 $newWarnings, 367 $errors[$line][$column] 368 ); 369 } else { 370 $errors[$line][$column] = $newWarnings; 371 } 372 }//end foreach 373 374 ksort($errors[$line]); 375 }//end foreach 376 377 ksort($errors); 378 $report['messages'] = $errors; 379 return $report; 380 381 }//end prepareFileReport() 382 383 384 /** 385 * Start recording time for the run. 386 * 387 * @return void 388 */ 389 public static function startTiming() 390 { 391 392 self::$startTime = microtime(true); 393 394 }//end startTiming() 395 396 397 /** 398 * Print information about the run. 399 * 400 * @return void 401 */ 402 public static function printRunTime() 403 { 404 $time = ((microtime(true) - self::$startTime) * 1000); 405 406 if ($time > 60000) { 407 $mins = floor($time / 60000); 408 $secs = round((($time % 60000) / 1000), 2); 409 $time = $mins.' mins'; 410 if ($secs !== 0) { 411 $time .= ", $secs secs"; 412 } 413 } else if ($time > 1000) { 414 $time = round(($time / 1000), 2).' secs'; 415 } else { 416 $time = round($time).'ms'; 417 } 418 419 $mem = round((memory_get_peak_usage(true) / (1024 * 1024)), 2).'Mb'; 420 echo "Time: $time; Memory: $mem".PHP_EOL.PHP_EOL; 421 422 }//end printRunTime() 423 424 425}//end class 426