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

PHPCSStandards / PHP_CodeSniffer / 14208192861

01 Apr 2025 11:37PM UTC coverage: 77.548% (-0.02%) from 77.564%
14208192861

push

github

jrfnl
Fixer: improve debug information

If an error/fixer conflict occurs while running the PHPCBF, additional information about the issue will be displayed in verbose mode, however, the code creating the debug info did not take into account that part of the fixer code may be in helper classes, instead of directly in the sniff class.

This should fix that and make the debug information more, well... informational ;-)

Related to slevomat/coding-standard 1739

0 of 9 new or added lines in 1 file covered. (0.0%)

19308 of 24898 relevant lines covered (77.55%)

77.06 hits per line

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

18.5
/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

20
class Fixer
21
{
22

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

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

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

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

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

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

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

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

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

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

110

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

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

134
    }//end startFile()
135

136

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

150
        $this->enabled = true;
×
151

152
        Common::pauseStatusMessages();
×
153

154
        $this->loops = 0;
×
155
        while ($this->loops < 50) {
×
156
            // Only needed once file content has changed.
157
            $contents = $this->getContents();
×
158

159
            if (PHP_CODESNIFFER_VERBOSITY > 2) {
×
160
                Common::forcePrintStatusMessage('---START FILE CONTENT---');
×
161
                $lines = explode($this->currentFile->eolChar, $contents);
×
162
                $max   = strlen(count($lines));
×
163
                foreach ($lines as $lineNum => $line) {
×
164
                    $lineNum++;
×
165
                    Common::forcePrintStatusMessage(str_pad($lineNum, $max, ' ', STR_PAD_LEFT).'|'.$line);
×
166
                }
167

168
                Common::forcePrintStatusMessage('--- END FILE CONTENT ---');
×
169
            }
170

171
            $this->inConflict = false;
×
172
            $this->currentFile->ruleset->populateTokenListeners();
×
173
            $this->currentFile->setContent($contents);
×
174
            $this->currentFile->process();
×
175

176
            $this->loops++;
×
177

178
            if (PHP_CODESNIFFER_CBF === true && PHP_CODESNIFFER_VERBOSITY > 0) {
×
179
                Common::forcePrintStatusMessage("\r".str_repeat(' ', 80)."\r", 0, true);
×
180
                $statusMessage = "=> Fixing file: $this->numFixes/$fixable violations remaining [made $this->loops pass";
×
181
                if ($this->loops > 1) {
×
182
                    $statusMessage .= 'es';
×
183
                }
184

185
                $statusMessage  .= ']... ';
×
186
                $suppressNewline = true;
×
187
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
188
                    $suppressNewline = false;
×
189
                }
190

191
                Common::forcePrintStatusMessage($statusMessage, 1, $suppressNewline);
×
192
            }
193

194
            if ($this->numFixes === 0 && $this->inConflict === false) {
×
195
                // Nothing left to do.
196
                break;
×
197
            } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
198
                Common::forcePrintStatusMessage("* fixed $this->numFixes violations, starting loop ".($this->loops + 1).' *', 1);
×
199
            }
200
        }//end while
201

202
        $this->enabled = false;
×
203

204
        Common::resumeStatusMessages();
×
205

206
        if ($this->numFixes > 0) {
×
207
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
208
                Common::forcePrintStatusMessage("*** Reached maximum number of loops with $this->numFixes violations left unfixed ***", 1);
×
209
            }
210

211
            return false;
×
212
        }
213

214
        return true;
×
215

216
    }//end fixFile()
217

218

219
    /**
220
     * Generates a text diff of the original file and the new content.
221
     *
222
     * @param string  $filePath Optional file path to diff the file against.
223
     *                          If not specified, the original version of the
224
     *                          file will be used.
225
     * @param boolean $colors   Print coloured output or not.
226
     *
227
     * @return string
228
     *
229
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException When the diff command fails.
230
     */
231
    public function generateDiff($filePath=null, $colors=true)
65✔
232
    {
233
        if ($filePath === null) {
65✔
234
            $filePath = $this->currentFile->getFilename();
5✔
235
        }
236

237
        $cwd = getcwd().DIRECTORY_SEPARATOR;
65✔
238
        if (strpos($filePath, $cwd) === 0) {
65✔
239
            $filename = substr($filePath, strlen($cwd));
65✔
240
        } else {
241
            $filename = $filePath;
×
242
        }
243

244
        $contents = $this->getContents();
65✔
245

246
        $tempName  = tempnam(sys_get_temp_dir(), 'phpcs-fixer');
65✔
247
        $fixedFile = fopen($tempName, 'w');
65✔
248
        fwrite($fixedFile, $contents);
65✔
249

250
        // We must use something like shell_exec() or proc_open() because whitespace at the end
251
        // of lines is critical to diff files.
252
        // Using proc_open() instead of shell_exec improves performance on Windows significantly,
253
        // while the results are the same (though more code is needed to get the results).
254
        // This is specifically due to proc_open allowing to set the "bypass_shell" option.
255
        $filename = escapeshellarg($filename);
65✔
256
        $cmd      = "diff -u -L$filename -LPHP_CodeSniffer $filename \"$tempName\"";
65✔
257

258
        // Stream 0 = STDIN, 1 = STDOUT, 2 = STDERR.
259
        $descriptorspec = [
39✔
260
            0 => [
65✔
261
                'pipe',
39✔
262
                'r',
39✔
263
            ],
39✔
264
            1 => [
39✔
265
                'pipe',
39✔
266
                'w',
39✔
267
            ],
39✔
268
            2 => [
39✔
269
                'pipe',
39✔
270
                'w',
39✔
271
            ],
39✔
272
        ];
39✔
273

274
        $options = null;
65✔
275
        if (stripos(PHP_OS, 'WIN') === 0) {
65✔
276
            $options = ['bypass_shell' => true];
26✔
277
        }
278

279
        $process = proc_open($cmd, $descriptorspec, $pipes, $cwd, null, $options);
65✔
280
        if (is_resource($process) === false) {
65✔
281
            throw new RuntimeException('Could not obtain a resource to execute the diff command.');
×
282
        }
283

284
        // We don't need these.
285
        fclose($pipes[0]);
65✔
286
        fclose($pipes[2]);
65✔
287

288
        // Stdout will contain the actual diff.
289
        $diff = stream_get_contents($pipes[1]);
65✔
290
        fclose($pipes[1]);
65✔
291

292
        proc_close($process);
65✔
293

294
        fclose($fixedFile);
65✔
295
        if (is_file($tempName) === true) {
65✔
296
            unlink($tempName);
65✔
297
        }
298

299
        if ($diff === false || $diff === '') {
65✔
300
            return '';
10✔
301
        }
302

303
        if ($colors === false) {
55✔
304
            return $diff;
50✔
305
        }
306

307
        $diffLines = explode(PHP_EOL, $diff);
5✔
308
        if (count($diffLines) === 1) {
5✔
309
            // Seems to be required for cygwin.
310
            $diffLines = explode("\n", $diff);
2✔
311
        }
312

313
        $diff = [];
5✔
314
        foreach ($diffLines as $line) {
5✔
315
            if (isset($line[0]) === true) {
5✔
316
                switch ($line[0]) {
5✔
317
                case '-':
5✔
318
                    $diff[] = "\033[31m$line\033[0m";
5✔
319
                    break;
5✔
320
                case '+':
5✔
321
                    $diff[] = "\033[32m$line\033[0m";
5✔
322
                    break;
5✔
323
                default:
324
                    $diff[] = $line;
5✔
325
                }
326
            }
327
        }
328

329
        $diff = implode(PHP_EOL, $diff);
5✔
330

331
        return $diff;
5✔
332

333
    }//end generateDiff()
334

335

336
    /**
337
     * Get a count of fixes that have been performed on the file.
338
     *
339
     * This value is reset every time a new file is started, or an existing
340
     * file is restarted.
341
     *
342
     * @return int
343
     */
344
    public function getFixCount()
×
345
    {
346
        return $this->numFixes;
×
347

348
    }//end getFixCount()
349

350

351
    /**
352
     * Get the current content of the file, as a string.
353
     *
354
     * @return string
355
     */
356
    public function getContents()
×
357
    {
358
        $contents = implode($this->tokens);
×
359
        return $contents;
×
360

361
    }//end getContents()
362

363

364
    /**
365
     * Get the current fixed content of a token.
366
     *
367
     * This function takes changesets into account so should be used
368
     * instead of directly accessing the token array.
369
     *
370
     * @param int $stackPtr The position of the token in the token stack.
371
     *
372
     * @return string
373
     */
374
    public function getTokenContent($stackPtr)
×
375
    {
376
        if ($this->inChangeset === true
×
377
            && isset($this->changeset[$stackPtr]) === true
×
378
        ) {
379
            return $this->changeset[$stackPtr];
×
380
        } else {
381
            return $this->tokens[$stackPtr];
×
382
        }
383

384
    }//end getTokenContent()
385

386

387
    /**
388
     * Start recording actions for a changeset.
389
     *
390
     * @return void|false
391
     */
392
    public function beginChangeset()
×
393
    {
394
        if ($this->inConflict === true) {
×
395
            return false;
×
396
        }
397

398
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
399
            $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
×
400
            if ($bt[1]['class'] === __CLASS__) {
×
401
                $sniff = 'Fixer';
×
402
            } else {
NEW
403
                $sniff = $this->getSniffCodeForDebug($bt[1]['class']);
×
404
            }
405

406
            $line = $bt[0]['line'];
×
407

408
            Common::forcePrintStatusMessage("=> Changeset started by $sniff:$line", 1);
×
409
        }
410

411
        $this->changeset   = [];
×
412
        $this->inChangeset = true;
×
413

414
    }//end beginChangeset()
415

416

417
    /**
418
     * Stop recording actions for a changeset, and apply logged changes.
419
     *
420
     * @return boolean
421
     */
422
    public function endChangeset()
×
423
    {
424
        if ($this->inConflict === true) {
×
425
            return false;
×
426
        }
427

428
        $this->inChangeset = false;
×
429

430
        $success = true;
×
431
        $applied = [];
×
432
        foreach ($this->changeset as $stackPtr => $content) {
×
433
            $success = $this->replaceToken($stackPtr, $content);
×
434
            if ($success === false) {
×
435
                break;
×
436
            } else {
437
                $applied[] = $stackPtr;
×
438
            }
439
        }
440

441
        if ($success === false) {
×
442
            // Rolling back all changes.
443
            foreach ($applied as $stackPtr) {
×
444
                $this->revertToken($stackPtr);
×
445
            }
446

447
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
448
                Common::forcePrintStatusMessage('=> Changeset failed to apply', 1);
×
449
            }
450
        } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
451
            $fixes = count($this->changeset);
×
452
            Common::forcePrintStatusMessage("=> Changeset ended: $fixes changes applied", 1);
×
453
        }
454

455
        $this->changeset = [];
×
456
        return true;
×
457

458
    }//end endChangeset()
459

460

461
    /**
462
     * Stop recording actions for a changeset, and discard logged changes.
463
     *
464
     * @return void
465
     */
466
    public function rollbackChangeset()
×
467
    {
468
        $this->inChangeset = false;
×
469
        $this->inConflict  = false;
×
470

471
        if (empty($this->changeset) === false) {
×
472
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
473
                $bt = debug_backtrace();
×
474
                if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
×
475
                    $sniff = $bt[2]['class'];
×
476
                    $line  = $bt[1]['line'];
×
477
                } else {
478
                    $sniff = $bt[1]['class'];
×
479
                    $line  = $bt[0]['line'];
×
480
                }
481

NEW
482
                $sniff = $this->getSniffCodeForDebug($sniff);
×
483

484
                $numChanges = count($this->changeset);
×
485

486
                Common::forcePrintStatusMessage("R: $sniff:$line rolled back the changeset ($numChanges changes)", 2);
×
487
                Common::forcePrintStatusMessage('=> Changeset rolled back', 1);
×
488
            }
489

490
            $this->changeset = [];
×
491
        }//end if
492

493
    }//end rollbackChangeset()
494

495

496
    /**
497
     * Replace the entire contents of a token.
498
     *
499
     * @param int    $stackPtr The position of the token in the token stack.
500
     * @param string $content  The new content of the token.
501
     *
502
     * @return bool If the change was accepted.
503
     */
504
    public function replaceToken($stackPtr, $content)
×
505
    {
506
        if ($this->inConflict === true) {
×
507
            return false;
×
508
        }
509

510
        if ($this->inChangeset === false
×
511
            && isset($this->fixedTokens[$stackPtr]) === true
×
512
        ) {
513
            $depth = 1;
×
514
            if (empty($this->changeset) === false) {
×
515
                $depth = 2;
×
516
            }
517

518
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
519
                Common::forcePrintStatusMessage("* token $stackPtr has already been modified, skipping *", $depth);
×
520
            }
521

522
            return false;
×
523
        }
524

525
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
526
            $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
×
527
            if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
×
528
                $sniff = $bt[2]['class'];
×
529
                $line  = $bt[1]['line'];
×
530
            } else {
531
                $sniff = $bt[1]['class'];
×
532
                $line  = $bt[0]['line'];
×
533
            }
534

NEW
535
            $sniff = $this->getSniffCodeForDebug($sniff);
×
536

537
            $tokens     = $this->currentFile->getTokens();
×
538
            $type       = $tokens[$stackPtr]['type'];
×
539
            $tokenLine  = $tokens[$stackPtr]['line'];
×
540
            $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]);
×
541
            $newContent = Common::prepareForOutput($content);
×
542
            if (trim($this->tokens[$stackPtr]) === '' && isset($this->tokens[($stackPtr + 1)]) === true) {
×
543
                // Add some context for whitespace only changes.
544
                $append      = Common::prepareForOutput($this->tokens[($stackPtr + 1)]);
×
545
                $oldContent .= $append;
×
546
                $newContent .= $append;
×
547
            }
548
        }//end if
549

550
        if ($this->inChangeset === true) {
×
551
            $this->changeset[$stackPtr] = $content;
×
552

553
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
554
                Common::forcePrintStatusMessage("Q: $sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"", 2);
×
555
            }
556

557
            return true;
×
558
        }
559

560
        if (isset($this->oldTokenValues[$stackPtr]) === false) {
×
561
            $this->oldTokenValues[$stackPtr] = [
×
562
                'curr' => $content,
×
563
                'prev' => $this->tokens[$stackPtr],
×
564
                'loop' => $this->loops,
×
565
            ];
566
        } else {
567
            if ($this->oldTokenValues[$stackPtr]['prev'] === $content
×
568
                && $this->oldTokenValues[$stackPtr]['loop'] === ($this->loops - 1)
×
569
            ) {
570
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
571
                    $depth = 1;
×
572
                    if (empty($this->changeset) === false) {
×
573
                        $depth = 2;
×
574
                    }
575

576
                    $loop = $this->oldTokenValues[$stackPtr]['loop'];
×
577

578
                    Common::forcePrintStatusMessage("**** $sniff:$line has possible conflict with another sniff on loop $loop; caused by the following change ****", $depth);
×
579
                    Common::forcePrintStatusMessage("**** replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\" ****", $depth);
×
580
                }
581

582
                if ($this->oldTokenValues[$stackPtr]['loop'] >= ($this->loops - 1)) {
×
583
                    $this->inConflict = true;
×
584
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
585
                        Common::forcePrintStatusMessage('**** ignoring all changes until next loop ****', $depth);
×
586
                    }
587
                }
588

589
                return false;
×
590
            }//end if
591

592
            $this->oldTokenValues[$stackPtr]['prev'] = $this->oldTokenValues[$stackPtr]['curr'];
×
593
            $this->oldTokenValues[$stackPtr]['curr'] = $content;
×
594
            $this->oldTokenValues[$stackPtr]['loop'] = $this->loops;
×
595
        }//end if
596

597
        $this->fixedTokens[$stackPtr] = $this->tokens[$stackPtr];
×
598
        $this->tokens[$stackPtr]      = $content;
×
599
        $this->numFixes++;
×
600

601
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
602
            $statusMessage = "$sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"";
×
603
            $depth         = 1;
×
604
            if (empty($this->changeset) === false) {
×
605
                $statusMessage = 'A: '.$statusMessage;
×
606
                $depth         = 2;
×
607
            }
608

609
            Common::forcePrintStatusMessage($statusMessage, $depth);
×
610
        }
611

612
        return true;
×
613

614
    }//end replaceToken()
615

616

617
    /**
618
     * Reverts the previous fix made to a token.
619
     *
620
     * @param int $stackPtr The position of the token in the token stack.
621
     *
622
     * @return bool If a change was reverted.
623
     */
624
    public function revertToken($stackPtr)
×
625
    {
626
        if (isset($this->fixedTokens[$stackPtr]) === false) {
×
627
            return false;
×
628
        }
629

630
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
631
            $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
×
632
            if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
×
633
                $sniff = $bt[2]['class'];
×
634
                $line  = $bt[1]['line'];
×
635
            } else {
636
                $sniff = $bt[1]['class'];
×
637
                $line  = $bt[0]['line'];
×
638
            }
639

NEW
640
            $sniff = $this->getSniffCodeForDebug($sniff);
×
641

642
            $tokens     = $this->currentFile->getTokens();
×
643
            $type       = $tokens[$stackPtr]['type'];
×
644
            $tokenLine  = $tokens[$stackPtr]['line'];
×
645
            $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]);
×
646
            $newContent = Common::prepareForOutput($this->fixedTokens[$stackPtr]);
×
647
            if (trim($this->tokens[$stackPtr]) === '' && isset($tokens[($stackPtr + 1)]) === true) {
×
648
                // Add some context for whitespace only changes.
649
                $append      = Common::prepareForOutput($this->tokens[($stackPtr + 1)]);
×
650
                $oldContent .= $append;
×
651
                $newContent .= $append;
×
652
            }
653
        }//end if
654

655
        $this->tokens[$stackPtr] = $this->fixedTokens[$stackPtr];
×
656
        unset($this->fixedTokens[$stackPtr]);
×
657
        $this->numFixes--;
×
658

659
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
660
            $statusMessage = "$sniff:$line reverted token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"";
×
661
            $depth         = 1;
×
662
            if (empty($this->changeset) === false) {
×
663
                $statusMessage = 'R: '.$statusMessage;
×
664
                $depth         = 2;
×
665
            }
666

667
            Common::forcePrintStatusMessage($statusMessage, $depth);
×
668
        }
669

670
        return true;
×
671

672
    }//end revertToken()
673

674

675
    /**
676
     * Replace the content of a token with a part of its current content.
677
     *
678
     * @param int $stackPtr The position of the token in the token stack.
679
     * @param int $start    The first character to keep.
680
     * @param int $length   The number of characters to keep. If NULL, the content of
681
     *                      the token from $start to the end of the content is kept.
682
     *
683
     * @return bool If the change was accepted.
684
     */
685
    public function substrToken($stackPtr, $start, $length=null)
×
686
    {
687
        $current = $this->getTokenContent($stackPtr);
×
688

689
        if ($length === null) {
×
690
            $newContent = substr($current, $start);
×
691
        } else {
692
            $newContent = substr($current, $start, $length);
×
693
        }
694

695
        return $this->replaceToken($stackPtr, $newContent);
×
696

697
    }//end substrToken()
698

699

700
    /**
701
     * Adds a newline to end of a token's content.
702
     *
703
     * @param int $stackPtr The position of the token in the token stack.
704
     *
705
     * @return bool If the change was accepted.
706
     */
707
    public function addNewline($stackPtr)
×
708
    {
709
        $current = $this->getTokenContent($stackPtr);
×
710
        return $this->replaceToken($stackPtr, $current.$this->currentFile->eolChar);
×
711

712
    }//end addNewline()
713

714

715
    /**
716
     * Adds a newline to the start of a token's content.
717
     *
718
     * @param int $stackPtr The position of the token in the token stack.
719
     *
720
     * @return bool If the change was accepted.
721
     */
722
    public function addNewlineBefore($stackPtr)
×
723
    {
724
        $current = $this->getTokenContent($stackPtr);
×
725
        return $this->replaceToken($stackPtr, $this->currentFile->eolChar.$current);
×
726

727
    }//end addNewlineBefore()
728

729

730
    /**
731
     * Adds content to the end of a token's current content.
732
     *
733
     * @param int    $stackPtr The position of the token in the token stack.
734
     * @param string $content  The content to add.
735
     *
736
     * @return bool If the change was accepted.
737
     */
738
    public function addContent($stackPtr, $content)
×
739
    {
740
        $current = $this->getTokenContent($stackPtr);
×
741
        return $this->replaceToken($stackPtr, $current.$content);
×
742

743
    }//end addContent()
744

745

746
    /**
747
     * Adds content to the start of a token's current content.
748
     *
749
     * @param int    $stackPtr The position of the token in the token stack.
750
     * @param string $content  The content to add.
751
     *
752
     * @return bool If the change was accepted.
753
     */
754
    public function addContentBefore($stackPtr, $content)
×
755
    {
756
        $current = $this->getTokenContent($stackPtr);
×
757
        return $this->replaceToken($stackPtr, $content.$current);
×
758

759
    }//end addContentBefore()
760

761

762
    /**
763
     * Adjust the indent of a code block.
764
     *
765
     * @param int $start  The position of the token in the token stack
766
     *                    to start adjusting the indent from.
767
     * @param int $end    The position of the token in the token stack
768
     *                    to end adjusting the indent.
769
     * @param int $change The number of spaces to adjust the indent by
770
     *                    (positive or negative).
771
     *
772
     * @return void
773
     */
774
    public function changeCodeBlockIndent($start, $end, $change)
×
775
    {
776
        $tokens = $this->currentFile->getTokens();
×
777

778
        $baseIndent = '';
×
779
        if ($change > 0) {
×
780
            $baseIndent = str_repeat(' ', $change);
×
781
        }
782

783
        $useChangeset = false;
×
784
        if ($this->inChangeset === false) {
×
785
            $this->beginChangeset();
×
786
            $useChangeset = true;
×
787
        }
788

789
        for ($i = $start; $i <= $end; $i++) {
×
790
            if ($tokens[$i]['column'] !== 1
×
791
                || $tokens[($i + 1)]['line'] !== $tokens[$i]['line']
×
792
            ) {
793
                continue;
×
794
            }
795

796
            $length = 0;
×
797
            if ($tokens[$i]['code'] === T_WHITESPACE
×
798
                || $tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE
×
799
            ) {
800
                $length = $tokens[$i]['length'];
×
801

802
                $padding = ($length + $change);
×
803
                if ($padding > 0) {
×
804
                    $padding = str_repeat(' ', $padding);
×
805
                } else {
806
                    $padding = '';
×
807
                }
808

809
                $newContent = $padding.ltrim($tokens[$i]['content']);
×
810
            } else {
811
                $newContent = $baseIndent.$tokens[$i]['content'];
×
812
            }
813

814
            $this->replaceToken($i, $newContent);
×
815
        }//end for
816

817
        if ($useChangeset === true) {
×
818
            $this->endChangeset();
×
819
        }
820

821
    }//end changeCodeBlockIndent()
822

823

824
    /**
825
     * Get the sniff code for the current sniff or the class name if the passed class is not a sniff.
826
     *
827
     * @param string $className Class name.
828
     *
829
     * @return string
830
     */
NEW
831
    private function getSniffCodeForDebug($className)
×
832
    {
833
        try {
NEW
834
            $sniffCode = Common::getSniffCode($className);
×
NEW
835
            return $sniffCode;
×
NEW
836
        } catch (InvalidArgumentException $e) {
×
837
            // Sniff code could not be determined. This may be an abstract sniff class or a helper class.
NEW
838
            return $className;
×
839
        }
840

841
    }//end getSniffCodeForDebug()
842

843

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