diff --git a/PHPCSUtils/BackCompat/BCFile.php b/PHPCSUtils/BackCompat/BCFile.php index 68d1624a..8c063439 100644 --- a/PHPCSUtils/BackCompat/BCFile.php +++ b/PHPCSUtils/BackCompat/BCFile.php @@ -228,7 +228,9 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr) // it's likely to be an array which might have arguments in it. This // could cause problems in our parsing below, so lets just skip to the // end of it. - if (isset($tokens[$i]['parenthesis_opener']) === true) { + if ($tokens[$i]['code'] !== T_TYPE_OPEN_PARENTHESIS + && isset($tokens[$i]['parenthesis_opener']) === true + ) { // Don't do this if it's the close parenthesis for the method. if ($i !== $tokens[$i]['parenthesis_closer']) { $i = ($tokens[$i]['parenthesis_closer'] + 1); @@ -324,6 +326,8 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr) case T_NS_SEPARATOR: case T_TYPE_UNION: case T_TYPE_INTERSECTION: + case T_TYPE_OPEN_PARENTHESIS: + case T_TYPE_CLOSE_PARENTHESIS: case T_FALSE: case T_TRUE: case T_NULL: diff --git a/Tests/BackCompat/BCFile/GetMethodParametersTest.inc b/Tests/BackCompat/BCFile/GetMethodParametersTest.inc index 17c8417e..b504f928 100644 --- a/Tests/BackCompat/BCFile/GetMethodParametersTest.inc +++ b/Tests/BackCompat/BCFile/GetMethodParametersTest.inc @@ -280,6 +280,23 @@ function newInInitializers( \Package\TypeB $newToo = new \Package\TypeB(10, 'string'), ) {} +/* testPHP82DNFTypes */ +function dnfTypes( + #[MyAttribute] + false|(Foo&Bar)|true $obj1, + (\Boo&\Pck\Bar)|(Boo&Baz) $obj2 = new Boo() +) {} + +/* testPHP82DNFTypesWithSpreadOperatorAndReference */ +function dnfInGlobalFunctionWithSpreadAndReference((Countable&MeMe)|iterable &$paramA, true|(Foo&Bar) ...$paramB) {} + +/* testPHP82DNFTypesIllegalNullable */ +// Intentional fatal error - nullable operator cannot be combined with DNF. +$dnf_closure = function (? ( MyClassA & /*comment*/ \Package\MyClassB & \Package\MyClassC ) $var): void {}; + +/* testPHP82DNFTypesInArrow */ +$dnf_arrow = fn((Hi&Ho)|FALSE &...$range): string => $a; + /* testFunctionCallFnPHPCS353-354 */ $value = $obj->fn(true); diff --git a/Tests/BackCompat/BCFile/GetMethodParametersTest.php b/Tests/BackCompat/BCFile/GetMethodParametersTest.php index 38e5817c..d473653d 100644 --- a/Tests/BackCompat/BCFile/GetMethodParametersTest.php +++ b/Tests/BackCompat/BCFile/GetMethodParametersTest.php @@ -2601,6 +2601,157 @@ public function testPHP81NewInInitializers() $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); } + /** + * Verify recognition of 8.2 DNF parameter type declarations. + * + * @return void + */ + public function testPHP82DNFTypes() + { + $php8Names = parent::usesPhp8NameTokens(); + + // Offsets are relative to the T_FUNCTION token. + $expected = []; + $expected[0] = [ + 'token' => 21, + 'name' => '$obj1', + 'content' => '#[MyAttribute] + false|(Foo&Bar)|true $obj1', + 'has_attributes' => true, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => 'false|(Foo&Bar)|true', + 'type_hint_token' => 11, + 'type_hint_end_token' => 19, + 'nullable_type' => false, + 'comma_token' => 22, + ]; + $expected[1] = [ + 'token' => ($php8Names === true) ? 37 : 41, + 'name' => '$obj2', + 'content' => '(\Boo&\Pck\Bar)|(Boo&Baz) $obj2 = new Boo()', + 'default' => 'new Boo()', + 'default_token' => ($php8Names === true) ? 41 : 45, + 'default_equal_token' => ($php8Names === true) ? 39 : 43, + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => '(\Boo&\Pck\Bar)|(Boo&Baz)', + 'type_hint_token' => 25, + 'type_hint_end_token' => ($php8Names === true) ? 35 : 39, + 'nullable_type' => false, + 'comma_token' => false, + ]; + + $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + + /** + * Verify recognition of PHP 8.2 DNF parameter type declarations when the variable + * has either a spread operator or a reference. + * + * @return void + */ + public function testPHP82DNFTypesWithSpreadOperatorAndReference() + { + // Offsets are relative to the T_FUNCTION token. + $expected = []; + $expected[0] = [ + 'token' => 13, + 'name' => '$paramA', + 'content' => '(Countable&MeMe)|iterable &$paramA', + 'has_attributes' => false, + 'pass_by_reference' => true, + 'reference_token' => 12, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => '(Countable&MeMe)|iterable', + 'type_hint_token' => 4, + 'type_hint_end_token' => 10, + 'nullable_type' => false, + 'comma_token' => 14, + ]; + $expected[1] = [ + 'token' => 25, + 'name' => '$paramB', + 'content' => 'true|(Foo&Bar) ...$paramB', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => true, + 'variadic_token' => 24, + 'type_hint' => 'true|(Foo&Bar)', + 'type_hint_token' => 16, + 'type_hint_end_token' => 22, + 'nullable_type' => false, + 'comma_token' => false, + ]; + + $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + + /** + * Verify recognition of PHP 8.2 DNF parameter type declarations using the nullability operator (not allowed). + * + * @return void + */ + public function testPHP82DNFTypesIllegalNullable() + { + $php8Names = parent::usesPhp8NameTokens(); + + // Offsets are relative to the T_FUNCTION token. + $expected = []; + $expected[0] = [ + 'token' => ($php8Names === true) ? 21 : 27, + 'name' => '$var', + 'content' => '? ( MyClassA & /*comment*/ \Package\MyClassB & \Package\MyClassC ) $var', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => '?(MyClassA&\Package\MyClassB&\Package\MyClassC)', + 'type_hint_token' => 5, + 'type_hint_end_token' => ($php8Names === true) ? 19 : 25, + 'nullable_type' => true, + 'comma_token' => false, + ]; + + $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + + /** + * Verify recognition of PHP 8.2 DNF parameter type declarations in an arrow function. + * + * @return void + */ + public function testPHP82DNFTypesInArrow() + { + // Offsets are relative to the T_FUNCTION token. + $expected = []; + $expected[0] = [ + 'token' => 12, + 'name' => '$range', + 'content' => '(Hi&Ho)|FALSE &...$range', + 'has_attributes' => false, + 'pass_by_reference' => true, + 'reference_token' => 10, + 'variable_length' => true, + 'variadic_token' => 11, + 'type_hint' => '(Hi&Ho)|FALSE', + 'type_hint_token' => 2, + 'type_hint_end_token' => 8, + 'nullable_type' => false, + 'comma_token' => false, + ]; + + $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + /** * Verify handling of a closure. *