diff --git a/PHPCSUtils/Tokens/Collections.php b/PHPCSUtils/Tokens/Collections.php index 6542a7fc..19944c18 100644 --- a/PHPCSUtils/Tokens/Collections.php +++ b/PHPCSUtils/Tokens/Collections.php @@ -476,6 +476,7 @@ class Collections * @since 1.0.0-alpha1 * @since 1.0.0-alpha4 Added the T_TYPE_UNION, T_FALSE, T_NULL tokens for PHP 8.0 union type support. * @since 1.0.0-alpha4 Added the T_TYPE_INTERSECTION token for PHP 8.1 intersection type support. + * @since 1.0.0-alpha4 Added the T_TRUE token for PHP 8.2 true type support. * * @deprecated 1.0.0-alpha4 Use the {@see Collections::parameterTypeTokens()} method instead. * @@ -486,6 +487,7 @@ class Collections \T_SELF => \T_SELF, \T_PARENT => \T_PARENT, \T_FALSE => \T_FALSE, + \T_TRUE => \T_TRUE, \T_NULL => \T_NULL, \T_STRING => \T_STRING, \T_NS_SEPARATOR => \T_NS_SEPARATOR, @@ -530,6 +532,7 @@ class Collections * @since 1.0.0-alpha1 * @since 1.0.0-alpha4 Added the T_TYPE_UNION, T_FALSE, T_NULL tokens for PHP 8.0 union type support. * @since 1.0.0-alpha4 Added the T_TYPE_INTERSECTION token for PHP 8.1 intersection type support. + * @since 1.0.0-alpha4 Added the T_TRUE token for PHP 8.2 true type support. * * @deprecated 1.0.0-alpha4 Use the {@see Collections::propertyTypeTokens()} method instead. * @@ -540,6 +543,7 @@ class Collections \T_SELF => \T_SELF, \T_PARENT => \T_PARENT, \T_FALSE => \T_FALSE, + \T_TRUE => \T_TRUE, \T_NULL => \T_NULL, \T_STRING => \T_STRING, \T_NS_SEPARATOR => \T_NS_SEPARATOR, @@ -553,6 +557,7 @@ class Collections * @since 1.0.0-alpha1 * @since 1.0.0-alpha4 Added the T_TYPE_UNION, T_FALSE, T_NULL tokens for PHP 8.0 union type support. * @since 1.0.0-alpha4 Added the T_TYPE_INTERSECTION token for PHP 8.1 intersection type support. + * @since 1.0.0-alpha4 Added the T_TRUE token for PHP 8.2 true type support. * * @deprecated 1.0.0-alpha4 Use the {@see Collections::returnTypeTokens()} method instead. * @@ -564,6 +569,7 @@ class Collections \T_PARENT => \T_PARENT, \T_STATIC => \T_STATIC, \T_FALSE => \T_FALSE, + \T_TRUE => \T_TRUE, \T_NULL => \T_NULL, \T_STRING => \T_STRING, \T_NS_SEPARATOR => \T_NS_SEPARATOR, @@ -922,6 +928,7 @@ public static function parameterPassingTokens() * @since 1.0.0-alpha4 Added the T_TYPE_UNION, T_FALSE, T_NULL tokens for PHP 8.0 union type support. * @since 1.0.0-alpha4 Added support for PHP 8.0 identifier name tokens. * @since 1.0.0-alpha4 Added the T_TYPE_INTERSECTION token for PHP 8.1 intersection type support. + * @since 1.0.0-alpha4 Added the T_TRUE token for PHP 8.2 true type support. * * @return array => */ @@ -962,6 +969,7 @@ public static function parameterTypeTokensBC() * @since 1.0.0-alpha4 Added the T_TYPE_UNION, T_FALSE, T_NULL tokens for PHP 8.0 union type support. * @since 1.0.0-alpha4 Added support for PHP 8.0 identifier name tokens. * @since 1.0.0-alpha4 Added the T_TYPE_INTERSECTION token for PHP 8.1 intersection type support. + * @since 1.0.0-alpha4 Added the T_TRUE token for PHP 8.2 true type support. * * @return array => */ @@ -1002,6 +1010,7 @@ public static function propertyTypeTokensBC() * @since 1.0.0-alpha4 Added the T_TYPE_UNION, T_FALSE, T_NULL tokens for PHP 8.0 union type support. * @since 1.0.0-alpha4 Added support for PHP 8.0 identifier name tokens. * @since 1.0.0-alpha4 Added the T_TYPE_INTERSECTION token for PHP 8.1 intersection type support. + * @since 1.0.0-alpha4 Added the T_TRUE token for PHP 8.2 true type support. * * @return array => */ diff --git a/PHPCSUtils/Utils/FunctionDeclarations.php b/PHPCSUtils/Utils/FunctionDeclarations.php index a5421d09..9c3d4544 100644 --- a/PHPCSUtils/Utils/FunctionDeclarations.php +++ b/PHPCSUtils/Utils/FunctionDeclarations.php @@ -148,6 +148,7 @@ 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. * * @see \PHP_CodeSniffer\Files\File::getMethodProperties() Original source. * @see \PHPCSUtils\BackCompat\BCFile::getMethodProperties() Cross-version compatible version of the original. @@ -157,6 +158,7 @@ public static function getName(File $phpcsFile, $stackPtr) * @since 1.0.0-alpha3 Added support for PHP 8.0 static return type. * @since 1.0.0-alpha4 Added support for PHP 8.0 union types. * @since 1.0.0-alpha4 Added support for PHP 8.1 intersection types. + * @since 1.0.0-alpha4 Added support for PHP 8.2 true type. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position in the stack of the function token to @@ -246,6 +248,12 @@ 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']; @@ -354,6 +362,7 @@ 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. * * @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source. * @see \PHPCSUtils\BackCompat\BCFile::getMethodParameters() Cross-version compatible version of the original. @@ -366,6 +375,7 @@ public static function getProperties(File $phpcsFile, $stackPtr) * @since 1.0.0-alpha4 Added support for PHP 8.0 parameter attributes. * @since 1.0.0-alpha4 Added support for PHP 8.1 readonly keyword for constructor property promotion. * @since 1.0.0-alpha4 Added support for PHP 8.1 intersection types. + * @since 1.0.0-alpha4 Added support for PHP 8.2 true type. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position in the stack of the function token @@ -433,8 +443,16 @@ public static function getParameters(File $phpcsFile, $stackPtr) $visibilityToken = null; $readonlyToken = null; + $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(Collections::parameterTypeTokens()[$tokens[$i]['code']]) === true + if (isset($parameterTypeTokens[$tokens[$i]['code']]) === true // Self and parent are valid, static invalid, but was probably intended as type declaration. || $tokens[$i]['code'] === \T_STATIC ) { diff --git a/PHPCSUtils/Utils/Variables.php b/PHPCSUtils/Utils/Variables.php index 3fe4924b..241d0c6e 100644 --- a/PHPCSUtils/Utils/Variables.php +++ b/PHPCSUtils/Utils/Variables.php @@ -82,6 +82,7 @@ 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. * * @see \PHP_CodeSniffer\Files\File::getMemberProperties() Original source. * @see \PHPCSUtils\BackCompat\BCFile::getMemberProperties() Cross-version compatible version of the original. @@ -91,6 +92,7 @@ class Variables * @since 1.0.0-alpha4 No longer gets confused by PHP 8.0 property attributes. * @since 1.0.0-alpha4 Added support for PHP 8.1 readonly properties. * @since 1.0.0-alpha4 Added support for PHP 8.1 intersection types. + * @since 1.0.0-alpha4 Added support for PHP 8.2 true type. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position in the stack of the `T_VARIABLE` token @@ -181,6 +183,12 @@ 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/Tokens/Collections/ParameterTypeTokensTest.php b/Tests/Tokens/Collections/ParameterTypeTokensTest.php index c072e9c6..1a9b2338 100644 --- a/Tests/Tokens/Collections/ParameterTypeTokensTest.php +++ b/Tests/Tokens/Collections/ParameterTypeTokensTest.php @@ -37,6 +37,7 @@ public function testParameterTypeTokens() \T_SELF => \T_SELF, \T_PARENT => \T_PARENT, \T_FALSE => \T_FALSE, + \T_TRUE => \T_TRUE, \T_NULL => \T_NULL, \T_STRING => \T_STRING, \T_NS_SEPARATOR => \T_NS_SEPARATOR, diff --git a/Tests/Tokens/Collections/PropertyTypeTokensTest.php b/Tests/Tokens/Collections/PropertyTypeTokensTest.php index 2a247a49..cfcd3ed3 100644 --- a/Tests/Tokens/Collections/PropertyTypeTokensTest.php +++ b/Tests/Tokens/Collections/PropertyTypeTokensTest.php @@ -37,6 +37,7 @@ public function testPropertyTypeTokens() \T_SELF => \T_SELF, \T_PARENT => \T_PARENT, \T_FALSE => \T_FALSE, + \T_TRUE => \T_TRUE, \T_NULL => \T_NULL, \T_STRING => \T_STRING, \T_NS_SEPARATOR => \T_NS_SEPARATOR, diff --git a/Tests/Tokens/Collections/ReturnTypeTokensTest.php b/Tests/Tokens/Collections/ReturnTypeTokensTest.php index c3a1f05d..c80535dc 100644 --- a/Tests/Tokens/Collections/ReturnTypeTokensTest.php +++ b/Tests/Tokens/Collections/ReturnTypeTokensTest.php @@ -38,6 +38,7 @@ public function testReturnTypeTokens() \T_PARENT => \T_PARENT, \T_STATIC => \T_STATIC, \T_FALSE => \T_FALSE, + \T_TRUE => \T_TRUE, \T_NULL => \T_NULL, \T_STRING => \T_STRING, \T_NS_SEPARATOR => \T_NS_SEPARATOR, diff --git a/Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc b/Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc new file mode 100644 index 00000000..d9f8c995 --- /dev/null +++ b/Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc @@ -0,0 +1,8 @@ +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 static function setUpTestFile() + public function testPHP82PseudoTypeTrue() { - self::$caseFile = \dirname(\dirname(__DIR__)) . '/DummyFile.inc'; - parent::setUpTestFile(); + $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); } /** - * Test passing a non-existent token pointer. + * Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true. * * @return void */ - public function testNonExistentToken() + public function testPHP82PseudoTypeFalseAndTrue() { - $this->expectPhpcsException('$stackPtr must be of type T_FUNCTION, T_CLOSURE or T_USE or an arrow function'); + $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, + ]; - FunctionDeclarations::getParameters(self::$phpcsFile, 10000); + $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + + /** + * Test helper. + * + * @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. + * + * @return void + */ + protected function getMethodParametersTestHelper($marker, $expected, $targetType = [\T_FUNCTION, \T_CLOSURE, \T_FN]) + { + $target = $this->getTargetToken($marker, $targetType); + $found = FunctionDeclarations::getParameters(self::$phpcsFile, $target); + + foreach ($expected as $key => $param) { + $expected[$key]['token'] += $target; + + if ($param['reference_token'] !== false) { + $expected[$key]['reference_token'] += $target; + } + if ($param['variadic_token'] !== false) { + $expected[$key]['variadic_token'] += $target; + } + if ($param['type_hint_token'] !== false) { + $expected[$key]['type_hint_token'] += $target; + } + if ($param['type_hint_end_token'] !== false) { + $expected[$key]['type_hint_end_token'] += $target; + } + if ($param['comma_token'] !== false) { + $expected[$key]['comma_token'] += $target; + } + if (isset($param['default_token'])) { + $expected[$key]['default_token'] += $target; + } + if (isset($param['default_equal_token'])) { + $expected[$key]['default_equal_token'] += $target; + } + if (isset($param['visibility_token'])) { + $expected[$key]['visibility_token'] += $target; + } + if (isset($param['readonly_token'])) { + $expected[$key]['readonly_token'] += $target; + } + } + + $this->assertSame($expected, $found); } } diff --git a/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.inc b/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.inc index 391ff60f..e78b70e9 100644 --- a/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.inc +++ b/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.inc @@ -17,3 +17,10 @@ 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 9d37d224..849b0cbf 100644 --- a/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.php +++ b/Tests/Utils/FunctionDeclarations/GetPropertiesDiffTest.php @@ -86,6 +86,52 @@ 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 055ad9c6..affc1f2e 100644 --- a/Tests/Utils/Variables/GetMemberPropertiesDiffTest.inc +++ b/Tests/Utils/Variables/GetMemberPropertiesDiffTest.inc @@ -11,3 +11,18 @@ 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 2023f7b9..4191c712 100644 --- a/Tests/Utils/Variables/GetMemberPropertiesDiffTest.php +++ b/Tests/Utils/Variables/GetMemberPropertiesDiffTest.php @@ -71,4 +71,94 @@ public function dataNotClassPropertyException() 'enum property' => ['/* testEnumProperty */'], ]; } + + /** + * Test the getMemberProperties() method. + * + * @dataProvider dataGetMemberProperties + * + * @param string $identifier Comment which precedes the test case. + * @param bool $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 function dataGetMemberProperties() + { + return [ + '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, + ], + ], + ]; + } }