comparison vendor/squizlabs/php_codesniffer/CodeSniffer/Fixer.php @ 0:c75dbcec494b

Initial commit from drush-created site
author Chris Cannam
date Thu, 05 Jul 2018 14:24:15 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:c75dbcec494b
1 <?php
2 /**
3 * A helper class for fixing errors.
4 *
5 * PHP version 5
6 *
7 * @category PHP
8 * @package PHP_CodeSniffer
9 * @author Greg Sherwood <gsherwood@squiz.net>
10 * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
11 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12 * @link http://pear.php.net/package/PHP_CodeSniffer
13 */
14
15 /**
16 * A helper class for fixing errors.
17 *
18 * Provides helper functions that act upon a token array and modify the file
19 * content.
20 *
21 * @category PHP
22 * @package PHP_CodeSniffer
23 * @author Greg Sherwood <gsherwood@squiz.net>
24 * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
25 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
26 * @version Release: @package_version@
27 * @link http://pear.php.net/package/PHP_CodeSniffer
28 */
29 class PHP_CodeSniffer_Fixer
30 {
31
32 /**
33 * Is the fixer enabled and fixing a file?
34 *
35 * Sniffs should check this value to ensure they are not
36 * doing extra processing to prepare for a fix when fixing is
37 * not required.
38 *
39 * @var boolean
40 */
41 public $enabled = false;
42
43 /**
44 * The number of times we have looped over a file.
45 *
46 * @var int
47 */
48 public $loops = 0;
49
50 /**
51 * The file being fixed.
52 *
53 * @var PHP_CodeSniffer_File
54 */
55 private $_currentFile = null;
56
57 /**
58 * The list of tokens that make up the file contents.
59 *
60 * This is a simplified list which just contains the token content and nothing
61 * else. This is the array that is updated as fixes are made, not the file's
62 * token array. Imploding this array will give you the file content back.
63 *
64 * @var array(int => string)
65 */
66 private $_tokens = array();
67
68 /**
69 * A list of tokens that have already been fixed.
70 *
71 * We don't allow the same token to be fixed more than once each time
72 * through a file as this can easily cause conflicts between sniffs.
73 *
74 * @var array(int)
75 */
76 private $_fixedTokens = array();
77
78 /**
79 * The last value of each fixed token.
80 *
81 * If a token is being "fixed" back to its last value, the fix is
82 * probably conflicting with another.
83 *
84 * @var array(int => string)
85 */
86 private $_oldTokenValues = array();
87
88 /**
89 * A list of tokens that have been fixed during a changeset.
90 *
91 * All changes in changeset must be able to be applied, or else
92 * the entire changeset is rejected.
93 *
94 * @var array()
95 */
96 private $_changeset = array();
97
98 /**
99 * Is there an open changeset.
100 *
101 * @var boolean
102 */
103 private $_inChangeset = false;
104
105 /**
106 * Is the current fixing loop in conflict?
107 *
108 * @var boolean
109 */
110 private $_inConflict = false;
111
112 /**
113 * The number of fixes that have been performed.
114 *
115 * @var int
116 */
117 private $_numFixes = 0;
118
119
120 /**
121 * Starts fixing a new file.
122 *
123 * @param PHP_CodeSniffer_File $phpcsFile The file being fixed.
124 *
125 * @return void
126 */
127 public function startFile($phpcsFile)
128 {
129 $this->_currentFile = $phpcsFile;
130 $this->_numFixes = 0;
131 $this->_fixedTokens = array();
132
133 $tokens = $phpcsFile->getTokens();
134 $this->_tokens = array();
135 foreach ($tokens as $index => $token) {
136 if (isset($token['orig_content']) === true) {
137 $this->_tokens[$index] = $token['orig_content'];
138 } else {
139 $this->_tokens[$index] = $token['content'];
140 }
141 }
142
143 }//end startFile()
144
145
146 /**
147 * Attempt to fix the file by processing it until no fixes are made.
148 *
149 * @return boolean
150 */
151 public function fixFile()
152 {
153 $fixable = $this->_currentFile->getFixableCount();
154 if ($fixable === 0) {
155 // Nothing to fix.
156 return false;
157 }
158
159 $stdin = false;
160 $cliValues = $this->_currentFile->phpcs->cli->getCommandLineValues();
161 if (empty($cliValues['files']) === true) {
162 $stdin = true;
163 }
164
165 $this->enabled = true;
166
167 $this->loops = 0;
168 while ($this->loops < 50) {
169 ob_start();
170
171 // Only needed once file content has changed.
172 $contents = $this->getContents();
173
174 if (PHP_CODESNIFFER_VERBOSITY > 2) {
175 @ob_end_clean();
176 echo '---START FILE CONTENT---'.PHP_EOL;
177 $lines = explode($this->_currentFile->eolChar, $contents);
178 $max = strlen(count($lines));
179 foreach ($lines as $lineNum => $line) {
180 $lineNum++;
181 echo str_pad($lineNum, $max, ' ', STR_PAD_LEFT).'|'.$line.PHP_EOL;
182 }
183
184 echo '--- END FILE CONTENT ---'.PHP_EOL;
185 ob_start();
186 }
187
188 $this->_inConflict = false;
189 $this->_currentFile->refreshTokenListeners();
190 $this->_currentFile->start($contents);
191 ob_end_clean();
192
193 $this->loops++;
194
195 if (PHP_CODESNIFFER_CBF === true && $stdin === false) {
196 echo "\r".str_repeat(' ', 80)."\r";
197 echo "\t=> Fixing file: $this->_numFixes/$fixable violations remaining [made $this->loops pass";
198 if ($this->loops > 1) {
199 echo 'es';
200 }
201
202 echo ']... ';
203 }
204
205 if ($this->_numFixes === 0 && $this->_inConflict === false) {
206 // Nothing left to do.
207 break;
208 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
209 echo "\t* fixed $this->_numFixes violations, starting loop ".($this->loops + 1).' *'.PHP_EOL;
210 }
211 }//end while
212
213 $this->enabled = false;
214
215 if ($this->_numFixes > 0) {
216 if (PHP_CODESNIFFER_VERBOSITY > 1) {
217 @ob_end_clean();
218 echo "\t*** Reached maximum number of loops with $this->_numFixes violations left unfixed ***".PHP_EOL;
219 ob_start();
220 }
221
222 return false;
223 }
224
225 return true;
226
227 }//end fixFile()
228
229
230 /**
231 * Generates a text diff of the original file and the new content.
232 *
233 * @param string $filePath Optional file path to diff the file against.
234 * If not specified, the original version of the
235 * file will be used.
236 * @param boolean $colors Print colored output or not.
237 *
238 * @return string
239 */
240 public function generateDiff($filePath=null, $colors=true)
241 {
242 if ($filePath === null) {
243 $filePath = $this->_currentFile->getFilename();
244 }
245
246 $cwd = getcwd().DIRECTORY_SEPARATOR;
247 if (strpos($filePath, $cwd) === 0) {
248 $filename = substr($filePath, strlen($cwd));
249 } else {
250 $filename = $filePath;
251 }
252
253 $contents = $this->getContents();
254
255 if (function_exists('sys_get_temp_dir') === true) {
256 // This is needed for HHVM support, but only available from 5.2.1.
257 $tempName = tempnam(sys_get_temp_dir(), 'phpcs-fixer');
258 $fixedFile = fopen($tempName, 'w');
259 } else {
260 $fixedFile = tmpfile();
261 $data = stream_get_meta_data($fixedFile);
262 $tempName = $data['uri'];
263 }
264
265 fwrite($fixedFile, $contents);
266
267 // We must use something like shell_exec() because whitespace at the end
268 // of lines is critical to diff files.
269 $filename = escapeshellarg($filename);
270 $cmd = "diff -u -L$filename -LPHP_CodeSniffer $filename \"$tempName\"";
271
272 $diff = shell_exec($cmd);
273
274 fclose($fixedFile);
275 if (is_file($tempName) === true) {
276 unlink($tempName);
277 }
278
279 if ($colors === false) {
280 return $diff;
281 }
282
283 $diffLines = explode(PHP_EOL, $diff);
284 if (count($diffLines) === 1) {
285 // Seems to be required for cygwin.
286 $diffLines = explode("\n", $diff);
287 }
288
289 $diff = array();
290 foreach ($diffLines as $line) {
291 if (isset($line[0]) === true) {
292 switch ($line[0]) {
293 case '-':
294 $diff[] = "\033[31m$line\033[0m";
295 break;
296 case '+':
297 $diff[] = "\033[32m$line\033[0m";
298 break;
299 default:
300 $diff[] = $line;
301 }
302 }
303 }
304
305 $diff = implode(PHP_EOL, $diff);
306
307 return $diff;
308
309 }//end generateDiff()
310
311
312 /**
313 * Get a count of fixes that have been performed on the file.
314 *
315 * This value is reset every time a new file is started, or an existing
316 * file is restarted.
317 *
318 * @return int
319 */
320 public function getFixCount()
321 {
322 return $this->_numFixes;
323
324 }//end getFixCount()
325
326
327 /**
328 * Get the current content of the file, as a string.
329 *
330 * @return string
331 */
332 public function getContents()
333 {
334 $contents = implode($this->_tokens);
335 return $contents;
336
337 }//end getContents()
338
339
340 /**
341 * Get the current fixed content of a token.
342 *
343 * This function takes changesets into account so should be used
344 * instead of directly accessing the token array.
345 *
346 * @param int $stackPtr The position of the token in the token stack.
347 *
348 * @return string
349 */
350 public function getTokenContent($stackPtr)
351 {
352 if ($this->_inChangeset === true
353 && isset($this->_changeset[$stackPtr]) === true
354 ) {
355 return $this->_changeset[$stackPtr];
356 } else {
357 return $this->_tokens[$stackPtr];
358 }
359
360 }//end getTokenContent()
361
362
363 /**
364 * Start recording actions for a changeset.
365 *
366 * @return void
367 */
368 public function beginChangeset()
369 {
370 if ($this->_inConflict === true) {
371 return false;
372 }
373
374 if (PHP_CODESNIFFER_VERBOSITY > 1) {
375 $bt = debug_backtrace();
376 $sniff = $bt[1]['class'];
377 $line = $bt[0]['line'];
378
379 @ob_end_clean();
380 echo "\t=> Changeset started by $sniff (line $line)".PHP_EOL;
381 ob_start();
382 }
383
384 $this->_changeset = array();
385 $this->_inChangeset = true;
386
387 }//end beginChangeset()
388
389
390 /**
391 * Stop recording actions for a changeset, and apply logged changes.
392 *
393 * @return boolean
394 */
395 public function endChangeset()
396 {
397 if ($this->_inConflict === true) {
398 return false;
399 }
400
401 $this->_inChangeset = false;
402
403 $success = true;
404 $applied = array();
405 foreach ($this->_changeset as $stackPtr => $content) {
406 $success = $this->replaceToken($stackPtr, $content);
407 if ($success === false) {
408 break;
409 } else {
410 $applied[] = $stackPtr;
411 }
412 }
413
414 if ($success === false) {
415 // Rolling back all changes.
416 foreach ($applied as $stackPtr) {
417 $this->revertToken($stackPtr);
418 }
419
420 if (PHP_CODESNIFFER_VERBOSITY > 1) {
421 @ob_end_clean();
422 echo "\t=> Changeset failed to apply".PHP_EOL;
423 ob_start();
424 }
425 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
426 $fixes = count($this->_changeset);
427 @ob_end_clean();
428 echo "\t=> Changeset ended: $fixes changes applied".PHP_EOL;
429 ob_start();
430 }
431
432 $this->_changeset = array();
433
434 }//end endChangeset()
435
436
437 /**
438 * Stop recording actions for a changeset, and discard logged changes.
439 *
440 * @return void
441 */
442 public function rollbackChangeset()
443 {
444 $this->_inChangeset = false;
445 $this->_inConflict = false;
446
447 if (empty($this->_changeset) === false) {
448 if (PHP_CODESNIFFER_VERBOSITY > 1) {
449 $bt = debug_backtrace();
450 if ($bt[1]['class'] === 'PHP_CodeSniffer_Fixer') {
451 $sniff = $bt[2]['class'];
452 $line = $bt[1]['line'];
453 } else {
454 $sniff = $bt[1]['class'];
455 $line = $bt[0]['line'];
456 }
457
458 $numChanges = count($this->_changeset);
459
460 @ob_end_clean();
461 echo "\t\tR: $sniff (line $line) rolled back the changeset ($numChanges changes)".PHP_EOL;
462 echo "\t=> Changeset rolled back".PHP_EOL;
463 ob_start();
464 }
465
466 $this->_changeset = array();
467 }//end if
468
469 }//end rollbackChangeset()
470
471
472 /**
473 * Replace the entire contents of a token.
474 *
475 * @param int $stackPtr The position of the token in the token stack.
476 * @param string $content The new content of the token.
477 *
478 * @return bool If the change was accepted.
479 */
480 public function replaceToken($stackPtr, $content)
481 {
482 if ($this->_inConflict === true) {
483 return false;
484 }
485
486 if ($this->_inChangeset === false
487 && isset($this->_fixedTokens[$stackPtr]) === true
488 ) {
489 $indent = "\t";
490 if (empty($this->_changeset) === false) {
491 $indent .= "\t";
492 }
493
494 if (PHP_CODESNIFFER_VERBOSITY > 1) {
495 @ob_end_clean();
496 echo "$indent* token $stackPtr has already been modified, skipping *".PHP_EOL;
497 ob_start();
498 }
499
500 return false;
501 }
502
503 if (PHP_CODESNIFFER_VERBOSITY > 1) {
504 $bt = debug_backtrace();
505 if ($bt[1]['class'] === 'PHP_CodeSniffer_Fixer') {
506 $sniff = $bt[2]['class'];
507 $line = $bt[1]['line'];
508 } else {
509 $sniff = $bt[1]['class'];
510 $line = $bt[0]['line'];
511 }
512
513 $tokens = $this->_currentFile->getTokens();
514 $type = $tokens[$stackPtr]['type'];
515 $oldContent = PHP_CodeSniffer::prepareForOutput($this->_tokens[$stackPtr]);
516 $newContent = PHP_CodeSniffer::prepareForOutput($content);
517 if (trim($this->_tokens[$stackPtr]) === '' && isset($this->_tokens[($stackPtr + 1)]) === true) {
518 // Add some context for whitespace only changes.
519 $append = PHP_CodeSniffer::prepareForOutput($this->_tokens[($stackPtr + 1)]);
520 $oldContent .= $append;
521 $newContent .= $append;
522 }
523 }//end if
524
525 if ($this->_inChangeset === true) {
526 $this->_changeset[$stackPtr] = $content;
527
528 if (PHP_CODESNIFFER_VERBOSITY > 1) {
529 @ob_end_clean();
530 echo "\t\tQ: $sniff (line $line) replaced token $stackPtr ($type) \"$oldContent\" => \"$newContent\"".PHP_EOL;
531 ob_start();
532 }
533
534 return true;
535 }
536
537 if (isset($this->_oldTokenValues[$stackPtr]) === false) {
538 $this->_oldTokenValues[$stackPtr] = array(
539 'curr' => $content,
540 'prev' => $this->_tokens[$stackPtr],
541 'loop' => $this->loops,
542 );
543 } else {
544 if ($this->_oldTokenValues[$stackPtr]['prev'] === $content
545 && $this->_oldTokenValues[$stackPtr]['loop'] === ($this->loops - 1)
546 ) {
547 if (PHP_CODESNIFFER_VERBOSITY > 1) {
548 $indent = "\t";
549 if (empty($this->_changeset) === false) {
550 $indent .= "\t";
551 }
552
553 $loop = $this->_oldTokenValues[$stackPtr]['loop'];
554
555 @ob_end_clean();
556 echo "$indent**** $sniff (line $line) has possible conflict with another sniff on loop $loop; caused by the following change ****".PHP_EOL;
557 echo "$indent**** replaced token $stackPtr ($type) \"$oldContent\" => \"$newContent\" ****".PHP_EOL;
558 }
559
560 if ($this->_oldTokenValues[$stackPtr]['loop'] >= ($this->loops - 1)) {
561 $this->_inConflict = true;
562 if (PHP_CODESNIFFER_VERBOSITY > 1) {
563 echo "$indent**** ignoring all changes until next loop ****".PHP_EOL;
564 }
565 }
566
567 if (PHP_CODESNIFFER_VERBOSITY > 1) {
568 ob_start();
569 }
570
571 return false;
572 }//end if
573
574 $this->_oldTokenValues[$stackPtr]['prev'] = $this->_oldTokenValues[$stackPtr]['curr'];
575 $this->_oldTokenValues[$stackPtr]['curr'] = $content;
576 $this->_oldTokenValues[$stackPtr]['loop'] = $this->loops;
577 }//end if
578
579 $this->_fixedTokens[$stackPtr] = $this->_tokens[$stackPtr];
580 $this->_tokens[$stackPtr] = $content;
581 $this->_numFixes++;
582
583 if (PHP_CODESNIFFER_VERBOSITY > 1) {
584 $indent = "\t";
585 if (empty($this->_changeset) === false) {
586 $indent .= "\tA: ";
587 }
588
589 @ob_end_clean();
590 echo "$indent$sniff (line $line) replaced token $stackPtr ($type) \"$oldContent\" => \"$newContent\"".PHP_EOL;
591 ob_start();
592 }
593
594 return true;
595
596 }//end replaceToken()
597
598
599 /**
600 * Reverts the previous fix made to a token.
601 *
602 * @param int $stackPtr The position of the token in the token stack.
603 *
604 * @return bool If a change was reverted.
605 */
606 public function revertToken($stackPtr)
607 {
608 if (isset($this->_fixedTokens[$stackPtr]) === false) {
609 return false;
610 }
611
612 if (PHP_CODESNIFFER_VERBOSITY > 1) {
613 $bt = debug_backtrace();
614 if ($bt[1]['class'] === 'PHP_CodeSniffer_Fixer') {
615 $sniff = $bt[2]['class'];
616 $line = $bt[1]['line'];
617 } else {
618 $sniff = $bt[1]['class'];
619 $line = $bt[0]['line'];
620 }
621
622 $tokens = $this->_currentFile->getTokens();
623 $type = $tokens[$stackPtr]['type'];
624 $oldContent = PHP_CodeSniffer::prepareForOutput($this->_tokens[$stackPtr]);
625 $newContent = PHP_CodeSniffer::prepareForOutput($this->_fixedTokens[$stackPtr]);
626 if (trim($this->_tokens[$stackPtr]) === '' && isset($tokens[($stackPtr + 1)]) === true) {
627 // Add some context for whitespace only changes.
628 $append = PHP_CodeSniffer::prepareForOutput($this->_tokens[($stackPtr + 1)]);
629 $oldContent .= $append;
630 $newContent .= $append;
631 }
632 }//end if
633
634 $this->_tokens[$stackPtr] = $this->_fixedTokens[$stackPtr];
635 unset($this->_fixedTokens[$stackPtr]);
636 $this->_numFixes--;
637
638 if (PHP_CODESNIFFER_VERBOSITY > 1) {
639 $indent = "\t";
640 if (empty($this->_changeset) === false) {
641 $indent .= "\tR: ";
642 }
643
644 @ob_end_clean();
645 echo "$indent$sniff (line $line) reverted token $stackPtr ($type) \"$oldContent\" => \"$newContent\"".PHP_EOL;
646 ob_start();
647 }
648
649 return true;
650
651 }//end revertToken()
652
653
654 /**
655 * Replace the content of a token with a part of its current content.
656 *
657 * @param int $stackPtr The position of the token in the token stack.
658 * @param int $start The first character to keep.
659 * @param int $length The number of chacters to keep. If NULL, the content of
660 * the token from $start to the end of the content is kept.
661 *
662 * @return bool If the change was accepted.
663 */
664 public function substrToken($stackPtr, $start, $length=null)
665 {
666 $current = $this->getTokenContent($stackPtr);
667
668 if ($length === null) {
669 $newContent = substr($current, $start);
670 } else {
671 $newContent = substr($current, $start, $length);
672 }
673
674 return $this->replaceToken($stackPtr, $newContent);
675
676 }//end substrToken()
677
678
679 /**
680 * Adds a newline to end of a token's content.
681 *
682 * @param int $stackPtr The position of the token in the token stack.
683 *
684 * @return bool If the change was accepted.
685 */
686 public function addNewline($stackPtr)
687 {
688 $current = $this->getTokenContent($stackPtr);
689 return $this->replaceToken($stackPtr, $current.$this->_currentFile->eolChar);
690
691 }//end addNewline()
692
693
694 /**
695 * Adds a newline to the start of a token's content.
696 *
697 * @param int $stackPtr The position of the token in the token stack.
698 *
699 * @return bool If the change was accepted.
700 */
701 public function addNewlineBefore($stackPtr)
702 {
703 $current = $this->getTokenContent($stackPtr);
704 return $this->replaceToken($stackPtr, $this->_currentFile->eolChar.$current);
705
706 }//end addNewlineBefore()
707
708
709 /**
710 * Adds content to the end of a token's current content.
711 *
712 * @param int $stackPtr The position of the token in the token stack.
713 * @param string $content The content to add.
714 *
715 * @return bool If the change was accepted.
716 */
717 public function addContent($stackPtr, $content)
718 {
719 $current = $this->getTokenContent($stackPtr);
720 return $this->replaceToken($stackPtr, $current.$content);
721
722 }//end addContent()
723
724
725 /**
726 * Adds content to the start of a token's current content.
727 *
728 * @param int $stackPtr The position of the token in the token stack.
729 * @param string $content The content to add.
730 *
731 * @return bool If the change was accepted.
732 */
733 public function addContentBefore($stackPtr, $content)
734 {
735 $current = $this->getTokenContent($stackPtr);
736 return $this->replaceToken($stackPtr, $content.$current);
737
738 }//end addContentBefore()
739
740
741 }//end class