diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index b578279546..3ebc347b73 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -3226,19 +3226,45 @@ protected function processAdditional() } if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) { - // Make sure this is not the colon from a parameter name. + // Make sure this is the colon for a return type. for ($y = ($x - 1); $y > 0; $y--) { if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) { break; } } - if ($this->tokens[$y]['code'] !== T_PARAM_NAME) { - $confirmed = true; + if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) { + // Definitely not a union, intersection or DNF return type, move on. + continue 2; + } + + if (isset($this->tokens[$y]['parenthesis_owner']) === true) { + if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION + || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE + || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN + ) { + $confirmed = true; + } + + break; + } + + // Arrow functions may not have the parenthesis_owner set correctly yet. + // Closure use tokens won't be parentheses owners until PHPCS 4.0. + if (isset($this->tokens[$y]['parenthesis_opener']) === true) { + for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) { + if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) { + break; + } + } + + if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) { + $confirmed = true; + } } break; - } + }//end if if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) { $confirmed = true; diff --git a/tests/Core/Tokenizer/PHP/DNFTypesTest.inc b/tests/Core/Tokenizer/PHP/DNFTypesTest.inc index f509c4f31d..5043ab7db5 100644 --- a/tests/Core/Tokenizer/PHP/DNFTypesTest.inc +++ b/tests/Core/Tokenizer/PHP/DNFTypesTest.inc @@ -53,6 +53,35 @@ callMe(label: CONST_A | CONST_B); /* testParensNoOwnerFunctionCallWithDNFLookALikeNamedParamIntersect */ callMe(label: CONST_A & CONST_B); +/* testSwitchControlStructureCondition */ +switch (CONST_A | CONST_B) { + /* testFunctionCallInSwitchCaseCondition */ + case get_bool(): + /* testFunctionCallInSwitchCaseBody */ + \Name\functionInSwitch(); + break; + + default: + /* testFunctionCallInSwitchDefaultBody */ + functionInSwitch(); + break; +} + +/* testIfAlternativeSyntaxCondition */ +if (true): + /* testFunctionCallInIfBody */ + \B\call(); +/* testElseIfAlternativeSyntaxCondition */ +elseif (10): + /* testFunctionCallInElseIfBody */ + C\call(); +endif; + +gotolabel: + /* testFunctionCallInGotoBody */ + \doSomething(); + + /* * DNF parentheses. */ @@ -165,6 +194,8 @@ $closureWithParamType = function ( /* testDNFTypeClosureParamIllegalNullable */ /* testParensOwnerClosureAmpersandInDefaultValue */ $closureWithReturnType = function ($string = NONSENSE & FAKE) /* testDNFTypeClosureReturn */ : (\Package\MyA&PackageB)|null {}; +$closureWithUseAndReturnType = function ($foo) use ($a) /* testDNFTypeClosureWithUseReturn */ : null|(Foo&\Bar)|false {}; + /* testParensOwnerArrowDNFUsedWithin */ $arrowWithParamType = fn ( /* testDNFTypeArrowParam */ diff --git a/tests/Core/Tokenizer/PHP/DNFTypesTest.php b/tests/Core/Tokenizer/PHP/DNFTypesTest.php index 9d7e395b3e..e1a9aaddf8 100644 --- a/tests/Core/Tokenizer/PHP/DNFTypesTest.php +++ b/tests/Core/Tokenizer/PHP/DNFTypesTest.php @@ -193,6 +193,33 @@ public static function dataNormalParentheses() 'parens without owner in arrow function return expression' => [ 'testMarker' => '/* testParensNoOwnerInArrowReturnExpression */', ], + 'parens with owner: switch condition' => [ + 'testMarker' => '/* testSwitchControlStructureCondition */', + ], + 'parens without owner in switch-case condition' => [ + 'testMarker' => '/* testFunctionCallInSwitchCaseCondition */', + ], + 'parens without owner in switch-case body' => [ + 'testMarker' => '/* testFunctionCallInSwitchCaseBody */', + ], + 'parens without owner in switch-default body' => [ + 'testMarker' => '/* testFunctionCallInSwitchDefaultBody */', + ], + 'parens with owner: if condition, alternative syntax' => [ + 'testMarker' => '/* testIfAlternativeSyntaxCondition */', + ], + 'parens without owner in if body, alternative syntax' => [ + 'testMarker' => '/* testFunctionCallInIfBody */', + ], + 'parens with owner: elseif condition, alternative syntax' => [ + 'testMarker' => '/* testElseIfAlternativeSyntaxCondition */', + ], + 'parens without owner in elseif body, alternative syntax' => [ + 'testMarker' => '/* testFunctionCallInElseIfBody */', + ], + 'parens without owner in goto body' => [ + 'testMarker' => '/* testFunctionCallInGotoBody */', + ], ]; }//end dataNormalParentheses() @@ -423,6 +450,10 @@ public static function dataDNFTypeParentheses() 'closure return type' => [ 'testMarker' => '/* testDNFTypeClosureReturn */', ], + 'closure with use return type' => [ + 'testMarker' => '/* testDNFTypeClosureWithUseReturn */', + ], + 'arrow function param type' => [ 'testMarker' => '/* testDNFTypeArrowParam */', ],