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

keradus / PHP-CS-Fixer / 17252691116

26 Aug 2025 11:09PM UTC coverage: 94.743% (-0.01%) from 94.755%
17252691116

push

github

keradus
chore: apply phpdoc_tag_no_named_arguments

28313 of 29884 relevant lines covered (94.74%)

45.64 hits per line

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

97.78
/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.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\AbstractPhpdocToTypeDeclarationFixer;
18
use PhpCsFixer\DocBlock\Annotation;
19
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
20
use PhpCsFixer\Fixer\ExperimentalFixerInterface;
21
use PhpCsFixer\FixerDefinition\CodeSample;
22
use PhpCsFixer\FixerDefinition\FixerDefinition;
23
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
24
use PhpCsFixer\Tokenizer\Token;
25
use PhpCsFixer\Tokenizer\Tokens;
26

27
/**
28
 * @phpstan-type _AutogeneratedInputConfiguration array{
29
 *  scalar_types?: bool,
30
 *  types_map?: array<string, string>,
31
 *  union_types?: bool,
32
 * }
33
 * @phpstan-type _AutogeneratedComputedConfiguration array{
34
 *  scalar_types: bool,
35
 *  types_map: array<string, string>,
36
 *  union_types: bool,
37
 * }
38
 *
39
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
40
 *
41
 * @phpstan-import-type _PhpTokenArray from Token
42
 *
43
 * @author Jan Gantzert <jan@familie-gantzert.de>
44
 *
45
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
46
 */
47
final class PhpdocToParamTypeFixer extends AbstractPhpdocToTypeDeclarationFixer implements ConfigurableFixerInterface, ExperimentalFixerInterface
48
{
49
    private const TYPE_CHECK_TEMPLATE = '<?php function f(%s $x) {}';
50

51
    /**
52
     * @var non-empty-list<_PhpTokenArray>
53
     */
54
    private const EXCLUDE_FUNC_NAMES = [
55
        [\T_STRING, '__clone'],
56
        [\T_STRING, '__destruct'],
57
    ];
58

59
    /**
60
     * @var array<string, true>
61
     */
62
    private const SKIPPED_TYPES = [
63
        'resource' => true,
64
        'static' => true,
65
        'void' => true,
66
    ];
67

68
    public function getDefinition(): FixerDefinitionInterface
69
    {
70
        return new FixerDefinition(
3✔
71
            'Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature.',
3✔
72
            [
3✔
73
                new CodeSample(
3✔
74
                    '<?php
3✔
75

76
/**
77
 * @param string $foo
78
 * @param string|null $bar
79
 */
80
function f($foo, $bar)
81
{}
82
'
3✔
83
                ),
3✔
84
                new CodeSample(
3✔
85
                    '<?php
3✔
86

87
/** @param Foo $foo */
88
function foo($foo) {}
89
/** @param string $foo */
90
function bar($foo) {}
91
',
3✔
92
                    ['scalar_types' => false]
3✔
93
                ),
3✔
94
                new CodeSample(
3✔
95
                    '<?php
3✔
96

97
/** @param Foo $foo */
98
function foo($foo) {}
99
/** @param int|string $foo */
100
function bar($foo) {}
101
',
3✔
102
                    ['union_types' => false]
3✔
103
                ),
3✔
104
            ],
3✔
105
            null,
3✔
106
            'The `@param` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. Manual actions are required if inherited signatures are not properly documented.'
3✔
107
        );
3✔
108
    }
109

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

115
    /**
116
     * {@inheritdoc}
117
     *
118
     * Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer.
119
     * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
120
     */
121
    public function getPriority(): int
122
    {
123
        return 8;
1✔
124
    }
125

126
    protected function isSkippedType(string $type): bool
127
    {
128
        return isset(self::SKIPPED_TYPES[$type]);
71✔
129
    }
130

131
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
132
    {
133
        $tokensToInsert = [];
83✔
134
        $typesToExclude = [];
83✔
135

136
        foreach ($tokens as $index => $token) {
83✔
137
            if ($token->isGivenKind(\T_DOC_COMMENT)) {
83✔
138
                $typesToExclude = array_merge($typesToExclude, self::getTypesToExclude($token->getContent()));
82✔
139

140
                continue;
82✔
141
            }
142

143
            if (!$token->isGivenKind([\T_FUNCTION, \T_FN])) {
83✔
144
                continue;
83✔
145
            }
146

147
            $funcName = $tokens->getNextMeaningfulToken($index);
83✔
148
            if ($tokens[$funcName]->equalsAny(self::EXCLUDE_FUNC_NAMES, false)) {
83✔
149
                continue;
×
150
            }
151

152
            $docCommentIndex = $this->findFunctionDocComment($tokens, $index);
83✔
153

154
            if (null === $docCommentIndex) {
83✔
155
                continue;
3✔
156
            }
157

158
            foreach ($this->getAnnotationsFromDocComment('param', $tokens, $docCommentIndex) as $paramTypeAnnotation) {
82✔
159
                $typesExpression = $paramTypeAnnotation->getTypeExpression();
82✔
160

161
                if (null === $typesExpression) {
82✔
162
                    continue;
8✔
163
                }
164

165
                $typeInfo = $this->getCommonTypeInfo($typesExpression, false);
75✔
166
                $unionTypes = null;
75✔
167

168
                if (null === $typeInfo) {
75✔
169
                    $unionTypes = $this->getUnionTypes($typesExpression, false);
16✔
170
                }
171

172
                if (null === $typeInfo && null === $unionTypes) {
75✔
173
                    continue;
10✔
174
                }
175

176
                if (null !== $typeInfo) {
67✔
177
                    $paramType = $typeInfo['commonType'];
61✔
178
                    $isNullable = $typeInfo['isNullable'];
61✔
179
                } elseif (null !== $unionTypes) {
6✔
180
                    $paramType = $unionTypes;
6✔
181
                    $isNullable = false;
6✔
182
                }
183

184
                if (!isset($paramType, $isNullable)) {
67✔
185
                    continue;
×
186
                }
187

188
                if (\in_array($paramType, $typesToExclude, true)) {
67✔
189
                    continue;
6✔
190
                }
191

192
                $startIndex = $tokens->getNextTokenOfKind($index, ['(']);
63✔
193
                $variableIndex = $this->findCorrectVariable($tokens, $startIndex, $paramTypeAnnotation);
63✔
194

195
                if (null === $variableIndex) {
63✔
196
                    continue;
4✔
197
                }
198

199
                $byRefIndex = $tokens->getPrevMeaningfulToken($variableIndex);
59✔
200
                \assert(\is_int($byRefIndex));
59✔
201

202
                if ($tokens[$byRefIndex]->equals('&')) {
59✔
203
                    $variableIndex = $byRefIndex;
3✔
204
                }
205

206
                if ($this->hasParamTypeHint($tokens, $variableIndex)) {
59✔
207
                    continue;
57✔
208
                }
209

210
                if (!$this->isValidSyntax(\sprintf(self::TYPE_CHECK_TEMPLATE, $paramType))) {
56✔
211
                    continue;
1✔
212
                }
213

214
                $tokensToInsert[$variableIndex] = array_merge(
55✔
215
                    $this->createTypeDeclarationTokens($paramType, $isNullable),
55✔
216
                    [new Token([\T_WHITESPACE, ' '])]
55✔
217
                );
55✔
218
            }
219
        }
220

221
        $tokens->insertSlices($tokensToInsert);
83✔
222
    }
223

224
    protected function createTokensFromRawType(string $type): Tokens
225
    {
226
        $typeTokens = Tokens::fromCode(\sprintf(self::TYPE_CHECK_TEMPLATE, $type));
55✔
227
        $typeTokens->clearRange(0, 4);
55✔
228
        $typeTokens->clearRange(\count($typeTokens) - 6, \count($typeTokens) - 1);
55✔
229
        $typeTokens->clearEmptyTokens();
55✔
230

231
        return $typeTokens;
55✔
232
    }
233

234
    private function findCorrectVariable(Tokens $tokens, int $startIndex, Annotation $paramTypeAnnotation): ?int
235
    {
236
        $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex);
63✔
237

238
        for ($index = $startIndex + 1; $index < $endIndex; ++$index) {
63✔
239
            if (!$tokens[$index]->isGivenKind(\T_VARIABLE)) {
61✔
240
                continue;
58✔
241
            }
242

243
            $variableName = $tokens[$index]->getContent();
61✔
244

245
            if ($paramTypeAnnotation->getVariableName() === $variableName) {
61✔
246
                return $index;
59✔
247
            }
248
        }
249

250
        return null;
4✔
251
    }
252

253
    /**
254
     * Determine whether the function already has a param type hint.
255
     *
256
     * @param int $index The index of the end of the function definition line, EG at { or ;
257
     */
258
    private function hasParamTypeHint(Tokens $tokens, int $index): bool
259
    {
260
        $prevIndex = $tokens->getPrevMeaningfulToken($index);
59✔
261

262
        return !$tokens[$prevIndex]->equalsAny([',', '(']);
59✔
263
    }
264
}
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