GlobalNamespaceImportFixer::applyFix()   B
last analyzed

Complexity

Conditions 10
Paths 55

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
c 1
b 0
f 0
dl 0
loc 34
rs 7.6666
cc 10
nc 55
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <[email protected]>
9
 *     Dariusz Rumiński <[email protected]>
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\WhitespacesAwareFixerInterface;
22
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
23
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
24
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
25
use PhpCsFixer\FixerDefinition\CodeSample;
26
use PhpCsFixer\FixerDefinition\FixerDefinition;
27
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
28
use PhpCsFixer\Preg;
29
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
30
use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer;
31
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
32
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
33
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
34
use PhpCsFixer\Tokenizer\CT;
35
use PhpCsFixer\Tokenizer\Token;
36
use PhpCsFixer\Tokenizer\Tokens;
37
use PhpCsFixer\Tokenizer\TokensAnalyzer;
38
39
/**
40
 * @author Gregor Harlan <[email protected]>
41
 */
42
final class GlobalNamespaceImportFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
43
{
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function getDefinition(): FixerDefinitionInterface
48
    {
49
        return new FixerDefinition(
50
            'Imports or fully qualifies global classes/functions/constants.',
51
            [
52
                new CodeSample(
53
                    '<?php
54
55
namespace Foo;
56
57
$d = new \DateTimeImmutable();
58
'
59
                ),
60
                new CodeSample(
61
                    '<?php
62
63
namespace Foo;
64
65
if (\count($x)) {
66
    /** @var \DateTimeImmutable $d */
67
    $d = new \DateTimeImmutable();
68
    $p = \M_PI;
69
}
70
',
71
                    ['import_classes' => true, 'import_constants' => true, 'import_functions' => true]
72
                ),
73
                new CodeSample(
74
                    '<?php
75
76
namespace Foo;
77
78
use DateTimeImmutable;
79
use function count;
80
use const M_PI;
81
82
if (count($x)) {
83
    /** @var DateTimeImmutable $d */
84
    $d = new DateTimeImmutable();
85
    $p = M_PI;
86
}
87
',
88
                    ['import_classes' => false, 'import_constants' => false, 'import_functions' => false]
89
                ),
90
            ]
91
        );
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     *
97
     * Must run before NoUnusedImportsFixer, OrderedImportsFixer.
98
     * Must run after NativeConstantInvocationFixer, NativeFunctionInvocationFixer.
99
     */
100
    public function getPriority(): int
101
    {
102
        return 0;
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function isCandidate(Tokens $tokens): bool
109
    {
110
        return $tokens->isAnyTokenKindsFound([T_DOC_COMMENT, T_NS_SEPARATOR, T_USE])
111
            && $tokens->isTokenKindFound(T_NAMESPACE)
112
            && 1 === $tokens->countTokenKind(T_NAMESPACE)
113
            && $tokens->isMonolithicPhp();
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
120
    {
121
        $namespaceAnalyses = (new NamespacesAnalyzer())->getDeclarations($tokens);
122
123
        if (1 !== \count($namespaceAnalyses) || '' === $namespaceAnalyses[0]->getFullName()) {
124
            return;
125
        }
126
127
        $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens);
128
129
        $newImports = [];
130
131
        if (true === $this->configuration['import_constants']) {
132
            $newImports['const'] = $this->importConstants($tokens, $useDeclarations);
133
        } elseif (false === $this->configuration['import_constants']) {
134
            $this->fullyQualifyConstants($tokens, $useDeclarations);
135
        }
136
137
        if (true === $this->configuration['import_functions']) {
138
            $newImports['function'] = $this->importFunctions($tokens, $useDeclarations);
139
        } elseif (false === $this->configuration['import_functions']) {
140
            $this->fullyQualifyFunctions($tokens, $useDeclarations);
141
        }
142
143
        if (true === $this->configuration['import_classes']) {
144
            $newImports['class'] = $this->importClasses($tokens, $useDeclarations);
145
        } elseif (false === $this->configuration['import_classes']) {
146
            $this->fullyQualifyClasses($tokens, $useDeclarations);
147
        }
148
149
        $newImports = array_filter($newImports);
150
151
        if ($newImports) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $newImports of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
152
            $this->insertImports($tokens, $newImports, $useDeclarations);
153
        }
154
    }
155
156
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
157
    {
158
        return new FixerConfigurationResolver([
159
            (new FixerOptionBuilder('import_constants', 'Whether to import, not import or ignore global constants.'))
160
                ->setDefault(null)
161
                ->setAllowedValues([true, false, null])
162
                ->getOption(),
163
            (new FixerOptionBuilder('import_functions', 'Whether to import, not import or ignore global functions.'))
164
                ->setDefault(null)
165
                ->setAllowedValues([true, false, null])
166
                ->getOption(),
167
            (new FixerOptionBuilder('import_classes', 'Whether to import, not import or ignore global classes.'))
168
                ->setDefault(true)
169
                ->setAllowedValues([true, false, null])
170
                ->getOption(),
171
        ]);
172
    }
173
174
    /**
175
     * @param NamespaceUseAnalysis[] $useDeclarations
176
     */
177
    private function importConstants(Tokens $tokens, array $useDeclarations): array
178
    {
179
        [$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) {
180
            return $declaration->isConstant();
181
        }, true);
182
183
        // find namespaced const declarations (`const FOO = 1`)
184
        // and add them to the not importable names (already used)
185
        for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
186
            $token = $tokens[$index];
187
188
            if ($token->isClassy()) {
189
                $index = $tokens->getNextTokenOfKind($index, ['{']);
190
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type null; however, parameter $searchIndex of PhpCsFixer\Tokenizer\Tokens::findBlockEnd() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

190
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, /** @scrutinizer ignore-type */ $index);
Loading history...
191
192
                continue;
193
            }
194
195
            if (!$token->isGivenKind(T_CONST)) {
196
                continue;
197
            }
198
199
            $index = $tokens->getNextMeaningfulToken($index);
200
            $other[$tokens[$index]->getContent()] = true;
201
        }
202
203
        $analyzer = new TokensAnalyzer($tokens);
204
205
        $indexes = [];
206
207
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
208
            $token = $tokens[$index];
209
210
            if (!$token->isGivenKind(T_STRING)) {
211
                continue;
212
            }
213
214
            $name = $token->getContent();
215
216
            if (isset($other[$name])) {
217
                continue;
218
            }
219
220
            if (!$analyzer->isConstantInvocation($index)) {
221
                continue;
222
            }
223
224
            $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index);
225
            if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) {
226
                if (!isset($global[$name])) {
227
                    // found an unqualified constant invocation
228
                    // add it to the not importable names (already used)
229
                    $other[$name] = true;
230
                }
231
232
                continue;
233
            }
234
235
            $prevIndex = $tokens->getPrevMeaningfulToken($nsSeparatorIndex);
0 ignored issues
show
Bug introduced by
It seems like $nsSeparatorIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...etPrevMeaningfulToken() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

235
            $prevIndex = $tokens->getPrevMeaningfulToken(/** @scrutinizer ignore-type */ $nsSeparatorIndex);
Loading history...
236
            if ($tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_STRING])) {
237
                continue;
238
            }
239
240
            $indexes[] = $index;
241
        }
242
243
        return $this->prepareImports($tokens, $indexes, $global, $other, true);
244
    }
245
246
    /**
247
     * @param NamespaceUseAnalysis[] $useDeclarations
248
     */
249
    private function importFunctions(Tokens $tokens, array $useDeclarations): array
250
    {
251
        [$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) {
252
            return $declaration->isFunction();
253
        }, false);
254
255
        // find function declarations
256
        // and add them to the not importable names (already used)
257
        foreach ($this->findFunctionDeclarations($tokens, 0, $tokens->count() - 1) as $name) {
258
            $other[strtolower($name)] = true;
259
        }
260
261
        $analyzer = new FunctionsAnalyzer();
262
263
        $indexes = [];
264
265
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
266
            $token = $tokens[$index];
267
268
            if (!$token->isGivenKind(T_STRING)) {
269
                continue;
270
            }
271
272
            $name = strtolower($token->getContent());
273
274
            if (isset($other[$name])) {
275
                continue;
276
            }
277
278
            if (!$analyzer->isGlobalFunctionCall($tokens, $index)) {
279
                continue;
280
            }
281
282
            $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index);
283
            if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) {
284
                if (!isset($global[$name])) {
285
                    $other[$name] = true;
286
                }
287
288
                continue;
289
            }
290
291
            $indexes[] = $index;
292
        }
293
294
        return $this->prepareImports($tokens, $indexes, $global, $other, false);
295
    }
296
297
    /**
298
     * @param NamespaceUseAnalysis[] $useDeclarations
299
     */
300
    private function importClasses(Tokens $tokens, array $useDeclarations): array
301
    {
302
        [$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) {
303
            return $declaration->isClass();
304
        }, false);
305
306
        /** @var DocBlock[] $docBlocks */
307
        $docBlocks = [];
308
309
        // find class declarations and class usages in docblocks
310
        // and add them to the not importable names (already used)
311
        for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
312
            $token = $tokens[$index];
313
314
            if ($token->isGivenKind(T_DOC_COMMENT)) {
315
                $docBlocks[$index] = new DocBlock($token->getContent());
316
317
                $this->traverseDocBlockTypes($docBlocks[$index], static function (string $type) use ($global, &$other): void {
318
                    if (false !== strpos($type, '\\')) {
319
                        return;
320
                    }
321
322
                    $name = strtolower($type);
323
324
                    if (!isset($global[$name])) {
325
                        $other[$name] = true;
326
                    }
327
                });
328
            }
329
330
            if (!$token->isClassy()) {
331
                continue;
332
            }
333
334
            $index = $tokens->getNextMeaningfulToken($index);
335
336
            if ($tokens[$index]->isGivenKind(T_STRING)) {
337
                $other[strtolower($tokens[$index]->getContent())] = true;
338
            }
339
        }
340
341
        $analyzer = new ClassyAnalyzer();
342
343
        $indexes = [];
344
345
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
346
            $token = $tokens[$index];
347
348
            if (!$token->isGivenKind(T_STRING)) {
349
                continue;
350
            }
351
352
            $name = strtolower($token->getContent());
353
354
            if (isset($other[$name])) {
355
                continue;
356
            }
357
358
            if (!$analyzer->isClassyInvocation($tokens, $index)) {
359
                continue;
360
            }
361
362
            $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index);
363
            if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) {
364
                if (!isset($global[$name])) {
365
                    $other[$name] = true;
366
                }
367
368
                continue;
369
            }
370
371
            if ($tokens[$tokens->getPrevMeaningfulToken($nsSeparatorIndex)]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_STRING])) {
0 ignored issues
show
Bug introduced by
It seems like $nsSeparatorIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...etPrevMeaningfulToken() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

371
            if ($tokens[$tokens->getPrevMeaningfulToken(/** @scrutinizer ignore-type */ $nsSeparatorIndex)]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_STRING])) {
Loading history...
372
                continue;
373
            }
374
375
            $indexes[] = $index;
376
        }
377
378
        $imports = [];
379
380
        foreach ($docBlocks as $index => $docBlock) {
381
            $changed = $this->traverseDocBlockTypes($docBlock, static function (string $type) use ($global, $other, &$imports) {
382
                if ('\\' !== $type[0]) {
383
                    return $type;
384
                }
385
386
                $name = substr($type, 1);
387
                $checkName = strtolower($name);
388
389
                if (false !== strpos($checkName, '\\') || isset($other[$checkName])) {
390
                    return $type;
391
                }
392
393
                if (isset($global[$checkName])) {
394
                    return \is_string($global[$checkName]) ? $global[$checkName] : $name;
395
                }
396
397
                $imports[$checkName] = $name;
398
399
                return $name;
400
            });
401
402
            if ($changed) {
403
                $tokens[$index] = new Token([T_DOC_COMMENT, $docBlock->getContent()]);
404
            }
405
        }
406
407
        return $imports + $this->prepareImports($tokens, $indexes, $global, $other, false);
408
    }
409
410
    /**
411
     * Removes the leading slash at the given indexes (when the name is not already used).
412
     *
413
     * @param int[] $indexes
414
     *
415
     * @return array array keys contain the names that must be imported
416
     */
417
    private function prepareImports(Tokens $tokens, array $indexes, array $global, array $other, bool $caseSensitive): array
418
    {
419
        $imports = [];
420
421
        foreach ($indexes as $index) {
422
            $name = $tokens[$index]->getContent();
423
            $checkName = $caseSensitive ? $name : strtolower($name);
424
425
            if (isset($other[$checkName])) {
426
                continue;
427
            }
428
429
            if (!isset($global[$checkName])) {
430
                $imports[$checkName] = $name;
431
            } elseif (\is_string($global[$checkName])) {
432
                $tokens[$index] = new Token([T_STRING, $global[$checkName]]);
433
            }
434
435
            $tokens->clearAt($tokens->getPrevMeaningfulToken($index));
0 ignored issues
show
Bug introduced by
It seems like $tokens->getPrevMeaningfulToken($index) can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tokens::clearAt() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

435
            $tokens->clearAt(/** @scrutinizer ignore-type */ $tokens->getPrevMeaningfulToken($index));
Loading history...
436
        }
437
438
        return $imports;
439
    }
440
441
    /**
442
     * @param NamespaceUseAnalysis[] $useDeclarations
443
     */
444
    private function insertImports(Tokens $tokens, array $imports, array $useDeclarations): void
445
    {
446
        if ($useDeclarations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $useDeclarations of type PhpCsFixer\Tokenizer\Ana...\NamespaceUseAnalysis[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
447
            $useDeclaration = end($useDeclarations);
448
            $index = $useDeclaration->getEndIndex() + 1;
449
        } else {
450
            $namespace = (new NamespacesAnalyzer())->getDeclarations($tokens)[0];
451
            $index = $namespace->getEndIndex() + 1;
452
        }
453
454
        $lineEnding = $this->whitespacesConfig->getLineEnding();
455
456
        if (!$tokens[$index]->isWhitespace() || false === strpos($tokens[$index]->getContent(), "\n")) {
457
            $tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding]));
458
        }
459
460
        foreach ($imports as $type => $typeImports) {
461
            foreach ($typeImports as $name) {
462
                $items = [
463
                    new Token([T_WHITESPACE, $lineEnding]),
464
                    new Token([T_USE, 'use']),
465
                    new Token([T_WHITESPACE, ' ']),
466
                ];
467
468
                if ('const' === $type) {
469
                    $items[] = new Token([CT::T_CONST_IMPORT, 'const']);
470
                    $items[] = new Token([T_WHITESPACE, ' ']);
471
                } elseif ('function' === $type) {
472
                    $items[] = new Token([CT::T_FUNCTION_IMPORT, 'function']);
473
                    $items[] = new Token([T_WHITESPACE, ' ']);
474
                }
475
476
                $items[] = new Token([T_STRING, $name]);
477
                $items[] = new Token(';');
478
479
                $tokens->insertAt($index, $items);
480
            }
481
        }
482
    }
483
484
    /**
485
     * @param NamespaceUseAnalysis[] $useDeclarations
486
     */
487
    private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations): void
488
    {
489
        if (!$tokens->isTokenKindFound(CT::T_CONST_IMPORT)) {
490
            return;
491
        }
492
493
        [$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) {
494
            return $declaration->isConstant() && !$declaration->isAliased();
495
        }, true);
496
497
        if (!$global) {
498
            return;
499
        }
500
501
        $analyzer = new TokensAnalyzer($tokens);
502
503
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
504
            $token = $tokens[$index];
505
506
            if (!$token->isGivenKind(T_STRING)) {
507
                continue;
508
            }
509
510
            if (!isset($global[$token->getContent()])) {
511
                continue;
512
            }
513
514
            if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) {
515
                continue;
516
            }
517
518
            if (!$analyzer->isConstantInvocation($index)) {
519
                continue;
520
            }
521
522
            $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\']));
523
        }
524
    }
525
526
    /**
527
     * @param NamespaceUseAnalysis[] $useDeclarations
528
     */
529
    private function fullyQualifyFunctions(Tokens $tokens, array $useDeclarations): void
530
    {
531
        if (!$tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) {
532
            return;
533
        }
534
535
        [$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) {
536
            return $declaration->isFunction() && !$declaration->isAliased();
537
        }, false);
538
539
        if (!$global) {
540
            return;
541
        }
542
543
        $analyzer = new FunctionsAnalyzer();
544
545
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
546
            $token = $tokens[$index];
547
548
            if (!$token->isGivenKind(T_STRING)) {
549
                continue;
550
            }
551
552
            if (!isset($global[strtolower($token->getContent())])) {
553
                continue;
554
            }
555
556
            if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) {
557
                continue;
558
            }
559
560
            if (!$analyzer->isGlobalFunctionCall($tokens, $index)) {
561
                continue;
562
            }
563
564
            $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\']));
565
        }
566
    }
567
568
    /**
569
     * @param NamespaceUseAnalysis[] $useDeclarations
570
     */
571
    private function fullyQualifyClasses(Tokens $tokens, array $useDeclarations): void
572
    {
573
        if (!$tokens->isTokenKindFound(T_USE)) {
574
            return;
575
        }
576
577
        [$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) {
578
            return $declaration->isClass() && !$declaration->isAliased();
579
        }, false);
580
581
        if (!$global) {
582
            return;
583
        }
584
585
        $analyzer = new ClassyAnalyzer();
586
587
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
588
            $token = $tokens[$index];
589
590
            if ($token->isGivenKind(T_DOC_COMMENT)) {
591
                $doc = new DocBlock($token->getContent());
592
593
                $changed = $this->traverseDocBlockTypes($doc, static function (string $type) use ($global) {
594
                    if (!isset($global[strtolower($type)])) {
595
                        return $type;
596
                    }
597
598
                    return '\\'.$type;
599
                });
600
601
                if ($changed) {
602
                    $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
603
                }
604
605
                continue;
606
            }
607
608
            if (!$token->isGivenKind(T_STRING)) {
609
                continue;
610
            }
611
612
            if (!isset($global[strtolower($token->getContent())])) {
613
                continue;
614
            }
615
616
            if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) {
617
                continue;
618
            }
619
620
            if (!$analyzer->isClassyInvocation($tokens, $index)) {
621
                continue;
622
            }
623
624
            $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\']));
625
        }
626
    }
627
628
    /**
629
     * @param NamespaceUseAnalysis[] $declarations
630
     */
631
    private function filterUseDeclarations(array $declarations, callable $callback, bool $caseSensitive): array
632
    {
633
        $global = [];
634
        $other = [];
635
636
        foreach ($declarations as $declaration) {
637
            if (!$callback($declaration)) {
638
                continue;
639
            }
640
641
            $fullName = ltrim($declaration->getFullName(), '\\');
642
643
            if (false !== strpos($fullName, '\\')) {
644
                $name = $caseSensitive ? $declaration->getShortName() : strtolower($declaration->getShortName());
645
                $other[$name] = true;
646
647
                continue;
648
            }
649
650
            $checkName = $caseSensitive ? $fullName : strtolower($fullName);
651
            $alias = $declaration->getShortName();
652
            $global[$checkName] = $alias === $fullName ? true : $alias;
653
        }
654
655
        return [$global, $other];
656
    }
657
658
    private function findFunctionDeclarations(Tokens $tokens, int $start, int $end): iterable
659
    {
660
        for ($index = $start; $index <= $end; ++$index) {
661
            $token = $tokens[$index];
662
663
            if ($token->isClassy()) {
664
                $classStart = $tokens->getNextTokenOfKind($index, ['{']);
665
                $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart);
0 ignored issues
show
Bug introduced by
It seems like $classStart can also be of type null; however, parameter $searchIndex of PhpCsFixer\Tokenizer\Tokens::findBlockEnd() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

665
                $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, /** @scrutinizer ignore-type */ $classStart);
Loading history...
666
667
                for ($index = $classStart; $index <= $classEnd; ++$index) {
668
                    if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
669
                        continue;
670
                    }
671
672
                    $methodStart = $tokens->getNextTokenOfKind($index, ['{', ';']);
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tokens::getNextTokenOfKind() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

672
                    $methodStart = $tokens->getNextTokenOfKind(/** @scrutinizer ignore-type */ $index, ['{', ';']);
Loading history...
673
674
                    if ($tokens[$methodStart]->equals(';')) {
675
                        $index = $methodStart;
676
677
                        continue;
678
                    }
679
680
                    $methodEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodStart);
681
682
                    foreach ($this->findFunctionDeclarations($tokens, $methodStart, $methodEnd) as $function) {
0 ignored issues
show
Bug introduced by
It seems like $methodStart can also be of type null; however, parameter $start of PhpCsFixer\Fixer\Import\...dFunctionDeclarations() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

682
                    foreach ($this->findFunctionDeclarations($tokens, /** @scrutinizer ignore-type */ $methodStart, $methodEnd) as $function) {
Loading history...
683
                        yield $function;
684
                    }
685
686
                    $index = $methodEnd;
687
                }
688
689
                continue;
690
            }
691
692
            if (!$token->isGivenKind(T_FUNCTION)) {
693
                continue;
694
            }
695
696
            $index = $tokens->getNextMeaningfulToken($index);
697
698
            if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) {
699
                $index = $tokens->getNextMeaningfulToken($index);
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...etNextMeaningfulToken() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

699
                $index = $tokens->getNextMeaningfulToken(/** @scrutinizer ignore-type */ $index);
Loading history...
700
            }
701
702
            if ($tokens[$index]->isGivenKind(T_STRING)) {
703
                yield $tokens[$index]->getContent();
704
            }
705
        }
706
    }
707
708
    private function traverseDocBlockTypes(DocBlock $doc, callable $callback): bool
709
    {
710
        $annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes());
711
712
        if (!$annotations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $annotations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
713
            return false;
714
        }
715
716
        $changed = false;
717
718
        foreach ($annotations as $annotation) {
719
            $types = $new = $annotation->getTypes();
720
721
            foreach ($types as $i => $fullType) {
722
                $newFullType = $fullType;
723
724
                Preg::matchAll('/[\\\\\w]+/', $fullType, $matches, PREG_OFFSET_CAPTURE);
725
726
                foreach (array_reverse($matches[0]) as [$type, $offset]) {
727
                    $newType = $callback($type);
728
729
                    if (null !== $newType && $type !== $newType) {
730
                        $newFullType = substr_replace($newFullType, $newType, $offset, \strlen($type));
731
                    }
732
                }
733
734
                $new[$i] = $newFullType;
735
            }
736
737
            if ($types !== $new) {
738
                $annotation->setTypes($new);
739
                $changed = true;
740
            }
741
        }
742
743
        return $changed;
744
    }
745
}
746