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

keradus / PHP-CS-Fixer / 17253322895

26 Aug 2025 11:52PM UTC coverage: 94.753% (+0.008%) from 94.745%
17253322895

push

github

keradus
add to git-blame-ignore-revs

28316 of 29884 relevant lines covered (94.75%)

45.64 hits per line

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

99.57
/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <fabien@symfony.com>
9
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14

15
namespace PhpCsFixer\Fixer\PhpUnit;
16

17
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
18
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
19
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
20
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
22
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
23
use PhpCsFixer\FixerDefinition\CodeSample;
24
use PhpCsFixer\FixerDefinition\FixerDefinition;
25
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
26
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
27
use PhpCsFixer\Tokenizer\CT;
28
use PhpCsFixer\Tokenizer\Token;
29
use PhpCsFixer\Tokenizer\Tokens;
30

31
/**
32
 * @phpstan-type _AutogeneratedInputConfiguration array{
33
 *  target?: '3.0'|'3.5'|'5.0'|'5.6'|'newest',
34
 * }
35
 * @phpstan-type _AutogeneratedComputedConfiguration array{
36
 *  target: '3.0'|'3.5'|'5.0'|'5.6'|'newest',
37
 * }
38
 *
39
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
40
 *
41
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
42
 *
43
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
44
 */
45
final class PhpUnitDedicateAssertFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
46
{
47
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
48
    use ConfigurableFixerTrait;
49

50
    /**
51
     * @var array<string, array{positive: string, negative: false|string, argument_count?: int, swap_arguments?: true}|true>
52
     */
53
    private const FIX_MAP = [
54
        'array_key_exists' => [
55
            'positive' => 'assertArrayHasKey',
56
            'negative' => 'assertArrayNotHasKey',
57
            'argument_count' => 2,
58
        ],
59
        'empty' => [
60
            'positive' => 'assertEmpty',
61
            'negative' => 'assertNotEmpty',
62
        ],
63
        'file_exists' => [
64
            'positive' => 'assertFileExists',
65
            'negative' => 'assertFileNotExists',
66
        ],
67
        'is_array' => true,
68
        'is_bool' => true,
69
        'is_callable' => true,
70
        'is_dir' => [
71
            'positive' => 'assertDirectoryExists',
72
            'negative' => 'assertDirectoryNotExists',
73
        ],
74
        'is_double' => true,
75
        'is_float' => true,
76
        'is_infinite' => [
77
            'positive' => 'assertInfinite',
78
            'negative' => 'assertFinite',
79
        ],
80
        'is_int' => true,
81
        'is_integer' => true,
82
        'is_long' => true,
83
        'is_nan' => [
84
            'positive' => 'assertNan',
85
            'negative' => false,
86
        ],
87
        'is_null' => [
88
            'positive' => 'assertNull',
89
            'negative' => 'assertNotNull',
90
        ],
91
        'is_numeric' => true,
92
        'is_object' => true,
93
        'is_readable' => [
94
            'positive' => 'assertIsReadable',
95
            'negative' => 'assertNotIsReadable',
96
        ],
97
        'is_real' => true,
98
        'is_resource' => true,
99
        'is_scalar' => true,
100
        'is_string' => true,
101
        'is_writable' => [
102
            'positive' => 'assertIsWritable',
103
            'negative' => 'assertNotIsWritable',
104
        ],
105
        'str_contains' => [ // since 7.5
106
            'positive' => 'assertStringContainsString',
107
            'negative' => 'assertStringNotContainsString',
108
            'argument_count' => 2,
109
            'swap_arguments' => true,
110
        ],
111
        'str_ends_with' => [ // since 3.4
112
            'positive' => 'assertStringEndsWith',
113
            'negative' => 'assertStringEndsNotWith',
114
            'argument_count' => 2,
115
            'swap_arguments' => true,
116
        ],
117
        'str_starts_with' => [ // since 3.4
118
            'positive' => 'assertStringStartsWith',
119
            'negative' => 'assertStringStartsNotWith',
120
            'argument_count' => 2,
121
            'swap_arguments' => true,
122
        ],
123
    ];
124

125
    /**
126
     * @var list<string>
127
     */
128
    private array $functions = [];
129

130
    public function isRisky(): bool
131
    {
132
        return true;
1✔
133
    }
134

135
    public function getDefinition(): FixerDefinitionInterface
136
    {
137
        return new FixerDefinition(
3✔
138
            'PHPUnit assertions like `assertInternalType`, `assertFileExists`, should be used over `assertTrue`.',
3✔
139
            [
3✔
140
                new CodeSample(
3✔
141
                    '<?php
3✔
142
final class MyTest extends \PHPUnit_Framework_TestCase
143
{
144
    public function testSomeTest()
145
    {
146
        $this->assertTrue(is_float( $a), "my message");
147
        $this->assertTrue(is_nan($a));
148
    }
149
}
150
'
3✔
151
                ),
3✔
152
                new CodeSample(
3✔
153
                    '<?php
3✔
154
final class MyTest extends \PHPUnit_Framework_TestCase
155
{
156
    public function testSomeTest()
157
    {
158
        $this->assertTrue(is_dir($a));
159
        $this->assertTrue(is_writable($a));
160
        $this->assertTrue(is_readable($a));
161
    }
162
}
163
',
3✔
164
                    ['target' => PhpUnitTargetVersion::VERSION_5_6]
3✔
165
                ),
3✔
166
            ],
3✔
167
            null,
3✔
168
            'Fixer could be risky if one is overriding PHPUnit\'s native methods.'
3✔
169
        );
3✔
170
    }
171

172
    /**
173
     * {@inheritdoc}
174
     *
175
     * Must run before NoUnusedImportsFixer, PhpUnitAssertNewNamesFixer, PhpUnitDedicateAssertInternalTypeFixer.
176
     * Must run after ModernizeStrposFixer, NoAliasFunctionsFixer, PhpUnitConstructFixer.
177
     */
178
    public function getPriority(): int
179
    {
180
        return -9;
1✔
181
    }
182

183
    protected function configurePostNormalisation(): void
184
    {
185
        // assertions added in 3.0: assertArrayNotHasKey assertArrayHasKey assertFileNotExists assertFileExists assertNotNull, assertNull
186
        $this->functions = [
123✔
187
            'array_key_exists',
123✔
188
            'file_exists',
123✔
189
            'is_null',
123✔
190
            'str_ends_with',
123✔
191
            'str_starts_with',
123✔
192
        ];
123✔
193

194
        if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_3_5)) {
123✔
195
            // assertions added in 3.5: assertInternalType assertNotEmpty assertEmpty
196
            $this->functions = array_merge($this->functions, [
123✔
197
                'empty',
123✔
198
                'is_array',
123✔
199
                'is_bool',
123✔
200
                'is_boolean',
123✔
201
                'is_callable',
123✔
202
                'is_double',
123✔
203
                'is_float',
123✔
204
                'is_int',
123✔
205
                'is_integer',
123✔
206
                'is_long',
123✔
207
                'is_numeric',
123✔
208
                'is_object',
123✔
209
                'is_real',
123✔
210
                'is_scalar',
123✔
211
                'is_string',
123✔
212
            ]);
123✔
213
        }
214

215
        if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_0)) {
123✔
216
            // assertions added in 5.0: assertFinite assertInfinite assertNan
217
            $this->functions = array_merge($this->functions, [
123✔
218
                'is_infinite',
123✔
219
                'is_nan',
123✔
220
            ]);
123✔
221
        }
222

223
        if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) {
123✔
224
            // assertions added in 5.6: assertDirectoryExists assertDirectoryNotExists assertIsReadable assertNotIsReadable assertIsWritable assertNotIsWritable
225
            $this->functions = array_merge($this->functions, [
123✔
226
                'is_dir',
123✔
227
                'is_readable',
123✔
228
                'is_writable',
123✔
229
            ]);
123✔
230
        }
231

232
        if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_7_5)) {
123✔
233
            $this->functions = array_merge($this->functions, [
123✔
234
                'str_contains',
123✔
235
            ]);
123✔
236
        }
237
    }
238

239
    protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
240
    {
241
        $argumentsAnalyzer = new ArgumentsAnalyzer();
112✔
242

243
        foreach ($this->getPreviousAssertCall($tokens, $startIndex, $endIndex) as $assertCall) {
112✔
244
            // test and fix for assertTrue/False to dedicated asserts
245
            if (\in_array($assertCall['loweredName'], ['asserttrue', 'assertfalse'], true)) {
109✔
246
                $this->fixAssertTrueFalse($tokens, $argumentsAnalyzer, $assertCall);
71✔
247

248
                continue;
71✔
249
            }
250

251
            if (\in_array(
100✔
252
                $assertCall['loweredName'],
100✔
253
                ['assertsame', 'assertnotsame', 'assertequals', 'assertnotequals'],
100✔
254
                true
100✔
255
            )) {
100✔
256
                $this->fixAssertSameEquals($tokens, $assertCall);
38✔
257
            }
258
        }
259
    }
260

261
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
262
    {
263
        return new FixerConfigurationResolver([
123✔
264
            (new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
123✔
265
                ->setAllowedTypes(['string'])
123✔
266
                ->setAllowedValues([
123✔
267
                    PhpUnitTargetVersion::VERSION_3_0,
123✔
268
                    PhpUnitTargetVersion::VERSION_3_5,
123✔
269
                    PhpUnitTargetVersion::VERSION_5_0,
123✔
270
                    PhpUnitTargetVersion::VERSION_5_6,
123✔
271
                    PhpUnitTargetVersion::VERSION_NEWEST,
123✔
272
                ])
123✔
273
                ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
123✔
274
                ->getOption(),
123✔
275
        ]);
123✔
276
    }
277

278
    /**
279
     * @param array{
280
     *     index: int,
281
     *     loweredName: string,
282
     *     openBraceIndex: int,
283
     *     closeBraceIndex: int,
284
     * } $assertCall
285
     */
286
    private function fixAssertTrueFalse(Tokens $tokens, ArgumentsAnalyzer $argumentsAnalyzer, array $assertCall): void
287
    {
288
        $testDefaultNamespaceTokenIndex = null;
71✔
289
        $testIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']);
71✔
290

291
        if (!$tokens[$testIndex]->isGivenKind([\T_EMPTY, \T_STRING])) {
71✔
292
            if ($this->fixAssertTrueFalseInstanceof($tokens, $assertCall, $testIndex)) {
16✔
293
                return;
6✔
294
            }
295

296
            if (!$tokens[$testIndex]->isGivenKind(\T_NS_SEPARATOR)) {
10✔
297
                return;
2✔
298
            }
299

300
            $testDefaultNamespaceTokenIndex = $testIndex;
8✔
301
            $testIndex = $tokens->getNextMeaningfulToken($testIndex);
8✔
302
        }
303

304
        $testOpenIndex = $tokens->getNextMeaningfulToken($testIndex);
64✔
305

306
        if (!$tokens[$testOpenIndex]->equals('(')) {
64✔
307
            return;
2✔
308
        }
309

310
        $testCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $testOpenIndex);
64✔
311
        $assertCallCloseIndex = $tokens->getNextMeaningfulToken($testCloseIndex);
64✔
312

313
        if (!$tokens[$assertCallCloseIndex]->equalsAny([')', ','])) {
64✔
314
            return;
1✔
315
        }
316

317
        $content = strtolower($tokens[$testIndex]->getContent());
64✔
318

319
        if (!\in_array($content, $this->functions, true)) {
64✔
320
            return;
3✔
321
        }
322

323
        $arguments = $argumentsAnalyzer->getArguments($tokens, $testOpenIndex, $testCloseIndex);
61✔
324
        $isPositive = 'asserttrue' === $assertCall['loweredName'];
61✔
325

326
        if (isset(self::FIX_MAP[$content]) && \is_array(self::FIX_MAP[$content])) {
61✔
327
            $fixDetails = self::FIX_MAP[$content];
28✔
328
            $expectedCount = $fixDetails['argument_count'] ?? 1;
28✔
329

330
            if ($expectedCount !== \count($arguments)) {
28✔
331
                return;
1✔
332
            }
333

334
            $isPositive = $isPositive ? 'positive' : 'negative';
27✔
335

336
            if (false === $fixDetails[$isPositive]) {
27✔
337
                return;
1✔
338
            }
339

340
            $tokens[$assertCall['index']] = new Token([\T_STRING, $fixDetails[$isPositive]]);
26✔
341
            $this->removeFunctionCall($tokens, $testDefaultNamespaceTokenIndex, $testIndex, $testOpenIndex, $testCloseIndex);
26✔
342

343
            if ($fixDetails['swap_arguments'] ?? false) {
26✔
344
                if (2 !== $expectedCount) {
8✔
345
                    throw new \RuntimeException('Can only swap two arguments, please update map or logic.');
×
346
                }
347

348
                $this->swapArguments($tokens, $arguments);
8✔
349
            }
350

351
            return;
26✔
352
        }
353

354
        if (1 !== \count($arguments)) {
34✔
355
            return;
1✔
356
        }
357

358
        $type = substr($content, 3);
33✔
359

360
        $tokens[$assertCall['index']] = new Token([\T_STRING, $isPositive ? 'assertInternalType' : 'assertNotInternalType']);
33✔
361
        $tokens[$testIndex] = new Token([\T_CONSTANT_ENCAPSED_STRING, "'".$type."'"]);
33✔
362
        $tokens[$testOpenIndex] = new Token(',');
33✔
363

364
        $tokens->clearTokenAndMergeSurroundingWhitespace($testCloseIndex);
33✔
365
        $commaIndex = $tokens->getPrevMeaningfulToken($testCloseIndex);
33✔
366

367
        if ($tokens[$commaIndex]->equals(',')) {
33✔
368
            $tokens->removeTrailingWhitespace($commaIndex);
1✔
369
            $tokens->clearAt($commaIndex);
1✔
370
        }
371

372
        if (!$tokens[$testOpenIndex + 1]->isWhitespace()) {
33✔
373
            $tokens->insertAt($testOpenIndex + 1, new Token([\T_WHITESPACE, ' ']));
29✔
374
        }
375

376
        if (null !== $testDefaultNamespaceTokenIndex) {
33✔
377
            $tokens->clearTokenAndMergeSurroundingWhitespace($testDefaultNamespaceTokenIndex);
2✔
378
        }
379
    }
380

381
    /**
382
     * @param array{
383
     *     index: int,
384
     *     loweredName: string,
385
     *     openBraceIndex: int,
386
     *     closeBraceIndex: int,
387
     * } $assertCall
388
     */
389
    private function fixAssertTrueFalseInstanceof(Tokens $tokens, array $assertCall, int $testIndex): bool
390
    {
391
        $isPositiveAssertion = 'asserttrue' === $assertCall['loweredName'];
16✔
392

393
        if ($tokens[$testIndex]->equals('!')) {
16✔
394
            $variableIndex = $tokens->getNextMeaningfulToken($testIndex);
2✔
395
            $isPositiveCondition = false;
2✔
396
        } else {
397
            $variableIndex = $testIndex;
15✔
398
            $isPositiveCondition = true;
15✔
399
        }
400

401
        if (!$tokens[$variableIndex]->isGivenKind(\T_VARIABLE)) {
16✔
402
            return false;
9✔
403
        }
404

405
        $instanceOfIndex = $tokens->getNextMeaningfulToken($variableIndex);
7✔
406

407
        if (!$tokens[$instanceOfIndex]->isGivenKind(\T_INSTANCEOF)) {
7✔
408
            return false;
1✔
409
        }
410

411
        $classEndIndex = $instanceOfIndex;
6✔
412
        $classPartTokens = [];
6✔
413

414
        do {
415
            $classEndIndex = $tokens->getNextMeaningfulToken($classEndIndex);
6✔
416
            $classPartTokens[] = $tokens[$classEndIndex];
6✔
417
        } while ($tokens[$classEndIndex]->isGivenKind([\T_STRING, \T_NS_SEPARATOR, \T_VARIABLE]));
6✔
418

419
        if ($tokens[$classEndIndex]->equalsAny([',', ')'])) { // do the fixing
6✔
420
            array_pop($classPartTokens);
5✔
421
            $isInstanceOfVar = reset($classPartTokens)->isGivenKind(\T_VARIABLE);
5✔
422
            $insertIndex = $testIndex - 1;
5✔
423
            $newTokens = [];
5✔
424

425
            foreach ($classPartTokens as $token) {
5✔
426
                $newTokens[++$insertIndex] = clone $token;
5✔
427
            }
428

429
            if (!$isInstanceOfVar) {
5✔
430
                $newTokens[++$insertIndex] = new Token([\T_DOUBLE_COLON, '::']);
4✔
431
                $newTokens[++$insertIndex] = new Token([CT::T_CLASS_CONSTANT, 'class']);
4✔
432
            }
433

434
            $newTokens[++$insertIndex] = new Token(',');
5✔
435
            $newTokens[++$insertIndex] = new Token([\T_WHITESPACE, ' ']);
5✔
436
            $newTokens[++$insertIndex] = clone $tokens[$variableIndex];
5✔
437

438
            for ($i = $classEndIndex - 1; $i >= $testIndex; --$i) {
5✔
439
                if (!$tokens[$i]->isComment()) {
5✔
440
                    $tokens->clearTokenAndMergeSurroundingWhitespace($i);
5✔
441
                }
442
            }
443

444
            $name = $isPositiveAssertion && $isPositiveCondition || !$isPositiveAssertion && !$isPositiveCondition
5✔
445
                ? 'assertInstanceOf'
4✔
446
                : 'assertNotInstanceOf';
2✔
447

448
            $tokens->insertSlices($newTokens);
5✔
449
            $tokens[$assertCall['index']] = new Token([\T_STRING, $name]);
5✔
450
        }
451

452
        return true;
6✔
453
    }
454

455
    /**
456
     * @param array{
457
     *     index: int,
458
     *     loweredName: string,
459
     *     openBraceIndex: int,
460
     *     closeBraceIndex: int,
461
     * } $assertCall
462
     */
463
    private function fixAssertSameEquals(Tokens $tokens, array $assertCall): void
464
    {
465
        // @ $this->/self::assertEquals/Same([$nextIndex])
466
        $expectedIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']);
38✔
467

468
        // do not fix
469
        // let $a = [1,2]; $b = "2";
470
        // "$this->assertEquals("2", count($a)); $this->assertEquals($b, count($a)); $this->assertEquals(2.1, count($a));"
471

472
        if ($tokens[$expectedIndex]->isGivenKind(\T_VARIABLE)) {
38✔
473
            if (!$tokens[$tokens->getNextMeaningfulToken($expectedIndex)]->equals(',')) {
4✔
474
                return;
2✔
475
            }
476
        } elseif (!$tokens[$expectedIndex]->isGivenKind([\T_LNUMBER, \T_VARIABLE])) {
34✔
477
            return;
4✔
478
        }
479

480
        // @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex])
481
        $commaIndex = $tokens->getNextMeaningfulToken($expectedIndex);
32✔
482

483
        if (!$tokens[$commaIndex]->equals(',')) {
32✔
484
            return;
2✔
485
        }
486

487
        // @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex,$countCallIndex])
488
        $countCallIndex = $tokens->getNextMeaningfulToken($commaIndex);
30✔
489

490
        if ($tokens[$countCallIndex]->isGivenKind(\T_NS_SEPARATOR)) {
30✔
491
            $defaultNamespaceTokenIndex = $countCallIndex;
6✔
492
            $countCallIndex = $tokens->getNextMeaningfulToken($countCallIndex);
6✔
493
        } else {
494
            $defaultNamespaceTokenIndex = null;
26✔
495
        }
496

497
        if (!$tokens[$countCallIndex]->isGivenKind(\T_STRING)) {
30✔
498
            return;
2✔
499
        }
500

501
        $lowerContent = strtolower($tokens[$countCallIndex]->getContent());
28✔
502

503
        if (!\in_array($lowerContent, ['count', 'sizeof'], true)) {
28✔
504
            return; // not a call to "count" or "sizeOf"
2✔
505
        }
506

507
        // @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex,[$defaultNamespaceTokenIndex,]$countCallIndex,$countCallOpenBraceIndex])
508
        $countCallOpenBraceIndex = $tokens->getNextMeaningfulToken($countCallIndex);
28✔
509

510
        if (!$tokens[$countCallOpenBraceIndex]->equals('(')) {
28✔
511
            return;
2✔
512
        }
513

514
        $countCallCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $countCallOpenBraceIndex);
26✔
515
        $afterCountCallCloseBraceIndex = $tokens->getNextMeaningfulToken($countCallCloseBraceIndex);
26✔
516

517
        if (!$tokens[$afterCountCallCloseBraceIndex]->equalsAny([')', ','])) {
26✔
518
            return;
2✔
519
        }
520

521
        $this->removeFunctionCall(
24✔
522
            $tokens,
24✔
523
            $defaultNamespaceTokenIndex,
24✔
524
            $countCallIndex,
24✔
525
            $countCallOpenBraceIndex,
24✔
526
            $countCallCloseBraceIndex
24✔
527
        );
24✔
528

529
        $tokens[$assertCall['index']] = new Token([
24✔
530
            \T_STRING,
24✔
531
            false === strpos($assertCall['loweredName'], 'not', 6) ? 'assertCount' : 'assertNotCount',
24✔
532
        ]);
24✔
533
    }
534

535
    private function removeFunctionCall(Tokens $tokens, ?int $callNSIndex, int $callIndex, int $openIndex, int $closeIndex): void
536
    {
537
        $tokens->clearTokenAndMergeSurroundingWhitespace($callIndex);
50✔
538

539
        if (null !== $callNSIndex) {
50✔
540
            $tokens->clearTokenAndMergeSurroundingWhitespace($callNSIndex);
12✔
541
        }
542

543
        $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex);
50✔
544
        $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex);
50✔
545

546
        if ($tokens[$commaIndex]->equals(',')) {
50✔
547
            $tokens->removeTrailingWhitespace($commaIndex);
5✔
548
            $tokens->clearAt($commaIndex);
5✔
549
        }
550

551
        $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex);
50✔
552
    }
553

554
    /**
555
     * @param array<int, int> $argumentsIndices
556
     */
557
    private function swapArguments(Tokens $tokens, array $argumentsIndices): void
558
    {
559
        [$firstArgumentIndex, $secondArgumentIndex] = array_keys($argumentsIndices);
8✔
560

561
        $firstArgumentEndIndex = $argumentsIndices[$firstArgumentIndex];
8✔
562
        $secondArgumentEndIndex = $argumentsIndices[$secondArgumentIndex];
8✔
563

564
        $firstClone = $this->cloneAndClearTokens($tokens, $firstArgumentIndex, $firstArgumentEndIndex);
8✔
565
        $secondClone = $this->cloneAndClearTokens($tokens, $secondArgumentIndex, $secondArgumentEndIndex);
8✔
566

567
        if (!$firstClone[0]->isWhitespace()) {
8✔
568
            array_unshift($firstClone, new Token([\T_WHITESPACE, ' ']));
8✔
569
        }
570

571
        $tokens->insertAt($secondArgumentIndex, $firstClone);
8✔
572

573
        if ($secondClone[0]->isWhitespace()) {
8✔
574
            array_shift($secondClone);
7✔
575
        }
576

577
        $tokens->insertAt($firstArgumentIndex, $secondClone);
8✔
578
    }
579

580
    /**
581
     * @return list<Token>
582
     */
583
    private function cloneAndClearTokens(Tokens $tokens, int $start, int $end): array
584
    {
585
        $clone = [];
8✔
586

587
        for ($i = $start; $i <= $end; ++$i) {
8✔
588
            if ('' === $tokens[$i]->getContent()) {
8✔
589
                continue;
1✔
590
            }
591

592
            $clone[] = clone $tokens[$i];
8✔
593
            $tokens->clearAt($i);
8✔
594
        }
595

596
        return $clone;
8✔
597
    }
598
}
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