• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 14512015305

17 Apr 2025 09:00AM UTC coverage: 77.943% (+0.3%) from 77.666%
14512015305

Pull #1010

github

web-flow
Merge c3fc31683 into 27a270068
Pull Request #1010: (Nearly) All status, debug, and progress output is now sent to STDERR instead of STDOUT

57 of 450 new or added lines in 18 files covered. (12.67%)

1 existing line in 1 file now uncovered.

19449 of 24953 relevant lines covered (77.94%)

78.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

17.05
/src/Fixer.php
1
<?php
2
/**
3
 * A helper class for fixing errors.
4
 *
5
 * Provides helper functions that act upon a token array and modify the file
6
 * content.
7
 *
8
 * @author    Greg Sherwood <gsherwood@squiz.net>
9
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
10
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
11
 */
12

13
namespace PHP_CodeSniffer;
14

15
use InvalidArgumentException;
16
use PHP_CodeSniffer\Exceptions\RuntimeException;
17
use PHP_CodeSniffer\Files\File;
18
use PHP_CodeSniffer\Util\Common;
19
use PHP_CodeSniffer\Util\Writers\StatusWriter;
20

21
class Fixer
22
{
23

24
    /**
25
     * Is the fixer enabled and fixing a file?
26
     *
27
     * Sniffs should check this value to ensure they are not
28
     * doing extra processing to prepare for a fix when fixing is
29
     * not required.
30
     *
31
     * @var boolean
32
     */
33
    public $enabled = false;
34

35
    /**
36
     * The number of times we have looped over a file.
37
     *
38
     * @var integer
39
     */
40
    public $loops = 0;
41

42
    /**
43
     * The file being fixed.
44
     *
45
     * @var \PHP_CodeSniffer\Files\File
46
     */
47
    private $currentFile = null;
48

49
    /**
50
     * The list of tokens that make up the file contents.
51
     *
52
     * This is a simplified list which just contains the token content and nothing
53
     * else. This is the array that is updated as fixes are made, not the file's
54
     * token array. Imploding this array will give you the file content back.
55
     *
56
     * @var array<int, string>
57
     */
58
    private $tokens = [];
59

60
    /**
61
     * A list of tokens that have already been fixed.
62
     *
63
     * We don't allow the same token to be fixed more than once each time
64
     * through a file as this can easily cause conflicts between sniffs.
65
     *
66
     * @var int[]
67
     */
68
    private $fixedTokens = [];
69

70
    /**
71
     * The last value of each fixed token.
72
     *
73
     * If a token is being "fixed" back to its last value, the fix is
74
     * probably conflicting with another.
75
     *
76
     * @var array<int, array<string, mixed>>
77
     */
78
    private $oldTokenValues = [];
79

80
    /**
81
     * A list of tokens that have been fixed during a changeset.
82
     *
83
     * All changes in changeset must be able to be applied, or else
84
     * the entire changeset is rejected.
85
     *
86
     * @var array
87
     */
88
    private $changeset = [];
89

90
    /**
91
     * Is there an open changeset.
92
     *
93
     * @var boolean
94
     */
95
    private $inChangeset = false;
96

97
    /**
98
     * Is the current fixing loop in conflict?
99
     *
100
     * @var boolean
101
     */
102
    private $inConflict = false;
103

104
    /**
105
     * The number of fixes that have been performed.
106
     *
107
     * @var integer
108
     */
109
    private $numFixes = 0;
110

111

112
    /**
113
     * Starts fixing a new file.
114
     *
115
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being fixed.
116
     *
117
     * @return void
118
     */
119
    public function startFile(File $phpcsFile)
×
120
    {
121
        $this->currentFile = $phpcsFile;
×
122
        $this->numFixes    = 0;
×
123
        $this->fixedTokens = [];
×
124

125
        $tokens       = $phpcsFile->getTokens();
×
126
        $this->tokens = [];
×
127
        foreach ($tokens as $index => $token) {
×
128
            if (isset($token['orig_content']) === true) {
×
129
                $this->tokens[$index] = $token['orig_content'];
×
130
            } else {
131
                $this->tokens[$index] = $token['content'];
×
132
            }
133
        }
134

135
    }//end startFile()
136

137

138
    /**
139
     * Attempt to fix the file by processing it until no fixes are made.
140
     *
141
     * @return boolean
142
     */
143
    public function fixFile()
×
144
    {
145
        $fixable = $this->currentFile->getFixableCount();
×
146
        if ($fixable === 0) {
×
147
            // Nothing to fix.
148
            return false;
×
149
        }
150

151
        $this->enabled = true;
×
152

NEW
153
        StatusWriter::pause();
×
154

155
        $this->loops = 0;
×
156
        while ($this->loops < 50) {
×
157
            ob_start();
×
158

159
            // Only needed once file content has changed.
160
            $contents = $this->getContents();
×
161

162
            if (PHP_CODESNIFFER_VERBOSITY > 2) {
×
163
                @ob_end_clean();
×
NEW
164
                StatusWriter::forceWrite('---START FILE CONTENT---');
×
165
                $lines = explode($this->currentFile->eolChar, $contents);
×
166
                $max   = strlen(count($lines));
×
167
                foreach ($lines as $lineNum => $line) {
×
168
                    $lineNum++;
×
NEW
169
                    StatusWriter::forceWrite(str_pad($lineNum, $max, ' ', STR_PAD_LEFT).'|'.$line);
×
170
                }
171

NEW
172
                StatusWriter::forceWrite('--- END FILE CONTENT ---');
×
173
                ob_start();
×
174
            }
175

176
            $this->inConflict = false;
×
177
            $this->currentFile->ruleset->populateTokenListeners();
×
178
            $this->currentFile->setContent($contents);
×
179
            $this->currentFile->process();
×
180
            ob_end_clean();
×
181

182
            $this->loops++;
×
183

184
            if (PHP_CODESNIFFER_CBF === true && PHP_CODESNIFFER_VERBOSITY > 0) {
×
NEW
185
                StatusWriter::forceWrite("\r".str_repeat(' ', 80)."\r", 0, 0);
×
NEW
186
                $statusMessage = "=> Fixing file: $this->numFixes/$fixable violations remaining [made $this->loops pass";
×
187
                if ($this->loops > 1) {
×
NEW
188
                    $statusMessage .= 'es';
×
189
                }
190

NEW
191
                $statusMessage .= ']... ';
×
NEW
192
                $newlines       = 0;
×
193
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
194
                    $newlines = 1;
×
195
                }
196

NEW
197
                StatusWriter::forceWrite($statusMessage, 1, $newlines);
×
198
            }
199

200
            if ($this->numFixes === 0 && $this->inConflict === false) {
×
201
                // Nothing left to do.
202
                break;
×
203
            } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
204
                StatusWriter::forceWrite("* fixed $this->numFixes violations, starting loop ".($this->loops + 1).' *', 1);
×
205
            }
206
        }//end while
207

208
        $this->enabled = false;
×
209

NEW
210
        StatusWriter::resume();
×
211

212
        if ($this->numFixes > 0) {
×
213
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
214
                if (ob_get_level() > 0) {
×
215
                    ob_end_clean();
×
216
                }
217

NEW
218
                StatusWriter::write("*** Reached maximum number of loops with $this->numFixes violations left unfixed ***", 1);
×
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 coloured output or not.
237
     *
238
     * @return string
239
     *
240
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException When the diff command fails.
241
     */
242
    public function generateDiff($filePath=null, $colors=true)
65✔
243
    {
244
        if ($filePath === null) {
65✔
245
            $filePath = $this->currentFile->getFilename();
5✔
246
        }
247

248
        $cwd = getcwd().DIRECTORY_SEPARATOR;
65✔
249
        if (strpos($filePath, $cwd) === 0) {
65✔
250
            $filename = substr($filePath, strlen($cwd));
65✔
251
        } else {
252
            $filename = $filePath;
×
253
        }
254

255
        $contents = $this->getContents();
65✔
256

257
        $tempName  = tempnam(sys_get_temp_dir(), 'phpcs-fixer');
65✔
258
        $fixedFile = fopen($tempName, 'w');
65✔
259
        fwrite($fixedFile, $contents);
65✔
260

261
        // We must use something like shell_exec() or proc_open() because whitespace at the end
262
        // of lines is critical to diff files.
263
        // Using proc_open() instead of shell_exec improves performance on Windows significantly,
264
        // while the results are the same (though more code is needed to get the results).
265
        // This is specifically due to proc_open allowing to set the "bypass_shell" option.
266
        $filename = escapeshellarg($filename);
65✔
267
        $cmd      = "diff -u -L$filename -LPHP_CodeSniffer $filename \"$tempName\"";
65✔
268

269
        // Stream 0 = STDIN, 1 = STDOUT, 2 = STDERR.
270
        $descriptorspec = [
39✔
271
            0 => [
65✔
272
                'pipe',
39✔
273
                'r',
39✔
274
            ],
39✔
275
            1 => [
39✔
276
                'pipe',
39✔
277
                'w',
39✔
278
            ],
39✔
279
            2 => [
39✔
280
                'pipe',
39✔
281
                'w',
39✔
282
            ],
39✔
283
        ];
39✔
284

285
        $options = null;
65✔
286
        if (stripos(PHP_OS, 'WIN') === 0) {
65✔
287
            $options = ['bypass_shell' => true];
26✔
288
        }
289

290
        $process = proc_open($cmd, $descriptorspec, $pipes, $cwd, null, $options);
65✔
291
        if (is_resource($process) === false) {
65✔
292
            throw new RuntimeException('Could not obtain a resource to execute the diff command.');
×
293
        }
294

295
        // We don't need these.
296
        fclose($pipes[0]);
65✔
297
        fclose($pipes[2]);
65✔
298

299
        // Stdout will contain the actual diff.
300
        $diff = stream_get_contents($pipes[1]);
65✔
301
        fclose($pipes[1]);
65✔
302

303
        proc_close($process);
65✔
304

305
        fclose($fixedFile);
65✔
306
        if (is_file($tempName) === true) {
65✔
307
            unlink($tempName);
65✔
308
        }
309

310
        if ($diff === false || $diff === '') {
65✔
311
            return '';
10✔
312
        }
313

314
        if ($colors === false) {
55✔
315
            return $diff;
50✔
316
        }
317

318
        $diffLines = explode(PHP_EOL, $diff);
5✔
319
        if (count($diffLines) === 1) {
5✔
320
            // Seems to be required for cygwin.
321
            $diffLines = explode("\n", $diff);
2✔
322
        }
323

324
        $diff = [];
5✔
325
        foreach ($diffLines as $line) {
5✔
326
            if (isset($line[0]) === true) {
5✔
327
                switch ($line[0]) {
5✔
328
                case '-':
5✔
329
                    $diff[] = "\033[31m$line\033[0m";
5✔
330
                    break;
5✔
331
                case '+':
5✔
332
                    $diff[] = "\033[32m$line\033[0m";
5✔
333
                    break;
5✔
334
                default:
335
                    $diff[] = $line;
5✔
336
                }
337
            }
338
        }
339

340
        $diff = implode(PHP_EOL, $diff);
5✔
341

342
        return $diff;
5✔
343

344
    }//end generateDiff()
345

346

347
    /**
348
     * Get a count of fixes that have been performed on the file.
349
     *
350
     * This value is reset every time a new file is started, or an existing
351
     * file is restarted.
352
     *
353
     * @return int
354
     */
355
    public function getFixCount()
×
356
    {
357
        return $this->numFixes;
×
358

359
    }//end getFixCount()
360

361

362
    /**
363
     * Get the current content of the file, as a string.
364
     *
365
     * @return string
366
     */
367
    public function getContents()
×
368
    {
369
        $contents = implode($this->tokens);
×
370
        return $contents;
×
371

372
    }//end getContents()
373

374

375
    /**
376
     * Get the current fixed content of a token.
377
     *
378
     * This function takes changesets into account so should be used
379
     * instead of directly accessing the token array.
380
     *
381
     * @param int $stackPtr The position of the token in the token stack.
382
     *
383
     * @return string
384
     */
385
    public function getTokenContent($stackPtr)
×
386
    {
387
        if ($this->inChangeset === true
×
388
            && isset($this->changeset[$stackPtr]) === true
×
389
        ) {
390
            return $this->changeset[$stackPtr];
×
391
        } else {
392
            return $this->tokens[$stackPtr];
×
393
        }
394

395
    }//end getTokenContent()
396

397

398
    /**
399
     * Start recording actions for a changeset.
400
     *
401
     * @return void|false
402
     */
403
    public function beginChangeset()
×
404
    {
405
        if ($this->inConflict === true) {
×
406
            return false;
×
407
        }
408

409
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
410
            $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
×
411
            if ($bt[1]['class'] === __CLASS__) {
×
412
                $sniff = 'Fixer';
×
413
            } else {
414
                $sniff = $this->getSniffCodeForDebug($bt[1]['class']);
×
415
            }
416

417
            $line = $bt[0]['line'];
×
418

419
            @ob_end_clean();
×
NEW
420
            StatusWriter::forceWrite("=> Changeset started by $sniff:$line", 1);
×
421
            ob_start();
×
422
        }
423

424
        $this->changeset   = [];
×
425
        $this->inChangeset = true;
×
426

427
    }//end beginChangeset()
428

429

430
    /**
431
     * Stop recording actions for a changeset, and apply logged changes.
432
     *
433
     * @return boolean
434
     */
435
    public function endChangeset()
×
436
    {
437
        if ($this->inConflict === true) {
×
438
            return false;
×
439
        }
440

441
        $this->inChangeset = false;
×
442

443
        $success = true;
×
444
        $applied = [];
×
445
        foreach ($this->changeset as $stackPtr => $content) {
×
446
            $success = $this->replaceToken($stackPtr, $content);
×
447
            if ($success === false) {
×
448
                break;
×
449
            } else {
450
                $applied[] = $stackPtr;
×
451
            }
452
        }
453

454
        if ($success === false) {
×
455
            // Rolling back all changes.
456
            foreach ($applied as $stackPtr) {
×
457
                $this->revertToken($stackPtr);
×
458
            }
459

460
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
461
                @ob_end_clean();
×
NEW
462
                StatusWriter::forceWrite('=> Changeset failed to apply', 1);
×
463
                ob_start();
×
464
            }
465
        } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
466
            $fixes = count($this->changeset);
×
467
            @ob_end_clean();
×
NEW
468
            StatusWriter::forceWrite("=> Changeset ended: $fixes changes applied", 1);
×
469
            ob_start();
×
470
        }
471

472
        $this->changeset = [];
×
473
        return true;
×
474

475
    }//end endChangeset()
476

477

478
    /**
479
     * Stop recording actions for a changeset, and discard logged changes.
480
     *
481
     * @return void
482
     */
483
    public function rollbackChangeset()
×
484
    {
485
        $this->inChangeset = false;
×
486
        $this->inConflict  = false;
×
487

488
        if (empty($this->changeset) === false) {
×
489
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
490
                $bt = debug_backtrace();
×
491
                if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
×
492
                    $sniff = $bt[2]['class'];
×
493
                    $line  = $bt[1]['line'];
×
494
                } else {
495
                    $sniff = $bt[1]['class'];
×
496
                    $line  = $bt[0]['line'];
×
497
                }
498

499
                $sniff = $this->getSniffCodeForDebug($sniff);
×
500

501
                $numChanges = count($this->changeset);
×
502

503
                @ob_end_clean();
×
NEW
504
                StatusWriter::forceWrite("R: $sniff:$line rolled back the changeset ($numChanges changes)", 2);
×
NEW
505
                StatusWriter::forceWrite('=> Changeset rolled back', 1);
×
UNCOV
506
                ob_start();
×
507
            }
508

509
            $this->changeset = [];
×
510
        }//end if
511

512
    }//end rollbackChangeset()
513

514

515
    /**
516
     * Replace the entire contents of a token.
517
     *
518
     * @param int    $stackPtr The position of the token in the token stack.
519
     * @param string $content  The new content of the token.
520
     *
521
     * @return bool If the change was accepted.
522
     */
523
    public function replaceToken($stackPtr, $content)
×
524
    {
525
        if ($this->inConflict === true) {
×
526
            return false;
×
527
        }
528

529
        if ($this->inChangeset === false
×
530
            && isset($this->fixedTokens[$stackPtr]) === true
×
531
        ) {
NEW
532
            $depth = 1;
×
533
            if (empty($this->changeset) === false) {
×
NEW
534
                $depth = 2;
×
535
            }
536

537
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
538
                @ob_end_clean();
×
NEW
539
                StatusWriter::forceWrite("* token $stackPtr has already been modified, skipping *", $depth);
×
540
                ob_start();
×
541
            }
542

543
            return false;
×
544
        }
545

546
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
547
            $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
×
548
            if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
×
549
                $sniff = $bt[2]['class'];
×
550
                $line  = $bt[1]['line'];
×
551
            } else {
552
                $sniff = $bt[1]['class'];
×
553
                $line  = $bt[0]['line'];
×
554
            }
555

556
            $sniff = $this->getSniffCodeForDebug($sniff);
×
557

558
            $tokens     = $this->currentFile->getTokens();
×
559
            $type       = $tokens[$stackPtr]['type'];
×
560
            $tokenLine  = $tokens[$stackPtr]['line'];
×
561
            $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]);
×
562
            $newContent = Common::prepareForOutput($content);
×
563
            if (trim($this->tokens[$stackPtr]) === '' && isset($this->tokens[($stackPtr + 1)]) === true) {
×
564
                // Add some context for whitespace only changes.
565
                $append      = Common::prepareForOutput($this->tokens[($stackPtr + 1)]);
×
566
                $oldContent .= $append;
×
567
                $newContent .= $append;
×
568
            }
569
        }//end if
570

571
        if ($this->inChangeset === true) {
×
572
            $this->changeset[$stackPtr] = $content;
×
573

574
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
575
                @ob_end_clean();
×
NEW
576
                StatusWriter::forceWrite("Q: $sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"", 2);
×
577
                ob_start();
×
578
            }
579

580
            return true;
×
581
        }
582

583
        if (isset($this->oldTokenValues[$stackPtr]) === false) {
×
584
            $this->oldTokenValues[$stackPtr] = [
×
585
                'curr' => $content,
×
586
                'prev' => $this->tokens[$stackPtr],
×
587
                'loop' => $this->loops,
×
588
            ];
589
        } else {
590
            if ($this->oldTokenValues[$stackPtr]['prev'] === $content
×
591
                && $this->oldTokenValues[$stackPtr]['loop'] === ($this->loops - 1)
×
592
            ) {
593
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
594
                    $depth = 1;
×
595
                    if (empty($this->changeset) === false) {
×
NEW
596
                        $depth = 2;
×
597
                    }
598

599
                    $loop = $this->oldTokenValues[$stackPtr]['loop'];
×
600

601
                    @ob_end_clean();
×
NEW
602
                    StatusWriter::forceWrite("**** $sniff:$line has possible conflict with another sniff on loop $loop; caused by the following change ****", $depth);
×
NEW
603
                    StatusWriter::forceWrite("**** replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\" ****", $depth);
×
604
                }
605

606
                if ($this->oldTokenValues[$stackPtr]['loop'] >= ($this->loops - 1)) {
×
607
                    $this->inConflict = true;
×
608
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
609
                        StatusWriter::forceWrite('**** ignoring all changes until next loop ****', $depth);
×
610
                    }
611
                }
612

613
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
614
                    ob_start();
×
615
                }
616

617
                return false;
×
618
            }//end if
619

620
            $this->oldTokenValues[$stackPtr]['prev'] = $this->oldTokenValues[$stackPtr]['curr'];
×
621
            $this->oldTokenValues[$stackPtr]['curr'] = $content;
×
622
            $this->oldTokenValues[$stackPtr]['loop'] = $this->loops;
×
623
        }//end if
624

625
        $this->fixedTokens[$stackPtr] = $this->tokens[$stackPtr];
×
626
        $this->tokens[$stackPtr]      = $content;
×
627
        $this->numFixes++;
×
628

629
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
630
            $statusMessage = "$sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"";
×
NEW
631
            $depth         = 1;
×
632
            if (empty($this->changeset) === false) {
×
NEW
633
                $statusMessage = 'A: '.$statusMessage;
×
NEW
634
                $depth         = 2;
×
635
            }
636

637
            if (ob_get_level() > 0) {
×
638
                ob_end_clean();
×
639
            }
640

NEW
641
            StatusWriter::forceWrite($statusMessage, $depth);
×
642
            ob_start();
×
643
        }
644

645
        return true;
×
646

647
    }//end replaceToken()
648

649

650
    /**
651
     * Reverts the previous fix made to a token.
652
     *
653
     * @param int $stackPtr The position of the token in the token stack.
654
     *
655
     * @return bool If a change was reverted.
656
     */
657
    public function revertToken($stackPtr)
×
658
    {
659
        if (isset($this->fixedTokens[$stackPtr]) === false) {
×
660
            return false;
×
661
        }
662

663
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
664
            $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
×
665
            if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
×
666
                $sniff = $bt[2]['class'];
×
667
                $line  = $bt[1]['line'];
×
668
            } else {
669
                $sniff = $bt[1]['class'];
×
670
                $line  = $bt[0]['line'];
×
671
            }
672

673
            $sniff = $this->getSniffCodeForDebug($sniff);
×
674

675
            $tokens     = $this->currentFile->getTokens();
×
676
            $type       = $tokens[$stackPtr]['type'];
×
677
            $tokenLine  = $tokens[$stackPtr]['line'];
×
678
            $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]);
×
679
            $newContent = Common::prepareForOutput($this->fixedTokens[$stackPtr]);
×
680
            if (trim($this->tokens[$stackPtr]) === '' && isset($tokens[($stackPtr + 1)]) === true) {
×
681
                // Add some context for whitespace only changes.
682
                $append      = Common::prepareForOutput($this->tokens[($stackPtr + 1)]);
×
683
                $oldContent .= $append;
×
684
                $newContent .= $append;
×
685
            }
686
        }//end if
687

688
        $this->tokens[$stackPtr] = $this->fixedTokens[$stackPtr];
×
689
        unset($this->fixedTokens[$stackPtr]);
×
690
        $this->numFixes--;
×
691

692
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
693
            $statusMessage = "$sniff:$line reverted token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"";
×
NEW
694
            $depth         = 1;
×
695
            if (empty($this->changeset) === false) {
×
NEW
696
                $statusMessage = 'R: '.$statusMessage;
×
NEW
697
                $depth         = 2;
×
698
            }
699

700
            @ob_end_clean();
×
NEW
701
            StatusWriter::forceWrite($statusMessage, $depth);
×
702
            ob_start();
×
703
        }
704

705
        return true;
×
706

707
    }//end revertToken()
708

709

710
    /**
711
     * Replace the content of a token with a part of its current content.
712
     *
713
     * @param int $stackPtr The position of the token in the token stack.
714
     * @param int $start    The first character to keep.
715
     * @param int $length   The number of characters to keep. If NULL, the content of
716
     *                      the token from $start to the end of the content is kept.
717
     *
718
     * @return bool If the change was accepted.
719
     */
720
    public function substrToken($stackPtr, $start, $length=null)
×
721
    {
722
        $current = $this->getTokenContent($stackPtr);
×
723

724
        if ($length === null) {
×
725
            $newContent = substr($current, $start);
×
726
        } else {
727
            $newContent = substr($current, $start, $length);
×
728
        }
729

730
        return $this->replaceToken($stackPtr, $newContent);
×
731

732
    }//end substrToken()
733

734

735
    /**
736
     * Adds a newline to end of a token's content.
737
     *
738
     * @param int $stackPtr The position of the token in the token stack.
739
     *
740
     * @return bool If the change was accepted.
741
     */
742
    public function addNewline($stackPtr)
×
743
    {
744
        $current = $this->getTokenContent($stackPtr);
×
745
        return $this->replaceToken($stackPtr, $current.$this->currentFile->eolChar);
×
746

747
    }//end addNewline()
748

749

750
    /**
751
     * Adds a newline to the start of a token's content.
752
     *
753
     * @param int $stackPtr The position of the token in the token stack.
754
     *
755
     * @return bool If the change was accepted.
756
     */
757
    public function addNewlineBefore($stackPtr)
×
758
    {
759
        $current = $this->getTokenContent($stackPtr);
×
760
        return $this->replaceToken($stackPtr, $this->currentFile->eolChar.$current);
×
761

762
    }//end addNewlineBefore()
763

764

765
    /**
766
     * Adds content to the end of a token's current content.
767
     *
768
     * @param int    $stackPtr The position of the token in the token stack.
769
     * @param string $content  The content to add.
770
     *
771
     * @return bool If the change was accepted.
772
     */
773
    public function addContent($stackPtr, $content)
×
774
    {
775
        $current = $this->getTokenContent($stackPtr);
×
776
        return $this->replaceToken($stackPtr, $current.$content);
×
777

778
    }//end addContent()
779

780

781
    /**
782
     * Adds content to the start of a token's current content.
783
     *
784
     * @param int    $stackPtr The position of the token in the token stack.
785
     * @param string $content  The content to add.
786
     *
787
     * @return bool If the change was accepted.
788
     */
789
    public function addContentBefore($stackPtr, $content)
×
790
    {
791
        $current = $this->getTokenContent($stackPtr);
×
792
        return $this->replaceToken($stackPtr, $content.$current);
×
793

794
    }//end addContentBefore()
795

796

797
    /**
798
     * Adjust the indent of a code block.
799
     *
800
     * @param int $start  The position of the token in the token stack
801
     *                    to start adjusting the indent from.
802
     * @param int $end    The position of the token in the token stack
803
     *                    to end adjusting the indent.
804
     * @param int $change The number of spaces to adjust the indent by
805
     *                    (positive or negative).
806
     *
807
     * @return void
808
     */
809
    public function changeCodeBlockIndent($start, $end, $change)
×
810
    {
811
        $tokens = $this->currentFile->getTokens();
×
812

813
        $baseIndent = '';
×
814
        if ($change > 0) {
×
815
            $baseIndent = str_repeat(' ', $change);
×
816
        }
817

818
        $useChangeset = false;
×
819
        if ($this->inChangeset === false) {
×
820
            $this->beginChangeset();
×
821
            $useChangeset = true;
×
822
        }
823

824
        for ($i = $start; $i <= $end; $i++) {
×
825
            if ($tokens[$i]['column'] !== 1
×
826
                || $tokens[($i + 1)]['line'] !== $tokens[$i]['line']
×
827
            ) {
828
                continue;
×
829
            }
830

831
            $length = 0;
×
832
            if ($tokens[$i]['code'] === T_WHITESPACE
×
833
                || $tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE
×
834
            ) {
835
                $length = $tokens[$i]['length'];
×
836

837
                $padding = ($length + $change);
×
838
                if ($padding > 0) {
×
839
                    $padding = str_repeat(' ', $padding);
×
840
                } else {
841
                    $padding = '';
×
842
                }
843

844
                $newContent = $padding.ltrim($tokens[$i]['content']);
×
845
            } else {
846
                $newContent = $baseIndent.$tokens[$i]['content'];
×
847
            }
848

849
            $this->replaceToken($i, $newContent);
×
850
        }//end for
851

852
        if ($useChangeset === true) {
×
853
            $this->endChangeset();
×
854
        }
855

856
    }//end changeCodeBlockIndent()
857

858

859
    /**
860
     * Get the sniff code for the current sniff or the class name if the passed class is not a sniff.
861
     *
862
     * @param string $className Class name.
863
     *
864
     * @return string
865
     */
866
    private function getSniffCodeForDebug($className)
×
867
    {
868
        try {
869
            $sniffCode = Common::getSniffCode($className);
×
870
            return $sniffCode;
×
871
        } catch (InvalidArgumentException $e) {
×
872
            // Sniff code could not be determined. This may be an abstract sniff class or a helper class.
873
            return $className;
×
874
        }
875

876
    }//end getSniffCodeForDebug()
877

878

879
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc