Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
peripherad
/
back
/
vendor
/
friendsofphp
/
php-cs-fixer
/
src
/
Fixer
/
ClassNotation
:
StaticPrivateMethodFixer.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?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; } }