• 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

98.9
/src/Fixer/FunctionNotation/FunctionDeclarationFixer.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\FunctionNotation;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
19
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
20
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
22
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
23
use PhpCsFixer\FixerDefinition\CodeSample;
24
use PhpCsFixer\FixerDefinition\FixerDefinition;
25
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
26
use PhpCsFixer\Future;
27
use PhpCsFixer\Tokenizer\CT;
28
use PhpCsFixer\Tokenizer\Tokens;
29
use PhpCsFixer\Tokenizer\TokensAnalyzer;
30

31
/**
32
 * Fixer for rules defined in PSR2 generally (¶1 and ¶6).
33
 *
34
 * @phpstan-type _AutogeneratedInputConfiguration array{
35
 *  closure_fn_spacing?: 'none'|'one',
36
 *  closure_function_spacing?: 'none'|'one',
37
 *  trailing_comma_single_line?: bool,
38
 * }
39
 * @phpstan-type _AutogeneratedComputedConfiguration array{
40
 *  closure_fn_spacing: 'none'|'one',
41
 *  closure_function_spacing: 'none'|'one',
42
 *  trailing_comma_single_line: bool,
43
 * }
44
 *
45
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
46
 *
47
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
48
 *
49
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
50
 */
51
final class FunctionDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface
52
{
53
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
54
    use ConfigurableFixerTrait;
55

56
    /**
57
     * @internal
58
     */
59
    public const SPACING_NONE = 'none';
60

61
    /**
62
     * @internal
63
     */
64
    public const SPACING_ONE = 'one';
65

66
    private const SUPPORTED_SPACINGS = [self::SPACING_NONE, self::SPACING_ONE];
67

68
    private string $singleLineWhitespaceOptions = " \t";
69

70
    public function isCandidate(Tokens $tokens): bool
71
    {
72
        return $tokens->isAnyTokenKindsFound([\T_FUNCTION, \T_FN]);
81✔
73
    }
74

75
    public function getDefinition(): FixerDefinitionInterface
76
    {
77
        return new FixerDefinition(
3✔
78
            'Spaces should be properly placed in a function declaration.',
3✔
79
            [
3✔
80
                new CodeSample(
3✔
81
                    <<<'PHP'
3✔
82
                        <?php
83

84
                        class Foo
85
                        {
86
                            public static function  bar   ( $baz , $foo )
87
                            {
88
                                return false;
89
                            }
90
                        }
91

92
                        function  foo  ($bar, $baz)
93
                        {
94
                            return false;
95
                        }
96

97
                        PHP
3✔
98
                ),
3✔
99
                new CodeSample(
3✔
100
                    <<<'PHP'
3✔
101
                        <?php
102
                        $f = function () {};
103

104
                        PHP,
3✔
105
                    ['closure_function_spacing' => self::SPACING_NONE]
3✔
106
                ),
3✔
107
                new CodeSample(
3✔
108
                    <<<'PHP'
3✔
109
                        <?php
110
                        $f = fn () => null;
111

112
                        PHP,
3✔
113
                    ['closure_fn_spacing' => self::SPACING_NONE]
3✔
114
                ),
3✔
115
            ]
3✔
116
        );
3✔
117
    }
118

119
    /**
120
     * {@inheritdoc}
121
     *
122
     * Must run before MethodArgumentSpaceFixer.
123
     * Must run after SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, UseArrowFunctionsFixer.
124
     */
125
    public function getPriority(): int
126
    {
127
        return 31;
1✔
128
    }
129

130
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
131
    {
132
        $tokensAnalyzer = new TokensAnalyzer($tokens);
70✔
133

134
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
70✔
135
            $token = $tokens[$index];
70✔
136

137
            if (!$token->isGivenKind([\T_FUNCTION, \T_FN])) {
70✔
138
                continue;
70✔
139
            }
140

141
            $startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', [\T_CLOSE_TAG]]);
70✔
142

143
            if (!$tokens[$startParenthesisIndex]->equals('(')) {
70✔
144
                continue;
×
145
            }
146

147
            $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex);
70✔
148

149
            if (false === $this->configuration['trailing_comma_single_line']
70✔
150
                && !$tokens->isPartialCodeMultiline($index, $endParenthesisIndex)
70✔
151
            ) {
152
                $commaIndex = $tokens->getPrevMeaningfulToken($endParenthesisIndex);
64✔
153

154
                if ($tokens[$commaIndex]->equals(',')) {
64✔
155
                    $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
3✔
156
                }
157
            }
158

159
            $startBraceIndex = $tokens->getNextTokenOfKind($endParenthesisIndex, [';', '{', [\T_DOUBLE_ARROW]]);
70✔
160

161
            // fix single-line whitespace before { or =>
162
            // eg: `function foo(){}` => `function foo() {}`
163
            // eg: `function foo()   {}` => `function foo() {}`
164
            // eg: `fn()   =>` => `fn() =>`
165
            if (
166
                $tokens[$startBraceIndex]->equalsAny(['{', [\T_DOUBLE_ARROW]])
70✔
167
                && (
168
                    !$tokens[$startBraceIndex - 1]->isWhitespace()
70✔
169
                    || $tokens[$startBraceIndex - 1]->isWhitespace($this->singleLineWhitespaceOptions)
70✔
170
                )
171
            ) {
172
                $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' ');
59✔
173
            }
174

175
            $afterParenthesisIndex = $tokens->getNextNonWhitespace($endParenthesisIndex);
70✔
176
            $afterParenthesisToken = $tokens[$afterParenthesisIndex];
70✔
177

178
            if ($afterParenthesisToken->isGivenKind(CT::T_USE_LAMBDA)) {
70✔
179
                // fix whitespace after CT:T_USE_LAMBDA (we might add a token, so do this before determining start and end parenthesis)
180
                $tokens->ensureWhitespaceAtIndex($afterParenthesisIndex + 1, 0, ' ');
17✔
181

182
                $useStartParenthesisIndex = $tokens->getNextTokenOfKind($afterParenthesisIndex, ['(']);
17✔
183
                $useEndParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $useStartParenthesisIndex);
17✔
184

185
                if (false === $this->configuration['trailing_comma_single_line']
17✔
186
                    && !$tokens->isPartialCodeMultiline($index, $useEndParenthesisIndex)
17✔
187
                ) {
188
                    $commaIndex = $tokens->getPrevMeaningfulToken($useEndParenthesisIndex);
17✔
189

190
                    if ($tokens[$commaIndex]->equals(',')) {
17✔
191
                        $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
1✔
192
                    }
193
                }
194

195
                // remove single-line edge whitespaces inside use parentheses
196
                $this->fixParenthesisInnerEdge($tokens, $useStartParenthesisIndex, $useEndParenthesisIndex);
17✔
197

198
                // fix whitespace before CT::T_USE_LAMBDA
199
                $tokens->ensureWhitespaceAtIndex($afterParenthesisIndex - 1, 1, ' ');
17✔
200
            }
201

202
            // remove single-line edge whitespaces inside parameters list parentheses
203
            $this->fixParenthesisInnerEdge($tokens, $startParenthesisIndex, $endParenthesisIndex);
70✔
204
            $isLambda = $tokensAnalyzer->isLambda($index);
70✔
205

206
            // remove whitespace before (
207
            // eg: `function foo () {}` => `function foo() {}`
208
            if (!$isLambda && $tokens[$startParenthesisIndex - 1]->isWhitespace() && !$tokens[$tokens->getPrevNonWhitespace($startParenthesisIndex - 1)]->isComment()) {
70✔
209
                $tokens->clearAt($startParenthesisIndex - 1);
10✔
210
            }
211

212
            $option = $token->isGivenKind(\T_FN) ? 'closure_fn_spacing' : 'closure_function_spacing';
70✔
213

214
            if ($isLambda && self::SPACING_NONE === $this->configuration[$option]) {
70✔
215
                // optionally remove whitespace after T_FUNCTION of a closure
216
                // eg: `function () {}` => `function() {}`
217
                if ($tokens[$index + 1]->isWhitespace()) {
19✔
218
                    $tokens->clearAt($index + 1);
17✔
219
                }
220
            } else {
221
                // otherwise, enforce whitespace after T_FUNCTION
222
                // eg: `function     foo() {}` => `function foo() {}`
223
                $tokens->ensureWhitespaceAtIndex($index + 1, 0, ' ');
52✔
224
            }
225

226
            if ($isLambda) {
70✔
227
                $prev = $tokens->getPrevMeaningfulToken($index);
40✔
228

229
                if ($tokens[$prev]->isGivenKind(\T_STATIC)) {
40✔
230
                    // fix whitespace after T_STATIC
231
                    // eg: `$a = static     function(){};` => `$a = static function(){};`
232
                    $tokens->ensureWhitespaceAtIndex($prev + 1, 0, ' ');
5✔
233
                }
234
            }
235
        }
236
    }
237

238
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
239
    {
240
        return new FixerConfigurationResolver([
92✔
241
            (new FixerOptionBuilder('closure_function_spacing', 'Spacing to use before open parenthesis for closures.'))
92✔
242
                ->setDefault(self::SPACING_ONE)
92✔
243
                ->setAllowedValues(self::SUPPORTED_SPACINGS)
92✔
244
                ->getOption(),
92✔
245
            (new FixerOptionBuilder('closure_fn_spacing', 'Spacing to use before open parenthesis for short arrow functions.'))
92✔
246
                ->setDefault(
92✔
247
                    Future::getV4OrV3(
92✔
248
                        self::SPACING_NONE,
92✔
249
                        self::SPACING_ONE
92✔
250
                    )
92✔
251
                )
92✔
252
                ->setAllowedValues(self::SUPPORTED_SPACINGS)
92✔
253
                ->getOption(),
92✔
254
            (new FixerOptionBuilder('trailing_comma_single_line', 'Whether trailing commas are allowed in single line signatures.'))
92✔
255
                ->setAllowedTypes(['bool'])
92✔
256
                ->setDefault(false)
92✔
257
                ->getOption(),
92✔
258
        ]);
92✔
259
    }
260

261
    private function fixParenthesisInnerEdge(Tokens $tokens, int $start, int $end): void
262
    {
263
        do {
264
            --$end;
70✔
265
        } while ($tokens->isEmptyAt($end));
70✔
266

267
        // remove single-line whitespace before `)`
268
        if ($tokens[$end]->isWhitespace($this->singleLineWhitespaceOptions)) {
70✔
269
            $tokens->clearAt($end);
19✔
270
        }
271

272
        // remove single-line whitespace after `(`
273
        if ($tokens[$start + 1]->isWhitespace($this->singleLineWhitespaceOptions)) {
70✔
274
            $tokens->clearAt($start + 1);
20✔
275
        }
276
    }
277
}
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