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

keradus / PHP-CS-Fixer / 17279562118

27 Aug 2025 09:47PM UTC coverage: 94.693%. Remained the same
17279562118

push

github

keradus
CS

28316 of 29903 relevant lines covered (94.69%)

45.61 hits per line

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

96.67
/src/Fixer/Import/GlobalNamespaceImportFixer.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\Import;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\DocBlock\Annotation;
19
use PhpCsFixer\DocBlock\DocBlock;
20
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
21
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
22
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
23
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
24
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
25
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
26
use PhpCsFixer\FixerDefinition\CodeSample;
27
use PhpCsFixer\FixerDefinition\FixerDefinition;
28
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
29
use PhpCsFixer\Preg;
30
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
31
use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer;
32
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
33
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
34
use PhpCsFixer\Tokenizer\CT;
35
use PhpCsFixer\Tokenizer\Processor\ImportProcessor;
36
use PhpCsFixer\Tokenizer\Token;
37
use PhpCsFixer\Tokenizer\Tokens;
38
use PhpCsFixer\Tokenizer\TokensAnalyzer;
39

40
/**
41
 * @phpstan-type _AutogeneratedInputConfiguration array{
42
 *  import_classes?: bool|null,
43
 *  import_constants?: bool|null,
44
 *  import_functions?: bool|null,
45
 * }
46
 * @phpstan-type _AutogeneratedComputedConfiguration array{
47
 *  import_classes: bool|null,
48
 *  import_constants: bool|null,
49
 *  import_functions: bool|null,
50
 * }
51
 *
52
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
53
 *
54
 * @author Gregor Harlan <gharlan@web.de>
55
 *
56
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
57
 */
58
final class GlobalNamespaceImportFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
59
{
60
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
61
    use ConfigurableFixerTrait;
62

63
    private ImportProcessor $importProcessor;
64

65
    public function __construct()
66
    {
67
        parent::__construct();
79✔
68

69
        $this->importProcessor = new ImportProcessor($this->whitespacesConfig);
79✔
70
    }
71

72
    public function getDefinition(): FixerDefinitionInterface
73
    {
74
        return new FixerDefinition(
3✔
75
            'Imports or fully qualifies global classes/functions/constants.',
3✔
76
            [
3✔
77
                new CodeSample(
3✔
78
                    '<?php
3✔
79

80
namespace Foo;
81

82
$d = new \DateTimeImmutable();
83
'
3✔
84
                ),
3✔
85
                new CodeSample(
3✔
86
                    '<?php
3✔
87

88
namespace Foo;
89

90
if (\count($x)) {
91
    /** @var \DateTimeImmutable $d */
92
    $d = new \DateTimeImmutable();
93
    $p = \M_PI;
94
}
95
',
3✔
96
                    ['import_classes' => true, 'import_constants' => true, 'import_functions' => true]
3✔
97
                ),
3✔
98
                new CodeSample(
3✔
99
                    '<?php
3✔
100

101
namespace Foo;
102

103
use DateTimeImmutable;
104
use function count;
105
use const M_PI;
106

107
if (count($x)) {
108
    /** @var DateTimeImmutable $d */
109
    $d = new DateTimeImmutable();
110
    $p = M_PI;
111
}
112
',
3✔
113
                    ['import_classes' => false, 'import_constants' => false, 'import_functions' => false]
3✔
114
                ),
3✔
115
            ]
3✔
116
        );
3✔
117
    }
118

119
    /**
120
     * {@inheritdoc}
121
     *
122
     * Must run before NoUnusedImportsFixer, OrderedImportsFixer, StatementIndentationFixer.
123
     * Must run after NativeConstantInvocationFixer, NativeFunctionInvocationFixer.
124
     */
125
    public function getPriority(): int
126
    {
127
        return 0;
1✔
128
    }
129

130
    public function isCandidate(Tokens $tokens): bool
131
    {
132
        return $tokens->isAnyTokenKindsFound([\T_DOC_COMMENT, \T_NS_SEPARATOR, \T_USE])
70✔
133
            && $tokens->isTokenKindFound(\T_NAMESPACE)
70✔
134
            && 1 === $tokens->countTokenKind(\T_NAMESPACE)
70✔
135
            && $tokens->isMonolithicPhp();
70✔
136
    }
137

138
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
139
    {
140
        $namespaceAnalyses = $tokens->getNamespaceDeclarations();
59✔
141

142
        if (1 !== \count($namespaceAnalyses) || $namespaceAnalyses[0]->isGlobalNamespace()) {
59✔
143
            return;
1✔
144
        }
145

146
        $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens);
58✔
147

148
        $newImports = [];
58✔
149

150
        if (true === $this->configuration['import_constants']) {
58✔
151
            $newImports['const'] = $this->importConstants($tokens, $useDeclarations);
16✔
152
        } elseif (false === $this->configuration['import_constants']) {
43✔
153
            $this->fullyQualifyConstants($tokens, $useDeclarations);
3✔
154
        }
155

156
        if (true === $this->configuration['import_functions']) {
58✔
157
            $newImports['function'] = $this->importFunctions($tokens, $useDeclarations);
16✔
158
        } elseif (false === $this->configuration['import_functions']) {
43✔
159
            $this->fullyQualifyFunctions($tokens, $useDeclarations);
3✔
160
        }
161

162
        if (true === $this->configuration['import_classes']) {
58✔
163
            $newImports['class'] = $this->importClasses($tokens, $useDeclarations);
51✔
164
        } elseif (false === $this->configuration['import_classes']) {
8✔
165
            $this->fullyQualifyClasses($tokens, $useDeclarations);
8✔
166
        }
167

168
        if (\count($newImports) > 0) {
58✔
169
            if (\count($useDeclarations) > 0) {
51✔
170
                $useDeclaration = end($useDeclarations);
39✔
171
                $atIndex = $useDeclaration->getEndIndex() + 1;
39✔
172
            } else {
173
                $namespace = $tokens->getNamespaceDeclarations()[0];
30✔
174
                $atIndex = $namespace->getEndIndex() + 1;
30✔
175
            }
176

177
            $this->importProcessor->insertImports($tokens, $newImports, $atIndex);
51✔
178
        }
179
    }
180

181
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
182
    {
183
        return new FixerConfigurationResolver([
79✔
184
            (new FixerOptionBuilder('import_constants', 'Whether to import, not import or ignore global constants.'))
79✔
185
                ->setDefault(null)
79✔
186
                ->setAllowedTypes(['null', 'bool'])
79✔
187
                ->getOption(),
79✔
188
            (new FixerOptionBuilder('import_functions', 'Whether to import, not import or ignore global functions.'))
79✔
189
                ->setDefault(null)
79✔
190
                ->setAllowedTypes(['null', 'bool'])
79✔
191
                ->getOption(),
79✔
192
            (new FixerOptionBuilder('import_classes', 'Whether to import, not import or ignore global classes.'))
79✔
193
                ->setDefault(true)
79✔
194
                ->setAllowedTypes(['null', 'bool'])
79✔
195
                ->getOption(),
79✔
196
        ]);
79✔
197
    }
198

199
    /**
200
     * @param list<NamespaceUseAnalysis> $useDeclarations
201
     *
202
     * @return array<string, class-string>
203
     */
204
    private function importConstants(Tokens $tokens, array $useDeclarations): array
205
    {
206
        [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isConstant(), true);
16✔
207

208
        // find namespaced const declarations (`const FOO = 1`)
209
        // and add them to the not importable names (already used)
210
        for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
16✔
211
            $token = $tokens[$index];
16✔
212

213
            if ($token->isClassy()) {
16✔
214
                $index = $tokens->getNextTokenOfKind($index, ['{']);
4✔
215
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
4✔
216

217
                continue;
4✔
218
            }
219

220
            if (!$token->isGivenKind(\T_CONST)) {
16✔
221
                continue;
16✔
222
            }
223

224
            $index = $tokens->getNextMeaningfulToken($index);
2✔
225
            $other[$tokens[$index]->getContent()] = true;
2✔
226
        }
227

228
        $analyzer = new TokensAnalyzer($tokens);
16✔
229
        $indices = [];
16✔
230

231
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
16✔
232
            $token = $tokens[$index];
16✔
233

234
            if (!$token->isGivenKind(\T_STRING)) {
16✔
235
                continue;
16✔
236
            }
237

238
            $name = $token->getContent();
16✔
239

240
            if (isset($other[$name])) {
16✔
241
                continue;
5✔
242
            }
243

244
            if (!$analyzer->isConstantInvocation($index)) {
16✔
245
                continue;
16✔
246
            }
247

248
            $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index);
12✔
249
            if (!$tokens[$nsSeparatorIndex]->isGivenKind(\T_NS_SEPARATOR)) {
12✔
250
                if (!isset($global[$name])) {
11✔
251
                    // found an unqualified constant invocation
252
                    // add it to the not importable names (already used)
253
                    $other[$name] = true;
3✔
254
                }
255

256
                continue;
11✔
257
            }
258

259
            $prevIndex = $tokens->getPrevMeaningfulToken($nsSeparatorIndex);
12✔
260
            if ($tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, \T_STRING])) {
12✔
261
                continue;
1✔
262
            }
263

264
            $indices[] = $index;
11✔
265
        }
266

267
        return $this->prepareImports($tokens, $indices, $global, $other, true);
16✔
268
    }
269

270
    /**
271
     * @param list<NamespaceUseAnalysis> $useDeclarations
272
     *
273
     * @return array<string, class-string>
274
     */
275
    private function importFunctions(Tokens $tokens, array $useDeclarations): array
276
    {
277
        [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isFunction(), false);
16✔
278

279
        // find function declarations
280
        // and add them to the not importable names (already used)
281
        foreach ($this->findFunctionDeclarations($tokens, 0, $tokens->count() - 1) as $name) {
16✔
282
            $other[strtolower($name)] = true;
2✔
283
        }
284

285
        $analyzer = new FunctionsAnalyzer();
16✔
286
        $indices = [];
16✔
287

288
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
16✔
289
            $token = $tokens[$index];
16✔
290

291
            if (!$token->isGivenKind(\T_STRING)) {
16✔
292
                continue;
16✔
293
            }
294

295
            $name = strtolower($token->getContent());
16✔
296

297
            if (isset($other[$name])) {
16✔
298
                continue;
4✔
299
            }
300

301
            if (!$analyzer->isGlobalFunctionCall($tokens, $index)) {
16✔
302
                continue;
16✔
303
            }
304

305
            $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index);
11✔
306
            if (!$tokens[$nsSeparatorIndex]->isGivenKind(\T_NS_SEPARATOR)) {
11✔
307
                if (!isset($global[$name])) {
10✔
308
                    $other[$name] = true;
2✔
309
                }
310

311
                continue;
10✔
312
            }
313

314
            $indices[] = $index;
10✔
315
        }
316

317
        return $this->prepareImports($tokens, $indices, $global, $other, false);
16✔
318
    }
319

320
    /**
321
     * @param list<NamespaceUseAnalysis> $useDeclarations
322
     *
323
     * @return array<string, class-string>
324
     */
325
    private function importClasses(Tokens $tokens, array $useDeclarations): array
326
    {
327
        [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isClass(), false);
51✔
328

329
        /** @var array<int, DocBlock> $docBlocks */
330
        $docBlocks = [];
51✔
331

332
        // find class declarations and class usages in docblocks
333
        // and add them to the not importable names (already used)
334
        for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
51✔
335
            $token = $tokens[$index];
51✔
336

337
            if ($token->isGivenKind(\T_DOC_COMMENT)) {
51✔
338
                $docBlocks[$index] = new DocBlock($token->getContent());
10✔
339

340
                $this->traverseDocBlockTypes($docBlocks[$index], static function (string $type) use ($global, &$other): void {
10✔
341
                    if (str_contains($type, '\\')) {
10✔
342
                        return;
10✔
343
                    }
344

345
                    $name = strtolower($type);
6✔
346

347
                    if (!isset($global[$name])) {
6✔
348
                        $other[$name] = true;
4✔
349
                    }
350
                });
10✔
351
            }
352

353
            if (!$token->isClassy()) {
51✔
354
                continue;
51✔
355
            }
356

357
            $index = $tokens->getNextMeaningfulToken($index);
9✔
358

359
            if ($tokens[$index]->isGivenKind(\T_STRING)) {
9✔
360
                $other[strtolower($tokens[$index]->getContent())] = true;
9✔
361
            }
362
        }
363

364
        $analyzer = new ClassyAnalyzer();
51✔
365
        $indices = [];
51✔
366

367
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
51✔
368
            $token = $tokens[$index];
51✔
369

370
            if (!$token->isGivenKind(\T_STRING)) {
51✔
371
                continue;
51✔
372
            }
373

374
            $name = strtolower($token->getContent());
51✔
375

376
            if (isset($other[$name])) {
51✔
377
                continue;
14✔
378
            }
379

380
            if (!$analyzer->isClassyInvocation($tokens, $index)) {
51✔
381
                continue;
51✔
382
            }
383

384
            $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index);
16✔
385
            if (!$tokens[$nsSeparatorIndex]->isGivenKind(\T_NS_SEPARATOR)) {
16✔
386
                if (!isset($global[$name])) {
14✔
387
                    $other[$name] = true;
1✔
388
                }
389

390
                continue;
14✔
391
            }
392

393
            if ($tokens[$tokens->getPrevMeaningfulToken($nsSeparatorIndex)]->isGivenKind([CT::T_NAMESPACE_OPERATOR, \T_STRING])) {
16✔
394
                continue;
1✔
395
            }
396

397
            $indices[] = $index;
15✔
398
        }
399

400
        $imports = [];
51✔
401

402
        foreach ($docBlocks as $index => $docBlock) {
51✔
403
            $changed = $this->traverseDocBlockTypes($docBlock, static function (string $type) use ($global, $other, &$imports): string {
10✔
404
                if ('\\' !== $type[0]) {
10✔
405
                    return $type;
6✔
406
                }
407

408
                /** @var class-string $name */
409
                $name = substr($type, 1);
9✔
410

411
                $checkName = strtolower($name);
9✔
412

413
                if (str_contains($checkName, '\\') || isset($other[$checkName])) {
9✔
414
                    return $type;
4✔
415
                }
416

417
                if (isset($global[$checkName])) {
5✔
418
                    return \is_string($global[$checkName]) ? $global[$checkName] : $name;
2✔
419
                }
420

421
                $imports[$checkName] = $name;
3✔
422

423
                return $name;
3✔
424
            });
10✔
425

426
            if ($changed) {
10✔
427
                $tokens[$index] = new Token([\T_DOC_COMMENT, $docBlock->getContent()]);
5✔
428
            }
429
        }
430

431
        return array_merge($imports, $this->prepareImports($tokens, $indices, $global, $other, false));
51✔
432
    }
433

434
    /**
435
     * Removes the leading slash at the given indices (when the name is not already used).
436
     *
437
     * @param list<int>                  $indices
438
     * @param array<string, string|true> $global
439
     * @param array<string, true>        $other
440
     *
441
     * @return array<string, class-string> array keys contain the names that must be imported
442
     */
443
    private function prepareImports(Tokens $tokens, array $indices, array $global, array $other, bool $caseSensitive): array
444
    {
445
        $imports = [];
51✔
446

447
        foreach ($indices as $index) {
51✔
448
            /** @var class-string $name */
449
            $name = $tokens[$index]->getContent();
34✔
450
            $checkName = $caseSensitive ? $name : strtolower($name);
34✔
451

452
            if (isset($other[$checkName])) {
34✔
453
                continue;
3✔
454
            }
455

456
            if (!isset($global[$checkName])) {
31✔
457
                $imports[$checkName] = $name;
31✔
458
            } elseif (\is_string($global[$checkName])) {
9✔
459
                $tokens[$index] = new Token([\T_STRING, $global[$checkName]]);
3✔
460
            }
461

462
            $tokens->clearAt($tokens->getPrevMeaningfulToken($index));
31✔
463
        }
464

465
        return $imports;
51✔
466
    }
467

468
    /**
469
     * @param list<NamespaceUseAnalysis> $useDeclarations
470
     */
471
    private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations): void
472
    {
473
        if (!$tokens->isTokenKindFound(CT::T_CONST_IMPORT)) {
3✔
474
            return;
×
475
        }
476

477
        [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isConstant() && !$declaration->isAliased(), true);
3✔
478

479
        if ([] === $global) {
3✔
480
            return;
×
481
        }
482

483
        $analyzer = new TokensAnalyzer($tokens);
3✔
484

485
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
3✔
486
            $token = $tokens[$index];
3✔
487

488
            if (!$token->isGivenKind(\T_STRING)) {
3✔
489
                continue;
3✔
490
            }
491

492
            if (!isset($global[$token->getContent()])) {
3✔
493
                continue;
3✔
494
            }
495

496
            if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(\T_NS_SEPARATOR)) {
3✔
497
                continue;
2✔
498
            }
499

500
            if (!$analyzer->isConstantInvocation($index)) {
3✔
501
                continue;
3✔
502
            }
503

504
            $tokens->insertAt($index, new Token([\T_NS_SEPARATOR, '\\']));
3✔
505
        }
506
    }
507

508
    /**
509
     * @param list<NamespaceUseAnalysis> $useDeclarations
510
     */
511
    private function fullyQualifyFunctions(Tokens $tokens, array $useDeclarations): void
512
    {
513
        if (!$tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) {
3✔
514
            return;
×
515
        }
516

517
        [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isFunction() && !$declaration->isAliased(), false);
3✔
518

519
        if ([] === $global) {
3✔
520
            return;
×
521
        }
522

523
        $analyzer = new FunctionsAnalyzer();
3✔
524

525
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
3✔
526
            $token = $tokens[$index];
3✔
527

528
            if (!$token->isGivenKind(\T_STRING)) {
3✔
529
                continue;
3✔
530
            }
531

532
            if (!isset($global[strtolower($token->getContent())])) {
3✔
533
                continue;
3✔
534
            }
535

536
            if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(\T_NS_SEPARATOR)) {
3✔
537
                continue;
2✔
538
            }
539

540
            if (!$analyzer->isGlobalFunctionCall($tokens, $index)) {
3✔
541
                continue;
3✔
542
            }
543

544
            $tokens->insertAt($index, new Token([\T_NS_SEPARATOR, '\\']));
3✔
545
        }
546
    }
547

548
    /**
549
     * @param list<NamespaceUseAnalysis> $useDeclarations
550
     */
551
    private function fullyQualifyClasses(Tokens $tokens, array $useDeclarations): void
552
    {
553
        if (!$tokens->isTokenKindFound(\T_USE)) {
8✔
554
            return;
×
555
        }
556

557
        [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isClass() && !$declaration->isAliased(), false);
8✔
558

559
        if ([] === $global) {
8✔
560
            return;
×
561
        }
562

563
        $analyzer = new ClassyAnalyzer();
8✔
564

565
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
8✔
566
            $token = $tokens[$index];
8✔
567

568
            if ($token->isGivenKind(\T_DOC_COMMENT)) {
8✔
569
                $doc = new DocBlock($token->getContent());
3✔
570

571
                $changed = $this->traverseDocBlockTypes($doc, static function (string $type) use ($global): string {
3✔
572
                    if (!isset($global[strtolower($type)])) {
3✔
573
                        return $type;
2✔
574
                    }
575

576
                    return '\\'.$type;
3✔
577
                });
3✔
578

579
                if ($changed) {
3✔
580
                    $tokens[$index] = new Token([\T_DOC_COMMENT, $doc->getContent()]);
3✔
581
                }
582

583
                continue;
3✔
584
            }
585

586
            if (!$token->isGivenKind(\T_STRING)) {
8✔
587
                continue;
8✔
588
            }
589

590
            if (!isset($global[strtolower($token->getContent())])) {
8✔
591
                continue;
8✔
592
            }
593

594
            if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(\T_NS_SEPARATOR)) {
8✔
595
                continue;
6✔
596
            }
597

598
            if (!$analyzer->isClassyInvocation($tokens, $index)) {
8✔
599
                continue;
8✔
600
            }
601

602
            $tokens->insertAt($index, new Token([\T_NS_SEPARATOR, '\\']));
7✔
603
        }
604
    }
605

606
    /**
607
     * @param list<NamespaceUseAnalysis> $declarations
608
     *
609
     * @return array{0: array<string, string|true>, 1: array<string, true>}
610
     */
611
    private function filterUseDeclarations(array $declarations, callable $callback, bool $caseSensitive): array
612
    {
613
        $global = [];
58✔
614
        $other = [];
58✔
615

616
        foreach ($declarations as $declaration) {
58✔
617
            if (!$callback($declaration)) {
47✔
618
                continue;
27✔
619
            }
620

621
            $fullName = ltrim($declaration->getFullName(), '\\');
47✔
622

623
            if (str_contains($fullName, '\\')) {
47✔
624
                $name = $caseSensitive ? $declaration->getShortName() : strtolower($declaration->getShortName());
3✔
625
                $other[$name] = true;
3✔
626

627
                continue;
3✔
628
            }
629

630
            $checkName = $caseSensitive ? $fullName : strtolower($fullName);
44✔
631
            $alias = $declaration->getShortName();
44✔
632
            $global[$checkName] = $alias === $fullName ? true : $alias;
44✔
633
        }
634

635
        return [$global, $other];
58✔
636
    }
637

638
    /**
639
     * @return iterable<string>
640
     */
641
    private function findFunctionDeclarations(Tokens $tokens, int $start, int $end): iterable
642
    {
643
        for ($index = $start; $index <= $end; ++$index) {
16✔
644
            $token = $tokens[$index];
16✔
645

646
            if ($token->isClassy()) {
16✔
647
                $classStart = $tokens->getNextTokenOfKind($index, ['{']);
5✔
648
                $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart);
5✔
649

650
                for ($index = $classStart; $index <= $classEnd; ++$index) {
5✔
651
                    if (!$tokens[$index]->isGivenKind(\T_FUNCTION)) {
5✔
652
                        continue;
5✔
653
                    }
654

655
                    $methodStart = $tokens->getNextTokenOfKind($index, ['{', ';']);
4✔
656

657
                    if ($tokens[$methodStart]->equals(';')) {
4✔
658
                        $index = $methodStart;
×
659

660
                        continue;
×
661
                    }
662

663
                    $methodEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodStart);
4✔
664

665
                    foreach ($this->findFunctionDeclarations($tokens, $methodStart, $methodEnd) as $function) {
4✔
666
                        yield $function;
1✔
667
                    }
668

669
                    $index = $methodEnd;
4✔
670
                }
671

672
                continue;
5✔
673
            }
674

675
            if (!$token->isGivenKind(\T_FUNCTION)) {
16✔
676
                continue;
16✔
677
            }
678

679
            $index = $tokens->getNextMeaningfulToken($index);
2✔
680

681
            if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) {
2✔
682
                $index = $tokens->getNextMeaningfulToken($index);
×
683
            }
684

685
            if ($tokens[$index]->isGivenKind(\T_STRING)) {
2✔
686
                yield $tokens[$index]->getContent();
2✔
687
            }
688
        }
689
    }
690

691
    private function traverseDocBlockTypes(DocBlock $doc, callable $callback): bool
692
    {
693
        $annotations = $doc->getAnnotationsOfType(Annotation::TAGS_WITH_TYPES);
12✔
694

695
        if (0 === \count($annotations)) {
12✔
696
            return false;
×
697
        }
698

699
        $changed = false;
12✔
700

701
        foreach ($annotations as $annotation) {
12✔
702
            $types = $new = $annotation->getTypes();
12✔
703

704
            foreach ($types as $i => $fullType) {
12✔
705
                $newFullType = $fullType;
12✔
706

707
                Preg::matchAll('/[\\\\\w]+(?![\\\\\w:])/', $fullType, $matches, \PREG_OFFSET_CAPTURE);
12✔
708

709
                foreach (array_reverse($matches[0]) as [$type, $offset]) {
12✔
710
                    $newType = $callback($type);
12✔
711

712
                    if (null !== $newType && $type !== $newType) {
12✔
713
                        $newFullType = substr_replace($newFullType, $newType, $offset, \strlen($type));
7✔
714
                    }
715
                }
716

717
                $new[$i] = $newFullType;
12✔
718
            }
719

720
            if ($types !== $new) {
12✔
721
                $annotation->setTypes($new);
7✔
722
                $changed = true;
7✔
723
            }
724
        }
725

726
        return $changed;
12✔
727
    }
728
}
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