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

keradus / PHP-CS-Fixer / 16999983712

15 Aug 2025 09:42PM UTC coverage: 94.75% (-0.09%) from 94.839%
16999983712

push

github

keradus
ci: more self-fixing checks on lowest/highest PHP

28263 of 29829 relevant lines covered (94.75%)

45.88 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
 * @author Jan Gantzert <jan@familie-gantzert.de>
42
 */
43
final class PhpdocToParamTypeFixer extends AbstractPhpdocToTypeDeclarationFixer implements ConfigurableFixerInterface, ExperimentalFixerInterface
44
{
45
    private const TYPE_CHECK_TEMPLATE = '<?php function f(%s $x) {}';
46

47
    /**
48
     * @var list<array{int, string}>
49
     */
50
    private const EXCLUDE_FUNC_NAMES = [
51
        [\T_STRING, '__clone'],
52
        [\T_STRING, '__destruct'],
53
    ];
54

55
    /**
56
     * @var array<string, true>
57
     */
58
    private const SKIPPED_TYPES = [
59
        'resource' => true,
60
        'static' => true,
61
        'void' => true,
62
    ];
63

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

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

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

93
/** @param Foo $foo */
94
function foo($foo) {}
95
/** @param int|string $foo */
96
function bar($foo) {}
97
',
3✔
98
                    ['union_types' => false]
3✔
99
                ),
3✔
100
            ],
3✔
101
            null,
3✔
102
            '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✔
103
        );
3✔
104
    }
105

106
    public function isCandidate(Tokens $tokens): bool
107
    {
108
        return $tokens->isAnyTokenKindsFound([\T_FUNCTION, \T_FN]);
83✔
109
    }
110

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

122
    protected function isSkippedType(string $type): bool
123
    {
124
        return isset(self::SKIPPED_TYPES[$type]);
71✔
125
    }
126

127
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
128
    {
129
        $tokensToInsert = [];
83✔
130
        $typesToExclude = [];
83✔
131

132
        foreach ($tokens as $index => $token) {
83✔
133
            if ($token->isGivenKind(\T_DOC_COMMENT)) {
83✔
134
                $typesToExclude = array_merge($typesToExclude, self::getTypesToExclude($token->getContent()));
82✔
135

136
                continue;
82✔
137
            }
138

139
            if (!$token->isGivenKind([\T_FUNCTION, \T_FN])) {
83✔
140
                continue;
83✔
141
            }
142

143
            $funcName = $tokens->getNextMeaningfulToken($index);
83✔
144
            if ($tokens[$funcName]->equalsAny(self::EXCLUDE_FUNC_NAMES, false)) {
83✔
145
                continue;
×
146
            }
147

148
            $docCommentIndex = $this->findFunctionDocComment($tokens, $index);
83✔
149

150
            if (null === $docCommentIndex) {
83✔
151
                continue;
3✔
152
            }
153

154
            foreach ($this->getAnnotationsFromDocComment('param', $tokens, $docCommentIndex) as $paramTypeAnnotation) {
82✔
155
                $typesExpression = $paramTypeAnnotation->getTypeExpression();
82✔
156

157
                if (null === $typesExpression) {
82✔
158
                    continue;
8✔
159
                }
160

161
                $typeInfo = $this->getCommonTypeInfo($typesExpression, false);
75✔
162
                $unionTypes = null;
75✔
163

164
                if (null === $typeInfo) {
75✔
165
                    $unionTypes = $this->getUnionTypes($typesExpression, false);
16✔
166
                }
167

168
                if (null === $typeInfo && null === $unionTypes) {
75✔
169
                    continue;
10✔
170
                }
171

172
                if (null !== $typeInfo) {
67✔
173
                    $paramType = $typeInfo['commonType'];
61✔
174
                    $isNullable = $typeInfo['isNullable'];
61✔
175
                } elseif (null !== $unionTypes) {
6✔
176
                    $paramType = $unionTypes;
6✔
177
                    $isNullable = false;
6✔
178
                }
179

180
                if (!isset($paramType, $isNullable)) {
67✔
181
                    continue;
×
182
                }
183

184
                if (\in_array($paramType, $typesToExclude, true)) {
67✔
185
                    continue;
6✔
186
                }
187

188
                $startIndex = $tokens->getNextTokenOfKind($index, ['(']);
63✔
189
                $variableIndex = $this->findCorrectVariable($tokens, $startIndex, $paramTypeAnnotation);
63✔
190

191
                if (null === $variableIndex) {
63✔
192
                    continue;
4✔
193
                }
194

195
                $byRefIndex = $tokens->getPrevMeaningfulToken($variableIndex);
59✔
196
                \assert(\is_int($byRefIndex));
59✔
197

198
                if ($tokens[$byRefIndex]->equals('&')) {
59✔
199
                    $variableIndex = $byRefIndex;
3✔
200
                }
201

202
                if ($this->hasParamTypeHint($tokens, $variableIndex)) {
59✔
203
                    continue;
57✔
204
                }
205

206
                if (!$this->isValidSyntax(\sprintf(self::TYPE_CHECK_TEMPLATE, $paramType))) {
56✔
207
                    continue;
1✔
208
                }
209

210
                $tokensToInsert[$variableIndex] = array_merge(
55✔
211
                    $this->createTypeDeclarationTokens($paramType, $isNullable),
55✔
212
                    [new Token([\T_WHITESPACE, ' '])]
55✔
213
                );
55✔
214
            }
215
        }
216

217
        $tokens->insertSlices($tokensToInsert);
83✔
218
    }
219

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

227
        return $typeTokens;
55✔
228
    }
229

230
    private function findCorrectVariable(Tokens $tokens, int $startIndex, Annotation $paramTypeAnnotation): ?int
231
    {
232
        $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex);
63✔
233

234
        for ($index = $startIndex + 1; $index < $endIndex; ++$index) {
63✔
235
            if (!$tokens[$index]->isGivenKind(\T_VARIABLE)) {
61✔
236
                continue;
58✔
237
            }
238

239
            $variableName = $tokens[$index]->getContent();
61✔
240

241
            if ($paramTypeAnnotation->getVariableName() === $variableName) {
61✔
242
                return $index;
59✔
243
            }
244
        }
245

246
        return null;
4✔
247
    }
248

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

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