From 37ef9af66f73bffa5515811babdb5ab7bbb7859f Mon Sep 17 00:00:00 2001 From: SpacePossum Date: Thu, 10 Feb 2022 12:51:45 +0100 Subject: [PATCH] PHP8.1 - Enum support --- doc/list.rst | 8 +- .../class_attributes_separation.rst | 4 +- doc/rules/class_notation/class_definition.rst | 4 +- .../no_unneeded_final_method.rst | 3 +- doc/rules/index.rst | 4 +- src/AbstractDoctrineAnnotationFixer.php | 9 +- src/Fixer/Basic/BracesFixer.php | 2 +- .../ClassAttributesSeparationFixer.php | 7 +- .../ClassNotation/ClassDefinitionFixer.php | 2 +- .../NoNullPropertyInitializationFixer.php | 2 +- .../NoUnneededFinalMethodFixer.php | 43 +++++-- .../OrderedClassElementsFixer.php | 4 +- .../ClassNotation/ProtectedToPrivateFixer.php | 15 ++- ...PhpUnitDedicateAssertInternalTypeFixer.php | 2 +- .../Phpdoc/PhpdocReturnSelfReferenceFixer.php | 2 +- .../Phpdoc/PhpdocVarWithoutNameFixer.php | 2 +- src/Tokenizer/Token.php | 12 +- src/Tokenizer/TokensAnalyzer.php | 10 +- tests/Fixer/Basic/PsrAutoloadingFixerTest.php | 17 +++ .../Casing/MagicMethodCasingFixerTest.php | 15 +++ .../ClassAttributesSeparationFixerTest.php | 100 ++++++++++++++- .../ClassDefinitionFixerTest.php | 27 ++++ ...NoBlankLinesAfterClassOpeningFixerTest.php | 28 ++++ .../NoUnneededFinalMethodFixerTest.php | 27 ++++ .../ProtectedToPrivateFixerTest.php | 41 +++++- ...ingleClassElementPerStatementFixerTest.php | 22 ++++ .../VisibilityRequiredFixerTest.php | 17 +++ .../Fixer/Comment/HeaderCommentFixerTest.php | 34 +++++ .../Import/GlobalNamespaceImportFixerTest.php | 59 +++++++++ .../NoSuperfluousPhpdocTagsFixerTest.php | 15 +++ .../Fixer/Phpdoc/PhpdocLineSpanFixerTest.php | 19 +++ .../PhpdocReturnSelfReferenceFixerTest.php | 63 +++++++++ .../Fixer/Phpdoc/PhpdocToCommentFixerTest.php | 41 ++++-- .../Phpdoc/PhpdocVarWithoutNameFixerTest.php | 16 +++ .../Semicolon/NoEmptyStatementFixerTest.php | 17 +++ .../Analyzer/CommentsAnalyzerTest.php | 4 + .../Analyzer/FunctionsAnalyzerTest.php | 10 ++ tests/Tokenizer/TokenTest.php | 19 +++ tests/Tokenizer/TokensAnalyzerTest.php | 121 ++++++++++++++++++ 39 files changed, 796 insertions(+), 51 deletions(-) diff --git a/doc/list.rst b/doc/list.rst index 59c35f8978b..67bc57f9200 100644 --- a/doc/list.rst +++ b/doc/list.rst @@ -166,9 +166,9 @@ 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>`_ @@ -176,7 +176,7 @@ List of Available Rules `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: @@ -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. diff --git a/doc/rules/class_notation/class_attributes_separation.rst b/doc/rules/class_notation/class_attributes_separation.rst index b6643c63a4f..6c842a99fd7 100644 --- a/doc/rules/class_notation/class_attributes_separation.rst +++ b/doc/rules/class_notation/class_attributes_separation.rst @@ -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 -------- diff --git a/doc/rules/class_notation/class_definition.rst b/doc/rules/class_notation/class_definition.rst index 630cf6c1128..58dba01f6b2 100644 --- a/doc/rules/class_notation/class_definition.rst +++ b/doc/rules/class_notation/class_definition.rst @@ -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 ------------- diff --git a/doc/rules/class_notation/no_unneeded_final_method.rst b/doc/rules/class_notation/no_unneeded_final_method.rst index 221cb48a8dd..5e954b4426c 100644 --- a/doc/rules/class_notation/no_unneeded_final_method.rst +++ b/doc/rules/class_notation/no_unneeded_final_method.rst @@ -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 ------- diff --git a/doc/rules/index.rst b/doc/rules/index.rst index 6a9828db05d..7aa4e51d274 100644 --- a/doc/rules/index.rst +++ b/doc/rules/index.rst @@ -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. @@ -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. diff --git a/src/AbstractDoctrineAnnotationFixer.php b/src/AbstractDoctrineAnnotationFixer.php index 8299f5b7b89..79d64171dfb 100644 --- a/src/AbstractDoctrineAnnotationFixer.php +++ b/src/AbstractDoctrineAnnotationFixer.php @@ -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()]); } @@ -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; } @@ -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 } } diff --git a/src/Fixer/Basic/BracesFixer.php b/src/Fixer/Basic/BracesFixer.php index f0dd9824caa..5b873664b38 100644 --- a/src/Fixer/Basic/BracesFixer.php +++ b/src/Fixer/Basic/BracesFixer.php @@ -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( diff --git a/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php b/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php index 735f1fe75f8..9903af992ba 100644 --- a/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php +++ b/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php @@ -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( @@ -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(), ]); @@ -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'], [';']); } diff --git a/src/Fixer/ClassNotation/ClassDefinitionFixer.php b/src/Fixer/ClassNotation/ClassDefinitionFixer.php index e9adfbd1460..6ff89b85df1 100644 --- a/src/Fixer/ClassNotation/ClassDefinitionFixer.php +++ b/src/Fixer/ClassNotation/ClassDefinitionFixer.php @@ -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( '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; diff --git a/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php b/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php index eec3ef50650..9c366863c6f 100644 --- a/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php +++ b/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php @@ -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( '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 @@ -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; @@ -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(); @@ -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; @@ -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; } } diff --git a/src/Fixer/ClassNotation/OrderedClassElementsFixer.php b/src/Fixer/ClassNotation/OrderedClassElementsFixer.php index b62bea3fed8..dc32cabfd7e 100644 --- a/src/Fixer/ClassNotation/OrderedClassElementsFixer.php +++ b/src/Fixer/ClassNotation/OrderedClassElementsFixer.php @@ -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) } /** @@ -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; } diff --git a/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php b/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php index 36648eb265f..40e85834602 100644 --- a/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php +++ b/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php @@ -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]); } @@ -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']; @@ -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; @@ -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)) { diff --git a/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php b/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php index e960653f793..0f98f272dd6 100644 --- a/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php @@ -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; } diff --git a/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php b/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php index cc083a32eb3..ea323096094 100644 --- a/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php +++ b/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php @@ -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()); } /** diff --git a/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php b/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php index 7bfca416015..80200cf9ba2 100644 --- a/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php +++ b/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php @@ -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]); } /** diff --git a/src/Tokenizer/Token.php b/src/Tokenizer/Token.php index 7ce9a507544..8f94e90f26e 100644 --- a/src/Tokenizer/Token.php +++ b/src/Tokenizer/Token.php @@ -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; } @@ -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 { diff --git a/src/Tokenizer/TokensAnalyzer.php b/src/Tokenizer/TokensAnalyzer.php index f34ff7b9546..faf8f617488 100644 --- a/src/Tokenizer/TokensAnalyzer.php +++ b/src/Tokenizer/TokensAnalyzer.php @@ -653,7 +653,7 @@ private function findClassyElements(int $classIndex, int $index): array continue; } - if ($token->isClassy()) { // anonymous class in class + if ($token->isGivenKind(T_CLASS)) { // anonymous class in class // check for nested anonymous classes inside the new call of an anonymous class, // for example `new class(function (){new class(function (){new class(function (){}){};}){};}){};` etc. // if class(XYZ) {} skip till `(` as XYZ might contain functions etc. @@ -686,7 +686,7 @@ private function findClassyElements(int $classIndex, int $index): array continue; } - if ($token->isClassy()) { // anonymous class in class + if ($token->isGivenKind(T_CLASS)) { // anonymous class in class [$index, $newElements] = $this->findClassyElements($index, $index); $elements += $newElements; } @@ -759,6 +759,12 @@ private function findClassyElements(int $classIndex, int $index): array 'token' => $token, 'type' => 'trait_import', ]; + } elseif ($token->isGivenKind(T_CASE)) { + $elements[$index] = [ + 'classIndex' => $classIndex, + 'token' => $token, + 'type' => 'case', + ]; } } diff --git a/tests/Fixer/Basic/PsrAutoloadingFixerTest.php b/tests/Fixer/Basic/PsrAutoloadingFixerTest.php index 5af50a60e44..d937bb08747 100644 --- a/tests/Fixer/Basic/PsrAutoloadingFixerTest.php +++ b/tests/Fixer/Basic/PsrAutoloadingFixerTest.php @@ -466,4 +466,21 @@ class extends stdClass {}; ', ]; } + + /** + * @requires PHP 8.1 + * @dataProvider providePhp81Cases + */ + public function testFix81(string $expected, ?string $input = null): void + { + $this->doTest($expected, $input, $this->getTestFile(__FILE__)); + } + + public function providePhp81Cases(): \Generator + { + yield 'enum with wrong casing' => [ + '__isset(...);', '__ISSET(...);', ]; + + yield 'enum' => [ + 'fixer->configure($config); + } + $this->doTest($expected, $input); } @@ -2253,5 +2257,99 @@ class Foo { private Bar & Something & Baz $d; }', ]; + + $input = ' [ + 'const' => 'one', + 'method' => 'one', + 'case' => 'one', + ]], + ]; + + yield [ + ' [ + 'const' => 'none', + 'method' => 'one', + 'case' => 'none', + ]], + ]; } } diff --git a/tests/Fixer/ClassNotation/ClassDefinitionFixerTest.php b/tests/Fixer/ClassNotation/ClassDefinitionFixerTest.php index 18084d81afd..8f8db669a92 100644 --- a/tests/Fixer/ClassNotation/ClassDefinitionFixerTest.php +++ b/tests/Fixer/ClassNotation/ClassDefinitionFixerTest.php @@ -653,6 +653,33 @@ public function provideMessyWhitespacesCases(): array ]; } + /** + * @dataProvider provideFix81Cases + * @requires PHP 8.1 + */ + public function testFix81(string $expected, ?string $input = null): void + { + $this->doTest($expected, $input); + } + + public function provideFix81Cases(): iterable + { + yield [ + "doTest($expected, $input); + } + + public function provideFix81Cases(): iterable + { + yield [ + ' [ + ' [ // ' [ + 'test(); + ', + 'test(); + ', + ]; } private function getAttributesAndMethods(bool $original): string diff --git a/tests/Fixer/ClassNotation/SingleClassElementPerStatementFixerTest.php b/tests/Fixer/ClassNotation/SingleClassElementPerStatementFixerTest.php index 524800b7c05..435f306c501 100644 --- a/tests/Fixer/ClassNotation/SingleClassElementPerStatementFixerTest.php +++ b/tests/Fixer/ClassNotation/SingleClassElementPerStatementFixerTest.php @@ -917,5 +917,27 @@ class Foo } ', ]; + + yield [ + "test());', + 'test());', + ]; } } diff --git a/tests/Fixer/Comment/HeaderCommentFixerTest.php b/tests/Fixer/Comment/HeaderCommentFixerTest.php index ec95c93c1dd..57fe1998bd8 100644 --- a/tests/Fixer/Comment/HeaderCommentFixerTest.php +++ b/tests/Fixer/Comment/HeaderCommentFixerTest.php @@ -802,4 +802,38 @@ public function testInvalidHeaderConfiguration(): void 'comment_type' => HeaderCommentFixer::HEADER_PHPDOC, ]); } + + /** + * @dataProvider provideFix81Cases + * @requires PHP 8.1 + */ + public function testFix81(array $configuration, string $expected, ?string $input = null): void + { + $this->fixer->configure($configuration); + + $this->doTest($expected, $input); + } + + public function provideFix81Cases(): iterable + { + yield [ + ['header' => 'tmp'], + 'fixer->configure([ + 'import_constants' => true, + 'import_functions' => true, + ]); + + $this->doTest($expected, $input); + } + + public function provideFix81Cases(): iterable + { + yield 'ignore enum methods' => [ + <<<'EXPECTED' + [ + <<<'EXPECTED' +fixer->configure($config); $this->doTest($expected, $input); } @@ -2073,5 +2074,19 @@ function foo(A & B & C $foo, A|array $bar) {}', */ function foo(A & B & C $foo, A|array $bar) {}', ]; + + yield 'remove_enum_inheritdoc' => [ + ' true], + ]; } } diff --git a/tests/Fixer/Phpdoc/PhpdocLineSpanFixerTest.php b/tests/Fixer/Phpdoc/PhpdocLineSpanFixerTest.php index defe0b904ad..a9f33319f3d 100644 --- a/tests/Fixer/Phpdoc/PhpdocLineSpanFixerTest.php +++ b/tests/Fixer/Phpdoc/PhpdocLineSpanFixerTest.php @@ -649,5 +649,24 @@ class Foo } ', ]; + + yield [ + 'someTest();', + 'someTest();', + ], ]; } @@ -208,4 +232,43 @@ public function a() {} ' ); } + + /** + * @dataProvider provideFix81Cases + * @requires PHP 8.1 + */ + public function testFix81(string $expected, string $input): void + { + $this->doTest($expected, $input); + } + + public function provideFix81Cases(): iterable + { + yield [ + 'test()); +', + 'test()); +', + ]; + } } diff --git a/tests/Fixer/Phpdoc/PhpdocToCommentFixerTest.php b/tests/Fixer/Phpdoc/PhpdocToCommentFixerTest.php index 1d77386e772..34da69d7704 100644 --- a/tests/Fixer/Phpdoc/PhpdocToCommentFixerTest.php +++ b/tests/Fixer/Phpdoc/PhpdocToCommentFixerTest.php @@ -799,11 +799,10 @@ public function testFix80(string $expected, ?string $input = null): void $this->doTest($expected, $input); } - public function provideFix80Cases(): array + public function provideFix80Cases(): iterable { - return [ - [ - 'doTest($expected); + } + + public function provideFix81Cases(): iterable + { + yield 'enum' => [ + ' [ + 'doTest($expected, $input); + } + + public function provideFix81Cases(): iterable + { + yield [ + ' [ + 'isClassy()); + } + /** * @dataProvider provideIsCommentCases */ @@ -554,6 +564,15 @@ public function provideToArrayCases(): \Generator ]; } + public function testGetClassyTokenKinds(): void + { + if (\defined('T_ENUM')) { + static::assertSame([T_CLASS, T_TRAIT, T_INTERFACE, T_ENUM], Token::getClassyTokenKinds()); + } else { + static::assertSame([T_CLASS, T_TRAIT, T_INTERFACE], Token::getClassyTokenKinds()); + } + } + private function getBraceToken(): Token { return new Token($this->getBraceTokenPrototype()); diff --git a/tests/Tokenizer/TokensAnalyzerTest.php b/tests/Tokenizer/TokensAnalyzerTest.php index 75a328ad985..24074e619f7 100644 --- a/tests/Tokenizer/TokensAnalyzerTest.php +++ b/tests/Tokenizer/TokensAnalyzerTest.php @@ -547,6 +547,120 @@ class Foo { final public const A = "1"; public final const B = "2"; +} + ', + ]; + + yield 'enum final const' => [ + [ + 11 => [ + 'classIndex' => 1, + 'type' => 'const', // A + ], + 24 => [ + 'classIndex' => 1, + 'type' => 'const', // B + ], + ], + ' [ + [ + 12 => [ + 'classIndex' => 1, + 'type' => 'const', // Spades + ], + 21 => [ + 'classIndex' => 1, + 'type' => 'case', // Hearts + ], + 32 => [ + 'classIndex' => 1, + 'type' => 'method', // function tests + ], + 81 => [ + 'classIndex' => 75, + 'type' => 'method', // function bar123 + ], + 135 => [ + 'classIndex' => 127, + 'type' => 'method', // function bar7 + ], + ], + ' [ + [ + 10 => [ + 'classIndex' => 1, + 'type' => 'case', + ], + 19 => [ + 'classIndex' => 1, + 'type' => 'case', + ], + 28 => [ + 'classIndex' => 1, + 'type' => 'method', + ], + ], + '= 80100) { + yield [ + [1 => false], + '