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

keradus / PHP-CS-Fixer / 14274638573

04 Apr 2025 09:18PM UTC coverage: 94.87% (-0.01%) from 94.88%
14274638573

push

github

web-flow
DX: move `symfony/polyfill-php84` to dev deps (#8559)

28256 of 29784 relevant lines covered (94.87%)

43.29 hits per line

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

39.36
/src/Fixer/Internal/ConfigurableFixerTemplateFixer.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\Internal;
16

17
use PhpCsFixer\AbstractDoctrineAnnotationFixer;
18
use PhpCsFixer\AbstractFixer;
19
use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer;
20
use PhpCsFixer\Console\Command\HelpCommand;
21
use PhpCsFixer\DocBlock\DocBlock;
22
use PhpCsFixer\Doctrine\Annotation\Tokens as DoctrineAnnotationTokens;
23
use PhpCsFixer\Fixer\AttributeNotation\OrderedAttributesFixer;
24
use PhpCsFixer\Fixer\Casing\ConstantCaseFixer;
25
use PhpCsFixer\Fixer\ClassNotation\FinalInternalClassFixer;
26
use PhpCsFixer\Fixer\Comment\HeaderCommentFixer;
27
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
28
use PhpCsFixer\Fixer\ControlStructure\NoBreakCommentFixer;
29
use PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer;
30
use PhpCsFixer\Fixer\FixerInterface;
31
use PhpCsFixer\Fixer\Import\OrderedImportsFixer;
32
use PhpCsFixer\Fixer\InternalFixerInterface;
33
use PhpCsFixer\Fixer\NamespaceNotation\BlankLinesBeforeNamespaceFixer;
34
use PhpCsFixer\Fixer\Phpdoc\GeneralPhpdocAnnotationRemoveFixer;
35
use PhpCsFixer\Fixer\Phpdoc\GeneralPhpdocTagRenameFixer;
36
use PhpCsFixer\Fixer\Phpdoc\PhpdocOrderByValueFixer;
37
use PhpCsFixer\Fixer\Phpdoc\PhpdocReturnSelfReferenceFixer;
38
use PhpCsFixer\Fixer\Phpdoc\PhpdocTagTypeFixer;
39
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
40
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
41
use PhpCsFixer\FixerDefinition\CodeSample;
42
use PhpCsFixer\FixerDefinition\FixerDefinition;
43
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
44
use PhpCsFixer\Preg;
45
use PhpCsFixer\StdinFileInfo;
46
use PhpCsFixer\Tests\AbstractDoctrineAnnotationFixerTestCase;
47
use PhpCsFixer\Tests\AbstractFixerTest;
48
use PhpCsFixer\Tests\AbstractFunctionReferenceFixerTest;
49
use PhpCsFixer\Tests\AbstractProxyFixerTest;
50
use PhpCsFixer\Tests\Fixer\Whitespace\AbstractNullableTypeDeclarationFixerTestCase;
51
use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
52
use PhpCsFixer\Tokenizer\CT;
53
use PhpCsFixer\Tokenizer\Token;
54
use PhpCsFixer\Tokenizer\Tokens;
55
use PhpCsFixer\Utils;
56

57
/**
58
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
59
 *
60
 * @internal
61
 *
62
 * @warning Does not support PHPUnit attributes
63
 */
64
final class ConfigurableFixerTemplateFixer extends AbstractFixer implements InternalFixerInterface
65
{
66
    public function getName(): string
67
    {
68
        return 'PhpCsFixerInternal/'.parent::getName();
3✔
69
    }
70

71
    public function getDefinition(): FixerDefinitionInterface
72
    {
73
        $fileInfo = $this->getExampleFixerFile();
3✔
74
        $file = $fileInfo->openFile('r');
3✔
75
        $content = $file->fread($file->getSize());
3✔
76
        if (false === $content) {
3✔
77
            throw new \RuntimeException('Cannot read example file.');
×
78
        }
79
        $tokens = Tokens::fromCode($content);
3✔
80

81
        $generalPhpdocAnnotationRemoveFixer = new GeneralPhpdocAnnotationRemoveFixer();
3✔
82
        $generalPhpdocAnnotationRemoveFixer->configure([
3✔
83
            'annotations' => [
3✔
84
                'implements',
3✔
85
                'phpstan-type',
3✔
86
            ],
3✔
87
        ]);
3✔
88
        $generalPhpdocAnnotationRemoveFixer->applyFix($fileInfo, $tokens);
3✔
89

90
        return new FixerDefinition(
3✔
91
            'Configurable Fixers must declare Template type.',
3✔
92
            [
3✔
93
                new CodeSample(
3✔
94
                    $tokens->generateCode()
3✔
95
                ),
3✔
96
            ],
3✔
97
            null,
3✔
98
            'This rule auto-adjust @implements and @phpstan-type, which heavily change information for SCA.'
3✔
99
        );
3✔
100
    }
101

102
    public function isCandidate(Tokens $tokens): bool
103
    {
104
        return $tokens->isTokenKindFound(T_CLASS);
1✔
105
    }
106

107
    public function isRisky(): bool
108
    {
109
        return true;
1✔
110
    }
111

112
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
113
    {
114
        $this->applyFixForSrc($file, $tokens);
1✔
115
        $this->applyFixForTest($file, $tokens);
1✔
116
    }
117

118
    private function applyFixForTest(\SplFileInfo $file, Tokens $tokens): void
119
    {
120
        if (!$this->isTestForFixerFile($file)) {
1✔
121
            return;
1✔
122
        }
123

124
        $classIndex = $tokens->getNextTokenOfKind(0, [[T_CLASS]]);
×
125

126
        $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex);
×
127
        if (!$this->isPHPDoc($tokens, $docBlockIndex)) {
×
128
            $docBlockIndex = $tokens->getNextMeaningfulToken($docBlockIndex);
×
129
            $tokens->insertAt($docBlockIndex, [
×
130
                new Token([T_DOC_COMMENT, "/**\n */"]),
×
131
                new Token([T_WHITESPACE, "\n"]),
×
132
            ]);
×
133
        }
134

135
        $doc = new DocBlock($tokens[$docBlockIndex]->getContent());
×
136
        if (!$doc->isMultiLine()) {
×
137
            throw new \RuntimeException('Non-multiline docblock not expected, please convert it manually!');
×
138
        }
139

140
        $covers = array_map(
×
141
            static function ($annotation): string {
×
142
                $parts = explode(' ', $annotation->getContent());
×
143

144
                return trim(array_pop($parts));
×
145
            },
×
146
            $doc->getAnnotationsOfType(['covers'])
×
147
        );
×
148
        $covers = array_filter(
×
149
            $covers,
×
150
            static fn ($className): bool => !str_contains($className, '\Abstract') && str_ends_with($className, 'Fixer')
×
151
        );
×
152

153
        if (1 !== \count($covers)) {
×
154
            throw new \RuntimeException('Non-single covers annotation, please handle manually!');
×
155
        }
156

157
        $fixerName = array_pop($covers);
×
158

159
        $allowedBaseClasses = [
×
160
            AbstractDoctrineAnnotationFixerTestCase::class,
×
161
            AbstractNullableTypeDeclarationFixerTestCase::class,
×
162
            AbstractFixerTestCase::class,
×
163
        ];
×
164

165
        $currentClassName = str_replace('\PhpCsFixer', '\PhpCsFixer\Tests', $fixerName).'Test';
×
166
        $baseClassName = false;
×
167
        while (true) {
×
168
            $baseClassName = get_parent_class($currentClassName);
×
169
            if (false === $baseClassName || \in_array($baseClassName, $allowedBaseClasses, true)) {
×
170
                break;
×
171
            }
172
            $currentClassName = $baseClassName;
×
173
        }
174

175
        if (false === $baseClassName) {
×
176
            throw new \RuntimeException('Cannot find valid parent class!');
×
177
        }
178

179
        $baseClassName = self::getShortClassName($baseClassName);
×
180

181
        $expectedAnnotation = \sprintf('extends %s<%s>', $baseClassName, $fixerName);
×
182
        $expectedAnnotationPresent = false;
×
183
        $expectedTypeImport = \sprintf('phpstan-import-type _AutogeneratedInputConfiguration from %s', $fixerName);
×
184
        $expectedTypeImportPresent = false;
×
185

186
        foreach ($doc->getAnnotationsOfType(['extends']) as $annotation) {
×
187
            $annotationContent = $annotation->getContent();
×
188
            Preg::match('#^.*?(?P<annotation>@extends\s+?(?P<class>\w+)\<[^>]+?\>\S*)\s*?$#s', $annotationContent, $matches);
×
189

190
            if (
191
                ($matches['class'] ?? '') === $baseClassName
×
192
            ) {
193
                if (($matches['annotation'] ?? '') !== '@'.$expectedAnnotation) {
×
194
                    $annotationStart = $annotation->getStart();
×
195
                    $annotation->remove();
×
196
                    $doc->getLine($annotationStart)->setContent(' * @'.$expectedAnnotation."\n");
×
197
                }
198
                $expectedAnnotationPresent = true;
×
199

200
                break;
×
201
            }
202
        }
203

204
        $implements = class_implements($fixerName);
×
205
        if (isset($implements[ConfigurableFixerInterface::class])) {
×
206
            foreach ($doc->getAnnotationsOfType(['phpstan-import-type']) as $annotation) {
×
207
                $annotationContent = $annotation->getContent();
×
208

209
                Preg::match('#^.*?(@'.preg_quote($expectedTypeImport, '\\').')\s*?$#s', $annotationContent, $matches);
×
210

211
                if ([] !== $matches) {
×
212
                    $expectedTypeImportPresent = true;
×
213
                }
214
            }
215
        } else {
216
            $expectedTypeImportPresent = true;
×
217
        }
218

219
        if (!$expectedAnnotationPresent || !$expectedTypeImportPresent) {
×
220
            $lines = $doc->getLines();
×
221
            $lastLine = end($lines);
×
222
            \assert(false !== $lastLine);
×
223

224
            $lastLine->setContent(
×
225
                ''
×
226
                .(!$expectedAnnotationPresent ? ' * @'.$expectedAnnotation."\n" : '')
×
227
                .(!$expectedTypeImportPresent ? ' * @'.$expectedTypeImport."\n" : '')
×
228
                .$lastLine->getContent()
×
229
            );
×
230
        }
231

232
        $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $doc->getContent()]);
×
233
    }
234

235
    private function applyFixForSrc(\SplFileInfo $file, Tokens $tokens): void
236
    {
237
        if ($file instanceof StdinFileInfo) {
1✔
238
            $file = $this->getExampleFixerFile();
1✔
239
        }
240

241
        $fixer = $this->getFixerForSrcFile($file);
1✔
242

243
        if (null === $fixer || !$fixer instanceof ConfigurableFixerInterface) {
1✔
244
            return;
×
245
        }
246

247
        $optionTypeInput = [];
1✔
248
        $optionTypeComputed = [];
1✔
249

250
        $configurationDefinition = $fixer->getConfigurationDefinition();
1✔
251
        foreach ($configurationDefinition->getOptions() as $option) {
1✔
252
            $optionName = $option->getName();
1✔
253
            $allowed = HelpCommand::getDisplayableAllowedValues($option);
1✔
254
            $allowedAfterNormalization = null;
1✔
255

256
            // manual handling of normalization
257
            if (null !== $option->getNormalizer()) {
1✔
258
                if ($fixer instanceof PhpdocOrderByValueFixer && 'annotations' === $optionName) {
×
259
                    $allowedAfterNormalization = 'array{'
×
260
                        .implode(
×
261
                            ', ',
×
262
                            array_map(
×
263
                                static fn ($value): string => \sprintf("'%s'?: '%s'", $value, strtolower($value)),
×
264
                                $allowed[0]->getAllowedValues()
×
265
                            )
×
266
                        )
×
267
                        .'}';
×
268
                } elseif ($fixer instanceof HeaderCommentFixer && \in_array($optionName, ['header', 'validator'], true)) {
×
269
                    // nothing to do
270
                } elseif ($fixer instanceof BlankLinesBeforeNamespaceFixer && \in_array($optionName, ['min_line_breaks', 'max_line_breaks'], true)) {
×
271
                    // nothing to do
272
                } elseif ($fixer instanceof PhpdocReturnSelfReferenceFixer && 'replacements' === $optionName) {
×
273
                    // nothing to do
274
                } elseif ($fixer instanceof GeneralPhpdocTagRenameFixer && 'replacements' === $optionName) {
×
275
                    // nothing to do
276
                } elseif ($fixer instanceof NoBreakCommentFixer && 'comment_text' === $optionName) {
×
277
                    // nothing to do
278
                } elseif ($fixer instanceof TrailingCommaInMultilineFixer && 'elements' === $optionName) {
×
279
                    // nothing to do
280
                } elseif ($fixer instanceof OrderedAttributesFixer && 'sort_algorithm' === $optionName) {
×
281
                    // nothing to do
282
                } elseif ($fixer instanceof OrderedAttributesFixer && 'order' === $optionName) {
×
283
                    $allowedAfterNormalization = 'array<string, int>';
×
284
                } elseif ($fixer instanceof FinalInternalClassFixer && \in_array($optionName, ['annotation_include', 'annotation_exclude', 'include', 'exclude'], true)) {
×
285
                    $allowedAfterNormalization = 'array<string, string>';
×
286
                } elseif ($fixer instanceof PhpdocTagTypeFixer && 'tags' === $optionName) {
×
287
                    // nothing to do
288
                } elseif ($fixer instanceof OrderedImportsFixer && 'sort_algorithm' === $optionName) {
×
289
                    // nothing to do
290
                } else {
291
                    throw new \LogicException(\sprintf('How to handle normalized types of "%s.%s"? Explicit instructions needed!', $fixer->getName(), $optionName));
×
292
                }
293
            }
294

295
            if (\is_array($allowed)) {
1✔
296
                // $allowed are allowed values
297
                $allowed = array_map(
1✔
298
                    static fn ($value): string => $value instanceof AllowedValueSubset
1✔
299
                        ? \sprintf('list<%s>', implode('|', array_map(static fn ($val) => "'".$val."'", $value->getAllowedValues())))
×
300
                        : Utils::toString($value),
1✔
301
                    $allowed
1✔
302
                );
1✔
303
            } else {
304
                // $allowed will be allowed types
305
                $allowed = array_map(
×
306
                    static fn ($value): string => Utils::convertArrayTypeToList($value),
×
307
                    $option->getAllowedTypes()
×
308
                );
×
309
            }
310

311
            sort($allowed);
1✔
312
            $allowed = implode('|', $allowed);
1✔
313

314
            if ('array' === $allowed) {
1✔
315
                $default = $option->getDefault();
×
316
                $getTypes = static fn ($values): array => array_unique(array_map(
×
317
                    static fn ($val) => \gettype($val),
×
318
                    $values
×
319
                ));
×
320
                $defaultKeyTypes = $getTypes(array_keys($default));
×
321
                $defaultValueTypes = $getTypes(array_values($default));
×
322
                $allowed = \sprintf(
×
323
                    'array<%s, %s>',
×
324
                    [] !== $defaultKeyTypes ? implode('|', $defaultKeyTypes) : 'array-key',
×
325
                    [] !== $defaultValueTypes ? implode('|', $defaultValueTypes) : 'mixed'
×
326
                );
×
327
            }
328

329
            $optionTypeInput[] = \sprintf('%s%s: %s', $optionName, $option->hasDefault() ? '?' : '', $allowed);
1✔
330
            $optionTypeComputed[] = \sprintf('%s: %s', $optionName, $allowedAfterNormalization ?? $allowed);
1✔
331
        }
332

333
        $expectedTemplateTypeInputAnnotation = \sprintf("phpstan-type _AutogeneratedInputConfiguration array{\n *  %s,\n * }", implode(",\n *  ", $optionTypeInput));
1✔
334
        $expectedTemplateTypeComputedAnnotation = \sprintf("phpstan-type _AutogeneratedComputedConfiguration array{\n *  %s,\n * }", implode(",\n *  ", $optionTypeComputed));
1✔
335
        $expectedImplementsWithTypesAnnotation = 'implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>';
1✔
336

337
        $classIndex = $tokens->getNextTokenOfKind(0, [[T_CLASS]]);
1✔
338

339
        $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex);
1✔
340
        if (!$this->isPHPDoc($tokens, $docBlockIndex)) {
1✔
341
            $docBlockIndex = $tokens->getNextMeaningfulToken($docBlockIndex);
×
342
            $tokens->insertAt($docBlockIndex, [
×
343
                new Token([T_DOC_COMMENT, "/**\n */"]),
×
344
                new Token([T_WHITESPACE, "\n"]),
×
345
            ]);
×
346
        }
347

348
        $doc = new DocBlock($tokens[$docBlockIndex]->getContent());
1✔
349
        if (!$doc->isMultiLine()) {
1✔
350
            throw new \RuntimeException('Non-multiline docblock not expected, please convert it manually!');
×
351
        }
352

353
        $templateTypeInputPresent = false;
1✔
354
        $templateTypeComputedPresent = false;
1✔
355
        $implementsWithTypesPresent = false;
1✔
356

357
        foreach ($doc->getAnnotationsOfType(['phpstan-type']) as $annotation) {
1✔
358
            $annotationContent = $annotation->getContent();
×
359
            $matches = [];
×
360
            Preg::match('#^.*?(?P<annotation>@phpstan-type\s+?(?P<typeName>.+?)\s+?(?P<typeContent>.+?))\s*?$#s', $annotationContent, $matches);
×
361

362
            if (
363
                ($matches['typeName'] ?? '') === '_AutogeneratedInputConfiguration'
×
364
            ) {
365
                if (($matches['annotation'] ?? '') !== '@'.$expectedTemplateTypeInputAnnotation) {
×
366
                    $annotationStart = $annotation->getStart();
×
367
                    $annotation->remove();
×
368
                    $doc->getLine($annotationStart)->setContent(' * @'.$expectedTemplateTypeInputAnnotation."\n");
×
369
                }
370

371
                $templateTypeInputPresent = true;
×
372

373
                continue;
×
374
            }
375

376
            if (
377
                ($matches['typeName'] ?? '') === '_AutogeneratedComputedConfiguration'
×
378
            ) {
379
                if (($matches['annotation'] ?? '') !== '@'.$expectedTemplateTypeComputedAnnotation) {
×
380
                    $annotationStart = $annotation->getStart();
×
381
                    $annotation->remove();
×
382
                    $doc->getLine($annotationStart)->setContent(' * @'.$expectedTemplateTypeComputedAnnotation."\n");
×
383
                }
384

385
                $templateTypeComputedPresent = true;
×
386

387
                continue;
×
388
            }
389
        }
390

391
        foreach ($doc->getAnnotationsOfType(['implements']) as $annotation) {
1✔
392
            $annotationContent = $annotation->getContent();
×
393
            Preg::match('#^.*?(?P<annotation>@implements\s+?(?P<class>\w+)\<[^>]+?\>\S*)\s*?$#s', $annotationContent, $matches);
×
394

395
            if (
396
                ($matches['class'] ?? '') === 'ConfigurableFixerInterface'
×
397
            ) {
398
                if (($matches['annotation'] ?? '') !== '@'.$expectedImplementsWithTypesAnnotation) {
×
399
                    $annotationStart = $annotation->getStart();
×
400
                    $annotation->remove();
×
401
                    $doc->getLine($annotationStart)->setContent(' * @'.$expectedImplementsWithTypesAnnotation."\n");
×
402
                }
403
                $implementsWithTypesPresent = true;
×
404

405
                break;
×
406
            }
407
        }
408

409
        if (!$templateTypeInputPresent || !$templateTypeComputedPresent || !$implementsWithTypesPresent) {
1✔
410
            $lines = $doc->getLines();
1✔
411
            $lastLine = end($lines);
1✔
412
            \assert(false !== $lastLine);
1✔
413

414
            $lastLine->setContent(
1✔
415
                ''
1✔
416
                .(!$templateTypeInputPresent ? ' * @'.$expectedTemplateTypeInputAnnotation."\n" : '')
1✔
417
                .(!$templateTypeComputedPresent ? ' * @'.$expectedTemplateTypeComputedAnnotation."\n" : '')
1✔
418
                .(!$implementsWithTypesPresent ? ' * @'.$expectedImplementsWithTypesAnnotation."\n" : '')
1✔
419
                .$lastLine->getContent()
1✔
420
            );
1✔
421
        }
422

423
        $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $doc->getContent()]);
1✔
424
    }
425

426
    private function getDocBlockIndex(Tokens $tokens, int $index): int
427
    {
428
        $modifiers = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT];
1✔
429

430
        if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required
1✔
431
            $modifiers[] = T_ATTRIBUTE;
1✔
432
        }
433

434
        if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required
1✔
435
            $modifiers[] = T_READONLY;
1✔
436
        }
437

438
        do {
439
            $index = $tokens->getPrevNonWhitespace($index);
1✔
440

441
            if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
1✔
442
                $index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]);
×
443
            }
444
        } while ($tokens[$index]->isGivenKind($modifiers));
1✔
445

446
        return $index;
1✔
447
    }
448

449
    private function isPHPDoc(Tokens $tokens, int $index): bool
450
    {
451
        return $tokens[$index]->isGivenKind(T_DOC_COMMENT);
1✔
452
    }
453

454
    private function getExampleFixerFile(): \SplFileInfo
455
    {
456
        $reflection = new \ReflectionClass(ConstantCaseFixer::class);
3✔
457
        $fileName = $reflection->getFileName();
3✔
458
        if (false === $fileName) {
3✔
459
            throw new \RuntimeException('Cannot read example fileName.');
×
460
        }
461

462
        return new \SplFileInfo($fileName);
3✔
463
    }
464

465
    private static function getShortClassName(string $longClassName): string
466
    {
467
        return \array_slice(explode('\\', $longClassName), -1)[0];
×
468
    }
469

470
    private function isTestForFixerFile(\SplFileInfo $file): bool
471
    {
472
        $basename = $file->getBasename('.php');
1✔
473

474
        return str_ends_with($basename, 'FixerTest')
1✔
475
            && !\in_array($basename, [
1✔
476
                self::getShortClassName(AbstractFunctionReferenceFixerTest::class),
1✔
477
                self::getShortClassName(AbstractFixerTest::class),
1✔
478
                self::getShortClassName(AbstractProxyFixerTest::class),
1✔
479
            ], true);
1✔
480
    }
481

482
    private function getFixerForSrcFile(\SplFileInfo $file): ?FixerInterface
483
    {
484
        $basename = $file->getBasename('.php');
1✔
485

486
        if (!str_ends_with($basename, 'Fixer')) {
1✔
487
            return null;
×
488
        }
489

490
        Preg::match('#.+src/(.+)\.php#', $file->getPathname(), $matches);
1✔
491
        if (!isset($matches[1])) {
1✔
492
            return null;
×
493
        }
494

495
        $className = 'PhpCsFixer\\'.str_replace('/', '\\', $matches[1]);
1✔
496

497
        $implements = class_implements($className);
1✔
498
        if (false === $implements || !isset($implements[ConfigurableFixerInterface::class])) {
1✔
499
            return null;
×
500
        }
501

502
        if (AbstractPhpdocToTypeDeclarationFixer::class === $className) {
1✔
503
            return new class extends AbstractPhpdocToTypeDeclarationFixer {
×
504
                protected function isSkippedType(string $type): bool
505
                {
506
                    throw new \LogicException('Not implemented.');
×
507
                }
508

509
                protected function createTokensFromRawType(string $type): Tokens
510
                {
511
                    throw new \LogicException('Not implemented.');
×
512
                }
513

514
                public function getDefinition(): FixerDefinitionInterface
515
                {
516
                    throw new \LogicException('Not implemented.');
×
517
                }
518

519
                protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
520
                {
521
                    throw new \LogicException('Not implemented.');
×
522
                }
523

524
                public function isCandidate(Tokens $tokens): bool
525
                {
526
                    throw new \LogicException('Not implemented.');
×
527
                }
528
            };
×
529
        } elseif (AbstractDoctrineAnnotationFixer::class === $className) {
1✔
530
            return new class extends AbstractDoctrineAnnotationFixer {
×
531
                protected function isSkippedType(string $type): bool
532
                {
533
                    throw new \LogicException('Not implemented.');
×
534
                }
535

536
                protected function createTokensFromRawType(string $type): Tokens
537
                {
538
                    throw new \LogicException('Not implemented.');
×
539
                }
540

541
                public function getDefinition(): FixerDefinitionInterface
542
                {
543
                    throw new \LogicException('Not implemented.');
×
544
                }
545

546
                protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
547
                {
548
                    throw new \LogicException('Not implemented.');
×
549
                }
550

551
                public function isCandidate(Tokens $tokens): bool
552
                {
553
                    throw new \LogicException('Not implemented.');
×
554
                }
555

556
                public function configure(array $configuration): void
557
                {
558
                    // void
559
                }
×
560

561
                protected function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens): void
562
                {
563
                    throw new \LogicException('Not implemented.');
×
564
                }
565

566
                public function getConfigurationDefinition(): FixerConfigurationResolverInterface
567
                {
568
                    return $this->createConfigurationDefinition();
×
569
                }
570
            };
×
571
        }
572

573
        $fixer = new $className();
1✔
574

575
        \assert($fixer instanceof FixerInterface);
1✔
576

577
        return $fixer;
1✔
578
    }
579
}
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