Chris@17: Chris@17: * @author Greg Sherwood Chris@17: * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) Chris@17: * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence Chris@17: */ Chris@17: Chris@17: namespace PHP_CodeSniffer\Reports; Chris@17: Chris@17: use PHP_CodeSniffer\Files\File; Chris@17: use PHP_CodeSniffer\Util\Timing; Chris@17: Chris@17: abstract class VersionControl implements Report Chris@17: { Chris@17: Chris@17: /** Chris@17: * The name of the report we want in the output. Chris@17: * Chris@17: * @var string Chris@17: */ Chris@17: protected $reportName = 'VERSION CONTROL'; Chris@17: Chris@17: Chris@17: /** Chris@17: * Generate a partial report for a single processed file. Chris@17: * Chris@17: * Function should return TRUE if it printed or stored data about the file Chris@17: * and FALSE if it ignored the file. Returning TRUE indicates that the file and Chris@17: * its data should be counted in the grand totals. Chris@17: * Chris@17: * @param array $report Prepared report data. Chris@17: * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. Chris@17: * @param bool $showSources Show sources? Chris@17: * @param int $width Maximum allowed line width. Chris@17: * Chris@17: * @return bool Chris@17: */ Chris@17: public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) Chris@17: { Chris@17: $blames = $this->getBlameContent($report['filename']); Chris@17: Chris@17: $authorCache = []; Chris@17: $praiseCache = []; Chris@17: $sourceCache = []; Chris@17: Chris@17: foreach ($report['messages'] as $line => $lineErrors) { Chris@17: $author = 'Unknown'; Chris@17: if (isset($blames[($line - 1)]) === true) { Chris@17: $blameAuthor = $this->getAuthor($blames[($line - 1)]); Chris@17: if ($blameAuthor !== false) { Chris@17: $author = $blameAuthor; Chris@17: } Chris@17: } Chris@17: Chris@17: if (isset($authorCache[$author]) === false) { Chris@17: $authorCache[$author] = 0; Chris@17: $praiseCache[$author] = [ Chris@17: 'good' => 0, Chris@17: 'bad' => 0, Chris@17: ]; Chris@17: } Chris@17: Chris@17: $praiseCache[$author]['bad']++; Chris@17: Chris@17: foreach ($lineErrors as $column => $colErrors) { Chris@17: foreach ($colErrors as $error) { Chris@17: $authorCache[$author]++; Chris@17: Chris@17: if ($showSources === true) { Chris@17: $source = $error['source']; Chris@17: if (isset($sourceCache[$author][$source]) === false) { Chris@17: $sourceCache[$author][$source] = [ Chris@17: 'count' => 1, Chris@17: 'fixable' => $error['fixable'], Chris@17: ]; Chris@17: } else { Chris@17: $sourceCache[$author][$source]['count']++; Chris@17: } Chris@17: } Chris@17: } Chris@17: } Chris@17: Chris@17: unset($blames[($line - 1)]); Chris@17: }//end foreach Chris@17: Chris@17: // Now go through and give the authors some credit for Chris@17: // all the lines that do not have errors. Chris@17: foreach ($blames as $line) { Chris@17: $author = $this->getAuthor($line); Chris@17: if ($author === false) { Chris@17: $author = 'Unknown'; Chris@17: } Chris@17: Chris@17: if (isset($authorCache[$author]) === false) { Chris@17: // This author doesn't have any errors. Chris@17: if (PHP_CODESNIFFER_VERBOSITY === 0) { Chris@17: continue; Chris@17: } Chris@17: Chris@17: $authorCache[$author] = 0; Chris@17: $praiseCache[$author] = [ Chris@17: 'good' => 0, Chris@17: 'bad' => 0, Chris@17: ]; Chris@17: } Chris@17: Chris@17: $praiseCache[$author]['good']++; Chris@17: }//end foreach Chris@17: Chris@17: foreach ($authorCache as $author => $errors) { Chris@17: echo "AUTHOR>>$author>>$errors".PHP_EOL; Chris@17: } Chris@17: Chris@17: foreach ($praiseCache as $author => $praise) { Chris@17: echo "PRAISE>>$author>>".$praise['good'].'>>'.$praise['bad'].PHP_EOL; Chris@17: } Chris@17: Chris@17: foreach ($sourceCache as $author => $sources) { Chris@17: foreach ($sources as $source => $sourceData) { Chris@17: $count = $sourceData['count']; Chris@17: $fixable = (int) $sourceData['fixable']; Chris@17: echo "SOURCE>>$author>>$source>>$count>>$fixable".PHP_EOL; Chris@17: } Chris@17: } Chris@17: Chris@17: return true; Chris@17: Chris@17: }//end generateFileReport() Chris@17: Chris@17: Chris@17: /** Chris@17: * Prints the author of all errors and warnings, as given by "version control blame". Chris@17: * Chris@17: * @param string $cachedData Any partial report data that was returned from Chris@17: * generateFileReport during the run. Chris@17: * @param int $totalFiles Total number of files processed during the run. Chris@17: * @param int $totalErrors Total number of errors found during the run. Chris@17: * @param int $totalWarnings Total number of warnings found during the run. Chris@17: * @param int $totalFixable Total number of problems that can be fixed. Chris@17: * @param bool $showSources Show sources? Chris@17: * @param int $width Maximum allowed line width. Chris@17: * @param bool $interactive Are we running in interactive mode? Chris@17: * @param bool $toScreen Is the report being printed to screen? Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function generate( Chris@17: $cachedData, Chris@17: $totalFiles, Chris@17: $totalErrors, Chris@17: $totalWarnings, Chris@17: $totalFixable, Chris@17: $showSources=false, Chris@17: $width=80, Chris@17: $interactive=false, Chris@17: $toScreen=true Chris@17: ) { Chris@17: $errorsShown = ($totalErrors + $totalWarnings); Chris@17: if ($errorsShown === 0) { Chris@17: // Nothing to show. Chris@17: return; Chris@17: } Chris@17: Chris@17: $lines = explode(PHP_EOL, $cachedData); Chris@17: array_pop($lines); Chris@17: Chris@17: if (empty($lines) === true) { Chris@17: return; Chris@17: } Chris@17: Chris@17: $authorCache = []; Chris@17: $praiseCache = []; Chris@17: $sourceCache = []; Chris@17: Chris@17: foreach ($lines as $line) { Chris@17: $parts = explode('>>', $line); Chris@17: switch ($parts[0]) { Chris@17: case 'AUTHOR': Chris@17: if (isset($authorCache[$parts[1]]) === false) { Chris@17: $authorCache[$parts[1]] = $parts[2]; Chris@17: } else { Chris@17: $authorCache[$parts[1]] += $parts[2]; Chris@17: } Chris@17: break; Chris@17: case 'PRAISE': Chris@17: if (isset($praiseCache[$parts[1]]) === false) { Chris@17: $praiseCache[$parts[1]] = [ Chris@17: 'good' => $parts[2], Chris@17: 'bad' => $parts[3], Chris@17: ]; Chris@17: } else { Chris@17: $praiseCache[$parts[1]]['good'] += $parts[2]; Chris@17: $praiseCache[$parts[1]]['bad'] += $parts[3]; Chris@17: } Chris@17: break; Chris@17: case 'SOURCE': Chris@17: if (isset($praiseCache[$parts[1]]) === false) { Chris@17: $praiseCache[$parts[1]] = []; Chris@17: } Chris@17: Chris@17: if (isset($sourceCache[$parts[1]][$parts[2]]) === false) { Chris@17: $sourceCache[$parts[1]][$parts[2]] = [ Chris@17: 'count' => $parts[3], Chris@17: 'fixable' => (bool) $parts[4], Chris@17: ]; Chris@17: } else { Chris@17: $sourceCache[$parts[1]][$parts[2]]['count'] += $parts[3]; Chris@17: } Chris@17: break; Chris@17: default: Chris@17: break; Chris@17: }//end switch Chris@17: }//end foreach Chris@17: Chris@17: // Make sure the report width isn't too big. Chris@17: $maxLength = 0; Chris@17: foreach ($authorCache as $author => $count) { Chris@17: $maxLength = max($maxLength, strlen($author)); Chris@17: if ($showSources === true && isset($sourceCache[$author]) === true) { Chris@17: foreach ($sourceCache[$author] as $source => $sourceData) { Chris@17: if ($source === 'count') { Chris@17: continue; Chris@17: } Chris@17: Chris@17: $maxLength = max($maxLength, (strlen($source) + 9)); Chris@17: } Chris@17: } Chris@17: } Chris@17: Chris@17: $width = min($width, ($maxLength + 30)); Chris@17: $width = max($width, 70); Chris@17: arsort($authorCache); Chris@17: Chris@17: echo PHP_EOL."\033[1m".'PHP CODE SNIFFER '.$this->reportName.' BLAME SUMMARY'."\033[0m".PHP_EOL; Chris@17: echo str_repeat('-', $width).PHP_EOL."\033[1m"; Chris@17: if ($showSources === true) { Chris@17: echo 'AUTHOR SOURCE'.str_repeat(' ', ($width - 43)).'(Author %) (Overall %) COUNT'.PHP_EOL; Chris@17: echo str_repeat('-', $width).PHP_EOL; Chris@17: } else { Chris@17: echo 'AUTHOR'.str_repeat(' ', ($width - 34)).'(Author %) (Overall %) COUNT'.PHP_EOL; Chris@17: echo str_repeat('-', $width).PHP_EOL; Chris@17: } Chris@17: Chris@17: echo "\033[0m"; Chris@17: Chris@17: if ($showSources === true) { Chris@17: $maxSniffWidth = ($width - 15); Chris@17: Chris@17: if ($totalFixable > 0) { Chris@17: $maxSniffWidth -= 4; Chris@17: } Chris@17: } Chris@17: Chris@17: $fixableSources = 0; Chris@17: Chris@17: foreach ($authorCache as $author => $count) { Chris@17: if ($praiseCache[$author]['good'] === 0) { Chris@17: $percent = 0; Chris@17: } else { Chris@17: $total = ($praiseCache[$author]['bad'] + $praiseCache[$author]['good']); Chris@17: $percent = round(($praiseCache[$author]['bad'] / $total * 100), 2); Chris@17: } Chris@17: Chris@17: $overallPercent = '('.round((($count / $errorsShown) * 100), 2).')'; Chris@17: $authorPercent = '('.$percent.')'; Chris@17: $line = str_repeat(' ', (6 - strlen($count))).$count; Chris@17: $line = str_repeat(' ', (12 - strlen($overallPercent))).$overallPercent.$line; Chris@17: $line = str_repeat(' ', (11 - strlen($authorPercent))).$authorPercent.$line; Chris@17: $line = $author.str_repeat(' ', ($width - strlen($author) - strlen($line))).$line; Chris@17: Chris@17: if ($showSources === true) { Chris@17: $line = "\033[1m$line\033[0m"; Chris@17: } Chris@17: Chris@17: echo $line.PHP_EOL; Chris@17: Chris@17: if ($showSources === true && isset($sourceCache[$author]) === true) { Chris@17: $errors = $sourceCache[$author]; Chris@17: asort($errors); Chris@17: $errors = array_reverse($errors); Chris@17: Chris@17: foreach ($errors as $source => $sourceData) { Chris@17: if ($source === 'count') { Chris@17: continue; Chris@17: } Chris@17: Chris@17: $count = $sourceData['count']; Chris@17: Chris@17: $srcLength = strlen($source); Chris@17: if ($srcLength > $maxSniffWidth) { Chris@17: $source = substr($source, 0, $maxSniffWidth); Chris@17: } Chris@17: Chris@17: $line = str_repeat(' ', (5 - strlen($count))).$count; Chris@17: Chris@17: echo ' '; Chris@17: if ($totalFixable > 0) { Chris@17: echo '['; Chris@17: if ($sourceData['fixable'] === true) { Chris@17: echo 'x'; Chris@17: $fixableSources++; Chris@17: } else { Chris@17: echo ' '; Chris@17: } Chris@17: Chris@17: echo '] '; Chris@17: } Chris@17: Chris@17: echo $source; Chris@17: if ($totalFixable > 0) { Chris@17: echo str_repeat(' ', ($width - 18 - strlen($source))); Chris@17: } else { Chris@17: echo str_repeat(' ', ($width - 14 - strlen($source))); Chris@17: } Chris@17: Chris@17: echo $line.PHP_EOL; Chris@17: }//end foreach Chris@17: }//end if Chris@17: }//end foreach Chris@17: Chris@17: echo str_repeat('-', $width).PHP_EOL; Chris@17: echo "\033[1m".'A TOTAL OF '.$errorsShown.' SNIFF VIOLATION'; Chris@17: if ($errorsShown !== 1) { Chris@17: echo 'S'; Chris@17: } Chris@17: Chris@17: echo ' WERE COMMITTED BY '.count($authorCache).' AUTHOR'; Chris@17: if (count($authorCache) !== 1) { Chris@17: echo 'S'; Chris@17: } Chris@17: Chris@17: echo "\033[0m"; Chris@17: Chris@17: if ($totalFixable > 0) { Chris@17: if ($showSources === true) { Chris@17: echo PHP_EOL.str_repeat('-', $width).PHP_EOL; Chris@17: echo "\033[1mPHPCBF CAN FIX THE $fixableSources MARKED SOURCES AUTOMATICALLY ($totalFixable VIOLATIONS IN TOTAL)\033[0m"; Chris@17: } else { Chris@17: echo PHP_EOL.str_repeat('-', $width).PHP_EOL; Chris@17: echo "\033[1mPHPCBF CAN FIX $totalFixable OF THESE SNIFF VIOLATIONS AUTOMATICALLY\033[0m"; Chris@17: } Chris@17: } Chris@17: Chris@17: echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL; Chris@17: Chris@17: if ($toScreen === true && $interactive === false) { Chris@17: Timing::printRunTime(); Chris@17: } Chris@17: Chris@17: }//end generate() Chris@17: Chris@17: Chris@17: /** Chris@17: * Extract the author from a blame line. Chris@17: * Chris@17: * @param string $line Line to parse. Chris@17: * Chris@17: * @return mixed string or false if impossible to recover. Chris@17: */ Chris@17: abstract protected function getAuthor($line); Chris@17: Chris@17: Chris@17: /** Chris@17: * Gets the blame output. Chris@17: * Chris@17: * @param string $filename File to blame. Chris@17: * Chris@17: * @return array Chris@17: */ Chris@17: abstract protected function getBlameContent($filename); Chris@17: Chris@17: Chris@17: }//end class