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

keradus / PHP-CS-Fixer / 16018263876

02 Jul 2025 06:58AM UTC coverage: 94.846% (-0.002%) from 94.848%
16018263876

push

github

keradus
debug2

28193 of 29725 relevant lines covered (94.85%)

45.34 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
 * @author Dariusz RumiƄski <dariusz.ruminski@gmail.com>
38
 * @author John Paul E. Balandan, CPA <paulbalandan@gmail.com>
39
 *
40
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
41
 *
42
 * @phpstan-type _AutogeneratedInputConfiguration array{
43
 *  elements?: list<'constant'|'function'|'property'>,
44
 * }
45
 * @phpstan-type _AutogeneratedComputedConfiguration array{
46
 *  elements: list<'constant'|'function'|'property'>,
47
 * }
48
 */
49
final class TypeDeclarationSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface
50
{
51
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
52
    use ConfigurableFixerTrait;
53
    private const PROPERTY_MODIFIERS = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR, FCT::T_READONLY];
54

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

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

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

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

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

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

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

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

134
                continue;
17✔
135
            }
136

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

140
                continue;
51✔
141
            }
142

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

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

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

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

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

177
        return $elements;
85✔
178
    }
179

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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