1 <?php
2 /**
3  * Version control report base class for PHP_CodeSniffer.
4  *
5  * PHP version 5
6  *
7  * @category  PHP
8  * @package   PHP_CodeSniffer
9  * @author    Ben Selby <benmatselby@gmail.com>
10  * @copyright 2009-2014 SQLI <www.sqli.com>
11  * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12  * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13  * @link      http://pear.php.net/package/PHP_CodeSniffer
14  */
15 
16 /**
17  * Version control report base class for PHP_CodeSniffer.
18  *
19  * PHP version 5
20  *
21  * @category  PHP
22  * @package   PHP_CodeSniffer
23  * @author    Ben Selby <benmatselby@gmail.com>
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: 1.2.2
28  * @link      http://pear.php.net/package/PHP_CodeSniffer
29  */
30 abstract class PHP_CodeSniffer_Reports_VersionControl implements PHP_CodeSniffer_Report
31 {
32 
33     /**
34      * The name of the report we want in the output.
35      *
36      * @var string
37      */
38     protected $reportName = 'VERSION CONTROL';
39 
40     /**
41      * A cache of author stats collected during the run.
42      *
43      * @var array
44      */
45     private $_authorCache = array();
46 
47     /**
48      * A cache of blame stats collected during the run.
49      *
50      * @var array
51      */
52     private $_praiseCache = array();
53 
54     /**
55      * A cache of source stats collected during the run.
56      *
57      * @var array
58      */
59     private $_sourceCache = array();
60 
61 
62     /**
63      * Generate a partial report for a single processed file.
64      *
65      * Function should return TRUE if it printed or stored data about the file
66      * and FALSE if it ignored the file. Returning TRUE indicates that the file and
67      * its data should be counted in the grand totals.
68      *
69      * @param array                $report      Prepared report data.
70      * @param PHP_CodeSniffer_File $phpcsFile   The file being reported on.
71      * @param boolean              $showSources Show sources?
72      * @param int                  $width       Maximum allowed line width.
73      *
74      * @return boolean
75      */
76     public function generateFileReport(
77         $report,
78         PHP_CodeSniffer_File $phpcsFile,
79         $showSources=false,
80         $width=80
81     ) {
82         $blames = $this->getBlameContent($report['filename']);
83 
84         foreach ($report['messages'] as $line => $lineErrors) {
85             $author = 'Unknown';
86             if (isset($blames[($line - 1)]) === true) {
87                 $blameAuthor = $this->getAuthor($blames[($line - 1)]);
88                 if ($blameAuthor !== false) {
89                     $author = $blameAuthor;
90                 }
91             }
92 
93             if (isset($this->_authorCache[$author]) === false) {
94                 $this->_authorCache[$author] = 0;
95                 $this->_praiseCache[$author] = array(
96                                                 'good' => 0,
97                                                 'bad'  => 0,
98                                                );
99             }
100 
101             $this->_praiseCache[$author]['bad']++;
102 
103             foreach ($lineErrors as $column => $colErrors) {
104                 foreach ($colErrors as $error) {
105                     $this->_authorCache[$author]++;
106 
107                     if ($showSources === true) {
108                         $source = $error['source'];
109                         if (isset($this->_sourceCache[$author][$source]) === false) {
110                             $this->_sourceCache[$author][$source] = array(
111                                                                      'count'   => 1,
112                                                                      'fixable' => $error['fixable'],
113                                                                     );
114                         } else {
115                             $this->_sourceCache[$author][$source]['count']++;
116                         }
117                     }
118                 }
119             }
120 
121             unset($blames[($line - 1)]);
122         }//end foreach
123 
124         // No go through and give the authors some credit for
125         // all the lines that do not have errors.
126         foreach ($blames as $line) {
127             $author = $this->getAuthor($line);
128             if ($author === false) {
129                 $author = 'Unknown';
130             }
131 
132             if (isset($this->_authorCache[$author]) === false) {
133                 // This author doesn't have any errors.
134                 if (PHP_CODESNIFFER_VERBOSITY === 0) {
135                     continue;
136                 }
137 
138                 $this->_authorCache[$author] = 0;
139                 $this->_praiseCache[$author] = array(
140                                                 'good' => 0,
141                                                 'bad'  => 0,
142                                                );
143             }
144 
145             $this->_praiseCache[$author]['good']++;
146         }//end foreach
147 
148         return true;
149 
150     }//end generateFileReport()
151 
152 
153     /**
154      * Prints the author of all errors and warnings, as given by "version control blame".
155      *
156      * @param string  $cachedData    Any partial report data that was returned from
157      *                               generateFileReport during the run.
158      * @param int     $totalFiles    Total number of files processed during the run.
159      * @param int     $totalErrors   Total number of errors found during the run.
160      * @param int     $totalWarnings Total number of warnings found during the run.
161      * @param int     $totalFixable  Total number of problems that can be fixed.
162      * @param boolean $showSources   Show sources?
163      * @param int     $width         Maximum allowed line width.
164      * @param boolean $toScreen      Is the report being printed to screen?
165      *
166      * @return void
167      */
168     public function generate(
169         $cachedData,
170         $totalFiles,
171         $totalErrors,
172         $totalWarnings,
173         $totalFixable,
174         $showSources=false,
175         $width=80,
176         $toScreen=true
177     ) {
178         $errorsShown = ($totalErrors + $totalWarnings);
179         if ($errorsShown === 0) {
180             // Nothing to show.
181             return;
182         }
183 
184         // Make sure the report width isn't too big.
185         $maxLength = 0;
186         foreach ($this->_authorCache as $author => $count) {
187             $maxLength = max($maxLength, strlen($author));
188             if ($showSources === true && isset($this->_sourceCache[$author]) === true) {
189                 foreach ($this->_sourceCache[$author] as $source => $sourceData) {
190                     if ($source === 'count') {
191                         continue;
192                     }
193 
194                     $maxLength = max($maxLength, (strlen($source) + 9));
195                 }
196             }
197         }
198 
199         $width = min($width, ($maxLength + 30));
200         $width = max($width, 70);
201         arsort($this->_authorCache);
202 
203         echo PHP_EOL."\033[1m".'PHP CODE SNIFFER '.$this->reportName.' BLAME SUMMARY'."\033[0m".PHP_EOL;
204         echo str_repeat('-', $width).PHP_EOL."\033[1m";
205         if ($showSources === true) {
206             echo 'AUTHOR   SOURCE'.str_repeat(' ', ($width - 43)).'(Author %) (Overall %) COUNT'.PHP_EOL;
207             echo str_repeat('-', $width).PHP_EOL;
208         } else {
209             echo 'AUTHOR'.str_repeat(' ', ($width - 34)).'(Author %) (Overall %) COUNT'.PHP_EOL;
210             echo str_repeat('-', $width).PHP_EOL;
211         }
212 
213         echo "\033[0m";
214 
215         if ($showSources === true) {
216             $maxSniffWidth = ($width - 15);
217 
218             if ($totalFixable > 0) {
219                 $maxSniffWidth -= 4;
220             }
221         }
222 
223         $fixableSources = 0;
224 
225         foreach ($this->_authorCache as $author => $count) {
226             if ($this->_praiseCache[$author]['good'] === 0) {
227                 $percent = 0;
228             } else {
229                 $total   = ($this->_praiseCache[$author]['bad'] + $this->_praiseCache[$author]['good']);
230                 $percent = round(($this->_praiseCache[$author]['bad'] / $total * 100), 2);
231             }
232 
233             $overallPercent = '('.round((($count / $errorsShown) * 100), 2).')';
234             $authorPercent  = '('.$percent.')';
235             $line           = str_repeat(' ', (6 - strlen($count))).$count;
236             $line           = str_repeat(' ', (12 - strlen($overallPercent))).$overallPercent.$line;
237             $line           = str_repeat(' ', (11 - strlen($authorPercent))).$authorPercent.$line;
238             $line           = $author.str_repeat(' ', ($width - strlen($author) - strlen($line))).$line;
239 
240             if ($showSources === true) {
241                 $line = "\033[1m$line\033[0m";
242             }
243 
244             echo $line.PHP_EOL;
245 
246             if ($showSources === true && isset($this->_sourceCache[$author]) === true) {
247                 $errors = $this->_sourceCache[$author];
248                 asort($errors);
249                 $errors = array_reverse($errors);
250 
251                 foreach ($errors as $source => $sourceData) {
252                     if ($source === 'count') {
253                         continue;
254                     }
255 
256                     $count = $sourceData['count'];
257 
258                     $srcLength = strlen($source);
259                     if ($srcLength > $maxSniffWidth) {
260                         $source = substr($source, 0, $maxSniffWidth);
261                     }
262 
263                     $line = str_repeat(' ', (5 - strlen($count))).$count;
264 
265                     echo '         ';
266                     if ($totalFixable > 0) {
267                         echo '[';
268                         if ($sourceData['fixable'] === true) {
269                             echo 'x';
270                             $fixableSources++;
271                         } else {
272                             echo ' ';
273                         }
274 
275                         echo '] ';
276                     }
277 
278                     echo $source;
279                     if ($totalFixable > 0) {
280                         echo str_repeat(' ', ($width - 18 - strlen($source)));
281                     } else {
282                         echo str_repeat(' ', ($width - 14 - strlen($source)));
283                     }
284 
285                     echo $line.PHP_EOL;
286                 }//end foreach
287             }//end if
288         }//end foreach
289 
290         echo str_repeat('-', $width).PHP_EOL;
291         echo "\033[1m".'A TOTAL OF '.$errorsShown.' SNIFF VIOLATION';
292         if ($errorsShown !== 1) {
293             echo 'S';
294         }
295 
296         echo ' WERE COMMITTED BY '.count($this->_authorCache).' AUTHOR';
297         if (count($this->_authorCache) !== 1) {
298             echo 'S';
299         }
300 
301         echo "\033[0m";
302 
303         if ($totalFixable > 0) {
304             if ($showSources === true) {
305                 echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
306                 echo "\033[1mPHPCBF CAN FIX THE $fixableSources MARKED SOURCES AUTOMATICALLY ($totalFixable VIOLATIONS IN TOTAL)\033[0m";
307             } else {
308                 echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
309                 echo "\033[1mPHPCBF CAN FIX $totalFixable OF THESE SNIFF VIOLATIONS AUTOMATICALLY\033[0m";
310             }
311         }
312 
313         echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL;
314 
315         if ($toScreen === true && PHP_CODESNIFFER_INTERACTIVE === false) {
316             PHP_CodeSniffer_Reporting::printRunTime();
317         }
318 
319     }//end generate()
320 
321 
322     /**
323      * Extract the author from a blame line.
324      *
325      * @param string $line Line to parse.
326      *
327      * @return mixed string or false if impossible to recover.
328      */
329     abstract protected function getAuthor($line);
330 
331 
332     /**
333      * Gets the blame output.
334      *
335      * @param string $filename File to blame.
336      *
337      * @return array
338      */
339     abstract protected function getBlameContent($filename);
340 
341 
342 }//end class
343