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

keradus / PHP-CS-Fixer / 17252691116

26 Aug 2025 11:09PM UTC coverage: 94.743% (-0.01%) from 94.755%
17252691116

push

github

keradus
chore: apply phpdoc_tag_no_named_arguments

28313 of 29884 relevant lines covered (94.74%)

45.64 hits per line

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

92.41
/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.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\DoctrineAnnotation;
16

17
use PhpCsFixer\AbstractDoctrineAnnotationFixer;
18
use PhpCsFixer\Doctrine\Annotation\DocLexer;
19
use PhpCsFixer\Doctrine\Annotation\Tokens;
20
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
21
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
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

30
/**
31
 * @phpstan-type _AutogeneratedInputConfiguration array{
32
 *  ignored_tags?: list<string>,
33
 *  indent_mixed_lines?: bool,
34
 * }
35
 * @phpstan-type _AutogeneratedComputedConfiguration array{
36
 *  ignored_tags: list<string>,
37
 *  indent_mixed_lines: bool,
38
 * }
39
 *
40
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
41
 *
42
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
43
 */
44
final class DoctrineAnnotationIndentationFixer extends AbstractDoctrineAnnotationFixer implements ConfigurableFixerInterface
45
{
46
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
47
    use ConfigurableFixerTrait;
48

49
    public function getDefinition(): FixerDefinitionInterface
50
    {
51
        return new FixerDefinition(
3✔
52
            'Doctrine annotations must be indented with four spaces.',
3✔
53
            [
3✔
54
                new CodeSample("<?php\n/**\n *  @Foo(\n *   foo=\"foo\"\n *  )\n */\nclass Bar {}\n"),
3✔
55
                new CodeSample(
3✔
56
                    "<?php\n/**\n *  @Foo({@Bar,\n *   @Baz})\n */\nclass Bar {}\n",
3✔
57
                    ['indent_mixed_lines' => true]
3✔
58
                ),
3✔
59
            ]
3✔
60
        );
3✔
61
    }
62

63
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
64
    {
65
        return new FixerConfigurationResolver([
198✔
66
            ...parent::createConfigurationDefinition()->getOptions(),
198✔
67
            (new FixerOptionBuilder('indent_mixed_lines', 'Whether to indent lines that have content before closing parenthesis.'))
198✔
68
                ->setAllowedTypes(['bool'])
198✔
69
                ->setDefault(false)
198✔
70
                ->getOption(),
198✔
71
        ]);
198✔
72
    }
73

74
    protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void
75
    {
76
        $annotationPositions = [];
139✔
77
        for ($index = 0, $max = \count($doctrineAnnotationTokens); $index < $max; ++$index) {
139✔
78
            if (!$doctrineAnnotationTokens[$index]->isType(DocLexer::T_AT)) {
139✔
79
                continue;
139✔
80
            }
81

82
            $annotationEndIndex = $doctrineAnnotationTokens->getAnnotationEnd($index);
112✔
83
            if (null === $annotationEndIndex) {
112✔
84
                return;
×
85
            }
86

87
            $annotationPositions[] = [$index, $annotationEndIndex];
112✔
88
            $index = $annotationEndIndex;
112✔
89
        }
90

91
        $indentLevel = 0;
139✔
92
        foreach ($doctrineAnnotationTokens as $index => $token) {
139✔
93
            if (!$token->isType(DocLexer::T_NONE) || !str_contains($token->getContent(), "\n")) {
139✔
94
                continue;
112✔
95
            }
96

97
            if (!$this->indentationCanBeFixed($doctrineAnnotationTokens, $index, $annotationPositions)) {
139✔
98
                continue;
139✔
99
            }
100

101
            $braces = $this->getLineBracesCount($doctrineAnnotationTokens, $index);
112✔
102
            $delta = $braces[0] - $braces[1];
112✔
103
            $mixedBraces = 0 === $delta && $braces[0] > 0;
112✔
104
            $extraIndentLevel = 0;
112✔
105

106
            if ($indentLevel > 0 && ($delta < 0 || $mixedBraces)) {
112✔
107
                --$indentLevel;
103✔
108

109
                if (true === $this->configuration['indent_mixed_lines'] && $this->isClosingLineWithMeaningfulContent($doctrineAnnotationTokens, $index)) {
103✔
110
                    $extraIndentLevel = 1;
10✔
111
                }
112
            }
113

114
            $token->setContent(Preg::replace(
112✔
115
                '/(\n( +\*)?) *$/',
112✔
116
                '$1'.str_repeat(' ', 4 * ($indentLevel + $extraIndentLevel) + 1),
112✔
117
                $token->getContent()
112✔
118
            ));
112✔
119

120
            if ($delta > 0 || $mixedBraces) {
112✔
121
                ++$indentLevel;
103✔
122
            }
123
        }
124
    }
125

126
    /**
127
     * @return array{int, int}
128
     */
129
    private function getLineBracesCount(Tokens $tokens, int $index): array
130
    {
131
        $opening = 0;
112✔
132
        $closing = 0;
112✔
133

134
        while (isset($tokens[++$index])) {
112✔
135
            $token = $tokens[$index];
112✔
136
            if ($token->isType(DocLexer::T_NONE) && str_contains($token->getContent(), "\n")) {
112✔
137
                break;
112✔
138
            }
139

140
            if ($token->isType([DocLexer::T_OPEN_PARENTHESIS, DocLexer::T_OPEN_CURLY_BRACES])) {
112✔
141
                ++$opening;
103✔
142

143
                continue;
103✔
144
            }
145

146
            if (!$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES])) {
112✔
147
                continue;
112✔
148
            }
149

150
            if ($opening > 0) {
103✔
151
                --$opening;
15✔
152
            } else {
153
                ++$closing;
103✔
154
            }
155
        }
156

157
        return [$opening, $closing];
112✔
158
    }
159

160
    private function isClosingLineWithMeaningfulContent(Tokens $tokens, int $index): bool
161
    {
162
        while (isset($tokens[++$index])) {
31✔
163
            $token = $tokens[$index];
31✔
164
            if ($token->isType(DocLexer::T_NONE)) {
31✔
165
                if (str_contains($token->getContent(), "\n")) {
×
166
                    return false;
×
167
                }
168

169
                continue;
×
170
            }
171

172
            return !$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES]);
31✔
173
        }
174

175
        return false;
×
176
    }
177

178
    /**
179
     * @param list<array{int, int}> $annotationPositions Pairs of begin and end indices of main annotations
180
     */
181
    private function indentationCanBeFixed(Tokens $tokens, int $newLineTokenIndex, array $annotationPositions): bool
182
    {
183
        foreach ($annotationPositions as $position) {
139✔
184
            if ($newLineTokenIndex >= $position[0] && $newLineTokenIndex <= $position[1]) {
112✔
185
                return true;
103✔
186
            }
187
        }
188

189
        for ($index = $newLineTokenIndex + 1, $max = \count($tokens); $index < $max; ++$index) {
139✔
190
            $token = $tokens[$index];
112✔
191

192
            if (str_contains($token->getContent(), "\n")) {
112✔
193
                return false;
×
194
            }
195

196
            return $tokens[$index]->isType(DocLexer::T_AT);
112✔
197
        }
198

199
        return false;
139✔
200
    }
201
}
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