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
:
ModifierKeywordsFixer.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\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; 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\FixerDefinition\VersionSpecification; use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\FCT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; /** * Fixer for rules defined in PSR2 ¶4.3, ¶4.5. * * @phpstan-type _AutogeneratedInputConfiguration array{ * elements?: list<'const'|'method'|'property'>, * } * @phpstan-type _AutogeneratedComputedConfiguration array{ * elements: list<'const'|'method'|'property'>, * } * * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> * * @no-named-arguments Parameter names are not covered by the backward compatibility promise. */ final class ModifierKeywordsFixer extends AbstractFixer implements ConfigurableFixerInterface { /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ use ConfigurableFixerTrait; private const PROPERTY_TYPE_DECLARATION_KINDS = [\T_STRING, \T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]; private const EXPECTED_KINDS_GENERIC = [\T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_STATIC, \T_VAR, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, FCT::T_READONLY, FCT::T_PRIVATE_SET, FCT::T_PROTECTED_SET, FCT::T_PUBLIC_SET]; private const EXPECTED_KINDS_PROPERTY_KINDS = [...self::EXPECTED_KINDS_GENERIC, ...self::PROPERTY_TYPE_DECLARATION_KINDS]; /** * @var list<'const'|'method'|'promoted_property'|'property'> */ private array $elements = ['const', 'method', 'property']; public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Classes, constants, properties, and methods MUST have visibility declared, and keyword modifiers MUST be in the following order:' .' inheritance modifier (`abstract` or `final`),' .' visibility modifier (`public`, `protected`, or `private`),' .' set-visibility modifier (`public(set)`, `protected(set)`, or `private(set)`),' .' scope modifier (`static`),' .' mutation modifier (`readonly`),' .' type declaration, name.', [ new CodeSample( <<<'PHP' <?php abstract class ClassName { const SAMPLE = 1; var $a; protected string $foo; static protected int $beep; static public final function bar() {} protected abstract function zim(); function zex() {} } PHP, ), new VersionSpecificCodeSample( <<<'PHP' <?php abstract class ClassName { const SAMPLE = 1; var $a; readonly protected string $foo; static protected int $beep; static public final function bar() {} protected abstract function zim(); function zex() {} } readonly final class ValueObject { // ... } PHP, new VersionSpecification(8_02_00), ), new VersionSpecificCodeSample( <<<'PHP' <?php abstract class ClassName { const SAMPLE = 1; var $a; protected abstract string $bar { get => "a"; set; } readonly final protected string $foo; static protected final int $beep; static public final function bar() {} protected abstract function zim(); function zex() {} } readonly final class ValueObject { // ... } PHP, new VersionSpecification(8_04_00), ), new CodeSample( <<<'PHP' <?php class Sample { const SAMPLE = 1; } PHP, ['elements' => ['const']], ), ], ); } /** * {@inheritdoc} * * Must run before ClassAttributesSeparationFixer. */ public function getPriority(): int { return 56; } public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } /** * @protected * * @TODO 4.0 move visibility from annotation to code when `VisibilityRequiredFixer` is removed */ public function createConfigurationDefinition(): FixerConfigurationResolverInterface { $elements = ['const', 'method', 'property']; return new FixerConfigurationResolver([ (new FixerOptionBuilder('elements', 'The structural elements to fix.')) ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($elements)]) ->setDefault($elements) ->getOption(), ]); } protected function configurePostNormalisation(): void { $this->elements = $this->configuration['elements']; if (\in_array('property', $this->elements, true)) { $this->elements[] = 'promoted_property'; } } protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); foreach (array_reverse($tokensAnalyzer->getClassyElements(), true) as $index => $element) { if (!\in_array($element['type'], $this->elements, true)) { continue; } $abstractFinalIndex = null; $visibilityIndex = null; $visibilitySetIndex = null; $staticIndex = null; $typeIndex = null; $readOnlyIndex = null; $prevIndex = $tokens->getPrevMeaningfulToken($index); $expectedKinds = 'property' === $element['type'] || 'promoted_property' === $element['type'] ? self::EXPECTED_KINDS_PROPERTY_KINDS : self::EXPECTED_KINDS_GENERIC; while ($tokens[$prevIndex]->isGivenKind($expectedKinds) || $tokens[$prevIndex]->equals('&')) { if ($tokens[$prevIndex]->isGivenKind([\T_ABSTRACT, \T_FINAL])) { $abstractFinalIndex = $prevIndex; } elseif ($tokens[$prevIndex]->isGivenKind(\T_STATIC)) { $staticIndex = $prevIndex; } elseif ($tokens[$prevIndex]->isGivenKind(FCT::T_READONLY)) { $readOnlyIndex = $prevIndex; } elseif ($tokens[$prevIndex]->isGivenKind([FCT::T_PRIVATE_SET, FCT::T_PROTECTED_SET, FCT::T_PUBLIC_SET])) { $visibilitySetIndex = $prevIndex; } elseif ($tokens[$prevIndex]->isGivenKind(self::PROPERTY_TYPE_DECLARATION_KINDS)) { $typeIndex = $prevIndex; } elseif (!$tokens[$prevIndex]->equals('&')) { $visibilityIndex = $prevIndex; } $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); } if (null !== $typeIndex) { $index = $typeIndex; } if ('property' === $element['type'] && $tokens[$prevIndex]->equals(',')) { continue; } $swapIndex = $staticIndex ?? $readOnlyIndex; // "static" property cannot be "readonly", so there can always be at most one swap if (null !== $swapIndex) { if ($this->isKeywordPlacedProperly($tokens, $swapIndex, $index)) { $index = $swapIndex; } else { $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $swapIndex, $index); } } if (null !== $visibilitySetIndex) { if ($this->isKeywordPlacedProperly($tokens, $visibilitySetIndex, $index)) { $index = $visibilitySetIndex; } else { $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $visibilitySetIndex, $index); } } if (null === $visibilityIndex) { $tokens->insertAt($index, [new Token(['promoted_property' === $element['type'] ? CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC : \T_PUBLIC, 'public']), new Token([\T_WHITESPACE, ' '])]); } else { if ($tokens[$visibilityIndex]->isGivenKind(\T_VAR)) { $tokens[$visibilityIndex] = new Token([\T_PUBLIC, 'public']); } if ($this->isKeywordPlacedProperly($tokens, $visibilityIndex, $index)) { $index = $visibilityIndex; } else { $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $visibilityIndex, $index); } } if (null === $abstractFinalIndex) { continue; } if ($this->isKeywordPlacedProperly($tokens, $abstractFinalIndex, $index)) { continue; } $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $abstractFinalIndex, $index); } } private function isKeywordPlacedProperly(Tokens $tokens, int $keywordIndex, int $comparedIndex): bool { return ' ' === $tokens[$keywordIndex + 1]->getContent() && ( $keywordIndex + 2 === $comparedIndex || $keywordIndex + 3 === $comparedIndex && $tokens[$keywordIndex + 2]->equals('&') ); } private function moveTokenAndEnsureSingleSpaceFollows(Tokens $tokens, int $fromIndex, int $toIndex): void { $tokens->insertAt($toIndex, [$tokens[$fromIndex], new Token([\T_WHITESPACE, ' '])]); $tokens->clearAt($fromIndex); if ($tokens[$fromIndex + 1]->isWhitespace()) { $tokens->clearAt($fromIndex + 1); } } }