diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc index a6a75a7e87..301f6efd4a 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc @@ -160,3 +160,15 @@ class SkipOverPHP84FinalProperties { final MyType|FALSE $propA; private static final NULL|MyClass $propB; } + +// PHP 8.4 asymmetric visibility +class WithAsym { + + private(set) NULL|TRUE $asym1 = TRUE; + + public private(set) ?bool $asym2 = FALSE; + + protected(set) FALSE|string|null $asym3 = NULL; + + public protected(set) Type|NULL|bool $asym4 = TRUE; +} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed index 2cc52294cd..5dcd342dc9 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.1.inc.fixed @@ -160,3 +160,15 @@ class SkipOverPHP84FinalProperties { final MyType|FALSE $propA; private static final NULL|MyClass $propB; } + +// PHP 8.4 asymmetric visibility +class WithAsym { + + private(set) NULL|TRUE $asym1 = true; + + public private(set) ?bool $asym2 = false; + + protected(set) FALSE|string|null $asym3 = null; + + public protected(set) Type|NULL|bool $asym4 = true; +} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php index a2725864fc..e4a6b80f79 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php @@ -66,6 +66,10 @@ public function getErrorList($testFile='') 129 => 1, 149 => 1, 153 => 1, + 167 => 1, + 169 => 1, + 171 => 1, + 173 => 1, ]; case 'LowerCaseConstantUnitTest.js': diff --git a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc index 84ea24b2e8..d540b9c2ec 100644 --- a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc +++ b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc @@ -20,3 +20,15 @@ enum SomeEnum { public const BAR = 'bar'; const BAZ = 'baz'; } + +// Don't break on asymmetric visibility +class WithAsym { + + private(set) string $asym1; + + public private(set) string $asym2; + + protected(set) string $asym3; + + public protected(set) string $asym4; +} diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc index 4db25459cc..6aa9ffb799 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc @@ -96,3 +96,17 @@ class FinalProperties { public FINAL ?int $wrongOrder1; static protected final ?string $wrongOrder2; } + +class AsymmetricVisibility { + private(set) int $foo, + $bar, + $var = 5; + + public private(set) readonly ?string $spaces; + + protected(set) array $unfixed; + + protected(set) public int $wrongOrder1; + + private(set) protected ?string $wrongOrder2; +} diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed index fd5d9fa59e..efb6a0da00 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed @@ -93,3 +93,17 @@ class FinalProperties { FINAL public ?int $wrongOrder1; final protected static ?string $wrongOrder2; } + +class AsymmetricVisibility { + private(set) int $foo, + $bar, + $var = 5; + + public private(set) readonly ?string $spaces; + + protected(set) array $unfixed; + + protected(set) public int $wrongOrder1; + + private(set) protected ?string $wrongOrder2; +} diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php index 6310098525..70abcec928 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php @@ -31,36 +31,41 @@ final class PropertyDeclarationUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 7 => 1, - 9 => 2, - 10 => 1, - 11 => 1, - 17 => 1, - 18 => 1, - 23 => 1, - 38 => 1, - 41 => 1, - 42 => 1, - 50 => 2, - 51 => 1, - 55 => 1, - 56 => 1, - 61 => 1, - 62 => 1, - 68 => 1, - 69 => 1, - 71 => 1, - 72 => 1, - 76 => 1, - 80 => 1, - 82 => 1, - 84 => 1, - 86 => 1, - 90 => 1, - 94 => 1, - 95 => 1, - 96 => 1, - 97 => 2, + 7 => 1, + 9 => 2, + 10 => 1, + 11 => 1, + 17 => 1, + 18 => 1, + 23 => 1, + 38 => 1, + 41 => 1, + 42 => 1, + 50 => 2, + 51 => 1, + 55 => 1, + 56 => 1, + 61 => 1, + 62 => 1, + 68 => 1, + 69 => 1, + 71 => 1, + 72 => 1, + 76 => 1, + 80 => 1, + 82 => 1, + 84 => 1, + 86 => 1, + 90 => 1, + 94 => 1, + 95 => 1, + 96 => 1, + 97 => 2, + 101 => 2, + 105 => 1, + 107 => 1, + 109 => 1, + 111 => 1, ]; }//end getErrorList() diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 73a0c4b4a8..b67eafef97 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -403,8 +403,11 @@ class PHP extends Tokenizer T_PLUS_EQUAL => 2, T_PRINT => 5, T_PRIVATE => 7, + T_PRIVATE_SET => 12, T_PUBLIC => 6, + T_PUBLIC_SET => 11, T_PROTECTED => 9, + T_PROTECTED_SET => 14, T_READONLY => 8, T_REQUIRE => 7, T_REQUIRE_ONCE => 12, @@ -1265,6 +1268,49 @@ protected function tokenize($string) } }//end if + /* + Asymmetric visibility for PHP < 8.4 + */ + + if ($tokenIsArray === true + && in_array($token[0], [T_PUBLIC, T_PROTECTED, T_PRIVATE], true) === true + && ($stackPtr + 3) < $numTokens + && $tokens[($stackPtr + 1)] === '(' + && is_array($tokens[($stackPtr + 2)]) === true + && $tokens[($stackPtr + 2)][0] === T_STRING + && strtolower($tokens[($stackPtr + 2)][1]) === 'set' + && $tokens[($stackPtr + 3)] === ')' + ) { + $newToken = []; + if ($token[0] === T_PUBLIC) { + $oldCode = 'T_PUBLIC'; + $newToken['code'] = T_PUBLIC_SET; + $newToken['type'] = 'T_PUBLIC_SET'; + } else if ($token[0] === T_PROTECTED) { + $oldCode = 'T_PROTECTED'; + $newToken['code'] = T_PROTECTED_SET; + $newToken['type'] = 'T_PROTECTED_SET'; + } else { + $oldCode = 'T_PRIVATE'; + $newToken['code'] = T_PRIVATE_SET; + $newToken['type'] = 'T_PRIVATE_SET'; + } + + $newToken['content'] = $token[1].'('.$tokens[($stackPtr + 2)][1].')'; + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $newCode = $newToken['type']; + echo "\t\t* tokens from $stackPtr changed from $oldCode to $newCode".PHP_EOL; + } + + // We processed an extra 3 tokens, for `(`, `set`, and `)`. + $stackPtr += 3; + + continue; + }//end if + /* As of PHP 8.0 fully qualified, partially qualified and namespace relative identifier names are tokenized differently. @@ -2189,6 +2235,7 @@ protected function tokenize($string) if ($tokenType === T_FUNCTION || $tokenType === T_FN || isset(Tokens::$methodPrefixes[$tokenType]) === true + || isset(Tokens::$scopeModifiers[$tokenType]) === true || $tokenType === T_VAR || $tokenType === T_READONLY ) { diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 73cf02ddd7..ee01f00603 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -180,6 +180,19 @@ define('T_ENUM', 'PHPCS_T_ENUM'); } +// Some PHP 8.4 tokens, replicated for lower versions. +if (defined('T_PUBLIC_SET') === false) { + define('T_PUBLIC_SET', 'PHPCS_T_PUBLIC_SET'); +} + +if (defined('T_PROTECTED_SET') === false) { + define('T_PROTECTED_SET', 'PHPCS_T_PROTECTED_SET'); +} + +if (defined('T_PRIVATE_SET') === false) { + define('T_PRIVATE_SET', 'PHPCS_T_PRIVATE_SET'); +} + // Tokens used for parsing doc blocks. define('T_DOC_COMMENT_STAR', 'PHPCS_T_DOC_COMMENT_STAR'); define('T_DOC_COMMENT_WHITESPACE', 'PHPCS_T_DOC_COMMENT_WHITESPACE'); @@ -463,9 +476,12 @@ final class Tokens * @var array */ public static $scopeModifiers = [ - T_PRIVATE => T_PRIVATE, - T_PUBLIC => T_PUBLIC, - T_PROTECTED => T_PROTECTED, + T_PRIVATE => T_PRIVATE, + T_PUBLIC => T_PUBLIC, + T_PROTECTED => T_PROTECTED, + T_PUBLIC_SET => T_PUBLIC_SET, + T_PROTECTED_SET => T_PROTECTED_SET, + T_PRIVATE_SET => T_PRIVATE_SET, ]; /** @@ -678,76 +694,79 @@ final class Tokens * https://wiki.php.net/rfc/context_sensitive_lexer */ public static $contextSensitiveKeywords = [ - T_ABSTRACT => T_ABSTRACT, - T_ARRAY => T_ARRAY, - T_AS => T_AS, - T_BREAK => T_BREAK, - T_CALLABLE => T_CALLABLE, - T_CASE => T_CASE, - T_CATCH => T_CATCH, - T_CLASS => T_CLASS, - T_CLONE => T_CLONE, - T_CONST => T_CONST, - T_CONTINUE => T_CONTINUE, - T_DECLARE => T_DECLARE, - T_DEFAULT => T_DEFAULT, - T_DO => T_DO, - T_ECHO => T_ECHO, - T_ELSE => T_ELSE, - T_ELSEIF => T_ELSEIF, - T_EMPTY => T_EMPTY, - T_ENDDECLARE => T_ENDDECLARE, - T_ENDFOR => T_ENDFOR, - T_ENDFOREACH => T_ENDFOREACH, - T_ENDIF => T_ENDIF, - T_ENDSWITCH => T_ENDSWITCH, - T_ENDWHILE => T_ENDWHILE, - T_ENUM => T_ENUM, - T_EVAL => T_EVAL, - T_EXIT => T_EXIT, - T_EXTENDS => T_EXTENDS, - T_FINAL => T_FINAL, - T_FINALLY => T_FINALLY, - T_FN => T_FN, - T_FOR => T_FOR, - T_FOREACH => T_FOREACH, - T_FUNCTION => T_FUNCTION, - T_GLOBAL => T_GLOBAL, - T_GOTO => T_GOTO, - T_IF => T_IF, - T_IMPLEMENTS => T_IMPLEMENTS, - T_INCLUDE => T_INCLUDE, - T_INCLUDE_ONCE => T_INCLUDE_ONCE, - T_INSTANCEOF => T_INSTANCEOF, - T_INSTEADOF => T_INSTEADOF, - T_INTERFACE => T_INTERFACE, - T_ISSET => T_ISSET, - T_LIST => T_LIST, - T_LOGICAL_AND => T_LOGICAL_AND, - T_LOGICAL_OR => T_LOGICAL_OR, - T_LOGICAL_XOR => T_LOGICAL_XOR, - T_MATCH => T_MATCH, - T_NAMESPACE => T_NAMESPACE, - T_NEW => T_NEW, - T_PRINT => T_PRINT, - T_PRIVATE => T_PRIVATE, - T_PROTECTED => T_PROTECTED, - T_PUBLIC => T_PUBLIC, - T_READONLY => T_READONLY, - T_REQUIRE => T_REQUIRE, - T_REQUIRE_ONCE => T_REQUIRE_ONCE, - T_RETURN => T_RETURN, - T_STATIC => T_STATIC, - T_SWITCH => T_SWITCH, - T_THROW => T_THROW, - T_TRAIT => T_TRAIT, - T_TRY => T_TRY, - T_UNSET => T_UNSET, - T_USE => T_USE, - T_VAR => T_VAR, - T_WHILE => T_WHILE, - T_YIELD => T_YIELD, - T_YIELD_FROM => T_YIELD_FROM, + T_ABSTRACT => T_ABSTRACT, + T_ARRAY => T_ARRAY, + T_AS => T_AS, + T_BREAK => T_BREAK, + T_CALLABLE => T_CALLABLE, + T_CASE => T_CASE, + T_CATCH => T_CATCH, + T_CLASS => T_CLASS, + T_CLONE => T_CLONE, + T_CONST => T_CONST, + T_CONTINUE => T_CONTINUE, + T_DECLARE => T_DECLARE, + T_DEFAULT => T_DEFAULT, + T_DO => T_DO, + T_ECHO => T_ECHO, + T_ELSE => T_ELSE, + T_ELSEIF => T_ELSEIF, + T_EMPTY => T_EMPTY, + T_ENDDECLARE => T_ENDDECLARE, + T_ENDFOR => T_ENDFOR, + T_ENDFOREACH => T_ENDFOREACH, + T_ENDIF => T_ENDIF, + T_ENDSWITCH => T_ENDSWITCH, + T_ENDWHILE => T_ENDWHILE, + T_ENUM => T_ENUM, + T_EVAL => T_EVAL, + T_EXIT => T_EXIT, + T_EXTENDS => T_EXTENDS, + T_FINAL => T_FINAL, + T_FINALLY => T_FINALLY, + T_FN => T_FN, + T_FOR => T_FOR, + T_FOREACH => T_FOREACH, + T_FUNCTION => T_FUNCTION, + T_GLOBAL => T_GLOBAL, + T_GOTO => T_GOTO, + T_IF => T_IF, + T_IMPLEMENTS => T_IMPLEMENTS, + T_INCLUDE => T_INCLUDE, + T_INCLUDE_ONCE => T_INCLUDE_ONCE, + T_INSTANCEOF => T_INSTANCEOF, + T_INSTEADOF => T_INSTEADOF, + T_INTERFACE => T_INTERFACE, + T_ISSET => T_ISSET, + T_LIST => T_LIST, + T_LOGICAL_AND => T_LOGICAL_AND, + T_LOGICAL_OR => T_LOGICAL_OR, + T_LOGICAL_XOR => T_LOGICAL_XOR, + T_MATCH => T_MATCH, + T_NAMESPACE => T_NAMESPACE, + T_NEW => T_NEW, + T_PRINT => T_PRINT, + T_PRIVATE => T_PRIVATE, + T_PRIVATE_SET => T_PRIVATE_SET, + T_PROTECTED => T_PROTECTED, + T_PROTECTED_SET => T_PROTECTED_SET, + T_PUBLIC => T_PUBLIC, + T_PUBLIC_SET => T_PUBLIC_SET, + T_READONLY => T_READONLY, + T_REQUIRE => T_REQUIRE, + T_REQUIRE_ONCE => T_REQUIRE_ONCE, + T_RETURN => T_RETURN, + T_STATIC => T_STATIC, + T_SWITCH => T_SWITCH, + T_THROW => T_THROW, + T_TRAIT => T_TRAIT, + T_TRY => T_TRY, + T_UNSET => T_UNSET, + T_USE => T_USE, + T_VAR => T_VAR, + T_WHILE => T_WHILE, + T_YIELD => T_YIELD, + T_YIELD_FROM => T_YIELD_FROM, ]; diff --git a/tests/Core/Tokenizers/PHP/BackfillAsymmetricVisibilityTest.inc b/tests/Core/Tokenizers/PHP/BackfillAsymmetricVisibilityTest.inc new file mode 100644 index 0000000000..161a93e853 --- /dev/null +++ b/tests/Core/Tokenizers/PHP/BackfillAsymmetricVisibilityTest.inc @@ -0,0 +1,59 @@ + + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\PHP; + +use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; + +final class BackfillAsymmetricVisibilityTest extends AbstractTokenizerTestCase +{ + + + /** + * Test that the asymmetric visibility keywords are tokenized as such. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param string $testType The expected token type + * @param string $testContent The token content to look for + * + * @dataProvider dataAsymmetricVisibility + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testAsymmetricVisibility($testMarker, $testType, $testContent) + { + $tokens = $this->phpcsFile->getTokens(); + $target = $this->getTargetToken( + $testMarker, + [ + T_PUBLIC_SET, + T_PROTECTED_SET, + T_PRIVATE_SET, + ] + ); + $tokenArray = $tokens[$target]; + + $this->assertSame( + $testType, + $tokenArray['type'], + 'Token tokenized as '.$tokenArray['type'].' (type)' + ); + $this->assertSame( + constant($testType), + $tokenArray['code'], + 'Token tokenized as '.$tokenArray['type'].' (code)' + ); + $this->assertSame( + $testContent, + $tokenArray['content'], + 'Token tokenized as '.$tokenArray['type'].' (content)' + ); + + }//end testAsymmetricVisibility() + + + /** + * Test that things that are not asymmetric visibility keywords are not + * tokenized as such. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param string $testType The expected token type + * @param string $testContent The token content to look for + * + * @dataProvider dataNotAsymmetricVisibility + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testNotAsymmetricVisibility($testMarker, $testType, $testContent) + { + $tokens = $this->phpcsFile->getTokens(); + $target = $this->getTargetToken( + $testMarker, + [ constant($testType) ], + $testContent + ); + $tokenArray = $tokens[$target]; + + $this->assertSame( + $testType, + $tokenArray['type'], + 'Token tokenized as '.$tokenArray['type'].' (type)' + ); + $this->assertSame( + constant($testType), + $tokenArray['code'], + 'Token tokenized as '.$tokenArray['type'].' (code)' + ); + + }//end testNotAsymmetricVisibility() + + + /** + * Data provider. + * + * @see testAsymmetricVisibility() + * + * @return array> + */ + public static function dataAsymmetricVisibility() + { + return [ + // Normal property declarations. + 'property, public set, no read visibility, lowercase' => [ + 'testMarker' => '/* testPublicSetProperty */', + 'testType' => 'T_PUBLIC_SET', + 'testContent' => 'public(set)', + ], + 'property, public set, no read visibility, uppercase' => [ + 'testMarker' => '/* testPublicSetPropertyUC */', + 'testType' => 'T_PUBLIC_SET', + 'testContent' => 'PUBLIC(SET)', + ], + 'property, public set, has read visibility, lowercase' => [ + 'testMarker' => '/* testPublicPublicSetProperty */', + 'testType' => 'T_PUBLIC_SET', + 'testContent' => 'public(set)', + ], + 'property, public set, has read visibility, uppercase' => [ + 'testMarker' => '/* testPublicPublicSetPropertyUC */', + 'testType' => 'T_PUBLIC_SET', + 'testContent' => 'PUBLIC(SET)', + ], + 'property, protected set, no read visibility, lowercase' => [ + 'testMarker' => '/* testProtectedSetProperty */', + 'testType' => 'T_PROTECTED_SET', + 'testContent' => 'protected(set)', + ], + 'property, protected set, no read visibility, uppercase' => [ + 'testMarker' => '/* testProtectedSetPropertyUC */', + 'testType' => 'T_PROTECTED_SET', + 'testContent' => 'PROTECTED(SET)', + ], + 'property, protected set, has read visibility, lowercase' => [ + 'testMarker' => '/* testPublicProtectedSetProperty */', + 'testType' => 'T_PROTECTED_SET', + 'testContent' => 'protected(set)', + ], + 'property, protected set, has read visibility, uppercase' => [ + 'testMarker' => '/* testPublicProtectedSetPropertyUC */', + 'testType' => 'T_PROTECTED_SET', + 'testContent' => 'PROTECTED(SET)', + ], + 'property, private set, no read visibility, lowercase' => [ + 'testMarker' => '/* testPrivateSetProperty */', + 'testType' => 'T_PRIVATE_SET', + 'testContent' => 'private(set)', + ], + 'property, private set, no read visibility, uppercase' => [ + 'testMarker' => '/* testPrivateSetPropertyUC */', + 'testType' => 'T_PRIVATE_SET', + 'testContent' => 'PRIVATE(SET)', + ], + 'property, private set, has read visibility, lowercase' => [ + 'testMarker' => '/* testPublicPrivateSetProperty */', + 'testType' => 'T_PRIVATE_SET', + 'testContent' => 'private(set)', + ], + 'property, private set, has read visibility, uppercase' => [ + 'testMarker' => '/* testPublicPrivateSetPropertyUC */', + 'testType' => 'T_PRIVATE_SET', + 'testContent' => 'PRIVATE(SET)', + ], + + // Constructor property promotion. + 'promotion, public set, no read visibility, lowercase' => [ + 'testMarker' => '/* testPublicSetCPP */', + 'testType' => 'T_PUBLIC_SET', + 'testContent' => 'public(set)', + ], + 'promotion, public set, no read visibility, uppercase' => [ + 'testMarker' => '/* testPublicSetCPPUC */', + 'testType' => 'T_PUBLIC_SET', + 'testContent' => 'PUBLIC(SET)', + ], + 'promotion, public set, has read visibility, lowercase' => [ + 'testMarker' => '/* testPublicPublicSetCPP */', + 'testType' => 'T_PUBLIC_SET', + 'testContent' => 'public(set)', + ], + 'promotion, public set, has read visibility, uppercase' => [ + 'testMarker' => '/* testPublicPublicSetCPPUC */', + 'testType' => 'T_PUBLIC_SET', + 'testContent' => 'PUBLIC(SET)', + ], + 'promotion, protected set, no read visibility, lowercase' => [ + 'testMarker' => '/* testProtectedSetCPP */', + 'testType' => 'T_PROTECTED_SET', + 'testContent' => 'protected(set)', + ], + 'promotion, protected set, no read visibility, uppercase' => [ + 'testMarker' => '/* testProtectedSetCPPUC */', + 'testType' => 'T_PROTECTED_SET', + 'testContent' => 'PROTECTED(SET)', + ], + 'promotion, protected set, has read visibility, lowercase' => [ + 'testMarker' => '/* testPublicProtectedSetCPP */', + 'testType' => 'T_PROTECTED_SET', + 'testContent' => 'protected(set)', + ], + 'promotion, protected set, has read visibility, uppercase' => [ + 'testMarker' => '/* testPublicProtectedSetCPPUC */', + 'testType' => 'T_PROTECTED_SET', + 'testContent' => 'PROTECTED(SET)', + ], + 'promotion, private set, no read visibility, lowercase' => [ + 'testMarker' => '/* testPrivateSetCPP */', + 'testType' => 'T_PRIVATE_SET', + 'testContent' => 'private(set)', + ], + 'promotion, private set, no read visibility, uppercase' => [ + 'testMarker' => '/* testPrivateSetCPPUC */', + 'testType' => 'T_PRIVATE_SET', + 'testContent' => 'PRIVATE(SET)', + ], + 'promotion, private set, has read visibility, lowercase' => [ + 'testMarker' => '/* testPublicPrivateSetCPP */', + 'testType' => 'T_PRIVATE_SET', + 'testContent' => 'private(set)', + ], + 'promotion, private set, has read visibility, uppercase' => [ + 'testMarker' => '/* testPublicPrivateSetCPPUC */', + 'testType' => 'T_PRIVATE_SET', + 'testContent' => 'PRIVATE(SET)', + ], + ]; + + }//end dataAsymmetricVisibility() + + + /** + * Data provider. + * + * @see testNotAsymmetricVisibility() + * + * @return array> + */ + public static function dataNotAsymmetricVisibility() + { + return [ + 'property, invalid case 1' => [ + 'testMarker' => '/* testInvalidUnsetProperty */', + 'testType' => 'T_PUBLIC', + 'testContent' => 'public', + ], + 'property, invalid case 2' => [ + 'testMarker' => '/* testInvalidSpaceProperty */', + 'testType' => 'T_PUBLIC', + 'testContent' => 'public', + ], + 'property, invalid case 3' => [ + 'testMarker' => '/* testInvalidCommentProperty */', + 'testType' => 'T_PROTECTED', + 'testContent' => 'protected', + ], + 'property, invalid case 4' => [ + 'testMarker' => '/* testInvalidGetProperty */', + 'testType' => 'T_PRIVATE', + 'testContent' => 'private', + ], + 'property, invalid case 5' => [ + 'testMarker' => '/* testInvalidNoParenProperty */', + 'testType' => 'T_PRIVATE', + 'testContent' => 'private', + ], + + // Constructor property promotion. + 'promotion, invalid case 1' => [ + 'testMarker' => '/* testInvalidUnsetCPP */', + 'testType' => 'T_PUBLIC', + 'testContent' => 'public', + ], + 'promotion, invalid case 2' => [ + 'testMarker' => '/* testInvalidSpaceCPP */', + 'testType' => 'T_PUBLIC', + 'testContent' => 'public', + ], + 'promotion, invalid case 3' => [ + 'testMarker' => '/* testInvalidCommentCPP */', + 'testType' => 'T_PROTECTED', + 'testContent' => 'protected', + ], + 'promotion, invalid case 4' => [ + 'testMarker' => '/* testInvalidGetCPP */', + 'testType' => 'T_PRIVATE', + 'testContent' => 'private', + ], + 'promotion, invalid case 5' => [ + 'testMarker' => '/* testInvalidNoParenCPP */', + 'testType' => 'T_PRIVATE', + 'testContent' => 'private', + ], + + // Context sensitivitiy. + 'protected as function name' => [ + 'testMarker' => '/* testProtectedFunctionName */', + 'testType' => 'T_STRING', + 'testContent' => 'protected', + ], + 'public as function name' => [ + 'testMarker' => '/* testPublicFunctionName */', + 'testType' => 'T_STRING', + 'testContent' => 'public', + ], + 'set as parameter type' => [ + 'testMarker' => '/* testSetParameterType */', + 'testType' => 'T_STRING', + 'testContent' => 'Set', + ], + + // Live coding. + 'live coding' => [ + 'testMarker' => '/* testLiveCoding */', + 'testType' => 'T_PRIVATE', + 'testContent' => 'private', + ], + ]; + + }//end dataNotAsymmetricVisibility() + + +}//end class diff --git a/tests/Core/Tokenizers/PHP/BitwiseOrTest.inc b/tests/Core/Tokenizers/PHP/BitwiseOrTest.inc index c2c4508e43..822b4413c4 100644 --- a/tests/Core/Tokenizers/PHP/BitwiseOrTest.inc +++ b/tests/Core/Tokenizers/PHP/BitwiseOrTest.inc @@ -84,6 +84,18 @@ class TypeUnion /* testTypeUnionWithPHP84FinalKeywordAndFQN */ final \MyClass|false $finalKeywordC; + /* testTypeUnionPropertyPrivateSet */ + private(set) Foo|Bar $asym1; + + /* testTypeUnionPropertyPublicPrivateSet */ + public private(set) Foo|Bar $asym2; + + /* testTypeUnionPropertyProtected */ + protected(set) Foo|Bar $asym3; + + /* testTypeUnionPropertyPublicProtected */ + public protected(set) Foo|Bar $asym4; + public function paramTypes( /* testTypeUnionParam1 */ int|float $paramA /* testBitwiseOrParamDefaultValue */ = CONSTANT_A | CONSTANT_B, diff --git a/tests/Core/Tokenizers/PHP/BitwiseOrTest.php b/tests/Core/Tokenizers/PHP/BitwiseOrTest.php index ee1ff84f2a..2385ae1e72 100644 --- a/tests/Core/Tokenizers/PHP/BitwiseOrTest.php +++ b/tests/Core/Tokenizers/PHP/BitwiseOrTest.php @@ -128,6 +128,10 @@ public static function dataTypeUnion() 'type for final property, no visibility' => ['/* testTypeUnionWithPHP84FinalKeyword */'], 'type for final property, reversed modifier order' => ['/* testTypeUnionWithPHP84FinalKeywordFirst */'], 'type for final property, no visibility, FQN type' => ['/* testTypeUnionWithPHP84FinalKeywordAndFQN */'], + 'type for private(set) property' => ['/* testTypeUnionPropertyPrivateSet */'], + 'type for public private(set) property' => ['/* testTypeUnionPropertyPublicPrivateSet */'], + 'type for protected(set) property' => ['/* testTypeUnionPropertyProtected */'], + 'type for public protected(set) property' => ['/* testTypeUnionPropertyPublicProtected */'], 'type for method parameter' => ['/* testTypeUnionParam1 */'], 'type for method parameter, first in multi-union' => ['/* testTypeUnionParam2 */'], 'type for method parameter, last in multi-union' => ['/* testTypeUnionParam3 */'], diff --git a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc index 2825f26e52..e019dfe8a3 100644 --- a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc @@ -102,6 +102,10 @@ namespace /* testNamespaceNameIsString1 */ my\ /* testNamespaceNameIsString2 */ /* testProtectedIsKeyword */ protected $protected; /* testPublicIsKeyword */ public $public; + /* testPrivateSetIsKeyword */ private(set) mixed $privateSet; + /* testProtectedSetIsKeyword */ protected(set) mixed $protectedSet; + /* testPublicSetIsKeyword */ public(set) mixed $publicSet; + /* testVarIsKeyword */ var $var; /* testStaticIsKeyword */ static $static; diff --git a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php index a8d746ae9f..1b6f871393 100644 --- a/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.php @@ -244,6 +244,18 @@ public static function dataKeywords() 'testMarker' => '/* testPublicIsKeyword */', 'expectedTokenType' => 'T_PUBLIC', ], + 'private(set): property declaration' => [ + 'testMarker' => '/* testPrivateSetIsKeyword */', + 'expectedTokenType' => 'T_PRIVATE_SET', + ], + 'protected(set): property declaration' => [ + 'testMarker' => '/* testProtectedSetIsKeyword */', + 'expectedTokenType' => 'T_PROTECTED_SET', + ], + 'public(set): property declaration' => [ + 'testMarker' => '/* testPublicSetIsKeyword */', + 'expectedTokenType' => 'T_PUBLIC_SET', + ], 'var: property declaration' => [ 'testMarker' => '/* testVarIsKeyword */', 'expectedTokenType' => 'T_VAR', diff --git a/tests/Core/Tokenizers/PHP/DNFTypesTest.inc b/tests/Core/Tokenizers/PHP/DNFTypesTest.inc index c1a38c79e5..ae9dc94488 100644 --- a/tests/Core/Tokenizers/PHP/DNFTypesTest.inc +++ b/tests/Core/Tokenizers/PHP/DNFTypesTest.inc @@ -181,6 +181,18 @@ abstract class DNFTypes { /* testDNFTypeWithPHP84FinalKeywordAndStatic */ final static (\className&\InterfaceName)|false $finalKeywordB; + /* testDNFTypePropertyWithPrivateSet */ + private(set) (A&B&C)|true $asym1; + + /* testDNFTypePropertyWithPublicPrivateSet */ + public private(set) (A&B&C)|true $asym2; + + /* testDNFTypePropertyWithProtectedSet */ + protected(set) (A&B&C)|true $asym3; + + /* testDNFTypePropertyWithPublicProtectedSet */ + public protected(set) (A&B&C)|true $asym4; + public function paramTypes( /* testDNFTypeParam1WithAttribute */ #[MyAttribute] diff --git a/tests/Core/Tokenizers/PHP/DNFTypesTest.php b/tests/Core/Tokenizers/PHP/DNFTypesTest.php index 3c9fcb80bc..98701f135f 100644 --- a/tests/Core/Tokenizers/PHP/DNFTypesTest.php +++ b/tests/Core/Tokenizers/PHP/DNFTypesTest.php @@ -450,7 +450,18 @@ public static function dataDNFTypeParentheses() 'OO property type: with final and static keyword' => [ 'testMarker' => '/* testDNFTypeWithPHP84FinalKeywordAndStatic */', ], - + 'OO property type: asymmetric visibility, private(set)' => [ + 'testMarker' => '/* testDNFTypePropertyWithPrivateSet */', + ], + 'OO property type: asymmetric vis, public private(set)' => [ + 'testMarker' => '/* testDNFTypePropertyWithPublicPrivateSet */', + ], + 'OO property type: asymmetric visibility, protected(set)' => [ + 'testMarker' => '/* testDNFTypePropertyWithProtectedSet */', + ], + 'OO property type: asymmetric vis, public protected(set)' => [ + 'testMarker' => '/* testDNFTypePropertyWithPublicProtectedSet */', + ], 'OO method param type: first param' => [ 'testMarker' => '/* testDNFTypeParam1WithAttribute */', ], diff --git a/tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.inc b/tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.inc index 4c018af631..c771175039 100644 --- a/tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.inc +++ b/tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.inc @@ -4,6 +4,12 @@ class Nullable { /* testNullableReadonlyOnly */ readonly ?int $prop; + + /* testNullablePrivateSetOnly */ + private(set) ?int $prop2; + + /* testNullablePublicPrivateSetOnly */ + public private(set) ?int $prop3; } class InlineThen diff --git a/tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.php b/tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.php index fc7ed4b105..257378f331 100644 --- a/tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.php +++ b/tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.php @@ -51,6 +51,8 @@ public static function dataNullable() { return [ 'property declaration, readonly, no visibility' => ['/* testNullableReadonlyOnly */'], + 'property declaration, private set' => ['/* testNullablePrivateSetOnly */'], + 'property declaration, public and private set' => ['/* testNullablePublicPrivateSetOnly */'], ]; }//end dataNullable() diff --git a/tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc b/tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc index fadc0df85a..54b3c06e19 100644 --- a/tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc +++ b/tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc @@ -69,6 +69,18 @@ class TypeIntersection /* testTypeIntersectionWithPHP84FinalKeywordFirst */ final private \className&InterfaceName $finalKeywordB; + /* testTypeIntersectionPropertyWithPrivateSet */ + private(set) Foo&Bar $asym1; + + /* testTypeIntersectionPropertyWithPublicPrivateSet */ + public private(set) Foo&Bar $asym2; + + /* testTypeIntersectionPropertyWithProtectedSet */ + protected(set) Foo&Bar $asym3; + + /* testTypeIntersectionPropertyWithPublicProtectedSet */ + public protected(set) Foo&Bar $asym4; + public function paramTypes( /* testTypeIntersectionParam1 */ Foo&Bar $paramA /* testBitwiseAndParamDefaultValue */ = CONSTANT_A & CONSTANT_B, diff --git a/tests/Core/Tokenizers/PHP/TypeIntersectionTest.php b/tests/Core/Tokenizers/PHP/TypeIntersectionTest.php index b191d7ebd4..3a4eb0070c 100644 --- a/tests/Core/Tokenizers/PHP/TypeIntersectionTest.php +++ b/tests/Core/Tokenizers/PHP/TypeIntersectionTest.php @@ -126,6 +126,10 @@ public static function dataTypeIntersection() 'type for static readonly property' => ['/* testTypeIntersectionPropertyWithStaticKeyword */'], 'type for final property' => ['/* testTypeIntersectionWithPHP84FinalKeyword */'], 'type for final property reversed modifier order' => ['/* testTypeIntersectionWithPHP84FinalKeywordFirst */'], + 'type for asymmetric visibility (private(set)) property' => ['/* testTypeIntersectionPropertyWithPrivateSet */'], + 'type for asymmetric visibility (public private(set)) prop' => ['/* testTypeIntersectionPropertyWithPublicPrivateSet */'], + 'type for asymmetric visibility (protected(set)) property' => ['/* testTypeIntersectionPropertyWithProtectedSet */'], + 'type for asymmetric visibility (public protected(set)) prop' => ['/* testTypeIntersectionPropertyWithPublicProtectedSet */'], 'type for method parameter' => ['/* testTypeIntersectionParam1 */'], 'type for method parameter, first in multi-intersect' => ['/* testTypeIntersectionParam2 */'], 'type for method parameter, last in multi-intersect' => ['/* testTypeIntersectionParam3 */'],