• 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

98.46
/src/Fixer/LanguageConstruct/FunctionToConstantFixer.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\LanguageConstruct;
16

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

32
/**
33
 * @phpstan-type _AutogeneratedInputConfiguration array{
34
 *  functions?: list<'get_called_class'|'get_class'|'get_class_this'|'php_sapi_name'|'phpversion'|'pi'>,
35
 * }
36
 * @phpstan-type _AutogeneratedComputedConfiguration array{
37
 *  functions: list<'get_called_class'|'get_class'|'get_class_this'|'php_sapi_name'|'phpversion'|'pi'>,
38
 * }
39
 *
40
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
41
 *
42
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
43
 */
44
final class FunctionToConstantFixer extends AbstractFixer implements ConfigurableFixerInterface
45
{
46
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
47
    use ConfigurableFixerTrait;
48

49
    /**
50
     * @var null|array<string, non-empty-list<Token>>
51
     */
52
    private static ?array $availableFunctions = null;
53

54
    /**
55
     * @var array<string, non-empty-list<Token>>
56
     */
57
    private array $functionsFixMap;
58

59
    public function __construct()
60
    {
61
        if (null === self::$availableFunctions) {
45✔
62
            self::$availableFunctions = [
1✔
63
                'get_called_class' => [
1✔
64
                    new Token([\T_STATIC, 'static']),
1✔
65
                    new Token([\T_DOUBLE_COLON, '::']),
1✔
66
                    new Token([CT::T_CLASS_CONSTANT, 'class']),
1✔
67
                ],
1✔
68
                'get_class' => [
1✔
69
                    new Token([\T_STRING, 'self']),
1✔
70
                    new Token([\T_DOUBLE_COLON, '::']),
1✔
71
                    new Token([CT::T_CLASS_CONSTANT, 'class']),
1✔
72
                ],
1✔
73
                'get_class_this' => [
1✔
74
                    new Token([\T_STATIC, 'static']),
1✔
75
                    new Token([\T_DOUBLE_COLON, '::']),
1✔
76
                    new Token([CT::T_CLASS_CONSTANT, 'class']),
1✔
77
                ],
1✔
78
                'php_sapi_name' => [new Token([\T_STRING, 'PHP_SAPI'])],
1✔
79
                'phpversion' => [new Token([\T_STRING, 'PHP_VERSION'])],
1✔
80
                'pi' => [new Token([\T_STRING, 'M_PI'])],
1✔
81
            ];
1✔
82
        }
83

84
        parent::__construct();
45✔
85
    }
86

87
    public function getDefinition(): FixerDefinitionInterface
88
    {
89
        return new FixerDefinition(
3✔
90
            'Replace core functions calls returning constants with the constants.',
3✔
91
            [
3✔
92
                new CodeSample(
3✔
93
                    "<?php\necho phpversion();\necho pi();\necho php_sapi_name();\nclass Foo\n{\n    public function Bar()\n    {\n        echo get_class();\n        echo get_called_class();\n    }\n}\n"
3✔
94
                ),
3✔
95
                new CodeSample(
3✔
96
                    "<?php\necho phpversion();\necho pi();\nclass Foo\n{\n    public function Bar()\n    {\n        echo get_class();\n        get_class(\$this);\n        echo get_called_class();\n    }\n}\n",
3✔
97
                    ['functions' => ['get_called_class', 'get_class_this', 'phpversion']]
3✔
98
                ),
3✔
99
            ],
3✔
100
            null,
3✔
101
            'Risky when any of the configured functions to replace are overridden.'
3✔
102
        );
3✔
103
    }
104

105
    /**
106
     * {@inheritdoc}
107
     *
108
     * Must run before NativeConstantInvocationFixer, NativeFunctionCasingFixer, NoExtraBlankLinesFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, SelfStaticAccessorFixer.
109
     * Must run after NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer.
110
     */
111
    public function getPriority(): int
112
    {
113
        return 2;
1✔
114
    }
115

116
    public function isCandidate(Tokens $tokens): bool
117
    {
118
        return $tokens->isTokenKindFound(\T_STRING);
32✔
119
    }
120

121
    public function isRisky(): bool
122
    {
123
        return true;
1✔
124
    }
125

126
    protected function configurePostNormalisation(): void
127
    {
128
        $this->functionsFixMap = [];
45✔
129

130
        foreach ($this->configuration['functions'] as $key) {
45✔
131
            $this->functionsFixMap[$key] = self::$availableFunctions[$key];
45✔
132
        }
133
    }
134

135
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
136
    {
137
        $functionAnalyzer = new FunctionsAnalyzer();
32✔
138

139
        for ($index = $tokens->count() - 4; $index > 0; --$index) {
32✔
140
            $candidate = $this->getReplaceCandidate($tokens, $functionAnalyzer, $index);
32✔
141
            if (null === $candidate) {
32✔
142
                continue;
32✔
143
            }
144

145
            $this->fixFunctionCallToConstant(
21✔
146
                $tokens,
21✔
147
                $index,
21✔
148
                $candidate[0], // brace open
21✔
149
                $candidate[1], // brace close
21✔
150
                $candidate[2]  // replacement
21✔
151
            );
21✔
152
        }
153
    }
154

155
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
156
    {
157
        $functionNames = array_keys(self::$availableFunctions);
45✔
158

159
        return new FixerConfigurationResolver([
45✔
160
            (new FixerOptionBuilder('functions', 'List of function names to fix.'))
45✔
161
                ->setAllowedTypes(['string[]'])
45✔
162
                ->setAllowedValues([new AllowedValueSubset($functionNames)])
45✔
163
                ->setDefault([
45✔
164
                    'get_called_class',
45✔
165
                    'get_class',
45✔
166
                    'get_class_this',
45✔
167
                    'php_sapi_name',
45✔
168
                    'phpversion',
45✔
169
                    'pi',
45✔
170
                ])
45✔
171
                ->getOption(),
45✔
172
        ]);
45✔
173
    }
174

175
    /**
176
     * @param non-empty-list<Token> $replacements
177
     */
178
    private function fixFunctionCallToConstant(Tokens $tokens, int $index, int $braceOpenIndex, int $braceCloseIndex, array $replacements): void
179
    {
180
        for ($i = $braceCloseIndex; $i >= $braceOpenIndex; --$i) {
21✔
181
            if ($tokens[$i]->isGivenKind([\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT])) {
21✔
182
                continue;
7✔
183
            }
184

185
            $tokens->clearTokenAndMergeSurroundingWhitespace($i);
21✔
186
        }
187

188
        if (
189
            $replacements[0]->isGivenKind([\T_CLASS_C, \T_STATIC])
21✔
190
            || ($replacements[0]->isGivenKind(\T_STRING) && 'self' === $replacements[0]->getContent())
21✔
191
        ) {
192
            $prevIndex = $tokens->getPrevMeaningfulToken($index);
10✔
193
            $prevToken = $tokens[$prevIndex];
10✔
194
            if ($prevToken->isGivenKind(\T_NS_SEPARATOR)) {
10✔
195
                $tokens->clearAt($prevIndex);
4✔
196
            }
197
        }
198

199
        $tokens->clearAt($index);
21✔
200
        $tokens->insertAt($index, $replacements);
21✔
201
    }
202

203
    /**
204
     * @return ?array{int, int, non-empty-list<Token>}
205
     */
206
    private function getReplaceCandidate(
207
        Tokens $tokens,
208
        FunctionsAnalyzer $functionAnalyzer,
209
        int $index
210
    ): ?array {
211
        if (!$tokens[$index]->isGivenKind(\T_STRING)) {
32✔
212
            return null;
32✔
213
        }
214

215
        $lowerContent = strtolower($tokens[$index]->getContent());
32✔
216

217
        if ('get_class' === $lowerContent) {
32✔
218
            return $this->fixGetClassCall($tokens, $functionAnalyzer, $index);
9✔
219
        }
220

221
        if (!isset($this->functionsFixMap[$lowerContent])) {
31✔
222
            return null;
20✔
223
        }
224

225
        if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
24✔
226
            return null;
6✔
227
        }
228

229
        // test if function call without parameters
230
        $braceOpenIndex = $tokens->getNextMeaningfulToken($index);
18✔
231
        if (!$tokens[$braceOpenIndex]->equals('(')) {
18✔
232
            return null;
×
233
        }
234

235
        $braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex);
18✔
236
        if (!$tokens[$braceCloseIndex]->equals(')')) {
18✔
237
            return null;
3✔
238
        }
239

240
        return $this->getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex);
15✔
241
    }
242

243
    /**
244
     * @return ?array{int, int, non-empty-list<Token>}
245
     */
246
    private function fixGetClassCall(
247
        Tokens $tokens,
248
        FunctionsAnalyzer $functionAnalyzer,
249
        int $index
250
    ): ?array {
251
        if (!isset($this->functionsFixMap['get_class']) && !isset($this->functionsFixMap['get_class_this'])) {
9✔
252
            return null;
×
253
        }
254

255
        if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
9✔
256
            return null;
1✔
257
        }
258

259
        $braceOpenIndex = $tokens->getNextMeaningfulToken($index);
8✔
260
        $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex);
8✔
261

262
        if ($braceCloseIndex === $tokens->getNextMeaningfulToken($braceOpenIndex)) { // no arguments passed
8✔
263
            if (isset($this->functionsFixMap['get_class'])) {
4✔
264
                return $this->getReplacementTokenClones('get_class', $braceOpenIndex, $braceCloseIndex);
4✔
265
            }
266
        } elseif (isset($this->functionsFixMap['get_class_this'])) {
7✔
267
            $isThis = false;
7✔
268

269
            for ($i = $braceOpenIndex + 1; $i < $braceCloseIndex; ++$i) {
7✔
270
                if ($tokens[$i]->equalsAny([[\T_WHITESPACE], [\T_COMMENT], [\T_DOC_COMMENT], ')'])) {
7✔
271
                    continue;
3✔
272
                }
273

274
                if ($tokens[$i]->isGivenKind(\T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) {
7✔
275
                    $isThis = true;
6✔
276

277
                    continue;
6✔
278
                }
279

280
                if (false === $isThis && $tokens[$i]->equals('(')) {
4✔
281
                    continue;
1✔
282
                }
283

284
                $isThis = false;
3✔
285

286
                break;
3✔
287
            }
288

289
            if ($isThis) {
7✔
290
                return $this->getReplacementTokenClones('get_class_this', $braceOpenIndex, $braceCloseIndex);
5✔
291
            }
292
        }
293

294
        return null;
4✔
295
    }
296

297
    /**
298
     * @return array{int, int, non-empty-list<Token>}
299
     */
300
    private function getReplacementTokenClones(string $lowerContent, int $braceOpenIndex, int $braceCloseIndex): array
301
    {
302
        $clones = array_map(
21✔
303
            static fn (Token $token): Token => clone $token,
21✔
304
            $this->functionsFixMap[$lowerContent],
21✔
305
        );
21✔
306

307
        return [
21✔
308
            $braceOpenIndex,
21✔
309
            $braceCloseIndex,
21✔
310
            $clones,
21✔
311
        ];
21✔
312
    }
313
}
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