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

PHPCSStandards / PHP_CodeSniffer / 14732727698

29 Apr 2025 01:40PM UTC coverage: 78.394% (+0.1%) from 78.295%
14732727698

push

github

web-flow
Merge pull request #1052 from PHPCSStandards/phpcs-4.0/remove-output-buffering-from-fixer

Fixer: remove output buffering

19546 of 24933 relevant lines covered (78.39%)

86.19 hits per line

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

26.02
/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()
9✔
144
    {
145
        $fixable = $this->currentFile->getFixableCount();
9✔
146
        if ($fixable === 0) {
9✔
147
            // Nothing to fix.
148
            return false;
×
149
        }
150

151
        $this->enabled = true;
9✔
152

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

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

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

170
                StatusWriter::forceWrite('--- END FILE CONTENT ---');
×
171
            }
172

173
            $this->inConflict = false;
9✔
174
            $this->currentFile->ruleset->populateTokenListeners();
9✔
175
            $this->currentFile->setContent($contents);
9✔
176
            $this->currentFile->process();
9✔
177

178
            $this->loops++;
9✔
179

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

187
                $statusMessage .= ']... ';
×
188
                $newlines       = 0;
×
189
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
190
                    $newlines = 1;
×
191
                }
192

193
                StatusWriter::forceWrite($statusMessage, 1, $newlines);
×
194
            }
195

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

204
        $this->enabled = false;
9✔
205

206
        StatusWriter::resume();
9✔
207

208
        if ($this->numFixes > 0 || $this->inConflict === true) {
9✔
209
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
6✔
210
                StatusWriter::write("*** Reached maximum number of loops with $this->numFixes violations left unfixed ***", 1);
×
211
            }
212

213
            return false;
6✔
214
        }
215

216
        return true;
3✔
217

218
    }//end fixFile()
219

220

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

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

246
        $contents = $this->getContents();
65✔
247

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

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

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

276
        $options = null;
65✔
277
        if (PHP_OS_FAMILY === 'Windows') {
65✔
278
            $options = ['bypass_shell' => true];
26✔
279
        }
280

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

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

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

294
        proc_close($process);
65✔
295

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

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

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

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

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

331
        $diff = implode(PHP_EOL, $diff);
5✔
332

333
        return $diff;
5✔
334

335
    }//end generateDiff()
336

337

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

350
    }//end getFixCount()
351

352

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

363
    }//end getContents()
364

365

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

386
    }//end getTokenContent()
387

388

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

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

408
            $line = $bt[0]['line'];
×
409

410
            StatusWriter::forceWrite("=> Changeset started by $sniff:$line", 1);
×
411
        }
412

413
        $this->changeset   = [];
×
414
        $this->inChangeset = true;
×
415

416
    }//end beginChangeset()
417

418

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

430
        $this->inChangeset = false;
×
431

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

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

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

457
        $this->changeset = [];
×
458
        return true;
×
459

460
    }//end endChangeset()
461

462

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

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

484
                $sniff = $this->getSniffCodeForDebug($sniff);
×
485

486
                $numChanges = count($this->changeset);
×
487

488
                StatusWriter::forceWrite("R: $sniff:$line rolled back the changeset ($numChanges changes)", 2);
×
489
                StatusWriter::forceWrite('=> Changeset rolled back', 1);
×
490
            }
491

492
            $this->changeset = [];
×
493
        }//end if
494

495
    }//end rollbackChangeset()
496

497

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

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

520
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
521
                StatusWriter::forceWrite("* token $stackPtr has already been modified, skipping *", $depth);
×
522
            }
523

524
            return false;
×
525
        }
526

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

537
            $sniff = $this->getSniffCodeForDebug($sniff);
×
538

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

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

555
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
556
                StatusWriter::forceWrite("Q: $sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"", 2);
×
557
            }
558

559
            return true;
×
560
        }
561

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

578
                    $loop = $this->oldTokenValues[$stackPtr]['loop'];
×
579

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

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

591
                return false;
×
592
            }//end if
593

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

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

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

611
            StatusWriter::forceWrite($statusMessage, $depth);
×
612
        }
613

614
        return true;
×
615

616
    }//end replaceToken()
617

618

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

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

642
            $sniff = $this->getSniffCodeForDebug($sniff);
×
643

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

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

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

669
            StatusWriter::forceWrite($statusMessage, $depth);
×
670
        }
671

672
        return true;
×
673

674
    }//end revertToken()
675

676

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

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

697
        return $this->replaceToken($stackPtr, $newContent);
×
698

699
    }//end substrToken()
700

701

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

714
    }//end addNewline()
715

716

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

729
    }//end addNewlineBefore()
730

731

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

745
    }//end addContent()
746

747

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

761
    }//end addContentBefore()
762

763

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

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

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

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

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

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

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

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

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

823
    }//end changeCodeBlockIndent()
824

825

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

843
    }//end getSniffCodeForDebug()
844

845

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