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

PHPCSStandards / PHP_CodeSniffer / 14516416464

17 Apr 2025 01:09PM UTC coverage: 77.945% (+0.3%) from 77.666%
14516416464

push

github

web-flow
Merge pull request #1010 from PHPCSStandards/phpcs-4.0/feature/sq-1612-stdout-vs-stderr

(Nearly) All status, debug, and progress output is now sent to STDERR instead of STDOUT

63 of 457 new or added lines in 18 files covered. (13.79%)

1 existing line in 1 file now uncovered.

19455 of 24960 relevant lines covered (77.94%)

78.64 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

153
        // Pause the StatusWriter to silence Tokenizer debug info about the file being retokenized for each loop.
NEW
154
        StatusWriter::pause();
×
155

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

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

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

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

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

183
            $this->loops++;
×
184

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

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

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

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

209
        $this->enabled = false;
×
210

NEW
211
        StatusWriter::resume();
×
212

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

NEW
219
                StatusWriter::write("*** Reached maximum number of loops with $this->numFixes violations left unfixed ***", 1);
×
220
                ob_start();
×
221
            }
222

223
            return false;
×
224
        }
225

226
        return true;
×
227

228
    }//end fixFile()
229

230

231
    /**
232
     * Generates a text diff of the original file and the new content.
233
     *
234
     * @param string  $filePath Optional file path to diff the file against.
235
     *                          If not specified, the original version of the
236
     *                          file will be used.
237
     * @param boolean $colors   Print coloured output or not.
238
     *
239
     * @return string
240
     *
241
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException When the diff command fails.
242
     */
243
    public function generateDiff($filePath=null, $colors=true)
65✔
244
    {
245
        if ($filePath === null) {
65✔
246
            $filePath = $this->currentFile->getFilename();
5✔
247
        }
248

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

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

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

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

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

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

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

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

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

304
        proc_close($process);
65✔
305

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

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

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

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

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

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

343
        return $diff;
5✔
344

345
    }//end generateDiff()
346

347

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

360
    }//end getFixCount()
361

362

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

373
    }//end getContents()
374

375

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

396
    }//end getTokenContent()
397

398

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

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

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

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

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

428
    }//end beginChangeset()
429

430

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

442
        $this->inChangeset = false;
×
443

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

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

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

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

476
    }//end endChangeset()
477

478

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

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

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

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

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

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

513
    }//end rollbackChangeset()
514

515

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

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

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

544
            return false;
×
545
        }
546

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

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

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

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

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

581
            return true;
×
582
        }
583

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

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

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

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

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

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

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

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

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

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

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

646
        return true;
×
647

648
    }//end replaceToken()
649

650

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

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

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

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

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

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

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

706
        return true;
×
707

708
    }//end revertToken()
709

710

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

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

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

733
    }//end substrToken()
734

735

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

748
    }//end addNewline()
749

750

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

763
    }//end addNewlineBefore()
764

765

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

779
    }//end addContent()
780

781

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

795
    }//end addContentBefore()
796

797

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

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

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

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

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

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

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

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

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

857
    }//end changeCodeBlockIndent()
858

859

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

877
    }//end getSniffCodeForDebug()
878

879

880
}//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

© 2025 Coveralls, Inc