From 368c69911508013afeeac541325c5e8f5c72d0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Rumi=C5=84ski?= Date: Tue, 10 Dec 2024 00:12:05 +0100 Subject: [PATCH 1/2] feat: PhpUnitTestCaseStaticMethodCallsFixer - cover PHPUnit v11.5 methods (#8314) --- .../PhpUnitTestCaseStaticMethodCallsFixer.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php b/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php index 10b68b28588..6523bf68ffb 100644 --- a/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php @@ -105,8 +105,35 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'assertClassNotHasStaticAttribute' => true, 'assertContains' => true, 'assertContainsEquals' => true, + 'assertContainsNotOnlyArray' => true, + 'assertContainsNotOnlyBool' => true, + 'assertContainsNotOnlyCallable' => true, + 'assertContainsNotOnlyClosedResource' => true, + 'assertContainsNotOnlyFloat' => true, + 'assertContainsNotOnlyInstancesOf' => true, + 'assertContainsNotOnlyInt' => true, + 'assertContainsNotOnlyIterable' => true, + 'assertContainsNotOnlyNull' => true, + 'assertContainsNotOnlyNumeric' => true, + 'assertContainsNotOnlyObject' => true, + 'assertContainsNotOnlyResource' => true, + 'assertContainsNotOnlyScalar' => true, + 'assertContainsNotOnlyString' => true, 'assertContainsOnly' => true, + 'assertContainsOnlyArray' => true, + 'assertContainsOnlyBool' => true, + 'assertContainsOnlyCallable' => true, + 'assertContainsOnlyClosedResource' => true, + 'assertContainsOnlyFloat' => true, 'assertContainsOnlyInstancesOf' => true, + 'assertContainsOnlyInt' => true, + 'assertContainsOnlyIterable' => true, + 'assertContainsOnlyNull' => true, + 'assertContainsOnlyNumeric' => true, + 'assertContainsOnlyObject' => true, + 'assertContainsOnlyResource' => true, + 'assertContainsOnlyScalar' => true, + 'assertContainsOnlyString' => true, 'assertCount' => true, 'assertDirectoryDoesNotExist' => true, 'assertDirectoryExists' => true, @@ -254,7 +281,20 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'containsEqual' => true, 'containsIdentical' => true, 'containsOnly' => true, + 'containsOnlyArray' => true, + 'containsOnlyBool' => true, + 'containsOnlyCallable' => true, + 'containsOnlyClosedResource' => true, + 'containsOnlyFloat' => true, 'containsOnlyInstancesOf' => true, + 'containsOnlyInt' => true, + 'containsOnlyIterable' => true, + 'containsOnlyNull' => true, + 'containsOnlyNumeric' => true, + 'containsOnlyObject' => true, + 'containsOnlyResource' => true, + 'containsOnlyScalar' => true, + 'containsOnlyString' => true, 'countOf' => true, 'directoryExists' => true, 'equalTo' => true, @@ -269,16 +309,28 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'greaterThan' => true, 'greaterThanOrEqual' => true, 'identicalTo' => true, + 'isArray' => true, + 'isBool' => true, + 'isCallable' => true, + 'isClosedResource' => true, 'isEmpty' => true, 'isFalse' => true, 'isFinite' => true, + 'isFloat' => true, 'isInfinite' => true, 'isInstanceOf' => true, + 'isInt' => true, + 'isIterable' => true, 'isJson' => true, 'isList' => true, 'isNan' => true, 'isNull' => true, + 'isNumeric' => true, + 'isObject' => true, 'isReadable' => true, + 'isResource' => true, + 'isScalar' => true, + 'isString' => true, 'isTrue' => true, 'isType' => true, 'isWritable' => true, From a3726ef887235d7cd7457991935b5ddfc6a3a7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Rumi=C5=84ski?= Date: Tue, 10 Dec 2024 11:53:16 +0100 Subject: [PATCH 2/2] feat: Tokenizer - initial support for PHP 8.4 property hooks (#8312) Co-authored-by: Greg Korba --- src/Tokenizer/CT.php | 2 + .../Transformer/BraceTransformer.php | 84 ++++++-- .../Transformer/BraceTransformerTest.php | 201 ++++++++++++++++++ 3 files changed, 272 insertions(+), 15 deletions(-) diff --git a/src/Tokenizer/CT.php b/src/Tokenizer/CT.php index a9815fe0e0b..eefe46d3fe4 100644 --- a/src/Tokenizer/CT.php +++ b/src/Tokenizer/CT.php @@ -58,6 +58,8 @@ final class CT public const T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE = 10_037; public const T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN = 10_038; public const T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE = 10_039; + public const T_PROPERTY_HOOK_BRACE_OPEN = 10_040; + public const T_PROPERTY_HOOK_BRACE_CLOSE = 10_041; private function __construct() {} diff --git a/src/Tokenizer/Transformer/BraceTransformer.php b/src/Tokenizer/Transformer/BraceTransformer.php index 85bfb86b63f..7bf452176b8 100644 --- a/src/Tokenizer/Transformer/BraceTransformer.php +++ b/src/Tokenizer/Transformer/BraceTransformer.php @@ -28,7 +28,8 @@ * - in `$foo->{$bar}` into CT::T_DYNAMIC_PROP_BRACE_OPEN and CT::T_DYNAMIC_PROP_BRACE_CLOSE, * - in `${$foo}` into CT::T_DYNAMIC_VAR_BRACE_OPEN and CT::T_DYNAMIC_VAR_BRACE_CLOSE, * - in `$array{$index}` into CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN and CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, - * - in `use some\a\{ClassA, ClassB, ClassC as C}` into CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE. + * - in `use some\a\{ClassA, ClassB, ClassC as C}` into CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE, + * - in `class PropertyHooks { public string $bar _{_ set(string $value) { } _}_` into CT::T_PROPERTY_HOOK_BRACE_OPEN, CT::T_PROPERTY_HOOK_BRACE_CLOSE. * * @author Dariusz RumiƄski * @@ -43,13 +44,14 @@ public function getRequiredPhpVersionId(): int public function process(Tokens $tokens, Token $token, int $index): void { - $this->transformIntoCurlyCloseBrace($tokens, $token, $index); - $this->transformIntoDollarCloseBrace($tokens, $token, $index); - $this->transformIntoDynamicPropBraces($tokens, $token, $index); - $this->transformIntoDynamicVarBraces($tokens, $token, $index); - $this->transformIntoCurlyIndexBraces($tokens, $token, $index); - $this->transformIntoGroupUseBraces($tokens, $token, $index); - $this->transformIntoDynamicClassConstantFetchBraces($tokens, $token, $index); + $this->transformIntoCurlyCloseBrace($tokens, $index); + $this->transformIntoDollarCloseBrace($tokens, $index); + $this->transformIntoDynamicPropBraces($tokens, $index); + $this->transformIntoDynamicVarBraces($tokens, $index); + $this->transformIntoPropertyHookBraces($tokens, $index); + $this->transformIntoCurlyIndexBraces($tokens, $index); + $this->transformIntoGroupUseBraces($tokens, $index); + $this->transformIntoDynamicClassConstantFetchBraces($tokens, $index); } public function getCustomTokens(): array @@ -67,6 +69,8 @@ public function getCustomTokens(): array CT::T_GROUP_IMPORT_BRACE_CLOSE, CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN, CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE, + CT::T_PROPERTY_HOOK_BRACE_OPEN, + CT::T_PROPERTY_HOOK_BRACE_CLOSE, ]; } @@ -75,8 +79,10 @@ public function getCustomTokens(): array * * This should be done at very beginning of curly braces transformations. */ - private function transformIntoCurlyCloseBrace(Tokens $tokens, Token $token, int $index): void + private function transformIntoCurlyCloseBrace(Tokens $tokens, int $index): void { + $token = $tokens[$index]; + if (!$token->isGivenKind(T_CURLY_OPEN)) { return; } @@ -96,16 +102,20 @@ private function transformIntoCurlyCloseBrace(Tokens $tokens, Token $token, int $tokens[$index] = new Token([CT::T_CURLY_CLOSE, '}']); } - private function transformIntoDollarCloseBrace(Tokens $tokens, Token $token, int $index): void + private function transformIntoDollarCloseBrace(Tokens $tokens, int $index): void { + $token = $tokens[$index]; + if ($token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) { $nextIndex = $tokens->getNextTokenOfKind($index, ['}']); $tokens[$nextIndex] = new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']); } } - private function transformIntoDynamicPropBraces(Tokens $tokens, Token $token, int $index): void + private function transformIntoDynamicPropBraces(Tokens $tokens, int $index): void { + $token = $tokens[$index]; + if (!$token->isObjectOperator()) { return; } @@ -121,8 +131,10 @@ private function transformIntoDynamicPropBraces(Tokens $tokens, Token $token, in $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}']); } - private function transformIntoDynamicVarBraces(Tokens $tokens, Token $token, int $index): void + private function transformIntoDynamicVarBraces(Tokens $tokens, int $index): void { + $token = $tokens[$index]; + if (!$token->equals('$')) { return; } @@ -145,8 +157,46 @@ private function transformIntoDynamicVarBraces(Tokens $tokens, Token $token, int $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}']); } - private function transformIntoCurlyIndexBraces(Tokens $tokens, Token $token, int $index): void + private function transformIntoPropertyHookBraces(Tokens $tokens, int $index): void { + if (\PHP_VERSION_ID < 8_04_00) { + return; // @TODO: drop condition when PHP 8.4+ is required or majority of the users are using 8.4+ + } + + $token = $tokens[$index]; + + if (!$token->equals('{')) { + return; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + // @TODO: drop condition when PHP 8.0+ is required + if (\defined('T_ATTRIBUTE')) { + // skip attributes + while ($tokens[$nextIndex]->isGivenKind(T_ATTRIBUTE)) { + $nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $nextIndex); + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } + } + + if (!$tokens[$nextIndex]->equalsAny([ + [T_STRING, 'get'], + [T_STRING, 'set'], + ])) { + return; + } + + $closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index); + + $tokens[$index] = new Token([CT::T_PROPERTY_HOOK_BRACE_OPEN, '{']); + $tokens[$closeIndex] = new Token([CT::T_PROPERTY_HOOK_BRACE_CLOSE, '}']); + } + + private function transformIntoCurlyIndexBraces(Tokens $tokens, int $index): void + { + $token = $tokens[$index]; + if (!$token->equals('{')) { return; } @@ -185,8 +235,10 @@ private function transformIntoCurlyIndexBraces(Tokens $tokens, Token $token, int $tokens[$closeIndex] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}']); } - private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, int $index): void + private function transformIntoGroupUseBraces(Tokens $tokens, int $index): void { + $token = $tokens[$index]; + if (!$token->equals('{')) { return; } @@ -203,12 +255,14 @@ private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, int $ $tokens[$closeIndex] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']); } - private function transformIntoDynamicClassConstantFetchBraces(Tokens $tokens, Token $token, int $index): void + private function transformIntoDynamicClassConstantFetchBraces(Tokens $tokens, int $index): void { if (\PHP_VERSION_ID < 8_03_00) { return; // @TODO: drop condition when PHP 8.3+ is required or majority of the users are using 8.3+ } + $token = $tokens[$index]; + if (!$token->equals('{')) { return; } diff --git a/tests/Tokenizer/Transformer/BraceTransformerTest.php b/tests/Tokenizer/Transformer/BraceTransformerTest.php index 35b405ba5ca..a9914755d29 100644 --- a/tests/Tokenizer/Transformer/BraceTransformerTest.php +++ b/tests/Tokenizer/Transformer/BraceTransformerTest.php @@ -52,6 +52,8 @@ public function testProcess(string $source, array $expectedTokens = []): void CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE, + CT::T_PROPERTY_HOOK_BRACE_OPEN, + CT::T_PROPERTY_HOOK_BRACE_CLOSE, ] ); } @@ -185,6 +187,8 @@ public function testProcess80(string $source, array $expectedTokens = []): void CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE, + CT::T_PROPERTY_HOOK_BRACE_OPEN, + CT::T_PROPERTY_HOOK_BRACE_CLOSE, ] ); } @@ -225,6 +229,8 @@ public function testPre84Process(string $source, array $expectedTokens = []): vo CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE, + CT::T_PROPERTY_HOOK_BRACE_OPEN, + CT::T_PROPERTY_HOOK_BRACE_CLOSE, ] ); } @@ -296,6 +302,201 @@ public static function providePre84ProcessCases(): iterable ]; } + /** + * @param _TransformerTestExpectedTokens $expectedTokens + * + * @dataProvider provideStarting84ProcessCases + * + * @requires PHP 8.4 + */ + public function testStarting84Process(string $source, array $expectedTokens = []): void + { + $this->doTest( + $source, + $expectedTokens, + [ + T_CURLY_OPEN, + CT::T_CURLY_CLOSE, + T_DOLLAR_OPEN_CURLY_BRACES, + CT::T_DOLLAR_CLOSE_CURLY_BRACES, + CT::T_DYNAMIC_PROP_BRACE_OPEN, + CT::T_DYNAMIC_PROP_BRACE_CLOSE, + CT::T_DYNAMIC_VAR_BRACE_OPEN, + CT::T_DYNAMIC_VAR_BRACE_CLOSE, + CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, + CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, + CT::T_GROUP_IMPORT_BRACE_OPEN, + CT::T_GROUP_IMPORT_BRACE_CLOSE, + CT::T_PROPERTY_HOOK_BRACE_OPEN, + CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ] + ); + } + + /** + * @return iterable}> + */ + public static function provideStarting84ProcessCases(): iterable + { + yield 'property hooks: property without default value' => [ + <<<'PHP' + bar = strtolower($value); + } + } // << this one + } + PHP, + [ + 13 => CT::T_PROPERTY_HOOK_BRACE_OPEN, + 40 => CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ], + ]; + + yield 'property hooks: property with default value (string)' => [ + <<<'PHP' + bar = strtolower($value); + } + } // << this one + } + PHP, + [ + 17 => CT::T_PROPERTY_HOOK_BRACE_OPEN, + 44 => CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ], + ]; + + yield 'property hooks: property with default value (array)' => [ + <<<'PHP' + bar = $value; + } + } // << this one + } + PHP, + [ + 21 => CT::T_PROPERTY_HOOK_BRACE_OPEN, + 43 => CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ], + ]; + + yield 'property hooks: property with default value (namespaced)' => [ + <<<'PHP' + bar = $value; + } + } // << this one + } + PHP, + [ + 17 => CT::T_PROPERTY_HOOK_BRACE_OPEN, + 39 => CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ], + ]; + + yield 'property hooks: property with setter attributes' => [ + <<<'PHP' + bar = strtolower($value); + } + } // << this one + } + PHP, + [ + 13 => CT::T_PROPERTY_HOOK_BRACE_OPEN, + 48 => CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ], + ]; + + yield 'property hooks: property with short setter' => [ + <<<'PHP' + bar = strtolower($value); + } + } // << this one + } + PHP, + [ + 13 => CT::T_PROPERTY_HOOK_BRACE_OPEN, + 35 => CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ], + ]; + + yield 'property hooks: property with short getter' => [ + <<<'PHP' + ucwords(mb_strtolower($this->bar)); + } // << this one + } + PHP, + [ + 13 => CT::T_PROPERTY_HOOK_BRACE_OPEN, + 32 => CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ], + ]; + + yield 'property hooks: some more curly braces within hook' => [ + <<<'PHP' + callable = $value; + } else { + $this->callable = static function (): void { + $foo = new class implements \Stringable { + public function __toString(): string { + echo 'Na'; + } + }; + + for ($i = 0; $i < 8; $i++) { + echo (string) $foo; + } + }; + } + } + } // << this one + } + PHP, + [ + 11 => CT::T_PROPERTY_HOOK_BRACE_OPEN, + 143 => CT::T_PROPERTY_HOOK_BRACE_CLOSE, + ], + ]; + } + /** * @dataProvider provideNotDynamicClassConstantFetchCases */