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

keradus / PHP-CS-Fixer / 16839019843

06 Aug 2025 10:45PM UTC coverage: 94.73% (-0.02%) from 94.749%
16839019843

push

github

web-flow
feat: introduce `PER-CS3.0` rulsets (#8841)

21 of 21 new or added lines in 5 files covered. (100.0%)

241 existing lines in 18 files now uncovered.

28255 of 29827 relevant lines covered (94.73%)

45.87 hits per line

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

97.73
/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\Tokenizer\Analyzer\Analysis\TypeAnalysis;
30
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
31
use PhpCsFixer\Tokenizer\FCT;
32
use PhpCsFixer\Tokenizer\Token;
33
use PhpCsFixer\Tokenizer\Tokens;
34
use PhpCsFixer\Tokenizer\TokensAnalyzer;
35

36
/**
37
 * @phpstan-type _AutogeneratedInputConfiguration array{
38
 *  elements?: list<'constant'|'function'|'property'>,
39
 * }
40
 * @phpstan-type _AutogeneratedComputedConfiguration array{
41
 *  elements: list<'constant'|'function'|'property'>,
42
 * }
43
 *
44
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
45
 *
46
 * @author Dariusz RumiƄski <dariusz.ruminski@gmail.com>
47
 * @author John Paul E. Balandan, CPA <paulbalandan@gmail.com>
48
 */
49
final class TypeDeclarationSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface
50
{
51
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
52
    use ConfigurableFixerTrait;
53

54
    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];
55

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

68
    public function __invoke(array   $c) {}
69
}
70
'
3✔
71
                ),
3✔
72
                new CodeSample(
3✔
73
                    '<?php
3✔
74
class Foo
75
{
76
    public int   $bar;
77

78
    public function baz(string     $a)
79
    {
80
        return fn(bool    $c): string => (string) $c;
81
    }
82
}
83
',
3✔
84
                    ['elements' => ['function']]
3✔
85
                ),
3✔
86
                new CodeSample(
3✔
87
                    '<?php
3✔
88
class Foo
89
{
90
    public int   $bar;
91

92
    public function baz(string     $a) {}
93
}
94
',
3✔
95
                    ['elements' => ['property']]
3✔
96
                ),
3✔
97
                new VersionSpecificCodeSample(
3✔
98
                    '<?php
3✔
99
class Foo
100
{
101
    public  const string   BAR = "";
102
}
103
',
3✔
104
                    new VersionSpecification(8_03_00),
3✔
105
                    ['elements' => ['constant']]
3✔
106
                ),
3✔
107
            ]
3✔
108
        );
3✔
109
    }
110

111
    public function isCandidate(Tokens $tokens): bool
112
    {
113
        return $tokens->isAnyTokenKindsFound([...Token::getClassyTokenKinds(), \T_FN, \T_FUNCTION]);
90✔
114
    }
115

116
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
117
    {
118
        return new FixerConfigurationResolver([
99✔
119
            (new FixerOptionBuilder('elements', 'Structural elements where the spacing after the type declaration should be fixed.'))
99✔
120
                ->setAllowedTypes(['string[]'])
99✔
121
                ->setAllowedValues([new AllowedValueSubset(['function', 'property', 'constant'])])
99✔
122
                ->setDefault(['function', 'property']) // @TODO add 'constant' on next major 4.0
99✔
123
                ->getOption(),
99✔
124
        ]);
99✔
125
    }
126

127
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
128
    {
129
        $functionsAnalyzer = new FunctionsAnalyzer();
88✔
130

131
        foreach (array_reverse($this->getElements($tokens), true) as $index => $type) {
88✔
132
            if ('property' === $type && \in_array('property', $this->configuration['elements'], true)) {
88✔
133
                $this->ensureSingleSpaceAtPropertyTypehint($tokens, $index);
20✔
134

135
                continue;
20✔
136
            }
137

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

141
                continue;
51✔
142
            }
143

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

147
                // implicit continue;
148
            }
149
        }
150
    }
151

152
    /**
153
     * @return array<int, string>
154
     *
155
     * @phpstan-return array<int, 'method'|'property'|'const'>
156
     */
157
    private function getElements(Tokens $tokens): array
158
    {
159
        $tokensAnalyzer = new TokensAnalyzer($tokens);
88✔
160

161
        $elements = array_map(
88✔
162
            static fn (array $element): string => $element['type'],
88✔
163
            array_filter(
88✔
164
                $tokensAnalyzer->getClassyElements(),
88✔
165
                static fn (array $element): bool => \in_array($element['type'], ['method', 'property', 'const'], true)
88✔
166
            )
88✔
167
        );
88✔
168

169
        foreach ($tokens as $index => $token) {
88✔
170
            if (
171
                $token->isGivenKind(\T_FN)
88✔
172
                || ($token->isGivenKind(\T_FUNCTION) && !isset($elements[$index]))
88✔
173
            ) {
174
                $elements[$index] = 'method';
47✔
175
            }
176
        }
177

178
        return $elements;
88✔
179
    }
180

181
    private function ensureSingleSpaceAtFunctionArgumentTypehint(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void
182
    {
183
        foreach (array_reverse($functionsAnalyzer->getFunctionArguments($tokens, $index)) as $argumentInfo) {
51✔
184
            $argumentType = $argumentInfo->getTypeAnalysis();
50✔
185

186
            if (null === $argumentType) {
50✔
187
                continue;
11✔
188
            }
189

190
            $tokens->ensureWhitespaceAtIndex($argumentType->getEndIndex() + 1, 0, ' ');
43✔
191
        }
192
    }
193

194
    private function ensureSingleSpaceAtPropertyTypehint(Tokens $tokens, int $index): void
195
    {
196
        $propertyIndex = $index;
20✔
197

198
        do {
199
            $index = $tokens->getPrevMeaningfulToken($index);
20✔
200
        } while (!$tokens[$index]->isGivenKind(self::PROPERTY_MODIFIERS));
20✔
201

202
        $propertyType = $this->collectTypeAnalysis($tokens, $index, $propertyIndex);
20✔
203

204
        if (null === $propertyType) {
20✔
205
            return;
3✔
206
        }
207

208
        $tokens->ensureWhitespaceAtIndex($propertyType->getEndIndex() + 1, 0, ' ');
17✔
209
    }
210

211
    private function ensureSingleSpaceAtConstantTypehint(Tokens $tokens, int $index): void
212
    {
213
        $constIndex = $index;
22✔
214
        $equalsIndex = $tokens->getNextTokenOfKind($constIndex, ['=']);
22✔
215

216
        if (null === $equalsIndex) {
22✔
UNCOV
217
            return;
×
218
        }
219

220
        $nameIndex = $tokens->getPrevMeaningfulToken($equalsIndex);
22✔
221

222
        if (!$tokens[$nameIndex]->isGivenKind(\T_STRING)) {
22✔
UNCOV
223
            return;
×
224
        }
225

226
        $typeEndIndex = $tokens->getPrevMeaningfulToken($nameIndex);
22✔
227

228
        if (null === $typeEndIndex || $tokens[$typeEndIndex]->isGivenKind(\T_CONST)) {
22✔
229
            return;
5✔
230
        }
231

232
        $tokens->ensureWhitespaceAtIndex($typeEndIndex + 1, 0, ' ');
17✔
233
    }
234

235
    private function collectTypeAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ?TypeAnalysis
236
    {
237
        $type = '';
20✔
238
        $typeStartIndex = $tokens->getNextMeaningfulToken($startIndex);
20✔
239
        $typeEndIndex = $typeStartIndex;
20✔
240

241
        for ($i = $typeStartIndex; $i < $endIndex; ++$i) {
20✔
242
            if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) {
17✔
243
                continue;
17✔
244
            }
245

246
            $type .= $tokens[$i]->getContent();
17✔
247
            $typeEndIndex = $i;
17✔
248
        }
249

250
        return '' !== $type ? new TypeAnalysis($type, $typeStartIndex, $typeEndIndex) : null;
20✔
251
    }
252
}
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