Browse Source

Ensure compatibility with PHP 7.4 arrow functions

Julien Falque 5 years ago
parent
commit
814df6b8d0

+ 1 - 1
.composer-require-checker.json

@@ -16,7 +16,7 @@
         "null", "true", "false",
         "static", "self", "parent",
         "array", "string", "int", "float", "bool", "iterable", "callable", "void",
-        "T_COALESCE_EQUAL"
+        "T_COALESCE_EQUAL", "T_FN"
     ],
     "php-core-extensions" : [
         "dom", "mbstring", "Phar",

+ 1 - 1
.travis.yml

@@ -26,7 +26,7 @@ jobs:
     include:
         -
             stage: Static Code Analysis
-            php: 7.2
+            php: 7.3
             env: COMPOSER_FLAGS="--prefer-stable"
             install:
                 - travis_retry ./dev-tools/install.sh

+ 2 - 0
phpstan.neon

@@ -11,3 +11,5 @@ parameters:
     ignoreErrors:
         - '/^.+::__construct\(\) does not call parent constructor from SplFileInfo\.$/'
         - '/^Return typehint of method PhpCsFixer\\Tests\\Test\\.+::createIsIdenticalStringConstraint\(\) has invalid type PHPUnit_Framework_Constraint_IsIdentical\.$/'
+        ## cannot analyse out of PHP 7.4
+        - '/^Constant T_FN not found\.$/'

+ 21 - 4
src/Fixer/FunctionNotation/FunctionDeclarationFixer.php

@@ -18,6 +18,8 @@ use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
 use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
+use PhpCsFixer\FixerDefinition\VersionSpecification;
+use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
 use PhpCsFixer\Tokenizer\CT;
 use PhpCsFixer\Tokenizer\Tokens;
 use PhpCsFixer\Tokenizer\TokensAnalyzer;
@@ -48,6 +50,10 @@ final class FunctionDeclarationFixer extends AbstractFixer implements Configurat
      */
     public function isCandidate(Tokens $tokens)
     {
+        if (\PHP_VERSION_ID >= 70400 && $tokens->isTokenKindFound(T_FN)) {
+            return true;
+        }
+
         return $tokens->isTokenKindFound(T_FUNCTION);
     }
 
@@ -82,6 +88,13 @@ $f = function () {};
 ',
                     ['closure_function_spacing' => self::SPACING_NONE]
                 ),
+                new VersionSpecificCodeSample(
+                    '<?php
+$f = fn () => null;
+',
+                    new VersionSpecification(70400),
+                    ['closure_function_spacing' => self::SPACING_NONE]
+                ),
             ]
         );
     }
@@ -96,7 +109,10 @@ $f = function () {};
         for ($index = $tokens->count() - 1; $index >= 0; --$index) {
             $token = $tokens[$index];
 
-            if (!$token->isGivenKind(T_FUNCTION)) {
+            if (
+                !$token->isGivenKind(T_FUNCTION)
+                && (\PHP_VERSION_ID < 70400 || !$token->isGivenKind(T_FN))
+            ) {
                 continue;
             }
 
@@ -106,13 +122,14 @@ $f = function () {};
             }
 
             $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex);
-            $startBraceIndex = $tokens->getNextTokenOfKind($endParenthesisIndex, [';', '{']);
+            $startBraceIndex = $tokens->getNextTokenOfKind($endParenthesisIndex, [';', '{', [T_DOUBLE_ARROW]]);
 
-            // fix single-line whitespace before {
+            // fix single-line whitespace before { or =>
             // eg: `function foo(){}` => `function foo() {}`
             // eg: `function foo()   {}` => `function foo() {}`
+            // eg: `fn()   =>` => `fn() =>`
             if (
-                $tokens[$startBraceIndex]->equals('{') &&
+                $tokens[$startBraceIndex]->equalsAny(['{', [T_DOUBLE_ARROW]]) &&
                 (
                     !$tokens[$startBraceIndex - 1]->isWhitespace() ||
                     $tokens[$startBraceIndex - 1]->isWhitespace($this->singleLineWhitespaceOptions)

+ 8 - 1
src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php

@@ -44,6 +44,10 @@ final class FunctionTypehintSpaceFixer extends AbstractFixer
      */
     public function isCandidate(Tokens $tokens)
     {
+        if (\PHP_VERSION_ID >= 70400 && $tokens->isTokenKindFound(T_FN)) {
+            return true;
+        }
+
         return $tokens->isTokenKindFound(T_FUNCTION);
     }
 
@@ -57,7 +61,10 @@ final class FunctionTypehintSpaceFixer extends AbstractFixer
         for ($index = $tokens->count() - 1; $index >= 0; --$index) {
             $token = $tokens[$index];
 
-            if (!$token->isGivenKind(T_FUNCTION)) {
+            if (
+                !$token->isGivenKind(T_FUNCTION)
+                && (\PHP_VERSION_ID < 70400 || !$token->isGivenKind(T_FN))
+            ) {
                 continue;
             }
 

+ 6 - 1
src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php

@@ -139,6 +139,11 @@ SAMPLE
      */
     protected function applyFix(\SplFileInfo $file, Tokens $tokens)
     {
+        $expectedTokens = [T_LIST, T_FUNCTION];
+        if (\PHP_VERSION_ID >= 70400) {
+            $expectedTokens[] = T_FN;
+        }
+
         for ($index = $tokens->count() - 1; $index > 0; --$index) {
             $token = $tokens[$index];
 
@@ -149,7 +154,7 @@ SAMPLE
             $meaningfulTokenBeforeParenthesis = $tokens[$tokens->getPrevMeaningfulToken($index)];
             if (
                 $meaningfulTokenBeforeParenthesis->isKeyword()
-                && !$meaningfulTokenBeforeParenthesis->isGivenKind([T_LIST, T_FUNCTION])
+                && !$meaningfulTokenBeforeParenthesis->isGivenKind($expectedTokens)
             ) {
                 continue;
             }

+ 8 - 1
src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php

@@ -49,6 +49,10 @@ function example($foo = "two words", $bar) {}
      */
     public function isCandidate(Tokens $tokens)
     {
+        if (\PHP_VERSION_ID >= 70400 && $tokens->isTokenKindFound(T_FN)) {
+            return true;
+        }
+
         return $tokens->isTokenKindFound(T_FUNCTION);
     }
 
@@ -66,7 +70,10 @@ function example($foo = "two words", $bar) {}
     protected function applyFix(\SplFileInfo $file, Tokens $tokens)
     {
         for ($i = 0, $l = $tokens->count(); $i < $l; ++$i) {
-            if (!$tokens[$i]->isGivenKind(T_FUNCTION)) {
+            if (
+                !$tokens[$i]->isGivenKind(T_FUNCTION)
+                && (\PHP_VERSION_ID < 70400 || !$tokens[$i]->isGivenKind(T_FN))
+            ) {
                 continue;
             }
 

+ 8 - 1
src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php

@@ -121,6 +121,10 @@ function my_foo()
      */
     public function isCandidate(Tokens $tokens)
     {
+        if (\PHP_VERSION_ID >= 70400 && $tokens->isTokenKindFound(T_FN)) {
+            return true;
+        }
+
         return \PHP_VERSION_ID >= 70000 && $tokens->isTokenKindFound(T_FUNCTION);
     }
 
@@ -161,7 +165,10 @@ function my_foo()
     protected function applyFix(\SplFileInfo $file, Tokens $tokens)
     {
         for ($index = $tokens->count() - 1; 0 < $index; --$index) {
-            if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
+            if (
+                !$tokens[$index]->isGivenKind(T_FUNCTION)
+                && (\PHP_VERSION_ID < 70400 || !$tokens[$index]->isGivenKind(T_FN))
+            ) {
                 continue;
             }
 

+ 19 - 3
src/Fixer/FunctionNotation/StaticLambdaFixer.php

@@ -43,6 +43,10 @@ final class StaticLambdaFixer extends AbstractFixer
      */
     public function isCandidate(Tokens $tokens)
     {
+        if (\PHP_VERSION_ID >= 70400 && $tokens->isTokenKindFound(T_FN)) {
+            return true;
+        }
+
         return $tokens->isTokenKindFound(T_FUNCTION);
     }
 
@@ -61,8 +65,13 @@ final class StaticLambdaFixer extends AbstractFixer
     {
         $analyzer = new TokensAnalyzer($tokens);
 
+        $expectedFunctionKinds = [T_FUNCTION];
+        if (\PHP_VERSION_ID >= 70400) {
+            $expectedFunctionKinds[] = T_FN;
+        }
+
         for ($index = $tokens->count() - 4; $index > 0; --$index) {
-            if (!$tokens[$index]->isGivenKind(T_FUNCTION) || !$analyzer->isLambda($index)) {
+            if (!$tokens[$index]->isGivenKind($expectedFunctionKinds) || !$analyzer->isLambda($index)) {
                 continue;
             }
 
@@ -71,11 +80,18 @@ final class StaticLambdaFixer extends AbstractFixer
                 continue; // lambda is already 'static'
             }
 
+            $argumentsStartIndex = $tokens->getNextTokenOfKind($index, ['(']);
+            $argumentsEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStartIndex);
+
             // figure out where the lambda starts ...
-            $lambdaOpenIndex = $tokens->getNextTokenOfKind($index, ['{']);
+            $lambdaOpenIndex = $tokens->getNextTokenOfKind($argumentsEndIndex, ['{', [T_DOUBLE_ARROW]]);
 
             // ... and where it ends
-            $lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex);
+            if ($tokens[$lambdaOpenIndex]->isGivenKind(T_DOUBLE_ARROW)) {
+                $lambdaEndIndex = $tokens->getNextTokenOfKind($lambdaOpenIndex, [';']);
+            } else {
+                $lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex);
+            }
 
             if ($this->hasPossibleReferenceToThis($tokens, $lambdaOpenIndex, $lambdaEndIndex)) {
                 continue;

+ 1 - 1
src/Tokenizer/Analyzer/FunctionsAnalyzer.php

@@ -81,7 +81,7 @@ final class FunctionsAnalyzer
         $type = '';
         $typeStartIndex = $tokens->getNextMeaningfulToken($typeColonIndex);
         $typeEndIndex = $typeStartIndex;
-        $functionBodyStart = $tokens->getNextTokenOfKind($typeColonIndex, ['{', ';']);
+        $functionBodyStart = $tokens->getNextTokenOfKind($typeColonIndex, ['{', ';', [T_DOUBLE_ARROW]]);
         for ($i = $typeStartIndex; $i < $functionBodyStart; ++$i) {
             if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) {
                 continue;

Some files were not shown because too many files changed in this diff