diff --git a/PHPCSUtils/BackCompat/BCFile.php b/PHPCSUtils/BackCompat/BCFile.php index 03bd5eed..4a786015 100644 --- a/PHPCSUtils/BackCompat/BCFile.php +++ b/PHPCSUtils/BackCompat/BCFile.php @@ -185,6 +185,12 @@ public static function getDeclarationName(File $phpcsFile, $stackPtr) * // This index will only be set if the property is readonly. * ``` * + * ... and if the promoted property uses asymmetric visibility, these additional array indexes will also be available: + * ```php + * 'set_visibility' => string, // The property set-visibility as declared. + * 'set_visibility_token' => integer, // The stack pointer to the set-visibility modifier token. + * ``` + * * PHPCS cross-version compatible version of the `File::getMethodParameters()` method. * * Changelog for the PHPCS native function: @@ -196,6 +202,7 @@ public static function getDeclarationName(File $phpcsFile, $stackPtr) * * @since 1.0.0 * @since 1.0.6 Sync with PHPCS 3.8.0, support for readonly properties without explicit visibility. PHPCS#3801. + * @since 1.1.0 Sync with PHPCS 3.13.1, support for asymmetric properties. PHPCS(new)#851 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position in the stack of the function token @@ -242,23 +249,24 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr) $closer = $tokens[$opener]['parenthesis_closer']; - $vars = []; - $currVar = null; - $paramStart = ($opener + 1); - $defaultStart = null; - $equalToken = null; - $paramCount = 0; - $hasAttributes = false; - $passByReference = false; - $referenceToken = false; - $variableLength = false; - $variadicToken = false; - $typeHint = ''; - $typeHintToken = false; - $typeHintEndToken = false; - $nullableType = false; - $visibilityToken = null; - $readonlyToken = null; + $vars = []; + $currVar = null; + $paramStart = ($opener + 1); + $defaultStart = null; + $equalToken = null; + $paramCount = 0; + $hasAttributes = false; + $passByReference = false; + $referenceToken = false; + $variableLength = false; + $variadicToken = false; + $typeHint = ''; + $typeHintToken = false; + $typeHintEndToken = false; + $nullableType = false; + $visibilityToken = null; + $setVisibilityToken = null; + $readonlyToken = null; for ($i = $paramStart; $i <= $closer; $i++) { // Check to see if this token has a parenthesis or bracket opener. If it does @@ -392,6 +400,13 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr) $visibilityToken = $i; } break; + case T_PUBLIC_SET: + case T_PROTECTED_SET: + case T_PRIVATE_SET: + if ($defaultStart === null) { + $setVisibilityToken = $i; + } + break; case T_READONLY: if ($defaultStart === null) { $readonlyToken = $i; @@ -426,16 +441,21 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr) $vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken; $vars[$paramCount]['nullable_type'] = $nullableType; - if ($visibilityToken !== null || $readonlyToken !== null) { + if ($visibilityToken !== null || $setVisibilityToken !== null || $readonlyToken !== null) { $vars[$paramCount]['property_visibility'] = 'public'; $vars[$paramCount]['visibility_token'] = false; - $vars[$paramCount]['property_readonly'] = false; if ($visibilityToken !== null) { $vars[$paramCount]['property_visibility'] = $tokens[$visibilityToken]['content']; $vars[$paramCount]['visibility_token'] = $visibilityToken; } + if ($setVisibilityToken !== null) { + $vars[$paramCount]['set_visibility'] = $tokens[$setVisibilityToken]['content']; + $vars[$paramCount]['set_visibility_token'] = $setVisibilityToken; + } + + $vars[$paramCount]['property_readonly'] = false; if ($readonlyToken !== null) { $vars[$paramCount]['property_readonly'] = true; $vars[$paramCount]['readonly_token'] = $readonlyToken; @@ -449,21 +469,22 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr) } // Reset the vars, as we are about to process the next parameter. - $currVar = null; - $paramStart = ($i + 1); - $defaultStart = null; - $equalToken = null; - $hasAttributes = false; - $passByReference = false; - $referenceToken = false; - $variableLength = false; - $variadicToken = false; - $typeHint = ''; - $typeHintToken = false; - $typeHintEndToken = false; - $nullableType = false; - $visibilityToken = null; - $readonlyToken = null; + $currVar = null; + $paramStart = ($i + 1); + $defaultStart = null; + $equalToken = null; + $hasAttributes = false; + $passByReference = false; + $referenceToken = false; + $variableLength = false; + $variadicToken = false; + $typeHint = ''; + $typeHintToken = false; + $typeHintEndToken = false; + $nullableType = false; + $visibilityToken = null; + $setVisibilityToken = null; + $readonlyToken = null; ++$paramCount; break; @@ -532,6 +553,9 @@ public static function getMethodProperties(File $phpcsFile, $stackPtr) * array( * 'scope' => string, // Public, private, or protected. * 'scope_specified' => boolean, // TRUE if the scope was explicitly specified. + * 'set_scope' => string|false, // Scope for asymmetric visibility. + * // Either public, private, or protected or + * // FALSE if no set scope is specified. * 'is_static' => boolean, // TRUE if the static keyword was found. * 'is_readonly' => boolean, // TRUE if the readonly keyword was found. * 'is_final' => boolean, // TRUE if the final keyword was found. @@ -598,19 +622,18 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr) } $valid = [ - T_PUBLIC => T_PUBLIC, - T_PRIVATE => T_PRIVATE, - T_PROTECTED => T_PROTECTED, - T_STATIC => T_STATIC, - T_VAR => T_VAR, - T_READONLY => T_READONLY, - T_FINAL => T_FINAL, + T_STATIC => T_STATIC, + T_VAR => T_VAR, + T_READONLY => T_READONLY, + T_FINAL => T_FINAL, ]; + $valid += Tokens::$scopeModifiers; $valid += Tokens::$emptyTokens; $scope = 'public'; $scopeSpecified = false; + $setScope = false; $isStatic = false; $isReadonly = false; $isFinal = false; @@ -643,6 +666,15 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr) $scope = 'protected'; $scopeSpecified = true; break; + case T_PUBLIC_SET: + $setScope = 'public'; + break; + case T_PROTECTED_SET: + $setScope = 'protected'; + break; + case T_PRIVATE_SET: + $setScope = 'private'; + break; case T_STATIC: $isStatic = true; break; @@ -692,6 +724,7 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr) return [ 'scope' => $scope, 'scope_specified' => $scopeSpecified, + 'set_scope' => $setScope, 'is_static' => $isStatic, 'is_readonly' => $isReadonly, 'is_final' => $isFinal, diff --git a/PHPCSUtils/Utils/FunctionDeclarations.php b/PHPCSUtils/Utils/FunctionDeclarations.php index 4d7b493b..ca1890c0 100644 --- a/PHPCSUtils/Utils/FunctionDeclarations.php +++ b/PHPCSUtils/Utils/FunctionDeclarations.php @@ -384,6 +384,12 @@ public static function getProperties(File $phpcsFile, $stackPtr) * // This index will only be set if the property is readonly. * ``` * + * ... and if the promoted property uses asymmetric visibility, these additional array indexes will also be available: + * ```php + * 'set_visibility' => string, // The property set-visibility as declared. + * 'set_visibility_token' => int, // The stack pointer to the set-visibility modifier token. + * ``` + * * Main differences with the PHPCS version: * - Defensive coding against incorrect calls to this method. * - More efficient and more stable checking whether a `T_USE` token is a closure use. @@ -457,23 +463,24 @@ public static function getParameters(File $phpcsFile, $stackPtr) $closer = $tokens[$opener]['parenthesis_closer']; - $vars = []; - $currVar = null; - $paramStart = ($opener + 1); - $defaultStart = null; - $equalToken = null; - $paramCount = 0; - $hasAttributes = false; - $passByReference = false; - $referenceToken = false; - $variableLength = false; - $variadicToken = false; - $typeHint = ''; - $typeHintToken = false; - $typeHintEndToken = false; - $nullableType = false; - $visibilityToken = null; - $readonlyToken = null; + $vars = []; + $currVar = null; + $paramStart = ($opener + 1); + $defaultStart = null; + $equalToken = null; + $paramCount = 0; + $hasAttributes = false; + $passByReference = false; + $referenceToken = false; + $variableLength = false; + $variadicToken = false; + $typeHint = ''; + $typeHintToken = false; + $typeHintEndToken = false; + $nullableType = false; + $visibilityToken = null; + $setVisibilityToken = null; + $readonlyToken = null; $parameterTypeTokens = Collections::parameterTypeTokens(); @@ -529,6 +536,12 @@ public static function getParameters(File $phpcsFile, $stackPtr) $visibilityToken = $i; break; + case \T_PUBLIC_SET: + case \T_PROTECTED_SET: + case \T_PRIVATE_SET: + $setVisibilityToken = $i; + break; + case \T_READONLY: $readonlyToken = $i; break; @@ -566,16 +579,21 @@ public static function getParameters(File $phpcsFile, $stackPtr) $vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken; $vars[$paramCount]['nullable_type'] = $nullableType; - if ($visibilityToken !== null || $readonlyToken !== null) { + if ($visibilityToken !== null || $setVisibilityToken !== null || $readonlyToken !== null) { $vars[$paramCount]['property_visibility'] = 'public'; $vars[$paramCount]['visibility_token'] = false; - $vars[$paramCount]['property_readonly'] = false; if ($visibilityToken !== null) { $vars[$paramCount]['property_visibility'] = $tokens[$visibilityToken]['content']; $vars[$paramCount]['visibility_token'] = $visibilityToken; } + if ($setVisibilityToken !== null) { + $vars[$paramCount]['set_visibility'] = $tokens[$setVisibilityToken]['content']; + $vars[$paramCount]['set_visibility_token'] = $setVisibilityToken; + } + + $vars[$paramCount]['property_readonly'] = false; if ($readonlyToken !== null) { $vars[$paramCount]['property_readonly'] = true; $vars[$paramCount]['readonly_token'] = $readonlyToken; @@ -589,21 +607,22 @@ public static function getParameters(File $phpcsFile, $stackPtr) } // Reset the vars, as we are about to process the next parameter. - $currVar = null; - $paramStart = ($i + 1); - $defaultStart = null; - $equalToken = null; - $hasAttributes = false; - $passByReference = false; - $referenceToken = false; - $variableLength = false; - $variadicToken = false; - $typeHint = ''; - $typeHintToken = false; - $typeHintEndToken = false; - $nullableType = false; - $visibilityToken = null; - $readonlyToken = null; + $currVar = null; + $paramStart = ($i + 1); + $defaultStart = null; + $equalToken = null; + $hasAttributes = false; + $passByReference = false; + $referenceToken = false; + $variableLength = false; + $variadicToken = false; + $typeHint = ''; + $typeHintToken = false; + $typeHintEndToken = false; + $nullableType = false; + $visibilityToken = null; + $setVisibilityToken = null; + $readonlyToken = null; ++$paramCount; break; diff --git a/PHPCSUtils/Utils/Variables.php b/PHPCSUtils/Utils/Variables.php index bbb64410..817134c4 100644 --- a/PHPCSUtils/Utils/Variables.php +++ b/PHPCSUtils/Utils/Variables.php @@ -102,6 +102,9 @@ final class Variables * array( * 'scope' => string, // Public, private, or protected. * 'scope_specified' => boolean, // TRUE if the scope was explicitly specified. + * 'set_scope' => string|false, // Scope for asymmetric visibility. + * // Either public, private, or protected or + * // FALSE if no set scope is specified. * 'is_static' => boolean, // TRUE if the static keyword was found. * 'is_readonly' => boolean, // TRUE if the readonly keyword was found. * 'is_final' => boolean, // TRUE if the final keyword was found. @@ -148,6 +151,7 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr) $scope = 'public'; $scopeSpecified = false; + $setScope = false; $isStatic = false; $isReadonly = false; $isFinal = false; @@ -180,6 +184,24 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr) $scope = 'protected'; $scopeSpecified = true; break; + case \T_PUBLIC_SET: + $setScope = 'public'; + if ($scopeSpecified === false) { + $scope = 'public'; + } + break; + case \T_PROTECTED_SET: + $setScope = 'protected'; + if ($scopeSpecified === false) { + $scope = 'public'; + } + break; + case \T_PRIVATE_SET: + $setScope = 'private'; + if ($scopeSpecified === false) { + $scope = 'public'; + } + break; case \T_STATIC: $isStatic = true; break; @@ -228,6 +250,7 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr) $returnValue = [ 'scope' => $scope, 'scope_specified' => $scopeSpecified, + 'set_scope' => $setScope, 'is_static' => $isStatic, 'is_readonly' => $isReadonly, 'is_final' => $isFinal, diff --git a/Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc b/Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc index 2a6162f9..ccc90fa6 100644 --- a/Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc +++ b/Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc @@ -375,3 +375,29 @@ class WithFinalProperties { /* testPHP84FinalComplexTypedProp */ final public (Foo&\Bar)|bool $val9; } + +class AsymVisibility { + /* testPHP84AsymPublicSetProperty */ + public(set) mixed $prop1; + /* testPHP84AsymPublicPublicSetProperty */ + public public(set) (A&B)|null $prop2; + /* testPHP84AsymPublicSetPublicProperty */ + public(set) public bool $prop3; + + /* testPHP84AsymProtectedSetProperty */ + protected(set) readonly mixed $prop4; + /* testPHP84AsymPublicProtectedSetProperty */ + public protected(set) string $prop5; + /* testPHP84AsymProtectedSetPublicProperty */ + protected(set) public ?float $prop6; + + /* testPHP84AsymPrivateSetProperty */ + private(set) string|int $prop7; + /* testPHP84AsymProtectedPrivateSetProperty */ + final protected private(set) $prop8; + /* testPHP84AsymPrivateSetPublicProperty */ + private(set) public mixed $prop9; + + /* testPHP84IllegalAsymPublicProtectedSetStaticProperty */ + public protected(set) static mixed $prop10; +} diff --git a/Tests/BackCompat/BCFile/GetMemberPropertiesTest.php b/Tests/BackCompat/BCFile/GetMemberPropertiesTest.php index e3680a4d..31e9516b 100644 --- a/Tests/BackCompat/BCFile/GetMemberPropertiesTest.php +++ b/Tests/BackCompat/BCFile/GetMemberPropertiesTest.php @@ -95,6 +95,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -109,6 +110,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -123,6 +125,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -137,6 +140,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -151,6 +155,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -165,6 +170,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -179,6 +185,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -193,6 +200,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -207,6 +215,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -221,6 +230,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -235,6 +245,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -249,6 +260,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -263,6 +275,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -277,6 +290,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -291,6 +305,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -305,6 +320,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -319,6 +335,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -333,6 +350,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -347,6 +365,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -361,6 +380,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -375,6 +395,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -389,6 +410,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -403,6 +425,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -417,6 +440,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -431,6 +455,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -445,6 +470,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -459,6 +485,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -473,6 +500,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -487,6 +515,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -501,6 +530,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -515,6 +545,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -529,6 +560,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -543,6 +575,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -557,6 +590,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -571,6 +605,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -585,6 +620,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -599,6 +635,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -613,6 +650,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -627,6 +665,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -641,6 +680,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -655,6 +695,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -669,6 +710,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -683,6 +725,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -697,6 +740,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -711,6 +755,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -725,6 +770,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -739,6 +785,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -753,6 +800,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -767,6 +815,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -781,6 +830,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -796,6 +846,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -810,6 +861,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -824,6 +876,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -838,6 +891,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -852,6 +906,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -866,6 +921,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -880,6 +936,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -894,6 +951,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -908,6 +966,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -922,6 +981,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -936,6 +996,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -950,6 +1011,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -964,6 +1026,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -978,6 +1041,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => true, 'is_final' => false, @@ -992,6 +1056,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => true, 'is_final' => false, @@ -1006,6 +1071,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1020,6 +1086,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1034,6 +1101,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1048,6 +1116,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1062,6 +1131,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1076,6 +1146,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1090,6 +1161,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1105,6 +1177,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1119,6 +1192,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1133,6 +1207,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1147,6 +1222,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -1161,6 +1237,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1175,6 +1252,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -1190,6 +1268,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => false, @@ -1204,6 +1283,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -1218,6 +1298,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'private', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => false, @@ -1232,6 +1313,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => false, @@ -1246,6 +1328,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => true, @@ -1260,6 +1343,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'protected', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => true, @@ -1274,6 +1358,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => true, @@ -1288,6 +1373,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => true, 'is_readonly' => false, 'is_final' => true, @@ -1302,6 +1388,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => true, 'is_final' => true, @@ -1316,6 +1403,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => true, @@ -1330,6 +1418,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => false, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => true, @@ -1344,6 +1433,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => true, @@ -1358,6 +1448,7 @@ public static function dataGetMemberProperties() 'expected' => [ 'scope' => 'public', 'scope_specified' => true, + 'set_scope' => false, 'is_static' => false, 'is_readonly' => false, 'is_final' => true, @@ -1367,6 +1458,157 @@ public static function dataGetMemberProperties() 'nullable_type' => false, ], ], + + 'php8.4-asym-public-set' => [ + 'identifier' => '/* testPHP84AsymPublicSetProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => false, + 'set_scope' => 'public', + 'is_static' => false, + 'is_readonly' => false, + 'is_final' => false, + 'type' => 'mixed', + 'type_token' => -2, + 'type_end_token' => -2, + 'nullable_type' => false, + ], + ], + 'php8.4-asym-public-public-set-dnf-type' => [ + 'identifier' => '/* testPHP84AsymPublicPublicSetProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => true, + 'set_scope' => 'public', + 'is_static' => false, + 'is_readonly' => false, + 'is_final' => false, + 'type' => '(A&B)|null', + 'type_token' => -8, + 'type_end_token' => -2, + 'nullable_type' => false, + ], + ], + 'php8.4-asym-public-set-public' => [ + 'identifier' => '/* testPHP84AsymPublicSetPublicProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => true, + 'set_scope' => 'public', + 'is_static' => false, + 'is_readonly' => false, + 'is_final' => false, + 'type' => 'bool', + 'type_token' => -2, + 'type_end_token' => -2, + 'nullable_type' => false, + ], + ], + 'php8.4-asym-protected-set-readonly' => [ + 'identifier' => '/* testPHP84AsymProtectedSetProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => false, + 'set_scope' => 'protected', + 'is_static' => false, + 'is_readonly' => true, + 'is_final' => false, + 'type' => 'mixed', + 'type_token' => -2, + 'type_end_token' => -2, + 'nullable_type' => false, + ], + ], + 'php8.4-asym-public-protected-set' => [ + 'identifier' => '/* testPHP84AsymPublicProtectedSetProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => true, + 'set_scope' => 'protected', + 'is_static' => false, + 'is_readonly' => false, + 'is_final' => false, + 'type' => 'string', + 'type_token' => -2, + 'type_end_token' => -2, + 'nullable_type' => false, + ], + ], + 'php8.4-asym-protected-set-public-nullable-type' => [ + 'identifier' => '/* testPHP84AsymProtectedSetPublicProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => true, + 'set_scope' => 'protected', + 'is_static' => false, + 'is_readonly' => false, + 'is_final' => false, + 'type' => '?float', + 'type_token' => -2, + 'type_end_token' => -2, + 'nullable_type' => true, + ], + ], + 'php8.4-asym-private-set-union-type' => [ + 'identifier' => '/* testPHP84AsymPrivateSetProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => false, + 'set_scope' => 'private', + 'is_static' => false, + 'is_readonly' => false, + 'is_final' => false, + 'type' => 'string|int', + 'type_token' => -4, + 'type_end_token' => -2, + 'nullable_type' => false, + ], + ], + 'php8.4-asym-final-protected-private-set' => [ + 'identifier' => '/* testPHP84AsymProtectedPrivateSetProperty */', + 'expected' => [ + 'scope' => 'protected', + 'scope_specified' => true, + 'set_scope' => 'private', + 'is_static' => false, + 'is_readonly' => false, + 'is_final' => true, + 'type' => '', + 'type_token' => false, + 'type_end_token' => false, + 'nullable_type' => false, + ], + ], + 'php8.4-asym-private-set-public' => [ + 'identifier' => '/* testPHP84AsymPrivateSetPublicProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => true, + 'set_scope' => 'private', + 'is_static' => false, + 'is_readonly' => false, + 'is_final' => false, + 'type' => 'mixed', + 'type_token' => -2, + 'type_end_token' => -2, + 'nullable_type' => false, + ], + ], + 'php8.4-illegal-asym-public-protected-set-static' => [ + 'identifier' => '/* testPHP84IllegalAsymPublicProtectedSetStaticProperty */', + 'expected' => [ + 'scope' => 'public', + 'scope_specified' => true, + 'set_scope' => 'protected', + 'is_static' => true, + 'is_readonly' => false, + 'is_final' => false, + 'type' => 'mixed', + 'type_token' => -2, + 'type_end_token' => -2, + 'nullable_type' => false, + ], + ], ]; } diff --git a/Tests/BackCompat/BCFile/GetMethodParametersTest.inc b/Tests/BackCompat/BCFile/GetMethodParametersTest.inc index ac374447..2d2a5003 100644 --- a/Tests/BackCompat/BCFile/GetMethodParametersTest.inc +++ b/Tests/BackCompat/BCFile/GetMethodParametersTest.inc @@ -217,6 +217,17 @@ class ConstructorPropertyPromotionWithOnlyReadOnly { public function __construct(readonly Foo&Bar $promotedProp, readonly ?bool $promotedToo,) {} } +class ConstructorPropertyPromotionWithAsymVisibility { + /* testPHP84ConstructorPropertyPromotionWithAsymVisibility */ + public function __construct( + protected(set) string|Book $book, + public private(set) ?Publisher $publisher, + Private(set) PROTECTED Author $author, + readonly public(set) int $pubYear, + protected(set) $illegalMissingType, + ) {} +} + /* testPHP8ConstructorPropertyPromotionGlobalFunction */ // Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method. function globalFunction(private $x) {} diff --git a/Tests/BackCompat/BCFile/GetMethodParametersTest.php b/Tests/BackCompat/BCFile/GetMethodParametersTest.php index 9fb1d1de..bb72c1dd 100644 --- a/Tests/BackCompat/BCFile/GetMethodParametersTest.php +++ b/Tests/BackCompat/BCFile/GetMethodParametersTest.php @@ -2106,6 +2106,120 @@ public function testPHP81ConstructorPropertyPromotionWithOnlyReadOnly() $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); } + /** + * Verify recognition of PHP8 constructor with property promotion using PHP 8.4 asymmetric visibility. + * + * @return void + */ + public function testPHP84ConstructorPropertyPromotionWithAsymVisibility() + { + // Offsets are relative to the T_FUNCTION token. + $expected = []; + $expected[0] = [ + 'token' => 12, + 'name' => '$book', + 'content' => 'protected(set) string|Book $book', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => 'string|Book', + 'type_hint_token' => 8, + 'type_hint_end_token' => 10, + 'nullable_type' => false, + 'property_visibility' => 'public', + 'visibility_token' => false, + 'set_visibility' => 'protected(set)', + 'set_visibility_token' => 6, + 'property_readonly' => false, + 'comma_token' => 13, + ]; + $expected[1] = [ + 'token' => 23, + 'name' => '$publisher', + 'content' => 'public private(set) ?Publisher $publisher', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => '?Publisher', + 'type_hint_token' => 21, + 'type_hint_end_token' => 21, + 'nullable_type' => true, + 'property_visibility' => 'public', + 'visibility_token' => 16, + 'set_visibility' => 'private(set)', + 'set_visibility_token' => 18, + 'property_readonly' => false, + 'comma_token' => 24, + ]; + $expected[2] = [ + 'token' => 33, + 'name' => '$author', + 'content' => 'Private(set) PROTECTED Author $author', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => 'Author', + 'type_hint_token' => 31, + 'type_hint_end_token' => 31, + 'nullable_type' => false, + 'property_visibility' => 'PROTECTED', + 'visibility_token' => 29, + 'set_visibility' => 'Private(set)', + 'set_visibility_token' => 27, + 'property_readonly' => false, + 'comma_token' => 34, + ]; + $expected[3] = [ + 'token' => 43, + 'name' => '$pubYear', + 'content' => 'readonly public(set) int $pubYear', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => 'int', + 'type_hint_token' => 41, + 'type_hint_end_token' => 41, + 'nullable_type' => false, + 'property_visibility' => 'public', + 'visibility_token' => false, + 'set_visibility' => 'public(set)', + 'set_visibility_token' => 39, + 'property_readonly' => true, + 'readonly_token' => 37, + 'comma_token' => 44, + ]; + $expected[4] = [ + 'token' => 49, + 'name' => '$illegalMissingType', + 'content' => 'protected(set) $illegalMissingType', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'reference_token' => false, + 'variable_length' => false, + 'variadic_token' => false, + 'type_hint' => '', + 'type_hint_token' => false, + 'type_hint_end_token' => false, + 'nullable_type' => false, + 'property_visibility' => 'public', + 'visibility_token' => false, + 'set_visibility' => 'protected(set)', + 'set_visibility_token' => 47, + 'property_readonly' => false, + 'comma_token' => 50, + ]; + + $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected); + } + /** * Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax. * @@ -3087,6 +3201,9 @@ protected function getMethodParametersTestHelper($marker, $expected, $targetType if (isset($param['visibility_token']) && \is_int($param['visibility_token']) === true) { $expected[$key]['visibility_token'] += $target; } + if (isset($param['set_visibility_token']) && \is_int($param['set_visibility_token']) === true) { + $expected[$key]['set_visibility_token'] += $target; + } if (isset($param['readonly_token'])) { $expected[$key]['readonly_token'] += $target; } diff --git a/Tests/Utils/FunctionDeclarations/GetParametersTest.php b/Tests/Utils/FunctionDeclarations/GetParametersTest.php index 0ebe8081..48cb2507 100644 --- a/Tests/Utils/FunctionDeclarations/GetParametersTest.php +++ b/Tests/Utils/FunctionDeclarations/GetParametersTest.php @@ -162,6 +162,9 @@ private function updateExpectedTokenPositions($targetPtr, $expected) if (isset($param['visibility_token']) && \is_int($param['visibility_token']) === true) { $expected[$key]['visibility_token'] += $targetPtr; } + if (isset($param['set_visibility_token']) && \is_int($param['set_visibility_token']) === true) { + $expected[$key]['set_visibility_token'] += $targetPtr; + } if (isset($param['readonly_token'])) { $expected[$key]['readonly_token'] += $targetPtr; }