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

keradus / PHP-CS-Fixer / 17678835382

12 Sep 2025 03:24PM UTC coverage: 94.69% (-0.06%) from 94.75%
17678835382

push

github

keradus
fix typo

1 of 1 new or added line in 1 file covered. (100.0%)

1042 existing lines in 177 files now uncovered.

28424 of 30018 relevant lines covered (94.69%)

45.5 hits per line

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

98.84
/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\Tokenizer\CT;
27
use PhpCsFixer\Tokenizer\Tokens;
28
use PhpCsFixer\Tokenizer\TokensAnalyzer;
29

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

255
    private function fixParenthesisInnerEdge(Tokens $tokens, int $start, int $end): void
256
    {
257
        do {
258
            --$end;
70✔
259
        } while ($tokens->isEmptyAt($end));
70✔
260

261
        // remove single-line whitespace before `)`
262
        if ($tokens[$end]->isWhitespace($this->singleLineWhitespaceOptions)) {
70✔
263
            $tokens->clearAt($end);
19✔
264
        }
265

266
        // remove single-line whitespace after `(`
267
        if ($tokens[$start + 1]->isWhitespace($this->singleLineWhitespaceOptions)) {
70✔
268
            $tokens->clearAt($start + 1);
20✔
269
        }
270
    }
271
}
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