• 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.39
/src/Fixer/Basic/PsrAutoloadingFixer.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\Basic;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\DocBlock\TypeExpression;
19
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
20
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
22
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
23
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
24
use PhpCsFixer\FixerDefinition\FileSpecificCodeSample;
25
use PhpCsFixer\FixerDefinition\FixerDefinition;
26
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
27
use PhpCsFixer\Preg;
28
use PhpCsFixer\StdinFileInfo;
29
use PhpCsFixer\Tokenizer\Token;
30
use PhpCsFixer\Tokenizer\Tokens;
31
use PhpCsFixer\Tokenizer\TokensAnalyzer;
32

33
/**
34
 * @phpstan-type _AutogeneratedInputConfiguration array{
35
 *  dir?: null|string,
36
 * }
37
 * @phpstan-type _AutogeneratedComputedConfiguration array{
38
 *  dir: null|string,
39
 * }
40
 *
41
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
42
 *
43
 * @author Jordi Boggiano <j.boggiano@seld.be>
44
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
45
 * @author Bram Gotink <bram@gotink.me>
46
 * @author Graham Campbell <hello@gjcampbell.co.uk>
47
 * @author Kuba Werłos <werlos@gmail.com>
48
 *
49
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
50
 */
51
final class PsrAutoloadingFixer extends AbstractFixer implements ConfigurableFixerInterface
52
{
53
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
54
    use ConfigurableFixerTrait;
55

56
    public function getDefinition(): FixerDefinitionInterface
57
    {
58
        return new FixerDefinition(
3✔
59
            'Classes must be in a path that matches their namespace, be at least one namespace deep and the class name should match the file name.',
3✔
60
            [
3✔
61
                new FileSpecificCodeSample(
3✔
62
                    '<?php
3✔
63
namespace PhpCsFixer\FIXER\Basic;
64
class InvalidName {}
65
',
3✔
66
                    new \SplFileInfo(__FILE__)
3✔
67
                ),
3✔
68
                new FileSpecificCodeSample(
3✔
69
                    '<?php
3✔
70
namespace PhpCsFixer\FIXER\Basic;
71
class InvalidName {}
72
',
3✔
73
                    new \SplFileInfo(__FILE__),
3✔
74
                    ['dir' => './src']
3✔
75
                ),
3✔
76
            ],
3✔
77
            null,
3✔
78
            'This fixer may change your class name, which will break the code that depends on the old name.'
3✔
79
        );
3✔
80
    }
81

82
    public function isCandidate(Tokens $tokens): bool
83
    {
84
        return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
47✔
85
    }
86

87
    public function isRisky(): bool
88
    {
89
        return true;
1✔
90
    }
91

92
    /**
93
     * {@inheritdoc}
94
     *
95
     * Must run before SelfAccessorFixer.
96
     */
97
    public function getPriority(): int
98
    {
99
        return -10;
1✔
100
    }
101

102
    public function supports(\SplFileInfo $file): bool
103
    {
104
        if ($file instanceof StdinFileInfo) {
199✔
105
            return false;
×
106
        }
107

108
        if (
109
            // ignore file with extension other than php
110
            ('php' !== $file->getExtension())
199✔
111
            // ignore file with name that cannot be a class name
112
            || !Preg::match('/^'.TypeExpression::REGEX_IDENTIFIER.'$/', $file->getBasename('.php'))
199✔
113
        ) {
114
            return false;
4✔
115
        }
116

117
        try {
118
            $tokens = Tokens::fromCode(\sprintf('<?php class %s {}', $file->getBasename('.php')));
195✔
119

120
            if ($tokens[3]->isKeyword() || $tokens[3]->isMagicConstant()) {
47✔
121
                // name cannot be a class name - detected by PHP 5.x
122
                return false;
47✔
123
            }
124
        } catch (\ParseError $e) {
148✔
125
            // name cannot be a class name - detected by PHP 7.x
126
            return false;
148✔
127
        }
128

129
        // ignore stubs/fixtures, since they typically contain invalid files for various reasons
130
        return !Preg::match('{[/\\\](stub|fixture)s?[/\\\]}i', $file->getRealPath());
47✔
131
    }
132

133
    protected function configurePostNormalisation(): void
134
    {
135
        if (null !== $this->configuration['dir']) {
208✔
136
            $realpath = realpath($this->configuration['dir']);
14✔
137

138
            if (false === $realpath) {
14✔
139
                throw new \InvalidArgumentException(\sprintf('Failed to resolve configured directory "%s".', $this->configuration['dir']));
×
140
            }
141

142
            $this->configuration['dir'] = $realpath;
14✔
143
        }
144
    }
145

146
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
147
    {
148
        return new FixerConfigurationResolver([
208✔
149
            (new FixerOptionBuilder('dir', 'If provided, the directory where the project code is placed.'))
208✔
150
                ->setAllowedTypes(['null', 'string'])
208✔
151
                ->setDefault(null)
208✔
152
                ->getOption(),
208✔
153
        ]);
208✔
154
    }
155

156
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
157
    {
158
        $tokenAnalyzer = new TokensAnalyzer($tokens);
47✔
159

160
        if (null !== $this->configuration['dir'] && !str_starts_with($file->getRealPath(), $this->configuration['dir'])) {
47✔
161
            return;
1✔
162
        }
163

164
        $namespace = null;
46✔
165
        $namespaceStartIndex = null;
46✔
166
        $namespaceEndIndex = null;
46✔
167

168
        $classyName = null;
46✔
169
        $classyIndex = null;
46✔
170

171
        foreach ($tokens as $index => $token) {
46✔
172
            if ($token->isGivenKind(\T_NAMESPACE)) {
46✔
173
                if (null !== $namespace) {
30✔
174
                    return;
2✔
175
                }
176

177
                $namespaceStartIndex = $tokens->getNextMeaningfulToken($index);
30✔
178
                $namespaceEndIndex = $tokens->getNextTokenOfKind($namespaceStartIndex, [';']);
30✔
179
                $namespace = trim($tokens->generatePartialCode($namespaceStartIndex, $namespaceEndIndex - 1));
30✔
180
            } elseif ($token->isClassy()) {
46✔
181
                if ($tokenAnalyzer->isAnonymousClass($index)) {
45✔
182
                    continue;
5✔
183
                }
184

185
                if (null !== $classyName) {
42✔
186
                    return;
3✔
187
                }
188

189
                $classyIndex = $tokens->getNextMeaningfulToken($index);
42✔
190
                $classyName = $tokens[$classyIndex]->getContent();
42✔
191
            }
192
        }
193

194
        if (null === $classyName) {
41✔
195
            return;
3✔
196
        }
197

198
        $expectedClassyName = $this->calculateClassyName($file, $namespace, $classyName);
38✔
199

200
        if ($classyName !== $expectedClassyName) {
38✔
201
            $tokens[$classyIndex] = new Token([\T_STRING, $expectedClassyName]);
24✔
202
        }
203

204
        if (null === $this->configuration['dir'] || null === $namespace) {
38✔
205
            return;
29✔
206
        }
207

208
        if (!is_dir($this->configuration['dir'])) {
10✔
209
            return;
×
210
        }
211

212
        $configuredDir = realpath($this->configuration['dir']);
10✔
213
        $fileDir = \dirname($file->getRealPath());
10✔
214

215
        if (\strlen($configuredDir) >= \strlen($fileDir)) {
10✔
216
            return;
2✔
217
        }
218

219
        $newNamespace = substr(str_replace('/', '\\', $fileDir), \strlen($configuredDir) + 1);
8✔
220
        $originalNamespace = substr($namespace, -\strlen($newNamespace));
8✔
221

222
        if ($originalNamespace !== $newNamespace && strtolower($originalNamespace) === strtolower($newNamespace)) {
8✔
223
            $tokens->clearRange($namespaceStartIndex, $namespaceEndIndex);
7✔
224
            $namespace = substr($namespace, 0, -\strlen($newNamespace)).$newNamespace;
7✔
225

226
            $newNamespace = Tokens::fromCode('<?php namespace '.$namespace.';');
7✔
227
            $newNamespace->clearRange(0, 2);
7✔
228
            $newNamespace->clearEmptyTokens();
7✔
229

230
            $tokens->insertAt($namespaceStartIndex, $newNamespace);
7✔
231
        }
232
    }
233

234
    private function calculateClassyName(\SplFileInfo $file, ?string $namespace, string $currentName): string
235
    {
236
        $name = $file->getBasename('.php');
38✔
237
        $maxNamespace = $this->calculateMaxNamespace($file, $namespace);
38✔
238

239
        if (null !== $this->configuration['dir']) {
38✔
240
            return ('' !== $maxNamespace ? (str_replace('\\', '_', $maxNamespace).'_') : '').$name;
13✔
241
        }
242

243
        $namespaceParts = array_reverse(explode('\\', $maxNamespace));
26✔
244

245
        foreach ($namespaceParts as $namespacePart) {
26✔
246
            $nameCandidate = \sprintf('%s_%s', $namespacePart, $name);
26✔
247

248
            if (strtolower($nameCandidate) !== strtolower(substr($currentName, -\strlen($nameCandidate)))) {
26✔
249
                break;
26✔
250
            }
251

252
            $name = $nameCandidate;
2✔
253
        }
254

255
        return $name;
26✔
256
    }
257

258
    private function calculateMaxNamespace(\SplFileInfo $file, ?string $namespace): string
259
    {
260
        if (null === $this->configuration['dir']) {
38✔
261
            $root = \dirname($file->getRealPath());
26✔
262

263
            while ($root !== \dirname($root)) {
26✔
264
                $root = \dirname($root);
26✔
265
            }
266
        } else {
267
            $root = realpath($this->configuration['dir']);
13✔
268
        }
269

270
        $namespaceAccordingToFileLocation = trim(str_replace(\DIRECTORY_SEPARATOR, '\\', substr(\dirname($file->getRealPath()), \strlen($root))), '\\');
38✔
271

272
        if (null === $namespace) {
38✔
273
            return $namespaceAccordingToFileLocation;
15✔
274
        }
275

276
        $namespaceAccordingToFileLocationPartsReversed = array_reverse(explode('\\', $namespaceAccordingToFileLocation));
23✔
277
        $namespacePartsReversed = array_reverse(explode('\\', $namespace));
23✔
278

279
        foreach ($namespacePartsReversed as $key => $namespaceParte) {
23✔
280
            if (!isset($namespaceAccordingToFileLocationPartsReversed[$key])) {
23✔
281
                break;
7✔
282
            }
283

284
            if (strtolower($namespaceParte) !== strtolower($namespaceAccordingToFileLocationPartsReversed[$key])) {
23✔
285
                break;
16✔
286
            }
287

288
            unset($namespaceAccordingToFileLocationPartsReversed[$key]);
17✔
289
        }
290

291
        return implode('\\', array_reverse($namespaceAccordingToFileLocationPartsReversed));
23✔
292
    }
293
}
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