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

keradus / PHP-CS-Fixer / 18051010410

26 Sep 2025 10:40PM UTC coverage: 94.308% (-0.02%) from 94.331%
18051010410

push

github

web-flow
chore: use accidentally missing `@auto:risky` (#9102)

28583 of 30308 relevant lines covered (94.31%)

45.24 hits per line

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

97.78
/src/Fixer/Whitespace/TypeDeclarationSpacesFixer.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\Whitespace;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
19
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
20
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
22
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
23
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
24
use PhpCsFixer\FixerDefinition\CodeSample;
25
use PhpCsFixer\FixerDefinition\FixerDefinition;
26
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
27
use PhpCsFixer\FixerDefinition\VersionSpecification;
28
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
29
use PhpCsFixer\Future;
30
use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
31
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
32
use PhpCsFixer\Tokenizer\FCT;
33
use PhpCsFixer\Tokenizer\Token;
34
use PhpCsFixer\Tokenizer\Tokens;
35
use PhpCsFixer\Tokenizer\TokensAnalyzer;
36

37
/**
38
 * @phpstan-type _AutogeneratedInputConfiguration array{
39
 *  elements?: list<'constant'|'function'|'property'>,
40
 * }
41
 * @phpstan-type _AutogeneratedComputedConfiguration array{
42
 *  elements: list<'constant'|'function'|'property'>,
43
 * }
44
 *
45
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
46
 *
47
 * @author Dariusz RumiƄski <dariusz.ruminski@gmail.com>
48
 * @author John Paul E. Balandan, CPA <paulbalandan@gmail.com>
49
 *
50
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
51
 */
52
final class TypeDeclarationSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface
53
{
54
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
55
    use ConfigurableFixerTrait;
56

57
    private const PROPERTY_MODIFIERS = [\T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_STATIC, \T_VAR, \T_FINAL, FCT::T_READONLY, FCT::T_PUBLIC_SET, FCT::T_PROTECTED_SET, FCT::T_PRIVATE_SET];
58

59
    public function getDefinition(): FixerDefinitionInterface
60
    {
61
        return new FixerDefinition(
3✔
62
            'Ensure single space between a variable and its type declaration in function arguments and properties.',
3✔
63
            [
3✔
64
                new CodeSample(
3✔
65
                    <<<'PHP'
3✔
66
                        <?php
67
                        class Bar
68
                        {
69
                            private string    $a;
70
                            private bool   $b;
71

72
                            public function __invoke(array   $c) {}
73
                        }
74

75
                        PHP
3✔
76
                ),
3✔
77
                new CodeSample(
3✔
78
                    <<<'PHP'
3✔
79
                        <?php
80
                        class Foo
81
                        {
82
                            public int   $bar;
83

84
                            public function baz(string     $a)
85
                            {
86
                                return fn(bool    $c): string => (string) $c;
87
                            }
88
                        }
89

90
                        PHP,
3✔
91
                    ['elements' => ['function']]
3✔
92
                ),
3✔
93
                new CodeSample(
3✔
94
                    <<<'PHP'
3✔
95
                        <?php
96
                        class Foo
97
                        {
98
                            public int   $bar;
99

100
                            public function baz(string     $a) {}
101
                        }
102

103
                        PHP,
3✔
104
                    ['elements' => ['property']]
3✔
105
                ),
3✔
106
                new VersionSpecificCodeSample(
3✔
107
                    <<<'PHP'
3✔
108
                        <?php
109
                        class Foo
110
                        {
111
                            public  const string   BAR = "";
112
                        }
113

114
                        PHP,
3✔
115
                    new VersionSpecification(8_03_00),
3✔
116
                    ['elements' => ['constant']]
3✔
117
                ),
3✔
118
            ]
3✔
119
        );
3✔
120
    }
121

122
    public function isCandidate(Tokens $tokens): bool
123
    {
124
        return $tokens->isAnyTokenKindsFound([...Token::getClassyTokenKinds(), \T_FN, \T_FUNCTION]);
91✔
125
    }
126

127
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
128
    {
129
        return new FixerConfigurationResolver([
100✔
130
            (new FixerOptionBuilder('elements', 'Structural elements where the spacing after the type declaration should be fixed.'))
100✔
131
                ->setAllowedTypes(['string[]'])
100✔
132
                ->setAllowedValues([new AllowedValueSubset(['function', 'property', 'constant'])])
100✔
133
                ->setDefault(
100✔
134
                    Future::getV4OrV3(['function', 'property', 'constant'], ['function', 'property'])
100✔
135
                )
100✔
136
                ->getOption(),
100✔
137
        ]);
100✔
138
    }
139

140
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
141
    {
142
        $functionsAnalyzer = new FunctionsAnalyzer();
89✔
143

144
        foreach (array_reverse($this->getElements($tokens), true) as $index => $type) {
89✔
145
            if ('property' === $type && \in_array('property', $this->configuration['elements'], true)) {
89✔
146
                $this->ensureSingleSpaceAtPropertyTypehint($tokens, $index);
21✔
147

148
                continue;
21✔
149
            }
150

151
            if ('method' === $type && \in_array('function', $this->configuration['elements'], true)) {
71✔
152
                $this->ensureSingleSpaceAtFunctionArgumentTypehint($functionsAnalyzer, $tokens, $index);
51✔
153

154
                continue;
51✔
155
            }
156

157
            if ('const' === $type && \in_array('constant', $this->configuration['elements'], true)) {
22✔
158
                $this->ensureSingleSpaceAtConstantTypehint($tokens, $index);
22✔
159

160
                // implicit continue;
161
            }
162
        }
163
    }
164

165
    /**
166
     * @return array<int, string>
167
     *
168
     * @phpstan-return array<int, 'method'|'property'|'const'>
169
     */
170
    private function getElements(Tokens $tokens): array
171
    {
172
        $tokensAnalyzer = new TokensAnalyzer($tokens);
89✔
173

174
        $elements = array_map(
89✔
175
            static fn (array $element): string => $element['type'],
89✔
176
            array_filter(
89✔
177
                $tokensAnalyzer->getClassyElements(),
89✔
178
                static fn (array $element): bool => \in_array($element['type'], ['method', 'property', 'const'], true)
89✔
179
            )
89✔
180
        );
89✔
181

182
        foreach ($tokens as $index => $token) {
89✔
183
            if (
184
                $token->isGivenKind(\T_FN)
89✔
185
                || ($token->isGivenKind(\T_FUNCTION) && !isset($elements[$index]))
89✔
186
            ) {
187
                $elements[$index] = 'method';
47✔
188
            }
189
        }
190

191
        return $elements;
89✔
192
    }
193

194
    private function ensureSingleSpaceAtFunctionArgumentTypehint(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void
195
    {
196
        foreach (array_reverse($functionsAnalyzer->getFunctionArguments($tokens, $index)) as $argumentInfo) {
51✔
197
            $argumentType = $argumentInfo->getTypeAnalysis();
50✔
198

199
            if (null === $argumentType) {
50✔
200
                continue;
11✔
201
            }
202

203
            $tokens->ensureWhitespaceAtIndex($argumentType->getEndIndex() + 1, 0, ' ');
43✔
204
        }
205
    }
206

207
    private function ensureSingleSpaceAtPropertyTypehint(Tokens $tokens, int $index): void
208
    {
209
        $propertyIndex = $index;
21✔
210

211
        do {
212
            $index = $tokens->getPrevMeaningfulToken($index);
21✔
213
        } while (!$tokens[$index]->isGivenKind(self::PROPERTY_MODIFIERS));
21✔
214

215
        $propertyType = $this->collectTypeAnalysis($tokens, $index, $propertyIndex);
21✔
216

217
        if (null === $propertyType) {
21✔
218
            return;
3✔
219
        }
220

221
        $tokens->ensureWhitespaceAtIndex($propertyType->getEndIndex() + 1, 0, ' ');
18✔
222
    }
223

224
    private function ensureSingleSpaceAtConstantTypehint(Tokens $tokens, int $index): void
225
    {
226
        $constIndex = $index;
22✔
227
        $equalsIndex = $tokens->getNextTokenOfKind($constIndex, ['=']);
22✔
228

229
        if (null === $equalsIndex) {
22✔
230
            return;
×
231
        }
232

233
        $nameIndex = $tokens->getPrevMeaningfulToken($equalsIndex);
22✔
234

235
        if (!$tokens[$nameIndex]->isGivenKind(\T_STRING)) {
22✔
236
            return;
×
237
        }
238

239
        $typeEndIndex = $tokens->getPrevMeaningfulToken($nameIndex);
22✔
240

241
        if (null === $typeEndIndex || $tokens[$typeEndIndex]->isGivenKind(\T_CONST)) {
22✔
242
            return;
5✔
243
        }
244

245
        $tokens->ensureWhitespaceAtIndex($typeEndIndex + 1, 0, ' ');
17✔
246
    }
247

248
    private function collectTypeAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ?TypeAnalysis
249
    {
250
        $type = '';
21✔
251
        $typeStartIndex = $tokens->getNextMeaningfulToken($startIndex);
21✔
252
        $typeEndIndex = $typeStartIndex;
21✔
253

254
        for ($i = $typeStartIndex; $i < $endIndex; ++$i) {
21✔
255
            if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) {
18✔
256
                continue;
18✔
257
            }
258

259
            $type .= $tokens[$i]->getContent();
18✔
260
            $typeEndIndex = $i;
18✔
261
        }
262

263
        return '' !== $type ? new TypeAnalysis($type, $typeStartIndex, $typeEndIndex) : null;
21✔
264
    }
265
}
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