• 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

99.14
/src/Fixer/ClassNotation/StaticPrivateMethodFixer.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\ClassNotation;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\FixerDefinition\CodeSample;
19
use PhpCsFixer\FixerDefinition\FixerDefinition;
20
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
21
use PhpCsFixer\Tokenizer\Token;
22
use PhpCsFixer\Tokenizer\Tokens;
23
use PhpCsFixer\Tokenizer\TokensAnalyzer;
24

25
/**
26
 * @author Filippo Tessarotto <zoeslam@gmail.com>
27
 */
28
final class StaticPrivateMethodFixer extends AbstractFixer
29
{
30
    /**
31
     * @var array<string, true>
32
     */
33
    private const MAGIC_METHODS = [
34
        '__clone' => true,
35
        '__construct' => true,
36
        '__destruct' => true,
37
        '__wakeup' => true,
38
    ];
39

40
    public function getDefinition(): FixerDefinitionInterface
41
    {
42
        return new FixerDefinition(
3✔
43
            'Converts private methods to `static` where possible.',
3✔
44
            [
3✔
45
                new CodeSample(
3✔
46
                    '<?php
3✔
47
class Foo
48
{
49
    public function bar()
50
    {
51
        return $this->baz();
52
    }
53

54
    private function baz()
55
    {
56
        return 1;
57
    }
58
}
59
'
3✔
60
                ),
3✔
61
            ],
3✔
62
            null,
3✔
63
            'Risky when the method:'
3✔
64
            .' contains dynamic generated calls to the instance,'
3✔
65
            .' is dynamically referenced,'
3✔
66
            .' is referenced inside a Trait the class uses.'
3✔
67
        );
3✔
68
    }
69

70
    /**
71
     * {@inheritdoc}
72
     *
73
     * Must run before StaticLambdaFixer.
74
     * Must run after ProtectedToPrivateFixer.
75
     */
76
    public function getPriority(): int
77
    {
78
        return 1;
1✔
79
    }
80

81
    public function isCandidate(Tokens $tokens): bool
82
    {
83
        return $tokens->isAllTokenKindsFound([\T_CLASS, \T_PRIVATE, \T_FUNCTION]);
17✔
84
    }
85

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

91
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
92
    {
93
        $tokensAnalyzer = new TokensAnalyzer($tokens);
17✔
94

95
        do {
96
            $anythingChanged = false;
17✔
97

98
            $end = \count($tokens) - 3; // min. number of tokens to form a class candidate to fix
17✔
99
            for ($index = $end; $index > 0; --$index) {
17✔
100
                if (!$tokens[$index]->isGivenKind(\T_CLASS)) {
17✔
101
                    continue;
17✔
102
                }
103

104
                $classOpen = $tokens->getNextTokenOfKind($index, ['{']);
17✔
105
                $classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
17✔
106

107
                $anythingChanged |= $this->fixClass($tokens, $tokensAnalyzer, $classOpen, $classClose);
17✔
108
            }
109
        } while ($anythingChanged);
17✔
110
    }
111

112
    private function fixClass(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $classOpen, int $classClose): bool
113
    {
114
        $fixedMethods = [];
17✔
115
        foreach ($this->getClassMethods($tokens, $classOpen, $classClose) as $methodData) {
17✔
116
            [$functionKeywordIndex, $methodOpen, $methodClose] = $methodData;
17✔
117

118
            if ($this->skipMethod($tokens, $tokensAnalyzer, $functionKeywordIndex, $methodOpen, $methodClose)) {
17✔
119
                continue;
17✔
120
            }
121

122
            $methodNameIndex = $tokens->getNextMeaningfulToken($functionKeywordIndex);
12✔
123
            $methodName = $tokens[$methodNameIndex]->getContent();
12✔
124
            $fixedMethods[$methodName] = true;
12✔
125

126
            $tokens->insertSlices([$functionKeywordIndex => [new Token([\T_STATIC, 'static']), new Token([\T_WHITESPACE, ' '])]]);
12✔
127
        }
128

129
        if (0 === \count($fixedMethods)) {
17✔
130
            return false;
17✔
131
        }
132

133
        $classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
12✔
134
        foreach ($this->getClassMethods($tokens, $classOpen, $classClose) as $methodData) {
12✔
135
            [, $methodOpen, $methodClose] = $methodData;
12✔
136

137
            $this->fixReferencesInFunction($tokens, $tokensAnalyzer, $methodOpen, $methodClose, $fixedMethods);
12✔
138
        }
139

140
        return true;
12✔
141
    }
142

143
    private function skipMethod(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $functionKeywordIndex, int $methodOpen, int $methodClose): bool
144
    {
145
        $methodNameIndex = $tokens->getNextMeaningfulToken($functionKeywordIndex);
17✔
146
        $methodName = strtolower($tokens[$methodNameIndex]->getContent());
17✔
147
        if (isset(self::MAGIC_METHODS[$methodName])) {
17✔
148
            return true;
1✔
149
        }
150

151
        $prevTokenIndex = $tokens->getPrevMeaningfulToken($functionKeywordIndex);
16✔
152
        if ($tokens[$prevTokenIndex]->isGivenKind(\T_FINAL)) {
16✔
153
            $prevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
1✔
154
        }
155
        if (!$tokens[$prevTokenIndex]->isGivenKind(\T_PRIVATE)) {
16✔
156
            return true;
13✔
157
        }
158

159
        $prePrevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
16✔
160
        if ($tokens[$prePrevTokenIndex]->isGivenKind(\T_STATIC)) {
16✔
161
            return true;
1✔
162
        }
163

164
        for ($index = $methodOpen + 1; $index < $methodClose - 1; ++$index) {
15✔
165
            if ($tokens[$index]->isGivenKind(\T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) {
13✔
166
                $anonymousClassOpen = $tokens->getNextTokenOfKind($index, ['{']);
1✔
167
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $anonymousClassOpen);
1✔
168

169
                continue;
1✔
170
            }
171

172
            if ($tokens[$index]->isGivenKind(\T_FUNCTION)) {
13✔
173
                return true;
1✔
174
            }
175

176
            if ($tokens[$index]->equals([\T_VARIABLE, '$this'])) {
13✔
177
                $operatorIndex = $tokens->getNextMeaningfulToken($index);
3✔
178
                $methodNameIndex = $tokens->getNextMeaningfulToken($operatorIndex);
3✔
179
                $argumentsBraceIndex = $tokens->getNextMeaningfulToken($methodNameIndex);
3✔
180

181
                if (
182
                    !$tokens[$operatorIndex]->isGivenKind(\T_OBJECT_OPERATOR)
3✔
183
                    || $methodName !== $tokens[$methodNameIndex]->getContent()
3✔
184
                    || !$tokens[$argumentsBraceIndex]->equals('(')
3✔
185
                ) {
186
                    return true;
2✔
187
                }
188
            }
189

190
            if ($tokens[$index]->equals([\T_STRING, 'debug_backtrace'])) {
13✔
191
                return true;
1✔
192
            }
193
        }
194

195
        return false;
12✔
196
    }
197

198
    /**
199
     * @param array<string, bool> $fixedMethods
200
     */
201
    private function fixReferencesInFunction(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $methodOpen, int $methodClose, array $fixedMethods): void
202
    {
203
        for ($index = $methodOpen + 1; $index < $methodClose - 1; ++$index) {
12✔
204
            if ($tokens[$index]->isGivenKind(\T_FUNCTION)) {
11✔
205
                $prevIndex = $tokens->getPrevMeaningfulToken($index);
2✔
206
                $closureStart = $tokens->getNextTokenOfKind($index, ['{']);
2✔
207
                $closureEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $closureStart);
2✔
208
                if (!$tokens[$prevIndex]->isGivenKind(\T_STATIC)) {
2✔
209
                    $this->fixReferencesInFunction($tokens, $tokensAnalyzer, $closureStart, $closureEnd, $fixedMethods);
2✔
210
                }
211

212
                $index = $closureEnd;
2✔
213

214
                continue;
2✔
215
            }
216

217
            if ($tokens[$index]->isGivenKind(\T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) {
11✔
218
                $anonymousClassOpen = $tokens->getNextTokenOfKind($index, ['{']);
3✔
219
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $anonymousClassOpen);
3✔
220

221
                continue;
3✔
222
            }
223

224
            if (!$tokens[$index]->equals([\T_VARIABLE, '$this'])) {
11✔
225
                continue;
11✔
226
            }
227

228
            $objectOperatorIndex = $tokens->getNextMeaningfulToken($index);
8✔
229
            if (!$tokens[$objectOperatorIndex]->isGivenKind(\T_OBJECT_OPERATOR)) {
8✔
230
                continue;
×
231
            }
232

233
            $methodNameIndex = $tokens->getNextMeaningfulToken($objectOperatorIndex);
8✔
234
            $argumentsBraceIndex = $tokens->getNextMeaningfulToken($methodNameIndex);
8✔
235
            if (!$tokens[$argumentsBraceIndex]->equals('(')) {
8✔
236
                continue;
4✔
237
            }
238

239
            $currentMethodName = $tokens[$methodNameIndex]->getContent();
8✔
240
            if (!isset($fixedMethods[$currentMethodName])) {
8✔
241
                continue;
1✔
242
            }
243

244
            $tokens[$index] = new Token([\T_STRING, 'self']);
8✔
245
            $tokens[$objectOperatorIndex] = new Token([\T_DOUBLE_COLON, '::']);
8✔
246
        }
247
    }
248

249
    /**
250
     * @return list<array{int, int, int}>
251
     */
252
    private function getClassMethods(Tokens $tokens, int $classOpen, int $classClose): array
253
    {
254
        $methods = [];
17✔
255
        for ($index = $classClose - 1; $index > $classOpen + 1; --$index) {
17✔
256
            if ($tokens[$index]->equals('}')) {
17✔
257
                $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
17✔
258

259
                continue;
17✔
260
            }
261

262
            if (!$tokens[$index]->isGivenKind(\T_FUNCTION)) {
17✔
263
                continue;
17✔
264
            }
265

266
            $functionKeywordIndex = $index;
17✔
267
            $prevTokenIndex = $tokens->getPrevMeaningfulToken($functionKeywordIndex);
17✔
268
            $prevPrevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
17✔
269
            if ($tokens[$prevTokenIndex]->isGivenKind(\T_ABSTRACT) || $tokens[$prevPrevTokenIndex]->isGivenKind(\T_ABSTRACT)) {
17✔
270
                continue;
2✔
271
            }
272

273
            $methodOpen = $tokens->getNextTokenOfKind($functionKeywordIndex, ['{']);
17✔
274
            $methodClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodOpen);
17✔
275

276
            $methods[] = [$functionKeywordIndex, $methodOpen, $methodClose];
17✔
277
        }
278

279
        return $methods;
17✔
280
    }
281
}
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