diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000000..3ef54ebb898 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/security/advisories/new) tab. + +Report security bugs in third-party libraries to the person or team maintaining that library. diff --git a/doc/rules/function_notation/use_arrow_functions.rst b/doc/rules/function_notation/use_arrow_functions.rst index b52b8180bc2..abbab8a1da6 100644 --- a/doc/rules/function_notation/use_arrow_functions.rst +++ b/doc/rules/function_notation/use_arrow_functions.rst @@ -2,7 +2,7 @@ Rule ``use_arrow_functions`` ============================ -Anonymous functions with one-liner return statement must use arrow functions. +Anonymous functions with return as the only statement must use arrow functions. Warning ------- diff --git a/doc/rules/index.rst b/doc/rules/index.rst index a5654990d3c..47b1b25873c 100644 --- a/doc/rules/index.rst +++ b/doc/rules/index.rst @@ -427,7 +427,7 @@ Function Notation Lambdas not (indirectly) referencing ``$this`` must be declared ``static``. - `use_arrow_functions <./function_notation/use_arrow_functions.rst>`_ *(risky)* - Anonymous functions with one-liner return statement must use arrow functions. + Anonymous functions with return as the only statement must use arrow functions. - `void_return <./function_notation/void_return.rst>`_ *(risky)* Add ``void`` return type to functions with missing or empty return statements, but priority is given to ``@return`` annotations. diff --git a/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php b/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php index 5cafd0a3083..4d18b8cd5f6 100644 --- a/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php +++ b/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php @@ -31,7 +31,7 @@ final class UseArrowFunctionsFixer extends AbstractFixer public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Anonymous functions with one-liner return statement must use arrow functions.', + 'Anonymous functions with return as the only statement must use arrow functions.', [ new CodeSample( <<<'SAMPLE' @@ -78,8 +78,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - // Find parameters end - // Abort if they are multilined + // Find parameters $parametersStart = $tokens->getNextMeaningfulToken($index); @@ -89,10 +88,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $parametersEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $parametersStart); - if ($this->isMultilined($tokens, $parametersStart, $parametersEnd)) { - continue; - } - // Find `use ()` start and end // Abort if it contains reference variables @@ -158,29 +153,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - // Abort if the `return` statement is multilined - - if ($this->isMultilined($tokens, $return, $semicolon)) { - continue; - } - // Transform the function to an arrow function $this->transform($tokens, $index, $useStart, $useEnd, $braceOpen, $return, $semicolon, $braceClose); } } - private function isMultilined(Tokens $tokens, int $start, int $end): bool - { - for ($i = $start; $i < $end; ++$i) { - if (str_contains($tokens[$i]->getContent(), "\n")) { - return true; - } - } - - return false; - } - private function transform(Tokens $tokens, int $index, ?int $useStart, ?int $useEnd, int $braceOpen, int $return, int $semicolon, int $braceClose): void { $tokensToInsert = [new Token([T_DOUBLE_ARROW, '=>'])]; 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, 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/Fixer/FunctionNotation/UseArrowFunctionsFixerTest.php b/tests/Fixer/FunctionNotation/UseArrowFunctionsFixerTest.php index a2cbbc44315..c168047a922 100644 --- a/tests/Fixer/FunctionNotation/UseArrowFunctionsFixerTest.php +++ b/tests/Fixer/FunctionNotation/UseArrowFunctionsFixerTest.php @@ -152,17 +152,29 @@ public static function provideFixCases(): iterable ]; yield [ - <<<'EXPECTED' + <<<'PHP' + + 1); + PHP, + <<<'PHP' 1; + PHP, + <<<'PHP' null/* foo */;', ' [ + CONST_A, + CONST_B, + ]; + PHP, + <<<'PHP' + 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 */