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

keradus / PHP-CS-Fixer / 22042339290

15 Feb 2026 08:14PM UTC coverage: 92.957% (-0.2%) from 93.171%
22042339290

push

github

keradus
test: check PHP env in CI jobs

29302 of 31522 relevant lines covered (92.96%)

44.04 hits per line

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

87.5
/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.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\ConfigurationException\InvalidFixerConfigurationException;
18
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
19
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
20
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
22
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
23
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
24
use PhpCsFixer\FixerDefinition\CodeSample;
25
use PhpCsFixer\FixerDefinition\FixerDefinition;
26
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
27
use PhpCsFixer\Future;
28
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
29
use PhpCsFixer\Tokenizer\CT;
30
use PhpCsFixer\Tokenizer\Token;
31
use PhpCsFixer\Tokenizer\Tokens;
32
use PhpCsFixer\Tokenizer\TokensAnalyzer;
33
use PhpCsFixer\Utils;
34
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
35

36
/**
37
 * @phpstan-type _AutogeneratedInputConfiguration array{
38
 *  call_type?: 'self'|'static'|'this',
39
 *  methods?: array<string, string>,
40
 *  target?: '10.0'|'11.0'|'newest',
41
 * }
42
 * @phpstan-type _AutogeneratedComputedConfiguration array{
43
 *  call_type: 'self'|'static'|'this',
44
 *  methods: array<string, string>,
45
 *  target: '10.0'|'11.0'|'newest',
46
 * }
47
 *
48
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
49
 *
50
 * @phpstan-import-type _PhpTokenArray from Token
51
 *
52
 * @author Filippo Tessarotto <zoeslam@gmail.com>
53
 *
54
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
55
 */
56
final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
57
{
58
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
59
    use ConfigurableFixerTrait;
60

61
    /**
62
     * @internal
63
     */
64
    public const CALL_TYPE_THIS = 'this';
65

66
    /**
67
     * @internal
68
     */
69
    public const CALL_TYPE_SELF = 'self';
70

71
    /**
72
     * @internal
73
     */
74
    public const CALL_TYPE_STATIC = 'static';
75

76
    /**
77
     * @var array<string, true>
78
     */
79
    private const METHODS = [
80
        // Assert methods
81
        'anything' => true,
82
        'arrayHasKey' => true,
83
        'assertArrayHasKey' => true,
84
        'assertArrayIsEqualToArrayIgnoringListOfKeys' => true,
85
        'assertArrayIsEqualToArrayOnlyConsideringListOfKeys' => true,
86
        'assertArrayIsIdenticalToArrayIgnoringListOfKeys' => true,
87
        'assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys' => true,
88
        'assertArrayNotHasKey' => true,
89
        'assertArraySubset' => true,
90
        'assertArraysAreIdentical' => true,
91
        'assertArraysAreIdenticalIgnoringOrder' => true,
92
        'assertArraysHaveIdenticalValues' => true,
93
        'assertArraysHaveIdenticalValuesIgnoringOrder' => true,
94
        'assertArraysAreEqual' => true,
95
        'assertArraysAreEqualIgnoringOrder' => true,
96
        'assertArraysHaveEqualValues' => true,
97
        'assertArraysHaveEqualValuesIgnoringOrder' => true,
98
        'assertAttributeContains' => true,
99
        'assertAttributeContainsOnly' => true,
100
        'assertAttributeCount' => true,
101
        'assertAttributeEmpty' => true,
102
        'assertAttributeEquals' => true,
103
        'assertAttributeGreaterThan' => true,
104
        'assertAttributeGreaterThanOrEqual' => true,
105
        'assertAttributeInstanceOf' => true,
106
        'assertAttributeInternalType' => true,
107
        'assertAttributeLessThan' => true,
108
        'assertAttributeLessThanOrEqual' => true,
109
        'assertAttributeNotContains' => true,
110
        'assertAttributeNotContainsOnly' => true,
111
        'assertAttributeNotCount' => true,
112
        'assertAttributeNotEmpty' => true,
113
        'assertAttributeNotEquals' => true,
114
        'assertAttributeNotInstanceOf' => true,
115
        'assertAttributeNotInternalType' => true,
116
        'assertAttributeNotSame' => true,
117
        'assertAttributeSame' => true,
118
        'assertClassHasAttribute' => true,
119
        'assertClassHasStaticAttribute' => true,
120
        'assertClassNotHasAttribute' => true,
121
        'assertClassNotHasStaticAttribute' => true,
122
        'assertContains' => true,
123
        'assertContainsEquals' => true,
124
        'assertContainsNotOnlyArray' => true,
125
        'assertContainsNotOnlyBool' => true,
126
        'assertContainsNotOnlyCallable' => true,
127
        'assertContainsNotOnlyClosedResource' => true,
128
        'assertContainsNotOnlyFloat' => true,
129
        'assertContainsNotOnlyInstancesOf' => true,
130
        'assertContainsNotOnlyInt' => true,
131
        'assertContainsNotOnlyIterable' => true,
132
        'assertContainsNotOnlyNull' => true,
133
        'assertContainsNotOnlyNumeric' => true,
134
        'assertContainsNotOnlyObject' => true,
135
        'assertContainsNotOnlyResource' => true,
136
        'assertContainsNotOnlyScalar' => true,
137
        'assertContainsNotOnlyString' => true,
138
        'assertContainsOnly' => true,
139
        'assertContainsOnlyArray' => true,
140
        'assertContainsOnlyBool' => true,
141
        'assertContainsOnlyCallable' => true,
142
        'assertContainsOnlyClosedResource' => true,
143
        'assertContainsOnlyFloat' => true,
144
        'assertContainsOnlyInstancesOf' => true,
145
        'assertContainsOnlyInt' => true,
146
        'assertContainsOnlyIterable' => true,
147
        'assertContainsOnlyNull' => true,
148
        'assertContainsOnlyNumeric' => true,
149
        'assertContainsOnlyObject' => true,
150
        'assertContainsOnlyResource' => true,
151
        'assertContainsOnlyScalar' => true,
152
        'assertContainsOnlyString' => true,
153
        'assertCount' => true,
154
        'assertDirectoryDoesNotExist' => true,
155
        'assertDirectoryExists' => true,
156
        'assertDirectoryIsNotReadable' => true,
157
        'assertDirectoryIsNotWritable' => true,
158
        'assertDirectoryIsReadable' => true,
159
        'assertDirectoryIsWritable' => true,
160
        'assertDirectoryNotExists' => true,
161
        'assertDirectoryNotIsReadable' => true,
162
        'assertDirectoryNotIsWritable' => true,
163
        'assertDoesNotMatchRegularExpression' => true,
164
        'assertEmpty' => true,
165
        'assertEquals' => true,
166
        'assertEqualsCanonicalizing' => true,
167
        'assertEqualsIgnoringCase' => true,
168
        'assertEqualsWithDelta' => true,
169
        'assertEqualXMLStructure' => true,
170
        'assertFalse' => true,
171
        'assertFileDoesNotExist' => true,
172
        'assertFileEquals' => true,
173
        'assertFileEqualsCanonicalizing' => true,
174
        'assertFileEqualsIgnoringCase' => true,
175
        'assertFileExists' => true,
176
        'assertFileIsNotReadable' => true,
177
        'assertFileIsNotWritable' => true,
178
        'assertFileIsReadable' => true,
179
        'assertFileIsWritable' => true,
180
        'assertFileMatchesFormat' => true,
181
        'assertFileMatchesFormatFile' => true,
182
        'assertFileNotEquals' => true,
183
        'assertFileNotEqualsCanonicalizing' => true,
184
        'assertFileNotEqualsIgnoringCase' => true,
185
        'assertFileNotExists' => true,
186
        'assertFileNotIsReadable' => true,
187
        'assertFileNotIsWritable' => true,
188
        'assertFinite' => true,
189
        'assertGreaterThan' => true,
190
        'assertGreaterThanOrEqual' => true,
191
        'assertInfinite' => true,
192
        'assertInstanceOf' => true,
193
        'assertInternalType' => true,
194
        'assertIsArray' => true,
195
        'assertIsBool' => true,
196
        'assertIsCallable' => true,
197
        'assertIsClosedResource' => true,
198
        'assertIsFloat' => true,
199
        'assertIsInt' => true,
200
        'assertIsIterable' => true,
201
        'assertIsList' => true,
202
        'assertIsNotArray' => true,
203
        'assertIsNotBool' => true,
204
        'assertIsNotCallable' => true,
205
        'assertIsNotClosedResource' => true,
206
        'assertIsNotFloat' => true,
207
        'assertIsNotInt' => true,
208
        'assertIsNotIterable' => true,
209
        'assertIsNotNumeric' => true,
210
        'assertIsNotObject' => true,
211
        'assertIsNotReadable' => true,
212
        'assertIsNotResource' => true,
213
        'assertIsNotScalar' => true,
214
        'assertIsNotString' => true,
215
        'assertIsNotWritable' => true,
216
        'assertIsNumeric' => true,
217
        'assertIsObject' => true,
218
        'assertIsReadable' => true,
219
        'assertIsResource' => true,
220
        'assertIsScalar' => true,
221
        'assertIsString' => true,
222
        'assertIsWritable' => true,
223
        'assertJson' => true,
224
        'assertJsonFileEqualsJsonFile' => true,
225
        'assertJsonFileNotEqualsJsonFile' => true,
226
        'assertJsonStringEqualsJsonFile' => true,
227
        'assertJsonStringEqualsJsonString' => true,
228
        'assertJsonStringNotEqualsJsonFile' => true,
229
        'assertJsonStringNotEqualsJsonString' => true,
230
        'assertLessThan' => true,
231
        'assertLessThanOrEqual' => true,
232
        'assertMatchesRegularExpression' => true,
233
        'assertNan' => true,
234
        'assertNotContains' => true,
235
        'assertNotContainsEquals' => true,
236
        'assertNotContainsOnly' => true,
237
        'assertNotCount' => true,
238
        'assertNotEmpty' => true,
239
        'assertNotEquals' => true,
240
        'assertNotEqualsCanonicalizing' => true,
241
        'assertNotEqualsIgnoringCase' => true,
242
        'assertNotEqualsWithDelta' => true,
243
        'assertNotFalse' => true,
244
        'assertNotInstanceOf' => true,
245
        'assertNotInternalType' => true,
246
        'assertNotIsReadable' => true,
247
        'assertNotIsWritable' => true,
248
        'assertNotNull' => true,
249
        'assertNotRegExp' => true,
250
        'assertNotSame' => true,
251
        'assertNotSameSize' => true,
252
        'assertNotTrue' => true,
253
        'assertNull' => true,
254
        'assertObjectEquals' => true,
255
        'assertObjectHasAttribute' => true,
256
        'assertObjectHasProperty' => true,
257
        'assertObjectNotEquals' => true,
258
        'assertObjectNotHasAttribute' => true,
259
        'assertObjectNotHasProperty' => true,
260
        'assertRegExp' => true,
261
        'assertSame' => true,
262
        'assertSameSize' => true,
263
        'assertStringContainsString' => true,
264
        'assertStringContainsStringIgnoringCase' => true,
265
        'assertStringContainsStringIgnoringLineEndings' => true,
266
        'assertStringEndsNotWith' => true,
267
        'assertStringEndsWith' => true,
268
        'assertStringEqualsFile' => true,
269
        'assertStringEqualsFileCanonicalizing' => true,
270
        'assertStringEqualsFileIgnoringCase' => true,
271
        'assertStringEqualsStringIgnoringLineEndings' => true,
272
        'assertStringMatchesFormat' => true,
273
        'assertStringMatchesFormatFile' => true,
274
        'assertStringNotContainsString' => true,
275
        'assertStringNotContainsStringIgnoringCase' => true,
276
        'assertStringNotEqualsFile' => true,
277
        'assertStringNotEqualsFileCanonicalizing' => true,
278
        'assertStringNotEqualsFileIgnoringCase' => true,
279
        'assertStringNotMatchesFormat' => true,
280
        'assertStringNotMatchesFormatFile' => true,
281
        'assertStringStartsNotWith' => true,
282
        'assertStringStartsWith' => true,
283
        'assertThat' => true,
284
        'assertTrue' => true,
285
        'assertXmlFileEqualsXmlFile' => true,
286
        'assertXmlFileNotEqualsXmlFile' => true,
287
        'assertXmlStringEqualsXmlFile' => true,
288
        'assertXmlStringEqualsXmlString' => true,
289
        'assertXmlStringNotEqualsXmlFile' => true,
290
        'assertXmlStringNotEqualsXmlString' => true,
291
        'attribute' => true,
292
        'attributeEqualTo' => true,
293
        'callback' => true,
294
        'classHasAttribute' => true,
295
        'classHasStaticAttribute' => true,
296
        'contains' => true,
297
        'containsEqual' => true,
298
        'containsIdentical' => true,
299
        'containsOnly' => true,
300
        'containsOnlyArray' => true,
301
        'containsOnlyBool' => true,
302
        'containsOnlyCallable' => true,
303
        'containsOnlyClosedResource' => true,
304
        'containsOnlyFloat' => true,
305
        'containsOnlyInstancesOf' => true,
306
        'containsOnlyInt' => true,
307
        'containsOnlyIterable' => true,
308
        'containsOnlyNull' => true,
309
        'containsOnlyNumeric' => true,
310
        'containsOnlyObject' => true,
311
        'containsOnlyResource' => true,
312
        'containsOnlyScalar' => true,
313
        'containsOnlyString' => true,
314
        'countOf' => true,
315
        'directoryExists' => true,
316
        'equalTo' => true,
317
        'equalToCanonicalizing' => true,
318
        'equalToIgnoringCase' => true,
319
        'equalToWithDelta' => true,
320
        'fail' => true,
321
        'fileExists' => true,
322
        'getCount' => true,
323
        'getObjectAttribute' => true,
324
        'getStaticAttribute' => true,
325
        'greaterThan' => true,
326
        'greaterThanOrEqual' => true,
327
        'identicalTo' => true,
328
        'isArray' => true,
329
        'isBool' => true,
330
        'isCallable' => true,
331
        'isClosedResource' => true,
332
        'isEmpty' => true,
333
        'isFalse' => true,
334
        'isFinite' => true,
335
        'isFloat' => true,
336
        'isInfinite' => true,
337
        'isInstanceOf' => true,
338
        'isInt' => true,
339
        'isIterable' => true,
340
        'isJson' => true,
341
        'isList' => true,
342
        'isNan' => true,
343
        'isNull' => true,
344
        'isNumeric' => true,
345
        'isObject' => true,
346
        'isReadable' => true,
347
        'isResource' => true,
348
        'isScalar' => true,
349
        'isString' => true,
350
        'isTrue' => true,
351
        'isType' => true,
352
        'isWritable' => true,
353
        'lessThan' => true,
354
        'lessThanOrEqual' => true,
355
        'logicalAnd' => true,
356
        'logicalNot' => true,
357
        'logicalOr' => true,
358
        'logicalXor' => true,
359
        'markTestIncomplete' => true,
360
        'markTestSkipped' => true,
361
        'matches' => true,
362
        'matchesRegularExpression' => true,
363
        'objectEquals' => true,
364
        'objectHasAttribute' => true,
365
        'readAttribute' => true,
366
        'resetCount' => true,
367
        'stringContains' => true,
368
        'stringEndsWith' => true,
369
        'stringEqualsStringIgnoringLineEndings' => true,
370
        'stringStartsWith' => true,
371

372
        // TestCase methods
373
        'any' => true,
374
        'at' => true,
375
        'atLeast' => true,
376
        'atLeastOnce' => true,
377
        'atMost' => true,
378
        'createStub' => true,
379
        'createConfiguredStub' => true,
380
        'createStubForIntersectionOfInterfaces' => true,
381
        'exactly' => true,
382
        'getStubBuilder' => true,
383
        'never' => true,
384
        'once' => true,
385
        'onConsecutiveCalls' => true,
386
        'returnArgument' => true,
387
        'returnCallback' => true,
388
        'returnSelf' => true,
389
        'returnValue' => true,
390
        'returnValueMap' => true,
391
        'setUpBeforeClass' => true,
392
        'tearDownAfterClass' => true,
393
        'throwException' => true,
394
    ];
395

396
    /**
397
     * @var array<string, bool>
398
     */
399
    private const ALLOWED_VALUES = [
400
        self::CALL_TYPE_THIS => true,
401
        self::CALL_TYPE_SELF => true,
402
        self::CALL_TYPE_STATIC => true,
403
    ];
404

405
    /**
406
     * @var non-empty-array<string, non-empty-list<_PhpTokenArray>>
407
     */
408
    private array $conversionMap = [
409
        self::CALL_TYPE_THIS => [[\T_OBJECT_OPERATOR, '->'], [\T_VARIABLE, '$this']],
410
        self::CALL_TYPE_SELF => [[\T_DOUBLE_COLON, '::'], [\T_STRING, 'self']],
411
        self::CALL_TYPE_STATIC => [[\T_DOUBLE_COLON, '::'], [\T_STATIC, 'static']],
412
    ];
413

414
    public function getDefinition(): FixerDefinitionInterface
415
    {
416
        $codeSample = <<<'PHP'
3✔
417
            <?php
418
            final class MyTest extends \PHPUnit_Framework_TestCase
419
            {
420
                public function testMe()
421
                {
422
                    $this->assertSame(1, 2);
423
                    self::assertSame(1, 2);
424
                    static::assertSame(1, 2);
425
                    static::assertTrue(false);
426
                }
427
            }
428

429
            PHP;
3✔
430

431
        return new FixerDefinition(
3✔
432
            'Calls to `PHPUnit\Framework\TestCase` static methods (like assertions) must all be of the same type, either `$this->`, `self::` or `static::`.',
3✔
433
            [
3✔
434
                new CodeSample($codeSample),
3✔
435
                new CodeSample($codeSample, ['call_type' => self::CALL_TYPE_THIS]),
3✔
436
                new CodeSample($codeSample, ['methods' => ['assertTrue' => self::CALL_TYPE_THIS]]),
3✔
437
            ],
3✔
438
            null,
3✔
439
            'Risky when PHPUnit methods are overridden or not accessible, or when project has PHPUnit incompatibilities.',
3✔
440
        );
3✔
441
    }
442

443
    /**
444
     * {@inheritdoc}
445
     *
446
     * Must run before SelfStaticAccessorFixer.
447
     */
448
    public function getPriority(): int
449
    {
450
        return 0;
1✔
451
    }
452

453
    public function isRisky(): bool
454
    {
455
        return true;
1✔
456
    }
457

458
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
459
    {
460
        return new FixerConfigurationResolver([
30✔
461
            (new FixerOptionBuilder('call_type', 'The call type to use for referring to PHPUnit methods.'))
30✔
462
                ->setAllowedTypes(['string'])
30✔
463
                ->setAllowedValues(array_keys(self::ALLOWED_VALUES))
30✔
464
                ->setDefault(Future::getV4OrV3(self::CALL_TYPE_THIS, self::CALL_TYPE_STATIC)) // vide https://github.com/sebastianbergmann/phpunit/issues/2104#issuecomment-192919598
30✔
465
                ->getOption(),
30✔
466
            (new FixerOptionBuilder('methods', 'Dictionary of `method` => `call_type` values that differ from the default strategy.'))
30✔
467
                ->setAllowedTypes(['array<string, string>'])
30✔
468
                ->setAllowedValues([static function (array $option): bool {
30✔
469
                    foreach ($option as $method => $value) {
30✔
470
                        if (!isset(self::METHODS[$method])) {
3✔
471
                            throw new InvalidOptionsException(
×
472
                                \sprintf(
×
473
                                    'Unexpected "methods" key, expected any of %s, got "%s".',
×
474
                                    Utils::naturalLanguageJoin(array_keys(self::METHODS)),
×
475
                                    \gettype($method).'#'.$method,
×
476
                                ),
×
477
                            );
×
478
                        }
479

480
                        if (!isset(self::ALLOWED_VALUES[$value])) {
3✔
481
                            throw new InvalidOptionsException(
×
482
                                \sprintf(
×
483
                                    'Unexpected value for method "%s", expected any of %s, got "%s".',
×
484
                                    $method,
×
485
                                    Utils::naturalLanguageJoin(array_keys(self::ALLOWED_VALUES)),
×
486
                                    \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value),
×
487
                                ),
×
488
                            );
×
489
                        }
490
                    }
491

492
                    return true;
30✔
493
                }])
30✔
494
                ->setDefault([])
30✔
495
                ->getOption(),
30✔
496
            (new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
30✔
497
                ->setAllowedTypes(['string'])
30✔
498
                ->setAllowedValues([
30✔
499
                    PhpUnitTargetVersion::VERSION_10_0,
30✔
500
                    PhpUnitTargetVersion::VERSION_11_0,
30✔
501
                    PhpUnitTargetVersion::VERSION_NEWEST,
30✔
502
                ])
30✔
503
                ->setDefault(Future::getV4OrV3(PhpUnitTargetVersion::VERSION_NEWEST, PhpUnitTargetVersion::VERSION_10_0))
30✔
504
                ->getOption(),
30✔
505
        ]);
30✔
506
    }
507

508
    protected function configurePostNormalisation(): void
509
    {
510
        $dynamicMethods = [];
30✔
511
        if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_11_0)) {
30✔
512
            // not statc since v11
513
            $dynamicMethods = [
2✔
514
                'any',
2✔
515
                'atLeast',
2✔
516
                'atLeastOnce',
2✔
517
                'atMost',
2✔
518
                'exactly',
2✔
519
                'never',
2✔
520
                'once',
2✔
521
                'throwException',
2✔
522
            ];
2✔
523
        }
524
        if (PhpUnitTargetVersion::VERSION_11_0 === $this->configuration['target']) {
30✔
525
            // not static since v11, removed in v12
526
            $dynamicMethods[] = 'onConsecutiveCalls';
2✔
527
            $dynamicMethods[] = 'returnArgument';
2✔
528
            $dynamicMethods[] = 'returnCallback';
2✔
529
            $dynamicMethods[] = 'returnSelf';
2✔
530
            $dynamicMethods[] = 'returnValue';
2✔
531
            $dynamicMethods[] = 'returnValueMap';
2✔
532
        }
533

534
        foreach ($dynamicMethods as $method) {
30✔
535
            if (isset($this->configuration['methods'][$method])) {
2✔
536
                throw new InvalidFixerConfigurationException(
1✔
537
                    $this->getName(),
1✔
538
                    \sprintf('Configuration cannot contain method "%s" and target "%s", it is dynamic in that PHPUnit version.', $method, $this->configuration['target']),
1✔
539
                );
1✔
540
            }
541

542
            $this->configuration['methods'][$method] = self::CALL_TYPE_THIS;
2✔
543
        }
544
    }
545

546
    protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
547
    {
548
        $analyzer = new TokensAnalyzer($tokens);
16✔
549

550
        for ($index = $startIndex; $index < $endIndex; ++$index) {
16✔
551
            // skip anonymous classes
552
            if ($tokens[$index]->isGivenKind(\T_CLASS)) {
16✔
553
                $index = $this->findEndOfNextBlock($tokens, $index);
1✔
554

555
                continue;
1✔
556
            }
557

558
            $callType = $this->configuration['call_type'];
16✔
559

560
            if ($tokens[$index]->isGivenKind(\T_FUNCTION)) {
16✔
561
                // skip lambda
562
                if ($analyzer->isLambda($index)) {
16✔
563
                    $index = $this->findEndOfNextBlock($tokens, $index);
3✔
564

565
                    continue;
3✔
566
                }
567

568
                // do not change `self` to `this` in static methods
569
                if (self::CALL_TYPE_THIS === $callType) {
16✔
570
                    $attributes = $analyzer->getMethodAttributes($index);
6✔
571

572
                    if (false !== $attributes['static']) {
6✔
573
                        $index = $this->findEndOfNextBlock($tokens, $index);
2✔
574

575
                        continue;
2✔
576
                    }
577
                }
578
            }
579

580
            if (!$tokens[$index]->isGivenKind(\T_STRING) || !isset(self::METHODS[$tokens[$index]->getContent()])) {
16✔
581
                continue;
16✔
582
            }
583

584
            $nextIndex = $tokens->getNextMeaningfulToken($index);
14✔
585

586
            if (!$tokens[$nextIndex]->equals('(')) {
14✔
587
                $index = $nextIndex;
1✔
588

589
                continue;
1✔
590
            }
591

592
            if ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) {
14✔
593
                continue;
1✔
594
            }
595

596
            $methodName = $tokens[$index]->getContent();
13✔
597

598
            if (isset($this->configuration['methods'][$methodName])) {
13✔
599
                $callType = $this->configuration['methods'][$methodName];
2✔
600
            }
601

602
            $operatorIndex = $tokens->getPrevMeaningfulToken($index);
13✔
603
            $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex);
13✔
604

605
            if (!$this->needsConversion($tokens, $index, $referenceIndex, $callType)) {
13✔
606
                continue;
13✔
607
            }
608

609
            $tokens[$operatorIndex] = new Token($this->conversionMap[$callType][0]);
11✔
610
            $tokens[$referenceIndex] = new Token($this->conversionMap[$callType][1]);
11✔
611
        }
612
    }
613

614
    private function needsConversion(Tokens $tokens, int $index, int $referenceIndex, string $callType): bool
615
    {
616
        $functionsAnalyzer = new FunctionsAnalyzer();
13✔
617

618
        return $functionsAnalyzer->isTheSameClassCall($tokens, $index)
13✔
619
            && !$tokens[$referenceIndex]->equals($this->conversionMap[$callType][1], false);
13✔
620
    }
621

622
    private function findEndOfNextBlock(Tokens $tokens, int $index): int
623
    {
624
        $nextIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
5✔
625

626
        return $tokens[$nextIndex]->equals('{')
5✔
627
            ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex)
4✔
628
            : $nextIndex;
5✔
629
    }
630
}
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