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