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

keradus / PHP-CS-Fixer / 17279562118

27 Aug 2025 09:47PM UTC coverage: 94.693%. Remained the same
17279562118

push

github

keradus
CS

28316 of 29903 relevant lines covered (94.69%)

45.61 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

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

90
function  foo  ($bar, $baz)
91
{
92
    return false;
93
}
94
'
3✔
95
                ),
3✔
96
                new CodeSample(
3✔
97
                    '<?php
3✔
98
$f = function () {};
99
',
3✔
100
                    ['closure_function_spacing' => self::SPACING_NONE]
3✔
101
                ),
3✔
102
                new CodeSample(
3✔
103
                    '<?php
3✔
104
$f = fn () => null;
105
',
3✔
106
                    ['closure_fn_spacing' => self::SPACING_NONE]
3✔
107
                ),
3✔
108
            ]
3✔
109
        );
3✔
110
    }
111

112
    /**
113
     * {@inheritdoc}
114
     *
115
     * Must run before MethodArgumentSpaceFixer.
116
     * Must run after SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, UseArrowFunctionsFixer.
117
     */
118
    public function getPriority(): int
119
    {
120
        return 31;
1✔
121
    }
122

123
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
124
    {
125
        $tokensAnalyzer = new TokensAnalyzer($tokens);
70✔
126

127
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
70✔
128
            $token = $tokens[$index];
70✔
129

130
            if (!$token->isGivenKind([\T_FUNCTION, \T_FN])) {
70✔
131
                continue;
70✔
132
            }
133

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

136
            if (!$tokens[$startParenthesisIndex]->equals('(')) {
70✔
137
                continue;
×
138
            }
139

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

142
            if (false === $this->configuration['trailing_comma_single_line']
70✔
143
                && !$tokens->isPartialCodeMultiline($index, $endParenthesisIndex)
70✔
144
            ) {
145
                $commaIndex = $tokens->getPrevMeaningfulToken($endParenthesisIndex);
64✔
146

147
                if ($tokens[$commaIndex]->equals(',')) {
64✔
148
                    $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
3✔
149
                }
150
            }
151

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

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

168
            $afterParenthesisIndex = $tokens->getNextNonWhitespace($endParenthesisIndex);
70✔
169
            $afterParenthesisToken = $tokens[$afterParenthesisIndex];
70✔
170

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

175
                $useStartParenthesisIndex = $tokens->getNextTokenOfKind($afterParenthesisIndex, ['(']);
17✔
176
                $useEndParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $useStartParenthesisIndex);
17✔
177

178
                if (false === $this->configuration['trailing_comma_single_line']
17✔
179
                    && !$tokens->isPartialCodeMultiline($index, $useEndParenthesisIndex)
17✔
180
                ) {
181
                    $commaIndex = $tokens->getPrevMeaningfulToken($useEndParenthesisIndex);
17✔
182

183
                    if ($tokens[$commaIndex]->equals(',')) {
17✔
184
                        $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
1✔
185
                    }
186
                }
187

188
                // remove single-line edge whitespaces inside use parentheses
189
                $this->fixParenthesisInnerEdge($tokens, $useStartParenthesisIndex, $useEndParenthesisIndex);
17✔
190

191
                // fix whitespace before CT::T_USE_LAMBDA
192
                $tokens->ensureWhitespaceAtIndex($afterParenthesisIndex - 1, 1, ' ');
17✔
193
            }
194

195
            // remove single-line edge whitespaces inside parameters list parentheses
196
            $this->fixParenthesisInnerEdge($tokens, $startParenthesisIndex, $endParenthesisIndex);
70✔
197
            $isLambda = $tokensAnalyzer->isLambda($index);
70✔
198

199
            // remove whitespace before (
200
            // eg: `function foo () {}` => `function foo() {}`
201
            if (!$isLambda && $tokens[$startParenthesisIndex - 1]->isWhitespace() && !$tokens[$tokens->getPrevNonWhitespace($startParenthesisIndex - 1)]->isComment()) {
70✔
202
                $tokens->clearAt($startParenthesisIndex - 1);
10✔
203
            }
204

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

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

219
            if ($isLambda) {
70✔
220
                $prev = $tokens->getPrevMeaningfulToken($index);
40✔
221

222
                if ($tokens[$prev]->isGivenKind(\T_STATIC)) {
40✔
223
                    // fix whitespace after T_STATIC
224
                    // eg: `$a = static     function(){};` => `$a = static function(){};`
225
                    $tokens->ensureWhitespaceAtIndex($prev + 1, 0, ' ');
5✔
226
                }
227
            }
228
        }
229
    }
230

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

249
    private function fixParenthesisInnerEdge(Tokens $tokens, int $start, int $end): void
250
    {
251
        do {
252
            --$end;
70✔
253
        } while ($tokens->isEmptyAt($end));
70✔
254

255
        // remove single-line whitespace before `)`
256
        if ($tokens[$end]->isWhitespace($this->singleLineWhitespaceOptions)) {
70✔
257
            $tokens->clearAt($end);
19✔
258
        }
259

260
        // remove single-line whitespace after `(`
261
        if ($tokens[$start + 1]->isWhitespace($this->singleLineWhitespaceOptions)) {
70✔
262
            $tokens->clearAt($start + 1);
20✔
263
        }
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