File "StaticPrivateMethodFixer.php"
Full Path: /var/www/html/back/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/StaticPrivateMethodFixer.php
File size: 9.91 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz RumiĆski <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Fixer\ClassNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;
/**
* @author Filippo Tessarotto <zoeslam@gmail.com>
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise.
*/
final class StaticPrivateMethodFixer extends AbstractFixer
{
/**
* @var array<string, true>
*/
private const MAGIC_METHODS = [
'__clone' => true,
'__construct' => true,
'__destruct' => true,
'__wakeup' => true,
];
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Converts private methods to `static` where possible.',
[
new CodeSample(
<<<'PHP'
<?php
class Foo
{
public function bar()
{
return $this->baz();
}
private function baz()
{
return 1;
}
}
PHP,
),
],
null,
'Risky when the method:'
.' contains dynamic generated calls to the instance,'
.' is dynamically referenced,'
.' is referenced inside a Trait the class uses.',
);
}
/**
* {@inheritdoc}
*
* Must run before StaticLambdaFixer.
* Must run after ProtectedToPrivateFixer.
*/
public function getPriority(): int
{
return 1;
}
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAllTokenKindsFound([\T_CLASS, \T_PRIVATE, \T_FUNCTION]);
}
public function isRisky(): bool
{
return true;
}
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$tokensAnalyzer = new TokensAnalyzer($tokens);
do {
$anythingChanged = false;
$end = \count($tokens) - 3; // min. number of tokens to form a class candidate to fix
for ($index = $end; $index > 0; --$index) {
if (!$tokens[$index]->isGivenKind(\T_CLASS)) {
continue;
}
$classOpen = $tokens->getNextTokenOfKind($index, ['{']);
$classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
$anythingChanged |= $this->fixClass($tokens, $tokensAnalyzer, $classOpen, $classClose);
}
} while ($anythingChanged);
}
private function fixClass(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $classOpen, int $classClose): bool
{
$fixedMethods = [];
foreach ($this->getClassMethods($tokens, $classOpen, $classClose) as $methodData) {
[$functionKeywordIndex, $methodOpen, $methodClose] = $methodData;
if ($this->skipMethod($tokens, $tokensAnalyzer, $functionKeywordIndex, $methodOpen, $methodClose)) {
continue;
}
$methodNameIndex = $tokens->getNextMeaningfulToken($functionKeywordIndex);
$methodName = $tokens[$methodNameIndex]->getContent();
$fixedMethods[$methodName] = true;
$tokens->insertSlices([$functionKeywordIndex => [new Token([\T_STATIC, 'static']), new Token([\T_WHITESPACE, ' '])]]);
}
if (0 === \count($fixedMethods)) {
return false;
}
$classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
foreach ($this->getClassMethods($tokens, $classOpen, $classClose) as $methodData) {
[, $methodOpen, $methodClose] = $methodData;
$this->fixReferencesInFunction($tokens, $tokensAnalyzer, $methodOpen, $methodClose, $fixedMethods);
}
return true;
}
private function skipMethod(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $functionKeywordIndex, int $methodOpen, int $methodClose): bool
{
$methodNameIndex = $tokens->getNextMeaningfulToken($functionKeywordIndex);
$methodName = strtolower($tokens[$methodNameIndex]->getContent());
if (isset(self::MAGIC_METHODS[$methodName])) {
return true;
}
$prevTokenIndex = $tokens->getPrevMeaningfulToken($functionKeywordIndex);
if ($tokens[$prevTokenIndex]->isGivenKind(\T_FINAL)) {
$prevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
}
if (!$tokens[$prevTokenIndex]->isGivenKind(\T_PRIVATE)) {
return true;
}
$prePrevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
if ($tokens[$prePrevTokenIndex]->isGivenKind(\T_STATIC)) {
return true;
}
for ($index = $methodOpen + 1; $index < $methodClose - 1; ++$index) {
if ($tokens[$index]->isGivenKind(\T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) {
$anonymousClassOpen = $tokens->getNextTokenOfKind($index, ['{']);
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $anonymousClassOpen);
continue;
}
if ($tokens[$index]->isGivenKind(\T_FUNCTION)) {
return true;
}
if ($tokens[$index]->equals([\T_VARIABLE, '$this'])) {
$operatorIndex = $tokens->getNextMeaningfulToken($index);
$methodNameIndex = $tokens->getNextMeaningfulToken($operatorIndex);
$argumentsBraceIndex = $tokens->getNextMeaningfulToken($methodNameIndex);
if (
!$tokens[$operatorIndex]->isGivenKind(\T_OBJECT_OPERATOR)
|| $methodName !== $tokens[$methodNameIndex]->getContent()
|| !$tokens[$argumentsBraceIndex]->equals('(')
) {
return true;
}
}
if ($tokens[$index]->equals([\T_STRING, 'debug_backtrace'])) {
return true;
}
}
return false;
}
/**
* @param array<string, bool> $fixedMethods
*/
private function fixReferencesInFunction(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $methodOpen, int $methodClose, array $fixedMethods): void
{
for ($index = $methodOpen + 1; $index < $methodClose - 1; ++$index) {
if ($tokens[$index]->isGivenKind(\T_FUNCTION)) {
$prevIndex = $tokens->getPrevMeaningfulToken($index);
$closureStart = $tokens->getNextTokenOfKind($index, ['{']);
$closureEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $closureStart);
if (!$tokens[$prevIndex]->isGivenKind(\T_STATIC)) {
$this->fixReferencesInFunction($tokens, $tokensAnalyzer, $closureStart, $closureEnd, $fixedMethods);
}
$index = $closureEnd;
continue;
}
if ($tokens[$index]->isGivenKind(\T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) {
$anonymousClassOpen = $tokens->getNextTokenOfKind($index, ['{']);
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $anonymousClassOpen);
continue;
}
if (!$tokens[$index]->equals([\T_VARIABLE, '$this'])) {
continue;
}
$objectOperatorIndex = $tokens->getNextMeaningfulToken($index);
if (!$tokens[$objectOperatorIndex]->isGivenKind(\T_OBJECT_OPERATOR)) {
continue;
}
$methodNameIndex = $tokens->getNextMeaningfulToken($objectOperatorIndex);
$argumentsBraceIndex = $tokens->getNextMeaningfulToken($methodNameIndex);
if (!$tokens[$argumentsBraceIndex]->equals('(')) {
continue;
}
$currentMethodName = $tokens[$methodNameIndex]->getContent();
if (!isset($fixedMethods[$currentMethodName])) {
continue;
}
$tokens[$index] = new Token([\T_STRING, 'self']);
$tokens[$objectOperatorIndex] = new Token([\T_DOUBLE_COLON, '::']);
}
}
/**
* @return list<array{int, int, int}>
*/
private function getClassMethods(Tokens $tokens, int $classOpen, int $classClose): array
{
$methods = [];
for ($index = $classClose - 1; $index > $classOpen + 1; --$index) {
if ($tokens[$index]->equals('}')) {
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
continue;
}
if (!$tokens[$index]->isGivenKind(\T_FUNCTION)) {
continue;
}
$functionKeywordIndex = $index;
$prevTokenIndex = $tokens->getPrevMeaningfulToken($functionKeywordIndex);
$prevPrevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
if ($tokens[$prevTokenIndex]->isGivenKind(\T_ABSTRACT) || $tokens[$prevPrevTokenIndex]->isGivenKind(\T_ABSTRACT)) {
continue;
}
$methodOpen = $tokens->getNextTokenOfKind($functionKeywordIndex, ['{']);
$methodClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodOpen);
$methods[] = [$functionKeywordIndex, $methodOpen, $methodClose];
}
return $methods;
}
}