File "OrderedClassElementsFixer.php"
Full Path: /var/www/html/back/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php
File size: 21.35 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\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\FCT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Utils;
/**
* @phpstan-type _ClassElement array{
* start: int,
* visibility: string,
* abstract: bool,
* static: bool,
* readonly: bool,
* type: string,
* name: string,
* end: int,
* }
* @phpstan-type _AutogeneratedInputConfiguration array{
* case_sensitive?: bool,
* order?: list<string>,
* sort_algorithm?: 'alpha'|'none',
* }
* @phpstan-type _AutogeneratedComputedConfiguration array{
* case_sensitive: bool,
* order: list<string>,
* sort_algorithm: 'alpha'|'none',
* }
*
* @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
*
* @author Gregor Harlan <gharlan@web.de>
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise.
*/
final class OrderedClassElementsFixer extends AbstractFixer implements ConfigurableFixerInterface
{
/** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
use ConfigurableFixerTrait;
/** @internal */
public const SORT_ALPHA = 'alpha';
/** @internal */
public const SORT_NONE = 'none';
private const SUPPORTED_SORT_ALGORITHMS = [
self::SORT_NONE,
self::SORT_ALPHA,
];
/**
* @var array<string, null|non-empty-list<string>> Array containing all class element base types (keys) and their parent types (values)
*/
private const TYPE_HIERARCHY = [
'use_trait' => null,
'public' => null,
'protected' => null,
'private' => null,
'case' => ['public'],
'constant' => null,
'constant_public' => ['constant', 'public'],
'constant_protected' => ['constant', 'protected'],
'constant_private' => ['constant', 'private'],
'property' => null,
'property_static' => ['property'],
'property_public' => ['property', 'public'],
'property_protected' => ['property', 'protected'],
'property_private' => ['property', 'private'],
'property_public_abstract' => ['property_abstract', 'property_public'],
'property_public_readonly' => ['property_readonly', 'property_public'],
'property_protected_abstract' => ['property_abstract', 'property_protected'],
'property_protected_readonly' => ['property_readonly', 'property_protected'],
'property_private_readonly' => ['property_readonly', 'property_private'],
'property_public_static' => ['property_static', 'property_public'],
'property_protected_static' => ['property_static', 'property_protected'],
'property_private_static' => ['property_static', 'property_private'],
'method' => null,
'method_abstract' => ['method'],
'method_static' => ['method'],
'method_public' => ['method', 'public'],
'method_protected' => ['method', 'protected'],
'method_private' => ['method', 'private'],
'method_public_abstract' => ['method_abstract', 'method_public'],
'method_protected_abstract' => ['method_abstract', 'method_protected'],
'method_private_abstract' => ['method_abstract', 'method_private'],
'method_public_abstract_static' => ['method_abstract', 'method_static', 'method_public'],
'method_protected_abstract_static' => ['method_abstract', 'method_static', 'method_protected'],
'method_private_abstract_static' => ['method_abstract', 'method_static', 'method_private'],
'method_public_static' => ['method_static', 'method_public'],
'method_protected_static' => ['method_static', 'method_protected'],
'method_private_static' => ['method_static', 'method_private'],
];
/**
* @var array<string, null> Array containing special method types
*/
private const SPECIAL_TYPES = [
'construct' => null,
'destruct' => null,
'magic' => null,
'phpunit' => null,
];
/**
* @var array<string, int> Resolved configuration array (type => position)
*/
private array $typePosition;
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
}
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Orders the elements of classes/interfaces/traits/enums.',
[
new CodeSample(
<<<'PHP'
<?php
final class Example
{
use BarTrait;
use BazTrait;
const C1 = 1;
const C2 = 2;
protected static $protStatProp;
public static $pubStatProp1;
public $pubProp1;
protected $protProp;
var $pubProp2;
private static $privStatProp;
private $privProp;
public static $pubStatProp2;
public $pubProp3;
protected function __construct() {}
private static function privStatFunc() {}
public function pubFunc1() {}
public function __toString() {}
protected function protFunc() {}
function pubFunc2() {}
public static function pubStatFunc1() {}
public function pubFunc3() {}
static function pubStatFunc2() {}
private function privFunc() {}
public static function pubStatFunc3() {}
protected static function protStatFunc() {}
public function __destruct() {}
}
PHP,
),
new CodeSample(
<<<'PHP'
<?php
class Example
{
public function A(){}
private function B(){}
}
PHP,
['order' => ['method_private', 'method_public']],
),
new CodeSample(
<<<'PHP'
<?php
class Example
{
public function D(){}
public function B(){}
public function A(){}
public function C(){}
}
PHP,
['order' => ['method_public'], 'sort_algorithm' => self::SORT_ALPHA],
),
new CodeSample(
<<<'PHP'
<?php
class Example
{
public function Aa(){}
public function AA(){}
public function AwS(){}
public function AWs(){}
}
PHP,
['order' => ['method_public'], 'sort_algorithm' => self::SORT_ALPHA, 'case_sensitive' => true],
),
],
'Accepts a subset of pre-defined element types, special element groups, and custom patterns.
Element types: `[\''.implode('\', \'', array_keys(self::TYPE_HIERARCHY)).'\']`
Special element types: `[\''.implode('\', \'', array_keys(self::SPECIAL_TYPES)).'\']`
Custom values:
- `method:*`: specify a single method name (e.g. `method:__invoke`) to set the order of that specific method.',
);
}
/**
* {@inheritdoc}
*
* Must run before ClassAttributesSeparationFixer, NoBlankLinesAfterClassOpeningFixer, PhpUnitDataProviderMethodOrderFixer, SpaceAfterSemicolonFixer.
* Must run after NoPhp4ConstructorFixer, ProtectedToPrivateFixer.
*/
public function getPriority(): int
{
return 65;
}
protected function configurePostNormalisation(): void
{
$this->typePosition = [];
$position = 0;
foreach ($this->configuration['order'] as $type) {
$this->typePosition[$type] = $position++;
}
foreach (self::TYPE_HIERARCHY as $type => $parents) {
if (isset($this->typePosition[$type])) {
continue;
}
if (null === $parents) {
$this->typePosition[$type] = null;
continue;
}
foreach ($parents as $parent) {
if (isset($this->typePosition[$parent])) {
$this->typePosition[$type] = $this->typePosition[$parent];
continue 2;
}
}
$this->typePosition[$type] = null;
}
$lastPosition = \count($this->configuration['order']);
foreach ($this->typePosition as &$pos) {
if (null === $pos) {
$pos = $lastPosition;
}
$pos *= 10; // last digit is used by phpunit method ordering
}
}
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
for ($i = 1, $count = $tokens->count(); $i < $count; ++$i) {
if (!$tokens[$i]->isClassy()) {
continue;
}
$i = $tokens->getNextTokenOfKind($i, ['{']);
$elements = $this->getElements($tokens, $i);
if (0 === \count($elements)) {
continue;
}
$endIndex = $elements[array_key_last($elements)]['end'];
$sorted = $this->sortElements($elements);
if ($sorted !== $elements) {
$this->sortTokens($tokens, $i, $endIndex, $sorted);
}
$i = $endIndex;
}
}
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
$builtIns = array_keys(array_merge(self::TYPE_HIERARCHY, self::SPECIAL_TYPES));
return new FixerConfigurationResolver([
(new FixerOptionBuilder('order', 'List of strings defining order of elements.'))
->setAllowedTypes(['string[]'])
->setAllowedValues([
static function (array $values) use ($builtIns): bool {
foreach ($values as $value) {
if (\in_array($value, $builtIns, true)) {
return true;
}
if ('method:' === substr($value, 0, 7)) {
return true;
}
}
return false;
},
])
->setDefault([
'use_trait',
'case',
'constant_public',
'constant_protected',
'constant_private',
'property_public',
'property_protected',
'property_private',
'construct',
'destruct',
'magic',
'phpunit',
'method_public',
'method_protected',
'method_private',
])
->getOption(),
(new FixerOptionBuilder('sort_algorithm', 'How multiple occurrences of same type statements should be sorted.'))
->setAllowedValues(self::SUPPORTED_SORT_ALGORITHMS)
->setDefault(self::SORT_NONE)
->getOption(),
(new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.'))
->setAllowedTypes(['bool'])
->setDefault(false)
->getOption(),
]);
}
/**
* @return list<_ClassElement>
*/
private function getElements(Tokens $tokens, int $startIndex): array
{
++$startIndex;
$elements = [];
while (true) {
$element = [
'start' => $startIndex,
'visibility' => 'public',
'abstract' => false,
'static' => false,
'readonly' => false,
];
for ($i = $startIndex;; ++$i) {
$token = $tokens[$i];
if ($token->isGivenKind(FCT::T_ATTRIBUTE)) {
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $i);
continue;
}
// class end
if ($token->equals('}')) {
return $elements;
}
if ($token->isGivenKind(\T_ABSTRACT)) {
$element['abstract'] = true;
continue;
}
if ($token->isGivenKind(\T_STATIC)) {
$element['static'] = true;
continue;
}
if ($token->isGivenKind(FCT::T_READONLY)) {
$element['readonly'] = true;
}
if ($token->isGivenKind([\T_PROTECTED, \T_PRIVATE])) {
$element['visibility'] = strtolower($token->getContent());
continue;
}
if (!$token->isGivenKind([CT::T_USE_TRAIT, \T_CASE, \T_CONST, \T_VARIABLE, \T_FUNCTION])) {
continue;
}
$type = $this->detectElementType($tokens, $i);
if (\is_array($type)) {
$element['type'] = $type[0];
$element['name'] = $type[1];
} else {
$element['type'] = $type;
}
if ('property' === $element['type']) {
$element['name'] = $tokens[$i]->getContent();
} elseif ('constant' === $element['type']) {
$equalsSignIndex = $tokens->getNextTokenOfKind($i, ['=']);
$element['name'] = $tokens[$tokens->getPrevMeaningfulToken($equalsSignIndex)]->getContent();
} elseif (\in_array($element['type'], ['use_trait', 'case', 'method', 'magic', 'construct', 'destruct'], true)) {
$element['name'] = $tokens[$tokens->getNextMeaningfulToken($i)]->getContent();
}
$element['end'] = $this->findElementEnd($tokens, $i);
break;
}
$elements[] = $element;
$startIndex = $element['end'] + 1;
}
}
/**
* @return list{string, string}|string type or array of type and name
*/
private function detectElementType(Tokens $tokens, int $index)
{
$token = $tokens[$index];
if ($token->isGivenKind(CT::T_USE_TRAIT)) {
return 'use_trait';
}
if ($token->isGivenKind(\T_CASE)) {
return 'case';
}
if ($token->isGivenKind(\T_CONST)) {
return 'constant';
}
if ($token->isGivenKind(\T_VARIABLE)) {
return 'property';
}
$nameToken = $tokens[$tokens->getNextMeaningfulToken($index)];
if ($nameToken->equals([\T_STRING, '__construct'], false)) {
return 'construct';
}
if ($nameToken->equals([\T_STRING, '__destruct'], false)) {
return 'destruct';
}
if (
$nameToken->equalsAny([
[\T_STRING, 'setUpBeforeClass'],
[\T_STRING, 'doSetUpBeforeClass'],
[\T_STRING, 'tearDownAfterClass'],
[\T_STRING, 'doTearDownAfterClass'],
[\T_STRING, 'setUp'],
[\T_STRING, 'doSetUp'],
[\T_STRING, 'assertPreConditions'],
[\T_STRING, 'assertPostConditions'],
[\T_STRING, 'tearDown'],
[\T_STRING, 'doTearDown'],
], false)
) {
return ['phpunit', strtolower($nameToken->getContent())];
}
return str_starts_with($nameToken->getContent(), '__') ? 'magic' : 'method';
}
private function findElementEnd(Tokens $tokens, int $index): int
{
$index = $tokens->getNextTokenOfKind($index, ['(', '{', ';', [CT::T_PROPERTY_HOOK_BRACE_OPEN]]);
if ($tokens[$index]->equals('(')) {
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$index = $tokens->getNextTokenOfKind($index, ['{', ';']);
}
if ($tokens[$index]->equals('{')) {
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
}
if ($tokens[$index]->isGivenKind(CT::T_PROPERTY_HOOK_BRACE_OPEN)) {
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PROPERTY_HOOK, $index);
}
for (++$index; $tokens[$index]->isWhitespace(" \t") || $tokens[$index]->isComment(); ++$index);
--$index;
return $tokens[$index]->isWhitespace() ? $index - 1 : $index;
}
/**
* @param list<_ClassElement> $elements
*
* @return list<_ClassElement>
*/
private function sortElements(array $elements): array
{
return Utils::stableSort(
$elements,
/**
* @return array{element: _ClassElement, position: int}
*/
fn (array $element): array => ['element' => $element, 'position' => $this->getTypePosition($element)],
/**
* @param array{element: _ClassElement, position: int} $a
* @param array{element: _ClassElement, position: int} $b
*
* @return -1|0|1
*/
fn (array $a, array $b): int => ($a['position'] === $b['position']) ? $this->sortGroupElements($a['element'], $b['element']) : $a['position'] <=> $b['position'],
);
}
/**
* @param _ClassElement $element
*/
private function getTypePosition(array $element): int
{
$type = $element['type'];
if (\in_array($type, ['method', 'magic', 'phpunit'], true) && isset($this->typePosition["method:{$element['name']}"])) {
return $this->typePosition["method:{$element['name']}"];
}
if (\array_key_exists($type, self::SPECIAL_TYPES)) {
if (isset($this->typePosition[$type])) {
$position = $this->typePosition[$type];
if ('phpunit' === $type) {
$position += [
'setupbeforeclass' => 1,
'dosetupbeforeclass' => 2,
'teardownafterclass' => 3,
'doteardownafterclass' => 4,
'setup' => 5,
'dosetup' => 6,
'assertpreconditions' => 7,
'assertpostconditions' => 8,
'teardown' => 9,
'doteardown' => 10,
][$element['name']];
}
return $position;
}
$type = 'method';
}
if (\in_array($type, ['constant', 'property', 'method'], true)) {
$type .= '_'.$element['visibility'];
if ($element['abstract']) {
$type .= '_abstract';
}
if ($element['static']) {
$type .= '_static';
}
if ($element['readonly']) {
$type .= '_readonly';
}
}
return $this->typePosition[$type];
}
/**
* @param _ClassElement $a
* @param _ClassElement $b
*/
private function sortGroupElements(array $a, array $b): int
{
if (self::SORT_ALPHA === $this->configuration['sort_algorithm']) {
return true === $this->configuration['case_sensitive']
? $a['name'] <=> $b['name']
: strcasecmp($a['name'], $b['name']);
}
return $a['start'] <=> $b['start'];
}
/**
* @param list<_ClassElement> $elements
*/
private function sortTokens(Tokens $tokens, int $startIndex, int $endIndex, array $elements): void
{
$replaceTokens = [];
foreach ($elements as $element) {
for ($i = $element['start']; $i <= $element['end']; ++$i) {
$replaceTokens[] = clone $tokens[$i];
}
}
$tokens->overrideRange($startIndex + 1, $endIndex, $replaceTokens);
}
}