• 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

99.28
/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.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\ConstantNotation;
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\Analyzer\NamespaceUsesAnalyzer;
27
use PhpCsFixer\Tokenizer\Token;
28
use PhpCsFixer\Tokenizer\Tokens;
29
use PhpCsFixer\Tokenizer\TokensAnalyzer;
30
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
31

32
/**
33
 * @phpstan-type _AutogeneratedInputConfiguration array{
34
 *  exclude?: list<string>,
35
 *  fix_built_in?: bool,
36
 *  include?: list<string>,
37
 *  scope?: 'all'|'namespaced',
38
 *  strict?: bool,
39
 * }
40
 * @phpstan-type _AutogeneratedComputedConfiguration array{
41
 *  exclude: list<string>,
42
 *  fix_built_in: bool,
43
 *  include: list<string>,
44
 *  scope: 'all'|'namespaced',
45
 *  strict: bool,
46
 * }
47
 *
48
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
49
 *
50
 * @author Filippo Tessarotto <zoeslam@gmail.com>
51
 *
52
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
53
 */
54
final class NativeConstantInvocationFixer extends AbstractFixer implements ConfigurableFixerInterface
55
{
56
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
57
    use ConfigurableFixerTrait;
58

59
    /**
60
     * @var array<string, true>
61
     */
62
    private array $constantsToEscape = [];
63

64
    /**
65
     * @var array<string, true>
66
     */
67
    private array $caseInsensitiveConstantsToEscape = [];
68

69
    public function getDefinition(): FixerDefinitionInterface
70
    {
71
        return new FixerDefinition(
3✔
72
            'Add leading `\` before constant invocation of internal constant to speed up resolving. Constant name match is case-sensitive, except for `null`, `false` and `true`.',
3✔
73
            [
3✔
74
                new CodeSample("<?php var_dump(PHP_VERSION, M_PI, MY_CUSTOM_PI);\n"),
3✔
75
                new CodeSample(
3✔
76
                    '<?php
3✔
77
namespace space1 {
78
    echo PHP_VERSION;
79
}
80
namespace {
81
    echo M_PI;
82
}
83
',
3✔
84
                    ['scope' => 'namespaced']
3✔
85
                ),
3✔
86
                new CodeSample(
3✔
87
                    "<?php var_dump(PHP_VERSION, M_PI, MY_CUSTOM_PI);\n",
3✔
88
                    [
3✔
89
                        'include' => [
3✔
90
                            'MY_CUSTOM_PI',
3✔
91
                        ],
3✔
92
                    ]
3✔
93
                ),
3✔
94
                new CodeSample(
3✔
95
                    "<?php var_dump(PHP_VERSION, M_PI, MY_CUSTOM_PI);\n",
3✔
96
                    [
3✔
97
                        'fix_built_in' => false,
3✔
98
                        'include' => [
3✔
99
                            'MY_CUSTOM_PI',
3✔
100
                        ],
3✔
101
                    ]
3✔
102
                ),
3✔
103
                new CodeSample(
3✔
104
                    "<?php var_dump(PHP_VERSION, M_PI, MY_CUSTOM_PI);\n",
3✔
105
                    [
3✔
106
                        'exclude' => [
3✔
107
                            'M_PI',
3✔
108
                        ],
3✔
109
                    ]
3✔
110
                ),
3✔
111
            ],
3✔
112
            null,
3✔
113
            'Risky when any of the constants are namespaced or overridden.'
3✔
114
        );
3✔
115
    }
116

117
    /**
118
     * {@inheritdoc}
119
     *
120
     * Must run before GlobalNamespaceImportFixer.
121
     * Must run after FunctionToConstantFixer.
122
     */
123
    public function getPriority(): int
124
    {
125
        return 1;
1✔
126
    }
127

128
    public function isCandidate(Tokens $tokens): bool
129
    {
130
        return $tokens->isTokenKindFound(\T_STRING);
51✔
131
    }
132

133
    public function isRisky(): bool
134
    {
135
        return true;
1✔
136
    }
137

138
    protected function configurePostNormalisation(): void
139
    {
140
        $uniqueConfiguredExclude = array_unique($this->configuration['exclude']);
69✔
141

142
        // Case-sensitive constants handling
143
        $constantsToEscape = array_values($this->configuration['include']);
69✔
144

145
        if (true === $this->configuration['fix_built_in']) {
69✔
146
            $getDefinedConstants = get_defined_constants(true);
69✔
147
            unset($getDefinedConstants['user']);
69✔
148
            foreach ($getDefinedConstants as $constants) {
69✔
149
                $constantsToEscape = [...$constantsToEscape, ...array_keys($constants)];
69✔
150
            }
151
        }
152

153
        $constantsToEscape = array_diff(
69✔
154
            array_unique($constantsToEscape),
69✔
155
            $uniqueConfiguredExclude
69✔
156
        );
69✔
157

158
        // Case-insensitive constants handling
159
        $caseInsensitiveConstantsToEscape = [];
69✔
160

161
        foreach ($constantsToEscape as $constantIndex => $constant) {
69✔
162
            $loweredConstant = strtolower($constant);
69✔
163
            if (\in_array($loweredConstant, ['null', 'false', 'true'], true)) {
69✔
164
                $caseInsensitiveConstantsToEscape[] = $loweredConstant;
69✔
165
                unset($constantsToEscape[$constantIndex]);
69✔
166
            }
167
        }
168

169
        $caseInsensitiveConstantsToEscape = array_diff(
69✔
170
            array_unique($caseInsensitiveConstantsToEscape),
69✔
171
            array_map(
69✔
172
                static fn (string $function): string => strtolower($function),
69✔
173
                $uniqueConfiguredExclude,
69✔
174
            ),
69✔
175
        );
69✔
176

177
        // Store the cache
178
        $this->constantsToEscape = array_fill_keys($constantsToEscape, true);
69✔
179
        ksort($this->constantsToEscape);
69✔
180

181
        $this->caseInsensitiveConstantsToEscape = array_fill_keys($caseInsensitiveConstantsToEscape, true);
69✔
182
        ksort($this->caseInsensitiveConstantsToEscape);
69✔
183
    }
184

185
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
186
    {
187
        if ('all' === $this->configuration['scope']) {
51✔
188
            $this->fixConstantInvocations($tokens, 0, \count($tokens) - 1);
49✔
189

190
            return;
49✔
191
        }
192

193
        $namespaces = $tokens->getNamespaceDeclarations();
3✔
194

195
        // 'scope' is 'namespaced' here
196
        foreach (array_reverse($namespaces) as $namespace) {
3✔
197
            if ($namespace->isGlobalNamespace()) {
3✔
198
                continue;
3✔
199
            }
200

201
            $this->fixConstantInvocations($tokens, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex());
2✔
202
        }
203
    }
204

205
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
206
    {
207
        $constantChecker = static function (array $value): bool {
69✔
208
            foreach ($value as $constantName) {
69✔
209
                if (trim($constantName) !== $constantName) {
69✔
210
                    throw new InvalidOptionsException(\sprintf(
1✔
211
                        'Each element must be a non-empty, trimmed string, got "%s" instead.',
1✔
212
                        get_debug_type($constantName)
1✔
213
                    ));
1✔
214
                }
215
            }
216

217
            return true;
69✔
218
        };
69✔
219

220
        return new FixerConfigurationResolver([
69✔
221
            (new FixerOptionBuilder('fix_built_in', 'Whether to fix constants returned by `get_defined_constants`. User constants are not accounted in this list and must be specified in the include one.'))
69✔
222
                ->setAllowedTypes(['bool'])
69✔
223
                ->setDefault(true)
69✔
224
                ->getOption(),
69✔
225
            (new FixerOptionBuilder('include', 'List of additional constants to fix.'))
69✔
226
                ->setAllowedTypes(['string[]'])
69✔
227
                ->setAllowedValues([$constantChecker])
69✔
228
                ->setDefault([])
69✔
229
                ->getOption(),
69✔
230
            (new FixerOptionBuilder('exclude', 'List of constants to ignore.'))
69✔
231
                ->setAllowedTypes(['string[]'])
69✔
232
                ->setAllowedValues([$constantChecker])
69✔
233
                ->setDefault(['null', 'false', 'true'])
69✔
234
                ->getOption(),
69✔
235
            (new FixerOptionBuilder('scope', 'Only fix constant invocations that are made within a namespace or fix all.'))
69✔
236
                ->setAllowedValues(['all', 'namespaced'])
69✔
237
                ->setDefault('all')
69✔
238
                ->getOption(),
69✔
239
            (new FixerOptionBuilder('strict', 'Whether leading `\` of constant invocation not meant to have it should be removed.'))
69✔
240
                ->setAllowedTypes(['bool'])
69✔
241
                ->setDefault(true)
69✔
242
                ->getOption(),
69✔
243
        ]);
69✔
244
    }
245

246
    private function fixConstantInvocations(Tokens $tokens, int $startIndex, int $endIndex): void
247
    {
248
        $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens);
50✔
249
        $useConstantDeclarations = [];
50✔
250

251
        foreach ($useDeclarations as $use) {
50✔
252
            if ($use->isConstant()) {
4✔
253
                $useConstantDeclarations[$use->getShortName()] = true;
1✔
254
            }
255
        }
256

257
        $tokenAnalyzer = new TokensAnalyzer($tokens);
50✔
258

259
        for ($index = $endIndex; $index > $startIndex; --$index) {
50✔
260
            $token = $tokens[$index];
50✔
261

262
            // test if we are at a constant call
263
            if (!$token->isGivenKind(\T_STRING)) {
50✔
264
                continue;
50✔
265
            }
266

267
            if (!$tokenAnalyzer->isConstantInvocation($index)) {
50✔
268
                continue;
44✔
269
            }
270

271
            $tokenContent = $token->getContent();
22✔
272
            $prevIndex = $tokens->getPrevMeaningfulToken($index);
22✔
273

274
            if (!isset($this->constantsToEscape[$tokenContent]) && !isset($this->caseInsensitiveConstantsToEscape[strtolower($tokenContent)])) {
22✔
275
                if (false === $this->configuration['strict']) {
15✔
276
                    continue;
×
277
                }
278

279
                if (!$tokens[$prevIndex]->isGivenKind(\T_NS_SEPARATOR)) {
15✔
280
                    continue;
14✔
281
                }
282

283
                $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
4✔
284

285
                if ($tokens[$prevPrevIndex]->isGivenKind(\T_STRING)) {
4✔
286
                    continue;
2✔
287
                }
288

289
                $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex);
3✔
290

291
                continue;
3✔
292
            }
293

294
            if (isset($useConstantDeclarations[$tokenContent])) {
18✔
295
                continue;
1✔
296
            }
297

298
            if ($tokens[$prevIndex]->isGivenKind(\T_NS_SEPARATOR)) {
18✔
299
                continue;
17✔
300
            }
301

302
            $tokens->insertAt($index, new Token([\T_NS_SEPARATOR, '\\']));
18✔
303
        }
304
    }
305
}
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