Chris@17
|
1 <?php
|
Chris@17
|
2 /**
|
Chris@17
|
3 * Full report for PHP_CodeSniffer.
|
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\Reports;
|
Chris@17
|
11
|
Chris@17
|
12 use PHP_CodeSniffer\Files\File;
|
Chris@17
|
13 use PHP_CodeSniffer\Util;
|
Chris@17
|
14
|
Chris@17
|
15 class Code implements Report
|
Chris@17
|
16 {
|
Chris@17
|
17
|
Chris@17
|
18
|
Chris@17
|
19 /**
|
Chris@17
|
20 * Generate a partial report for a single processed file.
|
Chris@17
|
21 *
|
Chris@17
|
22 * Function should return TRUE if it printed or stored data about the file
|
Chris@17
|
23 * and FALSE if it ignored the file. Returning TRUE indicates that the file and
|
Chris@17
|
24 * its data should be counted in the grand totals.
|
Chris@17
|
25 *
|
Chris@17
|
26 * @param array $report Prepared report data.
|
Chris@17
|
27 * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
|
Chris@17
|
28 * @param bool $showSources Show sources?
|
Chris@17
|
29 * @param int $width Maximum allowed line width.
|
Chris@17
|
30 *
|
Chris@17
|
31 * @return bool
|
Chris@17
|
32 */
|
Chris@17
|
33 public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
|
Chris@17
|
34 {
|
Chris@17
|
35 if ($report['errors'] === 0 && $report['warnings'] === 0) {
|
Chris@17
|
36 // Nothing to print.
|
Chris@17
|
37 return false;
|
Chris@17
|
38 }
|
Chris@17
|
39
|
Chris@17
|
40 // How many lines to show about and below the error line.
|
Chris@17
|
41 $surroundingLines = 2;
|
Chris@17
|
42
|
Chris@17
|
43 $file = $report['filename'];
|
Chris@17
|
44 $tokens = $phpcsFile->getTokens();
|
Chris@17
|
45 if (empty($tokens) === true) {
|
Chris@17
|
46 if (PHP_CODESNIFFER_VERBOSITY === 1) {
|
Chris@17
|
47 $startTime = microtime(true);
|
Chris@17
|
48 echo 'CODE report is parsing '.basename($file).' ';
|
Chris@17
|
49 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
50 echo "CODE report is forcing parse of $file".PHP_EOL;
|
Chris@17
|
51 }
|
Chris@17
|
52
|
Chris@17
|
53 try {
|
Chris@17
|
54 $phpcsFile->parse();
|
Chris@17
|
55 } catch (\Exception $e) {
|
Chris@17
|
56 // This is a second parse, so ignore exceptions.
|
Chris@17
|
57 // They would have been added to the file's error list already.
|
Chris@17
|
58 }
|
Chris@17
|
59
|
Chris@17
|
60 if (PHP_CODESNIFFER_VERBOSITY === 1) {
|
Chris@17
|
61 $timeTaken = ((microtime(true) - $startTime) * 1000);
|
Chris@17
|
62 if ($timeTaken < 1000) {
|
Chris@17
|
63 $timeTaken = round($timeTaken);
|
Chris@17
|
64 echo "DONE in {$timeTaken}ms";
|
Chris@17
|
65 } else {
|
Chris@17
|
66 $timeTaken = round(($timeTaken / 1000), 2);
|
Chris@17
|
67 echo "DONE in $timeTaken secs";
|
Chris@17
|
68 }
|
Chris@17
|
69
|
Chris@17
|
70 echo PHP_EOL;
|
Chris@17
|
71 }
|
Chris@17
|
72
|
Chris@17
|
73 $tokens = $phpcsFile->getTokens();
|
Chris@17
|
74 }//end if
|
Chris@17
|
75
|
Chris@17
|
76 // Create an array that maps lines to the first token on the line.
|
Chris@17
|
77 $lineTokens = [];
|
Chris@17
|
78 $lastLine = 0;
|
Chris@17
|
79 $stackPtr = 0;
|
Chris@17
|
80 foreach ($tokens as $stackPtr => $token) {
|
Chris@17
|
81 if ($token['line'] !== $lastLine) {
|
Chris@17
|
82 if ($lastLine > 0) {
|
Chris@17
|
83 $lineTokens[$lastLine]['end'] = ($stackPtr - 1);
|
Chris@17
|
84 }
|
Chris@17
|
85
|
Chris@17
|
86 $lastLine++;
|
Chris@17
|
87 $lineTokens[$lastLine] = [
|
Chris@17
|
88 'start' => $stackPtr,
|
Chris@17
|
89 'end' => null,
|
Chris@17
|
90 ];
|
Chris@17
|
91 }
|
Chris@17
|
92 }
|
Chris@17
|
93
|
Chris@17
|
94 // Make sure the last token in the file sits on an imaginary
|
Chris@17
|
95 // last line so it is easier to generate code snippets at the
|
Chris@17
|
96 // end of the file.
|
Chris@17
|
97 $lineTokens[$lastLine]['end'] = $stackPtr;
|
Chris@17
|
98
|
Chris@17
|
99 // Determine the longest code line we will be showing.
|
Chris@17
|
100 $maxSnippetLength = 0;
|
Chris@17
|
101 $eolLen = strlen($phpcsFile->eolChar);
|
Chris@17
|
102 foreach ($report['messages'] as $line => $lineErrors) {
|
Chris@17
|
103 $startLine = max(($line - $surroundingLines), 1);
|
Chris@17
|
104 $endLine = min(($line + $surroundingLines), $lastLine);
|
Chris@17
|
105
|
Chris@17
|
106 $maxLineNumLength = strlen($endLine);
|
Chris@17
|
107
|
Chris@17
|
108 for ($i = $startLine; $i <= $endLine; $i++) {
|
Chris@17
|
109 if ($i === 1) {
|
Chris@17
|
110 continue;
|
Chris@17
|
111 }
|
Chris@17
|
112
|
Chris@17
|
113 $lineLength = ($tokens[($lineTokens[$i]['start'] - 1)]['column'] + $tokens[($lineTokens[$i]['start'] - 1)]['length'] - $eolLen);
|
Chris@17
|
114 $maxSnippetLength = max($lineLength, $maxSnippetLength);
|
Chris@17
|
115 }
|
Chris@17
|
116 }
|
Chris@17
|
117
|
Chris@17
|
118 $maxSnippetLength += ($maxLineNumLength + 8);
|
Chris@17
|
119
|
Chris@17
|
120 // Determine the longest error message we will be showing.
|
Chris@17
|
121 $maxErrorLength = 0;
|
Chris@17
|
122 foreach ($report['messages'] as $line => $lineErrors) {
|
Chris@17
|
123 foreach ($lineErrors as $column => $colErrors) {
|
Chris@17
|
124 foreach ($colErrors as $error) {
|
Chris@17
|
125 $length = strlen($error['message']);
|
Chris@17
|
126 if ($showSources === true) {
|
Chris@17
|
127 $length += (strlen($error['source']) + 3);
|
Chris@17
|
128 }
|
Chris@17
|
129
|
Chris@17
|
130 $maxErrorLength = max($maxErrorLength, ($length + 1));
|
Chris@17
|
131 }
|
Chris@17
|
132 }
|
Chris@17
|
133 }
|
Chris@17
|
134
|
Chris@17
|
135 // The padding that all lines will require that are printing an error message overflow.
|
Chris@17
|
136 if ($report['warnings'] > 0) {
|
Chris@17
|
137 $typeLength = 7;
|
Chris@17
|
138 } else {
|
Chris@17
|
139 $typeLength = 5;
|
Chris@17
|
140 }
|
Chris@17
|
141
|
Chris@17
|
142 $errorPadding = str_repeat(' ', ($maxLineNumLength + 7));
|
Chris@17
|
143 $errorPadding .= str_repeat(' ', $typeLength);
|
Chris@17
|
144 $errorPadding .= ' ';
|
Chris@17
|
145 if ($report['fixable'] > 0) {
|
Chris@17
|
146 $errorPadding .= ' ';
|
Chris@17
|
147 }
|
Chris@17
|
148
|
Chris@17
|
149 $errorPaddingLength = strlen($errorPadding);
|
Chris@17
|
150
|
Chris@17
|
151 // The maximum amount of space an error message can use.
|
Chris@17
|
152 $maxErrorSpace = ($width - $errorPaddingLength);
|
Chris@17
|
153 if ($showSources === true) {
|
Chris@17
|
154 // Account for the chars used to print colors.
|
Chris@17
|
155 $maxErrorSpace += 8;
|
Chris@17
|
156 }
|
Chris@17
|
157
|
Chris@17
|
158 // Figure out the max report width we need and can use.
|
Chris@17
|
159 $fileLength = strlen($file);
|
Chris@17
|
160 $maxWidth = max(($fileLength + 6), ($maxErrorLength + $errorPaddingLength));
|
Chris@17
|
161 $width = max(min($width, $maxWidth), $maxSnippetLength);
|
Chris@17
|
162 if ($width < 70) {
|
Chris@17
|
163 $width = 70;
|
Chris@17
|
164 }
|
Chris@17
|
165
|
Chris@17
|
166 // Print the file header.
|
Chris@17
|
167 echo PHP_EOL."\033[1mFILE: ";
|
Chris@17
|
168 if ($fileLength <= ($width - 6)) {
|
Chris@17
|
169 echo $file;
|
Chris@17
|
170 } else {
|
Chris@17
|
171 echo '...'.substr($file, ($fileLength - ($width - 6)));
|
Chris@17
|
172 }
|
Chris@17
|
173
|
Chris@17
|
174 echo "\033[0m".PHP_EOL;
|
Chris@17
|
175 echo str_repeat('-', $width).PHP_EOL;
|
Chris@17
|
176
|
Chris@17
|
177 echo "\033[1m".'FOUND '.$report['errors'].' ERROR';
|
Chris@17
|
178 if ($report['errors'] !== 1) {
|
Chris@17
|
179 echo 'S';
|
Chris@17
|
180 }
|
Chris@17
|
181
|
Chris@17
|
182 if ($report['warnings'] > 0) {
|
Chris@17
|
183 echo ' AND '.$report['warnings'].' WARNING';
|
Chris@17
|
184 if ($report['warnings'] !== 1) {
|
Chris@17
|
185 echo 'S';
|
Chris@17
|
186 }
|
Chris@17
|
187 }
|
Chris@17
|
188
|
Chris@17
|
189 echo ' AFFECTING '.count($report['messages']).' LINE';
|
Chris@17
|
190 if (count($report['messages']) !== 1) {
|
Chris@17
|
191 echo 'S';
|
Chris@17
|
192 }
|
Chris@17
|
193
|
Chris@17
|
194 echo "\033[0m".PHP_EOL;
|
Chris@17
|
195
|
Chris@17
|
196 foreach ($report['messages'] as $line => $lineErrors) {
|
Chris@17
|
197 $startLine = max(($line - $surroundingLines), 1);
|
Chris@17
|
198 $endLine = min(($line + $surroundingLines), $lastLine);
|
Chris@17
|
199
|
Chris@17
|
200 $snippet = '';
|
Chris@17
|
201 if (isset($lineTokens[$startLine]) === true) {
|
Chris@17
|
202 for ($i = $lineTokens[$startLine]['start']; $i <= $lineTokens[$endLine]['end']; $i++) {
|
Chris@17
|
203 $snippetLine = $tokens[$i]['line'];
|
Chris@17
|
204 if ($lineTokens[$snippetLine]['start'] === $i) {
|
Chris@17
|
205 // Starting a new line.
|
Chris@17
|
206 if ($snippetLine === $line) {
|
Chris@17
|
207 $snippet .= "\033[1m".'>> ';
|
Chris@17
|
208 } else {
|
Chris@17
|
209 $snippet .= ' ';
|
Chris@17
|
210 }
|
Chris@17
|
211
|
Chris@17
|
212 $snippet .= str_repeat(' ', ($maxLineNumLength - strlen($snippetLine)));
|
Chris@17
|
213 $snippet .= $snippetLine.': ';
|
Chris@17
|
214 if ($snippetLine === $line) {
|
Chris@17
|
215 $snippet .= "\033[0m";
|
Chris@17
|
216 }
|
Chris@17
|
217 }
|
Chris@17
|
218
|
Chris@17
|
219 if (isset($tokens[$i]['orig_content']) === true) {
|
Chris@17
|
220 $tokenContent = $tokens[$i]['orig_content'];
|
Chris@17
|
221 } else {
|
Chris@17
|
222 $tokenContent = $tokens[$i]['content'];
|
Chris@17
|
223 }
|
Chris@17
|
224
|
Chris@17
|
225 if (strpos($tokenContent, "\t") !== false) {
|
Chris@17
|
226 $token = $tokens[$i];
|
Chris@17
|
227 $token['content'] = $tokenContent;
|
Chris@17
|
228 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
Chris@17
|
229 $tab = "\000";
|
Chris@17
|
230 } else {
|
Chris@17
|
231 $tab = "\033[30;1m»\033[0m";
|
Chris@17
|
232 }
|
Chris@17
|
233
|
Chris@17
|
234 $phpcsFile->tokenizer->replaceTabsInToken($token, $tab, "\000");
|
Chris@17
|
235 $tokenContent = $token['content'];
|
Chris@17
|
236 }
|
Chris@17
|
237
|
Chris@17
|
238 $tokenContent = Util\Common::prepareForOutput($tokenContent, ["\r", "\n", "\t"]);
|
Chris@17
|
239 $tokenContent = str_replace("\000", ' ', $tokenContent);
|
Chris@17
|
240
|
Chris@17
|
241 $underline = false;
|
Chris@17
|
242 if ($snippetLine === $line && isset($lineErrors[$tokens[$i]['column']]) === true) {
|
Chris@17
|
243 $underline = true;
|
Chris@17
|
244 }
|
Chris@17
|
245
|
Chris@17
|
246 // Underline invisible characters as well.
|
Chris@17
|
247 if ($underline === true && trim($tokenContent) === '') {
|
Chris@17
|
248 $snippet .= "\033[4m".' '."\033[0m".$tokenContent;
|
Chris@17
|
249 } else {
|
Chris@17
|
250 if ($underline === true) {
|
Chris@17
|
251 $snippet .= "\033[4m";
|
Chris@17
|
252 }
|
Chris@17
|
253
|
Chris@17
|
254 $snippet .= $tokenContent;
|
Chris@17
|
255
|
Chris@17
|
256 if ($underline === true) {
|
Chris@17
|
257 $snippet .= "\033[0m";
|
Chris@17
|
258 }
|
Chris@17
|
259 }
|
Chris@17
|
260 }//end for
|
Chris@17
|
261 }//end if
|
Chris@17
|
262
|
Chris@17
|
263 echo str_repeat('-', $width).PHP_EOL;
|
Chris@17
|
264
|
Chris@17
|
265 foreach ($lineErrors as $column => $colErrors) {
|
Chris@17
|
266 foreach ($colErrors as $error) {
|
Chris@17
|
267 $padding = ($maxLineNumLength - strlen($line));
|
Chris@17
|
268 echo 'LINE '.str_repeat(' ', $padding).$line.': ';
|
Chris@17
|
269
|
Chris@17
|
270 if ($error['type'] === 'ERROR') {
|
Chris@17
|
271 echo "\033[31mERROR\033[0m";
|
Chris@17
|
272 if ($report['warnings'] > 0) {
|
Chris@17
|
273 echo ' ';
|
Chris@17
|
274 }
|
Chris@17
|
275 } else {
|
Chris@17
|
276 echo "\033[33mWARNING\033[0m";
|
Chris@17
|
277 }
|
Chris@17
|
278
|
Chris@17
|
279 echo ' ';
|
Chris@17
|
280 if ($report['fixable'] > 0) {
|
Chris@17
|
281 echo '[';
|
Chris@17
|
282 if ($error['fixable'] === true) {
|
Chris@17
|
283 echo 'x';
|
Chris@17
|
284 } else {
|
Chris@17
|
285 echo ' ';
|
Chris@17
|
286 }
|
Chris@17
|
287
|
Chris@17
|
288 echo '] ';
|
Chris@17
|
289 }
|
Chris@17
|
290
|
Chris@17
|
291 $message = $error['message'];
|
Chris@17
|
292 $message = str_replace("\n", "\n".$errorPadding, $message);
|
Chris@17
|
293 if ($showSources === true) {
|
Chris@17
|
294 $message = "\033[1m".$message."\033[0m".' ('.$error['source'].')';
|
Chris@17
|
295 }
|
Chris@17
|
296
|
Chris@17
|
297 $errorMsg = wordwrap(
|
Chris@17
|
298 $message,
|
Chris@17
|
299 $maxErrorSpace,
|
Chris@17
|
300 PHP_EOL.$errorPadding
|
Chris@17
|
301 );
|
Chris@17
|
302
|
Chris@17
|
303 echo $errorMsg.PHP_EOL;
|
Chris@17
|
304 }//end foreach
|
Chris@17
|
305 }//end foreach
|
Chris@17
|
306
|
Chris@17
|
307 echo str_repeat('-', $width).PHP_EOL;
|
Chris@17
|
308 echo rtrim($snippet).PHP_EOL;
|
Chris@17
|
309 }//end foreach
|
Chris@17
|
310
|
Chris@17
|
311 echo str_repeat('-', $width).PHP_EOL;
|
Chris@17
|
312 if ($report['fixable'] > 0) {
|
Chris@17
|
313 echo "\033[1m".'PHPCBF CAN FIX THE '.$report['fixable'].' MARKED SNIFF VIOLATIONS AUTOMATICALLY'."\033[0m".PHP_EOL;
|
Chris@17
|
314 echo str_repeat('-', $width).PHP_EOL;
|
Chris@17
|
315 }
|
Chris@17
|
316
|
Chris@17
|
317 return true;
|
Chris@17
|
318
|
Chris@17
|
319 }//end generateFileReport()
|
Chris@17
|
320
|
Chris@17
|
321
|
Chris@17
|
322 /**
|
Chris@17
|
323 * Prints all errors and warnings for each file processed.
|
Chris@17
|
324 *
|
Chris@17
|
325 * @param string $cachedData Any partial report data that was returned from
|
Chris@17
|
326 * generateFileReport during the run.
|
Chris@17
|
327 * @param int $totalFiles Total number of files processed during the run.
|
Chris@17
|
328 * @param int $totalErrors Total number of errors found during the run.
|
Chris@17
|
329 * @param int $totalWarnings Total number of warnings found during the run.
|
Chris@17
|
330 * @param int $totalFixable Total number of problems that can be fixed.
|
Chris@17
|
331 * @param bool $showSources Show sources?
|
Chris@17
|
332 * @param int $width Maximum allowed line width.
|
Chris@17
|
333 * @param bool $interactive Are we running in interactive mode?
|
Chris@17
|
334 * @param bool $toScreen Is the report being printed to screen?
|
Chris@17
|
335 *
|
Chris@17
|
336 * @return void
|
Chris@17
|
337 */
|
Chris@17
|
338 public function generate(
|
Chris@17
|
339 $cachedData,
|
Chris@17
|
340 $totalFiles,
|
Chris@17
|
341 $totalErrors,
|
Chris@17
|
342 $totalWarnings,
|
Chris@17
|
343 $totalFixable,
|
Chris@17
|
344 $showSources=false,
|
Chris@17
|
345 $width=80,
|
Chris@17
|
346 $interactive=false,
|
Chris@17
|
347 $toScreen=true
|
Chris@17
|
348 ) {
|
Chris@17
|
349 if ($cachedData === '') {
|
Chris@17
|
350 return;
|
Chris@17
|
351 }
|
Chris@17
|
352
|
Chris@17
|
353 echo $cachedData;
|
Chris@17
|
354
|
Chris@17
|
355 if ($toScreen === true && $interactive === false) {
|
Chris@17
|
356 Util\Timing::printRunTime();
|
Chris@17
|
357 }
|
Chris@17
|
358
|
Chris@17
|
359 }//end generate()
|
Chris@17
|
360
|
Chris@17
|
361
|
Chris@17
|
362 }//end class
|