diff --git a/PHPCSUtils/BackCompat/BCFile.php b/PHPCSUtils/BackCompat/BCFile.php index 0fc4fed3..af4ec95e 100644 --- a/PHPCSUtils/BackCompat/BCFile.php +++ b/PHPCSUtils/BackCompat/BCFile.php @@ -362,6 +362,7 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr) case T_TYPE_UNION: case T_TYPE_INTERSECTION: case T_FALSE: + case T_TRUE: case T_NULL: // Part of a type hint or default value. if ($defaultStart === null) { diff --git a/PHPCSUtils/Utils/FunctionDeclarations.php b/PHPCSUtils/Utils/FunctionDeclarations.php index 26872ea2..e24bcf27 100644 --- a/PHPCSUtils/Utils/FunctionDeclarations.php +++ b/PHPCSUtils/Utils/FunctionDeclarations.php @@ -148,7 +148,6 @@ public static function getName(File $phpcsFile, $stackPtr) * - Defensive coding against incorrect calls to this method. * - More efficient checking whether a function has a body. * - Support for PHP 8.0 identifier name tokens in return types, cross-version PHP & PHPCS. - * - Support for the PHP 8.2 `true` type. * - The results of this function call are cached during a PHPCS run for faster response times. * * @see \PHP_CodeSniffer\Files\File::getMethodProperties() Original source. @@ -248,12 +247,6 @@ public static function getProperties(File $phpcsFile, $stackPtr) $hasBody = false; $returnTypeTokens = Collections::returnTypeTokens(); - /* - * BC PHPCS < 3.x.x: The union type separator is not (yet) retokenized correctly - * for union types containing the `true` type. - */ - $returnTypeTokens[\T_BITWISE_OR] = \T_BITWISE_OR; - $parenthesisCloser = null; if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) { $parenthesisCloser = $tokens[$stackPtr]['parenthesis_closer']; @@ -367,7 +360,6 @@ public static function getProperties(File $phpcsFile, $stackPtr) * - More efficient and more stable looping of the default value. * - Clearer exception message when a non-closure use token was passed to the function. * - Support for PHP 8.0 identifier name tokens in parameter types, cross-version PHP & PHPCS. - * - Support for the PHP 8.2 `true` type. * - The results of this function call are cached during a PHPCS run for faster response times. * * @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source. @@ -447,12 +439,6 @@ public static function getParameters(File $phpcsFile, $stackPtr) $parameterTypeTokens = Collections::parameterTypeTokens(); - /* - * BC PHPCS < 3.x.x: The union type separator is not (yet) retokenized correctly - * for union types containing the `true` type. - */ - $parameterTypeTokens[\T_BITWISE_OR] = \T_BITWISE_OR; - for ($i = $paramStart; $i <= $closer; $i++) { if (isset($parameterTypeTokens[$tokens[$i]['code']]) === true /* diff --git a/PHPCSUtils/Utils/Variables.php b/PHPCSUtils/Utils/Variables.php index 4b5e70e9..a97135f4 100644 --- a/PHPCSUtils/Utils/Variables.php +++ b/PHPCSUtils/Utils/Variables.php @@ -82,7 +82,6 @@ final class Variables * other non-property variables passed to the method. * - Defensive coding against incorrect calls to this method. * - Support PHP 8.0 identifier name tokens in property types, cross-version PHP & PHPCS. - * - Support for the PHP 8.2 `true` type. * - The results of this function call are cached during a PHPCS run for faster response times. * * @see \PHP_CodeSniffer\Files\File::getMemberProperties() Original source. @@ -183,12 +182,6 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr) $nullableType = false; $propertyTypeTokens = Collections::propertyTypeTokens(); - /* - * BC PHPCS < 3.x.x: The union type separator is not (yet) retokenized correctly - * for union types containing the `true` type. - */ - $propertyTypeTokens[\T_BITWISE_OR] = \T_BITWISE_OR; - if ($i < $stackPtr) { // We've found a type. for ($i; $i < $stackPtr; $i++) { diff --git a/Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc b/Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc index 743db513..eca7b4ac 100644 --- a/Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc +++ b/Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc @@ -220,11 +220,11 @@ $anon = class() { public ?int|float $unionTypesNullable; /* testPHP8PseudoTypeNull */ - // Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. + // PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. public null $pseudoTypeNull; /* testPHP8PseudoTypeFalse */ - // Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. + // PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. public false $pseudoTypeFalse; /* testPHP8PseudoTypeFalseAndBool */ @@ -316,6 +316,21 @@ $anon = class() { public ?Foo&Bar $nullableIntersectionType; }; +$anon = class() { + /* testPHP82PseudoTypeTrue */ + public true $pseudoTypeTrue; + + /* testPHP82NullablePseudoTypeTrue */ + static protected ?true $pseudoTypeNullableTrue; + + /* testPHP82PseudoTypeTrueInUnion */ + private int|string|true $pseudoTypeTrueInUnion; + + /* testPHP82PseudoTypeFalseAndTrue */ + // Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method. + readonly true|FALSE $pseudoTypeFalseAndTrue; +}; + class WhitespaceAndCommentsInTypes { /* testUnionTypeWithWhitespaceAndComment */ public int | /*comment*/ string $hasWhitespaceAndComment; diff --git a/Tests/BackCompat/BCFile/GetMemberPropertiesTest.php b/Tests/BackCompat/BCFile/GetMemberPropertiesTest.php index 67b2ff90..61964fc6 100644 --- a/Tests/BackCompat/BCFile/GetMemberPropertiesTest.php +++ b/Tests/BackCompat/BCFile/GetMemberPropertiesTest.php @@ -721,7 +721,7 @@ public static function dataGetMemberProperties() 'is_static' => false, 'is_readonly' => false, // Missing static, but that's OK as not an allowed syntax. - 'type' => 'callable||void', + 'type' => 'callable|void', 'type_token' => -6, // Offset from the T_VARIABLE token. 'type_end_token' => -2, // Offset from the T_VARIABLE token. 'nullable_type' => false, @@ -1044,6 +1044,58 @@ public static function dataGetMemberProperties() 'nullable_type' => false, ], ], + 'php8.2-pseudo-type-true' => [ + '/* testPHP82PseudoTypeTrue */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'is_readonly' => false, + 'type' => 'true', + 'type_token' => -2, // Offset from the T_VARIABLE token. + 'type_end_token' => -2, // Offset from the T_VARIABLE token. + 'nullable_type' => false, + ], + ], + 'php8.2-pseudo-type-true-nullable' => [ + '/* testPHP82NullablePseudoTypeTrue */', + [ + 'scope' => 'protected', + 'scope_specified' => true, + 'is_static' => true, + 'is_readonly' => false, + 'type' => '?true', + 'type_token' => -2, // Offset from the T_VARIABLE token. + 'type_end_token' => -2, // Offset from the T_VARIABLE token. + 'nullable_type' => true, + ], + ], + 'php8.2-pseudo-type-true-in-union' => [ + '/* testPHP82PseudoTypeTrueInUnion */', + [ + 'scope' => 'private', + 'scope_specified' => true, + 'is_static' => false, + 'is_readonly' => false, + 'type' => 'int|string|true', + 'type_token' => -6, // Offset from the T_VARIABLE token. + 'type_end_token' => -2, // Offset from the T_VARIABLE token. + 'nullable_type' => false, + ], + ], + 'php8.2-pseudo-type-invalid-true-false-union' => [ + '/* testPHP82PseudoTypeFalseAndTrue */', + [ + 'scope' => 'public', + 'scope_specified' => false, + 'is_static' => false, + 'is_readonly' => true, + 'type' => 'true|FALSE', + 'type_token' => -4, // Offset from the T_VARIABLE token. + 'type_end_token' => -2, // Offset from the T_VARIABLE token. + 'nullable_type' => false, + ], + ], ]; } diff --git a/Tests/BackCompat/BCFile/GetMethodParametersTest.inc b/Tests/BackCompat/BCFile/GetMethodParametersTest.inc index 17ba4a98..17c8417e 100644 --- a/Tests/BackCompat/BCFile/GetMethodParametersTest.inc +++ b/Tests/BackCompat/BCFile/GetMethodParametersTest.inc @@ -159,11 +159,11 @@ function unionTypesAllPseudoTypes(false|mixed|self|parent|iterable|Resource $var $closure = function (?int|float $number) {}; /* testPHP8PseudoTypeNull */ -// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. +// PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. function pseudoTypeNull(null $var = null) {} /* testPHP8PseudoTypeFalse */ -// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. +// PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. function pseudoTypeFalse(false $var = false) {} /* testPHP8PseudoTypeFalseAndBool */ @@ -267,6 +267,13 @@ $closure = function (string&int $numeric_string) {}; // Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method. $closure = function (?Foo&Bar $object) {}; +/* testPHP82PseudoTypeTrue */ +function pseudoTypeTrue(?true $var = true) {} + +/* testPHP82PseudoTypeFalseAndTrue */ +// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method. +function pseudoTypeFalseAndTrue(true|false $var = true) {} + /* testPHP81NewInInitializers */ function newInInitializers( TypeA $new = new TypeA(self::CONST_VALUE), diff --git a/Tests/BackCompat/BCFile/GetMethodParametersTest.php b/Tests/BackCompat/BCFile/GetMethodParametersTest.php index d23ed800..1e94026e 100644 --- a/Tests/BackCompat/BCFile/GetMethodParametersTest.php +++ b/Tests/BackCompat/BCFile/GetMethodParametersTest.php @@ -2436,6 +2436,66 @@ public function testPHP81NullableIntersectionTypes() $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); } + /** + * Verify recognition of PHP 8.2 stand-alone `true` type. + * + * @return void + */ + public function testPHP82PseudoTypeTrue() + { + $expected = []; + $expected[0] = [ + 'token' => 7, // Offset from the T_FUNCTION token. + 'name' => '$var', + 'content' => '?true $var = true', + 'default' => 'true', + 'default_token' => 11, // Offset from the T_FUNCTION token. + 'default_equal_token' => 9, // Offset from the T_FUNCTION token. + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => '?true', + 'type_hint_token' => 5, // Offset from the T_FUNCTION token. + 'type_hint_end_token' => 5, // Offset from the T_FUNCTION token. + 'nullable_type' => true, + 'comma_token' => false, + ]; + + $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + + /** + * Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true. + * + * @return void + */ + public function testPHP82PseudoTypeFalseAndTrue() + { + $expected = []; + $expected[0] = [ + 'token' => 8, // Offset from the T_FUNCTION token. + 'name' => '$var', + 'content' => 'true|false $var = true', + 'default' => 'true', + 'default_token' => 12, // Offset from the T_FUNCTION token. + 'default_equal_token' => 10, // Offset from the T_FUNCTION token. + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => 'true|false', + 'type_hint_token' => 4, // Offset from the T_FUNCTION token. + 'type_hint_end_token' => 6, // Offset from the T_FUNCTION token. + 'nullable_type' => false, + 'comma_token' => false, + ]; + + $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + /** * Verify behaviour when the default value uses the "new" keyword, as is allowed per PHP 8.1. * diff --git a/Tests/BackCompat/BCFile/GetMethodPropertiesTest.inc b/Tests/BackCompat/BCFile/GetMethodPropertiesTest.inc index 3d149e83..94ffdd1e 100644 --- a/Tests/BackCompat/BCFile/GetMethodPropertiesTest.inc +++ b/Tests/BackCompat/BCFile/GetMethodPropertiesTest.inc @@ -109,11 +109,11 @@ function unionTypesAllPseudoTypes($var) : false|MIXED|self|parent|static|iterabl $closure = function () use($a) :?int|float {}; /* testPHP8PseudoTypeNull */ -// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. +// PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. function pseudoTypeNull(): null {} /* testPHP8PseudoTypeFalse */ -// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. +// PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. function pseudoTypeFalse(): false {} /* testPHP8PseudoTypeFalseAndBool */ @@ -158,6 +158,13 @@ $closure = function (): string&int {}; // Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method. $closure = function (): ?Foo&Bar {}; +/* testPHP82PseudoTypeTrue */ +function pseudoTypeTrue(): ?true {} + +/* testPHP82PseudoTypeFalseAndTrue */ +// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method. +function pseudoTypeFalseAndTrue(): true|false {} + /* testNotAFunction */ return true; diff --git a/Tests/BackCompat/BCFile/GetMethodPropertiesTest.php b/Tests/BackCompat/BCFile/GetMethodPropertiesTest.php index 75ed7da7..365c49c6 100644 --- a/Tests/BackCompat/BCFile/GetMethodPropertiesTest.php +++ b/Tests/BackCompat/BCFile/GetMethodPropertiesTest.php @@ -1014,6 +1014,52 @@ public function testPHP81NullableIntersectionTypes() $this->getMethodPropertiesTestHelper('/* ' . __FUNCTION__ . ' */', $expected); } + /** + * Verify recognition of PHP8.1 intersection type declaration with (illegal) nullability. + * + * @return void + */ + public function testPHP82PseudoTypeTrue() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => '?true', + 'return_type_token' => 8, // Offset from the T_FUNCTION token. + 'return_type_end_token' => 8, // Offset from the T_FUNCTION token. + 'nullable_return_type' => true, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + + /** + * Verify recognition of PHP8.1 intersection type declaration with (illegal) nullability. + * + * @return void + */ + public function testPHP82PseudoTypeFalseAndTrue() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'true|false', + 'return_type_token' => 7, // Offset from the T_FUNCTION token. + 'return_type_end_token' => 9, // Offset from the T_FUNCTION token. + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + /** * Test for incorrect tokenization of array return type declarations in PHPCS < 2.8.0. * diff --git a/Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc b/Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc deleted file mode 100644 index d9f8c995..00000000 --- a/Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc +++ /dev/null @@ -1,8 +0,0 @@ -expectPhpcsException('$stackPtr must be of type T_FUNCTION, T_CLOSURE or T_USE or an arrow function'); - - FunctionDeclarations::getParameters(self::$phpcsFile, 10000); - } - - /** - * Verify recognition of PHP 8.2 stand-alone `true` type. - * - * @return void - */ - public function testPHP82PseudoTypeTrue() - { - $expected = []; - $expected[0] = [ - 'token' => 7, // Offset from the T_FUNCTION token. - 'name' => '$var', - 'content' => '?true $var = true', - 'default' => 'true', - 'default_token' => 11, // Offset from the T_FUNCTION token. - 'default_equal_token' => 9, // Offset from the T_FUNCTION token. - 'has_attributes' => false, - 'pass_by_reference' => false, - 'reference_token' => false, - 'variable_length' => false, - 'variadic_token' => false, - 'type_hint' => '?true', - 'type_hint_token' => 5, // Offset from the T_FUNCTION token. - 'type_hint_end_token' => 5, // Offset from the T_FUNCTION token. - 'nullable_type' => true, - 'comma_token' => false, - ]; - - $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); - } - - /** - * Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true. - * - * @return void - */ - public function testPHP82PseudoTypeFalseAndTrue() - { - $expected = []; - $expected[0] = [ - 'token' => 8, // Offset from the T_FUNCTION token. - 'name' => '$var', - 'content' => 'true|false $var = true', - 'default' => 'true', - 'default_token' => 12, // Offset from the T_FUNCTION token. - 'default_equal_token' => 10, // Offset from the T_FUNCTION token. - 'has_attributes' => false, - 'pass_by_reference' => false, - 'reference_token' => false, - 'variable_length' => false, - 'variadic_token' => false, - 'type_hint' => 'true|false', - 'type_hint_token' => 4, // Offset from the T_FUNCTION token. - 'type_hint_end_token' => 6, // Offset from the T_FUNCTION token. - 'nullable_type' => false, - 'comma_token' => false, - ]; - - $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); - } - - /** - * Test helper. + * Initialize PHPCS & tokenize the test case file. * - * @param string $marker The comment which preceeds the test. - * @param array $expected The expected function output. - * @param array $targetType Optional. The token type to search for after $marker. - * Defaults to the function/closure/arrow tokens. + * @beforeClass * * @return void */ - protected function getMethodParametersTestHelper($marker, $expected, $targetType = [\T_FUNCTION, \T_CLOSURE, \T_FN]) + public static function setUpTestFile() { - $target = $this->getTargetToken($marker, $targetType); - $found = FunctionDeclarations::getParameters(self::$phpcsFile, $target); - $expected = $this->updateExpectedTokenPositions($target, $expected); - - $this->assertSame($expected, $found); + self::$caseFile = \dirname(\dirname(__DIR__)) . '/DummyFile.inc'; + parent::setUpTestFile(); } /** - * Test helper to translate token offsets to absolute positions in an "expected" array. - * - * @param int $targetPtr The token pointer to the target token from which - * the offset is calculated. - * @param array> $expected The expected function output containing offsets. - * - * @return array> - */ - private function updateExpectedTokenPositions($targetPtr, $expected) - { - foreach ($expected as $key => $param) { - $expected[$key]['token'] += $targetPtr; - - if ($param['reference_token'] !== false) { - $expected[$key]['reference_token'] += $targetPtr; - } - if ($param['variadic_token'] !== false) { - $expected[$key]['variadic_token'] += $targetPtr; - } - if ($param['type_hint_token'] !== false) { - $expected[$key]['type_hint_token'] += $targetPtr; - } - if ($param['type_hint_end_token'] !== false) { - $expected[$key]['type_hint_end_token'] += $targetPtr; - } - if ($param['comma_token'] !== false) { - $expected[$key]['comma_token'] += $targetPtr; - } - if (isset($param['default_token'])) { - $expected[$key]['default_token'] += $targetPtr; - } - if (isset($param['default_equal_token'])) { - $expected[$key]['default_equal_token'] += $targetPtr; - } - if (isset($param['visibility_token']) && $param['visibility_token'] !== false) { - $expected[$key]['visibility_token'] += $targetPtr; - } - if (isset($param['readonly_token'])) { - $expected[$key]['readonly_token'] += $targetPtr; - } - } - - return $expected; - } - - /** - * Verify that the build-in caching is used when caching is enabled. + * Test passing a non-existent token pointer. * * @return void */ - public function testResultIsCached() + public function testNonExistentToken() { - // The test case used is specifically selected to be one which will always reach the cache check. - $methodName = 'PHPCSUtils\\Utils\\FunctionDeclarations::getParameters'; - $testMarker = '/* testPHP82PseudoTypeTrue */'; - $expected = [ - 0 => [ - 'token' => 7, // Offset from the T_FUNCTION token. - 'name' => '$var', - 'content' => '?true $var = true', - 'default' => 'true', - 'default_token' => 11, // Offset from the T_FUNCTION token. - 'default_equal_token' => 9, // Offset from the T_FUNCTION token. - 'has_attributes' => false, - 'pass_by_reference' => false, - 'reference_token' => false, - 'variable_length' => false, - 'variadic_token' => false, - 'type_hint' => '?true', - 'type_hint_token' => 5, // Offset from the T_FUNCTION token. - 'type_hint_end_token' => 5, // Offset from the T_FUNCTION token. - 'nullable_type' => true, - 'comma_token' => false, - ], - ]; - - $stackPtr = $this->getTargetToken($testMarker, Collections::functionDeclarationTokens()); - $expected = $this->updateExpectedTokenPositions($stackPtr, $expected); - - // Verify the caching works. - $origStatus = Cache::$enabled; - Cache::$enabled = true; - - $resultFirstRun = FunctionDeclarations::getParameters(self::$phpcsFile, $stackPtr); - $isCached = Cache::isCached(self::$phpcsFile, $methodName, $stackPtr); - $resultSecondRun = FunctionDeclarations::getParameters(self::$phpcsFile, $stackPtr); - - if ($origStatus === false) { - Cache::clear(); - } - Cache::$enabled = $origStatus; + $this->expectPhpcsException('$stackPtr must be of type T_FUNCTION, T_CLOSURE or T_USE or an arrow function'); - $this->assertSame($expected, $resultFirstRun, 'First result did not match expectation'); - $this->assertTrue($isCached, 'Cache::isCached() could not find the cached value'); - $this->assertSame($resultFirstRun, $resultSecondRun, 'Second result did not match first'); + FunctionDeclarations::getParameters(self::$phpcsFile, 10000); } } diff --git a/Tests/Utils/FunctionDeclarations/GetParametersTest.php b/Tests/Utils/FunctionDeclarations/GetParametersTest.php index 995501a0..d0ad91c7 100644 --- a/Tests/Utils/FunctionDeclarations/GetParametersTest.php +++ b/Tests/Utils/FunctionDeclarations/GetParametersTest.php @@ -10,7 +10,9 @@ namespace PHPCSUtils\Tests\Utils\FunctionDeclarations; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tests\BackCompat\BCFile\GetMethodParametersTest as BCFile_GetMethodParametersTest; +use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\FunctionDeclarations; /** @@ -113,41 +115,108 @@ public function testNoParams($commentString, $targetTokenType = [\T_FUNCTION, \T */ protected function getMethodParametersTestHelper($marker, $expected, $targetType = [\T_FUNCTION, \T_CLOSURE, \T_FN]) { - $target = $this->getTargetToken($marker, $targetType); - $found = FunctionDeclarations::getParameters(self::$phpcsFile, $target); + $target = $this->getTargetToken($marker, $targetType); + $found = FunctionDeclarations::getParameters(self::$phpcsFile, $target); + $expected = $this->updateExpectedTokenPositions($target, $expected); + $this->assertSame($expected, $found); + } + + /** + * Test helper to translate token offsets to absolute positions in an "expected" array. + * + * @param int $targetPtr The token pointer to the target token from which + * the offset is calculated. + * @param array> $expected The expected function output containing offsets. + * + * @return array> + */ + private function updateExpectedTokenPositions($targetPtr, $expected) + { foreach ($expected as $key => $param) { - $expected[$key]['token'] += $target; + $expected[$key]['token'] += $targetPtr; if ($param['reference_token'] !== false) { - $expected[$key]['reference_token'] += $target; + $expected[$key]['reference_token'] += $targetPtr; } if ($param['variadic_token'] !== false) { - $expected[$key]['variadic_token'] += $target; + $expected[$key]['variadic_token'] += $targetPtr; } if ($param['type_hint_token'] !== false) { - $expected[$key]['type_hint_token'] += $target; + $expected[$key]['type_hint_token'] += $targetPtr; } if ($param['type_hint_end_token'] !== false) { - $expected[$key]['type_hint_end_token'] += $target; + $expected[$key]['type_hint_end_token'] += $targetPtr; } if ($param['comma_token'] !== false) { - $expected[$key]['comma_token'] += $target; + $expected[$key]['comma_token'] += $targetPtr; } if (isset($param['default_token'])) { - $expected[$key]['default_token'] += $target; + $expected[$key]['default_token'] += $targetPtr; } if (isset($param['default_equal_token'])) { - $expected[$key]['default_equal_token'] += $target; + $expected[$key]['default_equal_token'] += $targetPtr; } if (isset($param['visibility_token']) && $param['visibility_token'] !== false) { - $expected[$key]['visibility_token'] += $target; + $expected[$key]['visibility_token'] += $targetPtr; } if (isset($param['readonly_token'])) { - $expected[$key]['readonly_token'] += $target; + $expected[$key]['readonly_token'] += $targetPtr; } } - $this->assertSame($expected, $found); + return $expected; + } + + /** + * Verify that the build-in caching is used when caching is enabled. + * + * @return void + */ + public function testResultIsCached() + { + // The test case used is specifically selected to be one which will always reach the cache check. + $methodName = 'PHPCSUtils\\Utils\\FunctionDeclarations::getParameters'; + $testMarker = '/* testPHP82PseudoTypeTrue */'; + $expected = [ + 0 => [ + 'token' => 7, // Offset from the T_FUNCTION token. + 'name' => '$var', + 'content' => '?true $var = true', + 'default' => 'true', + 'default_token' => 11, // Offset from the T_FUNCTION token. + 'default_equal_token' => 9, // Offset from the T_FUNCTION token. + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => '?true', + 'type_hint_token' => 5, // Offset from the T_FUNCTION token. + 'type_hint_end_token' => 5, // Offset from the T_FUNCTION token. + 'nullable_type' => true, + 'comma_token' => false, + ], + ]; + + $stackPtr = $this->getTargetToken($testMarker, Collections::functionDeclarationTokens()); + $expected = $this->updateExpectedTokenPositions($stackPtr, $expected); + + // Verify the caching works. + $origStatus = Cache::$enabled; + Cache::$enabled = true; + + $resultFirstRun = FunctionDeclarations::getParameters(self::$phpcsFile, $stackPtr); + $isCached = Cache::isCached(self::$phpcsFile, $methodName, $stackPtr); + $resultSecondRun = FunctionDeclarations::getParameters(self::$phpcsFile, $stackPtr); + + if ($origStatus === false) { + Cache::clear(); + } + Cache::$enabled = $origStatus; + + $this->assertSame($expected, $resultFirstRun, 'First result did not match expectation'); + $this->assertTrue($isCached, 'Cache::isCached() could not find the cached value'); + $this->assertSame($resultFirstRun, $resultSecondRun, 'Second result did not match first'); } } diff --git a/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.inc b/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.inc index e78b70e9..391ff60f 100644 --- a/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.inc +++ b/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.inc @@ -17,10 +17,3 @@ trait FooTrait { $func(); } } - -/* testPHP82PseudoTypeTrue */ -function pseudoTypeTrue(): ?true {} - -/* testPHP82PseudoTypeFalseAndTrue */ -// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method. -function pseudoTypeFalseAndTrue(): true|false {} diff --git a/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.php b/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.php index 03259499..f02460f8 100644 --- a/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.php +++ b/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.php @@ -88,52 +88,6 @@ public function testMessyPhpcsAnnotationsStaticClosure() $this->getPropertiesTestHelper('/* ' . __FUNCTION__ . ' */', $expected); } - /** - * Verify recognition of PHP 8.2 stand-alone `true` type. - * - * @return void - */ - public function testPHP82PseudoTypeTrue() - { - $expected = [ - 'scope' => 'public', - 'scope_specified' => false, - 'return_type' => '?true', - 'return_type_token' => 8, // Offset from the T_FUNCTION token. - 'return_type_end_token' => 8, // Offset from the T_FUNCTION token. - 'nullable_return_type' => true, - 'is_abstract' => false, - 'is_final' => false, - 'is_static' => false, - 'has_body' => true, - ]; - - $this->getPropertiesTestHelper('/* ' . __FUNCTION__ . ' */', $expected); - } - - /** - * Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true. - * - * @return void - */ - public function testPHP82PseudoTypeFalseAndTrue() - { - $expected = [ - 'scope' => 'public', - 'scope_specified' => false, - 'return_type' => 'true|false', - 'return_type_token' => 7, // Offset from the T_FUNCTION token. - 'return_type_end_token' => 9, // Offset from the T_FUNCTION token. - 'nullable_return_type' => false, - 'is_abstract' => false, - 'is_final' => false, - 'is_static' => false, - 'has_body' => true, - ]; - - $this->getPropertiesTestHelper('/* ' . __FUNCTION__ . ' */', $expected); - } - /** * Test helper. * diff --git a/Tests/Utils/Variables/GetMemberPropertiesDiffTest.inc b/Tests/Utils/Variables/GetMemberPropertiesDiffTest.inc index affc1f2e..055ad9c6 100644 --- a/Tests/Utils/Variables/GetMemberPropertiesDiffTest.inc +++ b/Tests/Utils/Variables/GetMemberPropertiesDiffTest.inc @@ -11,18 +11,3 @@ enum Suit /* testEnumProperty */ protected $anonymous; } - -$anon = class() { - /* testPHP82PseudoTypeTrue */ - public true $pseudoTypeTrue; - - /* testPHP82NullablePseudoTypeTrue */ - static protected ?true $pseudoTypeNullableTrue; - - /* testPHP82PseudoTypeTrueInUnion */ - private int|string|true $pseudoTypeTrueInUnion; - - /* testPHP82PseudoTypeFalseAndTrue */ - // Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method. - readonly true|FALSE $pseudoTypeFalseAndTrue; -}; diff --git a/Tests/Utils/Variables/GetMemberPropertiesDiffTest.php b/Tests/Utils/Variables/GetMemberPropertiesDiffTest.php index 8786f5cd..1fdd4d82 100644 --- a/Tests/Utils/Variables/GetMemberPropertiesDiffTest.php +++ b/Tests/Utils/Variables/GetMemberPropertiesDiffTest.php @@ -10,7 +10,6 @@ namespace PHPCSUtils\Tests\Utils\Variables; -use PHPCSUtils\Internal\Cache; use PHPCSUtils\TestUtils\UtilityMethodTestCase; use PHPCSUtils\Utils\Variables; @@ -72,133 +71,4 @@ public static function dataNotClassPropertyException() 'enum property' => ['/* testEnumProperty */'], ]; } - - /** - * Test the getMemberProperties() method. - * - * @dataProvider dataGetMemberProperties - * - * @param string $identifier Comment which precedes the test case. - * @param array $expected Expected function output. - * - * @return void - */ - public function testGetMemberProperties($identifier, $expected) - { - $variable = $this->getTargetToken($identifier, \T_VARIABLE); - $result = Variables::getMemberProperties(self::$phpcsFile, $variable); - - if (isset($expected['type_token']) && $expected['type_token'] !== false) { - $expected['type_token'] += $variable; - } - if (isset($expected['type_end_token']) && $expected['type_end_token'] !== false) { - $expected['type_end_token'] += $variable; - } - - $this->assertSame($expected, $result); - } - - /** - * Data provider. - * - * @see testGetMemberProperties() - * - * @return array - */ - public static function dataGetMemberProperties() - { - return [ - 'php8.2-pseudo-type-true' => [ - 'identifier' => '/* testPHP82PseudoTypeTrue */', - 'expected' => [ - 'scope' => 'public', - 'scope_specified' => true, - 'is_static' => false, - 'is_readonly' => false, - 'type' => 'true', - 'type_token' => -2, // Offset from the T_VARIABLE token. - 'type_end_token' => -2, // Offset from the T_VARIABLE token. - 'nullable_type' => false, - ], - ], - 'php8.2-pseudo-type-true-nullable' => [ - 'identifier' => '/* testPHP82NullablePseudoTypeTrue */', - 'expected' => [ - 'scope' => 'protected', - 'scope_specified' => true, - 'is_static' => true, - 'is_readonly' => false, - 'type' => '?true', - 'type_token' => -2, // Offset from the T_VARIABLE token. - 'type_end_token' => -2, // Offset from the T_VARIABLE token. - 'nullable_type' => true, - ], - ], - 'php8.2-pseudo-type-true-in-union' => [ - 'identifier' => '/* testPHP82PseudoTypeTrueInUnion */', - 'expected' => [ - 'scope' => 'private', - 'scope_specified' => true, - 'is_static' => false, - 'is_readonly' => false, - 'type' => 'int|string|true', - 'type_token' => -6, // Offset from the T_VARIABLE token. - 'type_end_token' => -2, // Offset from the T_VARIABLE token. - 'nullable_type' => false, - ], - ], - 'php8.2-pseudo-type-invalid-true-false-union' => [ - 'identifier' => '/* testPHP82PseudoTypeFalseAndTrue */', - 'expected' => [ - 'scope' => 'public', - 'scope_specified' => false, - 'is_static' => false, - 'is_readonly' => true, - 'type' => 'true|FALSE', - 'type_token' => -4, // Offset from the T_VARIABLE token. - 'type_end_token' => -2, // Offset from the T_VARIABLE token. - 'nullable_type' => false, - ], - ], - ]; - } - - /** - * Verify that the build-in caching is used when caching is enabled. - * - * @return void - */ - public function testResultIsCached() - { - $methodName = 'PHPCSUtils\\Utils\\Variables::getMemberProperties'; - $cases = $this->dataGetMemberProperties(); - $identifier = $cases['php8.2-pseudo-type-true-in-union']['identifier']; - $expected = $cases['php8.2-pseudo-type-true-in-union']['expected']; - - $variable = $this->getTargetToken($identifier, \T_VARIABLE); - - if (isset($expected['type_token']) && $expected['type_token'] !== false) { - $expected['type_token'] += $variable; - } - if (isset($expected['type_end_token']) && $expected['type_end_token'] !== false) { - $expected['type_end_token'] += $variable; - } - - // Verify the caching works. - $origStatus = Cache::$enabled; - Cache::$enabled = true; - - $resultFirstRun = Variables::getMemberProperties(self::$phpcsFile, $variable); - $isCached = Cache::isCached(self::$phpcsFile, $methodName, $variable); - $resultSecondRun = Variables::getMemberProperties(self::$phpcsFile, $variable); - - if ($origStatus === false) { - Cache::clear(); - } - Cache::$enabled = $origStatus; - - $this->assertSame($expected, $resultFirstRun, 'First result did not match expectation'); - $this->assertTrue($isCached, 'Cache::isCached() could not find the cached value'); - $this->assertSame($resultFirstRun, $resultSecondRun, 'Second result did not match first'); - } } diff --git a/Tests/Utils/Variables/GetMemberPropertiesTest.php b/Tests/Utils/Variables/GetMemberPropertiesTest.php index 30e07e5b..ed3ccd80 100644 --- a/Tests/Utils/Variables/GetMemberPropertiesTest.php +++ b/Tests/Utils/Variables/GetMemberPropertiesTest.php @@ -10,7 +10,9 @@ namespace PHPCSUtils\Tests\Utils\Variables; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tests\BackCompat\BCFile\GetMemberPropertiesTest as BCFile_GetMemberPropertiesTest; +use PHPCSUtils\Utils\Variables; /** * Tests for the \PHPCSUtils\Utils\Variables::getMemberProperties method. @@ -79,4 +81,43 @@ public static function dataGetMemberProperties() return $data; } + + /** + * Verify that the build-in caching is used when caching is enabled. + * + * @return void + */ + public function testResultIsCached() + { + $methodName = 'PHPCSUtils\\Utils\\Variables::getMemberProperties'; + $cases = self::dataGetMemberProperties(); + $identifier = $cases['php8.2-pseudo-type-true-in-union'][0]; + $expected = $cases['php8.2-pseudo-type-true-in-union'][1]; + + $variable = $this->getTargetToken($identifier, \T_VARIABLE); + + if (isset($expected['type_token']) && $expected['type_token'] !== false) { + $expected['type_token'] += $variable; + } + if (isset($expected['type_end_token']) && $expected['type_end_token'] !== false) { + $expected['type_end_token'] += $variable; + } + + // Verify the caching works. + $origStatus = Cache::$enabled; + Cache::$enabled = true; + + $resultFirstRun = Variables::getMemberProperties(self::$phpcsFile, $variable); + $isCached = Cache::isCached(self::$phpcsFile, $methodName, $variable); + $resultSecondRun = Variables::getMemberProperties(self::$phpcsFile, $variable); + + if ($origStatus === false) { + Cache::clear(); + } + Cache::$enabled = $origStatus; + + $this->assertSame($expected, $resultFirstRun, 'First result did not match expectation'); + $this->assertTrue($isCached, 'Cache::isCached() could not find the cached value'); + $this->assertSame($resultFirstRun, $resultSecondRun, 'Second result did not match first'); + } }