Skip to content

Commit

Permalink
bug #6280 Fix bunch of enum issus (SpacePossum)
Browse files Browse the repository at this point in the history
This PR was merged into the master branch.

Discussion
----------

Fix bunch of enum issus

resolves the issue described in this comment
#6204 (comment)

Commits
-------

37ef9af PHP8.1 - Enum support
  • Loading branch information
SpacePossum committed Feb 16, 2022
2 parents c753f74 + 37ef9af commit ad4a709
Show file tree
Hide file tree
Showing 39 changed files with 796 additions and 51 deletions.
8 changes: 4 additions & 4 deletions doc/list.rst
Expand Up @@ -166,17 +166,17 @@ List of Available Rules
Configuration options:

- | ``elements``
| Dictionary of `const|method|property|trait_import` => `none|one|only_if_meta` values.
| Dictionary of `const|method|property|trait_import|case` => `none|one|only_if_meta` values.
| Allowed types: ``array``
| Default value: ``['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'none']``
| Default value: ``['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'none', 'case' => 'none']``

Part of rule sets `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_

`Source PhpCsFixer\\Fixer\\ClassNotation\\ClassAttributesSeparationFixer <./../src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php>`_
- `class_definition <./rules/class_notation/class_definition.rst>`_

Whitespace around the keywords of a class, trait or interfaces definition should be one space.
Whitespace around the keywords of a class, trait, enum or interfaces definition should be one space.

Configuration options:

Expand Down Expand Up @@ -1578,7 +1578,7 @@ List of Available Rules
`Source PhpCsFixer\\Fixer\\ControlStructure\\NoUnneededCurlyBracesFixer <./../src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php>`_
- `no_unneeded_final_method <./rules/class_notation/no_unneeded_final_method.rst>`_

A ``final`` class must not have ``final`` methods and ``private`` methods must not be ``final``.
Removes ``final`` from methods where possible.

*warning risky* Risky when child class overrides a ``private`` method.

Expand Down
4 changes: 2 additions & 2 deletions doc/rules/class_notation/class_attributes_separation.rst
Expand Up @@ -11,12 +11,12 @@ Configuration
``elements``
~~~~~~~~~~~~

Dictionary of ``const|method|property|trait_import`` =>
Dictionary of ``const|method|property|trait_import|case`` =>
``none|one|only_if_meta`` values.

Allowed types: ``array``

Default value: ``['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'none']``
Default value: ``['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'none', 'case' => 'none']``

Examples
--------
Expand Down
4 changes: 2 additions & 2 deletions doc/rules/class_notation/class_definition.rst
Expand Up @@ -2,8 +2,8 @@
Rule ``class_definition``
=========================

Whitespace around the keywords of a class, trait or interfaces definition should
be one space.
Whitespace around the keywords of a class, trait, enum or interfaces definition
should be one space.

Configuration
-------------
Expand Down
3 changes: 1 addition & 2 deletions doc/rules/class_notation/no_unneeded_final_method.rst
Expand Up @@ -2,8 +2,7 @@
Rule ``no_unneeded_final_method``
=================================

A ``final`` class must not have ``final`` methods and ``private`` methods must
not be ``final``.
Removes ``final`` from methods where possible.

Warning
-------
Expand Down
4 changes: 2 additions & 2 deletions doc/rules/index.rst
Expand Up @@ -144,7 +144,7 @@ Class Notation
Class, trait and interface elements must be separated with one or none blank line.
- `class_definition <./class_notation/class_definition.rst>`_

Whitespace around the keywords of a class, trait or interfaces definition should be one space.
Whitespace around the keywords of a class, trait, enum or interfaces definition should be one space.
- `final_class <./class_notation/final_class.rst>`_ *(risky)*

All classes must be final, except abstract ones and Doctrine entities.
Expand All @@ -165,7 +165,7 @@ Class Notation
Convert PHP4-style constructors to ``__construct``.
- `no_unneeded_final_method <./class_notation/no_unneeded_final_method.rst>`_ *(risky)*

A ``final`` class must not have ``final`` methods and ``private`` methods must not be ``final``.
Removes ``final`` from methods where possible.
- `ordered_class_elements <./class_notation/ordered_class_elements.rst>`_

Orders the elements of classes/interfaces/traits.
Expand Down
9 changes: 7 additions & 2 deletions src/AbstractDoctrineAnnotationFixer.php
Expand Up @@ -58,6 +58,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
$docCommentToken,
$this->configuration['ignored_tags']
);

$this->fixAnnotations($doctrineAnnotationTokens);
$tokens[$index] = new Token([T_DOC_COMMENT, $doctrineAnnotationTokens->getCode()]);
}
Expand Down Expand Up @@ -211,7 +212,7 @@ private function nextElementAcceptsDoctrineAnnotations(Tokens $tokens, int $inde
}
} while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL]));

if ($tokens[$index]->isClassy()) {
if ($tokens[$index]->isGivenKind(T_CLASS)) {
return true;
}

Expand All @@ -225,6 +226,10 @@ private function nextElementAcceptsDoctrineAnnotations(Tokens $tokens, int $inde
$index = $tokens->getNextMeaningfulToken($index);
}

return isset($this->classyElements[$index]);
if (!isset($this->classyElements[$index])) {
return false;
}

return $tokens[$this->classyElements[$index]['classIndex']]->isGivenKind(T_CLASS); // interface, enums and traits cannot have doctrine annotations
}
}
2 changes: 1 addition & 1 deletion src/Fixer/Basic/BracesFixer.php
Expand Up @@ -297,7 +297,7 @@ private function fixDoWhile(Tokens $tokens): void

private function fixIndents(Tokens $tokens): void
{
$classyTokens = Token::getClassyTokenKinds();
$classyTokens = [T_CLASS, T_TRAIT, T_INTERFACE]; // FIXME use Token::getClassyTokenKinds() when ENUM tests are made
$classyAndFunctionTokens = array_merge([T_FUNCTION], $classyTokens);
$controlTokens = $this->getControlTokens();
$indentTokens = array_filter(
Expand Down
7 changes: 4 additions & 3 deletions src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php
Expand Up @@ -204,11 +204,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('elements', 'Dictionary of `const|method|property|trait_import` => `none|one|only_if_meta` values.'))
(new FixerOptionBuilder('elements', 'Dictionary of `const|method|property|trait_import|case` => `none|one|only_if_meta` values.'))
->setAllowedTypes(['array'])
->setAllowedValues([static function (array $option): bool {
foreach ($option as $type => $spacing) {
$supportedTypes = ['const', 'method', 'property', 'trait_import'];
$supportedTypes = ['const', 'method', 'property', 'trait_import', 'case'];

if (!\in_array($type, $supportedTypes, true)) {
throw new InvalidOptionsException(
Expand Down Expand Up @@ -241,6 +241,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn
'method' => self::SPACING_ONE,
'property' => self::SPACING_ONE,
'trait_import' => self::SPACING_NONE,
'case' => self::SPACING_NONE,
])
->getOption(),
]);
Expand Down Expand Up @@ -542,7 +543,7 @@ private function getLastTokenIndexOfClassElement(Tokens $tokens, array $class, a
if (!$tokens[$elementEndIndex]->equals(';')) {
$elementEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($element['index'], ['{']));
}
} else { // const or property
} else { // 'const', 'property', enum-'case', or 'method' of an interface
$elementEndIndex = $tokens->getNextTokenOfKind($element['index'], [';']);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Fixer/ClassNotation/ClassDefinitionFixer.php
Expand Up @@ -38,7 +38,7 @@ final class ClassDefinitionFixer extends AbstractFixer implements ConfigurableFi
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Whitespace around the keywords of a class, trait or interfaces definition should be one space.',
'Whitespace around the keywords of a class, trait, enum or interfaces definition should be one space.',
[
new CodeSample(
'<?php
Expand Down
Expand Up @@ -68,7 +68,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
$classLevel = 0;

for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
if ($tokens[$index]->isClassy()) {
if ($tokens[$index]->isGivenKind([T_CLASS, T_TRAIT])) { // Enums and interfaces do not have properties
++$classLevel;
$inClass[$classLevel] = 1;

Expand Down
43 changes: 33 additions & 10 deletions src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php
Expand Up @@ -36,7 +36,7 @@ final class NoUnneededFinalMethodFixer extends AbstractFixer implements Configur
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'A `final` class must not have `final` methods and `private` methods must not be `final`.',
'Removes `final` from methods where possible.',
[
new CodeSample(
'<?php
Expand Down Expand Up @@ -78,7 +78,15 @@ final private function bar1() {}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAllTokenKindsFound([T_CLASS, T_FINAL]);
if (!$tokens->isAllTokenKindsFound([T_FINAL, T_FUNCTION])) {
return false;
}

if (\defined('T_ENUM') && $tokens->isTokenKindFound(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required
return true;
}

return $tokens->isTokenKindFound(T_CLASS);
}

public function isRisky(): bool
Expand All @@ -91,10 +99,10 @@ public function isRisky(): bool
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
foreach ($this->getClassMethods($tokens) as $element) {
foreach ($this->getMethods($tokens) as $element) {
$index = $element['method_final_index'];

if ($element['class_is_final']) {
if ($element['method_of_enum'] || $element['class_is_final']) {
$this->clearFinal($tokens, $index);

continue;
Expand All @@ -121,11 +129,12 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn
]);
}

private function getClassMethods(Tokens $tokens): \Generator
private function getMethods(Tokens $tokens): \Generator
{
$tokensAnalyzer = new TokensAnalyzer($tokens);
$modifierKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_STATIC];

$enums = [];
$classesAreFinal = [];
$elements = $tokensAnalyzer->getClassyElements();

Expand All @@ -144,13 +153,10 @@ private function getClassMethods(Tokens $tokens): \Generator

$classIndex = $element['classIndex'];

if (!\array_key_exists($classIndex, $classesAreFinal)) {
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)];
$classesAreFinal[$classIndex] = $prevToken->isGivenKind(T_FINAL);
if (!\array_key_exists($classIndex, $enums)) {
$enums[$classIndex] = \defined('T_ENUM') && $tokens[$classIndex]->isGivenKind(T_ENUM); // @TODO: drop condition when PHP 8.1+ is required
}

$element['class_is_final'] = $classesAreFinal[$classIndex];
$element['method_is_constructor'] = '__construct' === strtolower($tokens[$tokens->getNextMeaningfulToken($index)]->getContent());
$element['method_final_index'] = null;
$element['method_is_private'] = false;

Expand All @@ -166,6 +172,23 @@ private function getClassMethods(Tokens $tokens): \Generator
}
} while ($tokens[$previous]->isGivenKind($modifierKinds));

if ($enums[$classIndex]) {
$element['method_of_enum'] = true;

yield $element;

continue;
}

if (!\array_key_exists($classIndex, $classesAreFinal)) {
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)];
$classesAreFinal[$classIndex] = $prevToken->isGivenKind(T_FINAL);
}

$element['method_of_enum'] = false;
$element['class_is_final'] = $classesAreFinal[$classIndex];
$element['method_is_constructor'] = '__construct' === strtolower($tokens[$tokens->getNextMeaningfulToken($index)]->getContent());

yield $element;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Fixer/ClassNotation/OrderedClassElementsFixer.php
Expand Up @@ -150,7 +150,7 @@ public function configure(array $configuration): void
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
return $tokens->isAnyTokenKindsFound([T_CLASS, T_TRAIT, T_INTERFACE]); // FIXME use Token::getClassyTokenKinds(false)
}

/**
Expand Down Expand Up @@ -237,7 +237,7 @@ public function getPriority(): int
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
for ($i = 1, $count = $tokens->count(); $i < $count; ++$i) {
if (!$tokens[$i]->isClassy()) {
if (!$tokens[$i]->isGivenKind([T_CLASS, T_TRAIT, T_INTERFACE])) { // FIXME use "isClassy"
continue;
}

Expand Down
15 changes: 14 additions & 1 deletion src/Fixer/ClassNotation/ProtectedToPrivateFixer.php
Expand Up @@ -68,6 +68,10 @@ public function getPriority(): int
*/
public function isCandidate(Tokens $tokens): bool
{
if (\defined('T_ENUM') && $tokens->isAllTokenKindsFound([T_ENUM, T_PROTECTED])) { // @TODO: drop condition when PHP 8.1+ is required
return true;
}

return $tokens->isAllTokenKindsFound([T_CLASS, T_FINAL, T_PROTECTED]);
}

Expand All @@ -84,6 +88,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
}

$classesCandidate = [];
$classElementTypes = ['method' => true, 'property' => true, 'const' => true];

foreach ($tokensAnalyzer->getClassyElements() as $index => $element) {
$classIndex = $element['classIndex'];
Expand All @@ -93,7 +98,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
}

if (false === $classesCandidate[$classIndex]) {
continue; // not "final" class, "extends", is "anonymous" or uses trait
continue; // not "final" class, "extends", is "anonymous", enum or uses trait
}

if (!isset($classElementTypes[$element['type']])) {
continue;
}

$previous = $index;
Expand Down Expand Up @@ -125,6 +134,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void

private function isClassCandidate(Tokens $tokens, int $classIndex): bool
{
if (\defined('T_ENUM') && $tokens[$classIndex]->isGivenKind(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required
return true;
}

$prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)];

if (!$prevToken->isGivenKind(T_FINAL)) {
Expand Down
Expand Up @@ -130,7 +130,7 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en
$tokenAnalyzer = new TokensAnalyzer($tokens);

for ($index = $startIndex; $index < $endIndex; ++$index) {
if (!$tokens[$index]->isClassy() || !$tokenAnalyzer->isAnonymousClass($index)) {
if (!$tokens[$index]->isGivenKind(T_CLASS) || !$tokenAnalyzer->isAnonymousClass($index)) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php
Expand Up @@ -102,7 +102,7 @@ public function test2()
*/
public function isCandidate(Tokens $tokens): bool
{
return \count($tokens) > 10 && $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]);
return \count($tokens) > 10 && $tokens->isAllTokenKindsFound([T_DOC_COMMENT, T_FUNCTION]) && $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php
Expand Up @@ -70,7 +70,7 @@ public function getPriority(): int
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
return $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_TRAIT]);
}

/**
Expand Down
12 changes: 10 additions & 2 deletions src/Tokenizer/Token.php
Expand Up @@ -102,7 +102,15 @@ public static function getCastTokenKinds(): array
*/
public static function getClassyTokenKinds(): array
{
static $classTokens = [T_CLASS, T_TRAIT, T_INTERFACE];
static $classTokens;

if (null === $classTokens) {
$classTokens = [T_CLASS, T_TRAIT, T_INTERFACE];

if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
$classTokens[] = T_ENUM;
}
}

return $classTokens;
}
Expand Down Expand Up @@ -366,7 +374,7 @@ public function isCast(): bool
}

/**
* Check if token is one of classy tokens: T_CLASS, T_INTERFACE or T_TRAIT.
* Check if token is one of classy tokens: T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM.
*/
public function isClassy(): bool
{
Expand Down

0 comments on commit ad4a709

Please sign in to comment.