Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FunctionDeclarations::getParameters(): allow for readonly properties without visibility #456

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions PHPCSUtils/Utils/FunctionDeclarations.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 constructor property promotion with the PHP 8.1 readonly keyword without explicit visibility.
* - Support for the PHP 8.2 `true` type.
*
* @see \PHP_CodeSniffer\Files\File::getMethodProperties() Original source.
Expand Down Expand Up @@ -345,11 +346,12 @@ public static function getProperties(File $phpcsFile, $stackPtr)
*
* Parameters declared using PHP 8 constructor property promotion, have these additional array indexes:
* ```php
* 'property_visibility' => string, // The property visibility as declared.
* 'visibility_token' => int, // The stack pointer to the visibility modifier token.
* 'property_readonly' => bool, // TRUE if the readonly keyword was found.
* 'readonly_token' => int, // The stack pointer to the readonly modifier token.
* // This index will only be set if the property is readonly.
* 'property_visibility' => string, // The property visibility as declared.
* 'visibility_token' => int|false, // The stack pointer to the visibility modifier token.
* // or FALSE if the visibility is not explicitly declared.
* 'property_readonly' => bool, // TRUE if the readonly keyword was found.
* 'readonly_token' => int, // The stack pointer to the readonly modifier token.
* // This index will only be set if the property is readonly.
* ```
*
* Main differences with the PHPCS version:
Expand Down Expand Up @@ -533,15 +535,20 @@ public static function getParameters(File $phpcsFile, $stackPtr)
$vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken;
$vars[$paramCount]['nullable_type'] = $nullableType;

if ($visibilityToken !== null) {
$vars[$paramCount]['property_visibility'] = $tokens[$visibilityToken]['content'];
$vars[$paramCount]['visibility_token'] = $visibilityToken;
if ($visibilityToken !== null || $readonlyToken !== null) {
$vars[$paramCount]['property_visibility'] = 'public';
$vars[$paramCount]['visibility_token'] = false;
$vars[$paramCount]['property_readonly'] = false;
}

if ($readonlyToken !== null) {
$vars[$paramCount]['property_readonly'] = true;
$vars[$paramCount]['readonly_token'] = $readonlyToken;
if ($visibilityToken !== null) {
$vars[$paramCount]['property_visibility'] = $tokens[$visibilityToken]['content'];
$vars[$paramCount]['visibility_token'] = $visibilityToken;
}

if ($readonlyToken !== null) {
$vars[$paramCount]['property_readonly'] = true;
$vars[$paramCount]['readonly_token'] = $readonlyToken;
}
}

if ($tokens[$i]['code'] === \T_COMMA) {
Expand Down
5 changes: 5 additions & 0 deletions Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ function pseudoTypeTrue(?true $var = true) {}
/* testPHP82PseudoTypeFalseAndTrue */
// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method.
function pseudoTypeFalseAndTrue(true|false $var = true) {}

class ConstructorPropertyPromotionWithOnlyReadOnly {
/* testPHP81ConstructorPropertyPromotionWithOnlyReadOnly */
public function __construct(readonly Foo&Bar $promotedProp, readonly ?bool $promotedToo,) {}
}
53 changes: 52 additions & 1 deletion Tests/Utils/FunctionDeclarations/GetParametersDiffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,57 @@ public function testPHP82PseudoTypeFalseAndTrue()
$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Verify recognition of PHP8 constructor with property promotion using PHP 8.1 readonly
* keyword without explicit visibility.
*
* @return void
*/
public function testPHP81ConstructorPropertyPromotionWithOnlyReadOnly()
{
$expected = [];
$expected[0] = [
'token' => 10, // Offset from the T_FUNCTION token.
'name' => '$promotedProp',
'content' => 'readonly Foo&Bar $promotedProp',
'has_attributes' => false,
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => 'Foo&Bar',
'type_hint_token' => 6, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 8, // Offset from the T_FUNCTION token.
'nullable_type' => false,
'property_visibility' => 'public',
'visibility_token' => false,
'property_readonly' => true,
'readonly_token' => 4, // Offset from the T_FUNCTION token.
'comma_token' => 11,
];
$expected[1] = [
'token' => 18, // Offset from the T_FUNCTION token.
'name' => '$promotedToo',
'content' => 'readonly ?bool $promotedToo',
'has_attributes' => false,
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => '?bool',
'type_hint_token' => 16, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 16, // Offset from the T_FUNCTION token.
'nullable_type' => true,
'property_visibility' => 'public',
'visibility_token' => false,
'property_readonly' => true,
'readonly_token' => 13, // Offset from the T_FUNCTION token.
'comma_token' => 19,
];

$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Test helper.
*
Expand Down Expand Up @@ -155,7 +206,7 @@ private function updateExpectedTokenPositions($targetPtr, $expected)
if (isset($param['default_equal_token'])) {
$expected[$key]['default_equal_token'] += $targetPtr;
}
if (isset($param['visibility_token'])) {
if (isset($param['visibility_token']) && $param['visibility_token'] !== false) {
$expected[$key]['visibility_token'] += $targetPtr;
}
if (isset($param['readonly_token'])) {
Expand Down