Skip to content

Commit 487164f

Browse files
committed
File::getMethodParameters(): allow for readonly properties without visibility
Follow up on PR 3516 which was included in PHPCS 3.7.0. Turns out that constructor property promotion also allows for declaring properties with the `readonly` keyword, but without explicit visibility set. See: https://3v4l.org/nli62 Readonly properties without explicit visibility are already handled correctly in the `File::getMemberProperties()` method, but were not handled correctly in the `File::getMethodParameters()` method. Fixed now. Includes updated documentation and a unit test.
1 parent ed8e00d commit 487164f

File tree

3 files changed

+60
-11
lines changed

3 files changed

+60
-11
lines changed

src/Files/File.php

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,10 +1309,12 @@ public function getDeclarationName($stackPtr)
13091309
* 'default_equal_token' => integer, // The stack pointer to the equals sign.
13101310
*
13111311
* Parameters declared using PHP 8 constructor property promotion, have these additional array indexes:
1312-
* 'property_visibility' => string, // The property visibility as declared.
1313-
* 'visibility_token' => integer, // The stack pointer to the visibility modifier token.
1314-
* 'property_readonly' => bool, // TRUE if the readonly keyword was found.
1315-
* 'readonly_token' => integer, // The stack pointer to the readonly modifier token.
1312+
* 'property_visibility' => string, // The property visibility as declared.
1313+
* 'visibility_token' => integer|false, // The stack pointer to the visibility modifier token
1314+
* // or FALSE if the visibility is not explicitly declared.
1315+
* 'property_readonly' => boolean, // TRUE if the readonly keyword was found.
1316+
* 'readonly_token' => integer, // The stack pointer to the readonly modifier token.
1317+
* // This index will only be set if the property is readonly.
13161318
*
13171319
* @param int $stackPtr The position in the stack of the function token
13181320
* to acquire the parameters for.
@@ -1530,15 +1532,20 @@ public function getMethodParameters($stackPtr)
15301532
$vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken;
15311533
$vars[$paramCount]['nullable_type'] = $nullableType;
15321534

1533-
if ($visibilityToken !== null) {
1534-
$vars[$paramCount]['property_visibility'] = $this->tokens[$visibilityToken]['content'];
1535-
$vars[$paramCount]['visibility_token'] = $visibilityToken;
1535+
if ($visibilityToken !== null || $readonlyToken !== null) {
1536+
$vars[$paramCount]['property_visibility'] = 'public';
1537+
$vars[$paramCount]['visibility_token'] = false;
15361538
$vars[$paramCount]['property_readonly'] = false;
1537-
}
15381539

1539-
if ($readonlyToken !== null) {
1540-
$vars[$paramCount]['property_readonly'] = true;
1541-
$vars[$paramCount]['readonly_token'] = $readonlyToken;
1540+
if ($visibilityToken !== null) {
1541+
$vars[$paramCount]['property_visibility'] = $this->tokens[$visibilityToken]['content'];
1542+
$vars[$paramCount]['visibility_token'] = $visibilityToken;
1543+
}
1544+
1545+
if ($readonlyToken !== null) {
1546+
$vars[$paramCount]['property_readonly'] = true;
1547+
$vars[$paramCount]['readonly_token'] = $readonlyToken;
1548+
}
15421549
}
15431550

15441551
if ($this->tokens[$i]['code'] === T_COMMA) {

tests/Core/File/GetMethodParametersTest.inc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ class ConstructorPropertyPromotionWithReadOnly {
113113
public function __construct(public readonly ?int $promotedProp, readonly private string|bool &$promotedToo) {}
114114
}
115115

116+
class ConstructorPropertyPromotionWithOnlyReadOnly {
117+
/* testPHP81ConstructorPropertyPromotionWithOnlyReadOnly */
118+
public function __construct(readonly Foo&Bar $promotedProp, readonly ?bool $promotedToo,) {}
119+
}
120+
116121
/* testPHP8ConstructorPropertyPromotionGlobalFunction */
117122
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
118123
function globalFunction(private $x) {}

tests/Core/File/GetMethodParametersTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,43 @@ public function testPHP81ConstructorPropertyPromotionWithReadOnly()
846846
}//end testPHP81ConstructorPropertyPromotionWithReadOnly()
847847

848848

849+
/**
850+
* Verify recognition of PHP8 constructor with property promotion using PHP 8.1 readonly
851+
* keyword without explicit visibility.
852+
*
853+
* @return void
854+
*/
855+
public function testPHP81ConstructorPropertyPromotionWithOnlyReadOnly()
856+
{
857+
$expected = [];
858+
$expected[0] = [
859+
'name' => '$promotedProp',
860+
'content' => 'readonly Foo&Bar $promotedProp',
861+
'has_attributes' => false,
862+
'pass_by_reference' => false,
863+
'variable_length' => false,
864+
'type_hint' => 'Foo&Bar',
865+
'nullable_type' => false,
866+
'property_visibility' => 'public',
867+
'property_readonly' => true,
868+
];
869+
$expected[1] = [
870+
'name' => '$promotedToo',
871+
'content' => 'readonly ?bool $promotedToo',
872+
'has_attributes' => false,
873+
'pass_by_reference' => false,
874+
'variable_length' => false,
875+
'type_hint' => '?bool',
876+
'nullable_type' => true,
877+
'property_visibility' => 'public',
878+
'property_readonly' => true,
879+
];
880+
881+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
882+
883+
}//end testPHP81ConstructorPropertyPromotionWithOnlyReadOnly()
884+
885+
849886
/**
850887
* Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax.
851888
*

0 commit comments

Comments
 (0)