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

PHP 8.0 | FunctionDeclarations::getParameters(): add support for constructor property promotion #169

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
21 changes: 21 additions & 0 deletions PHPCSUtils/Utils/FunctionDeclarations.php
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,12 @@ public static function getProperties(File $phpcsFile, $stackPtr)
* 'default_equal_token' => integer, // The stack pointer to the equals sign.
* ```
*
* Parameters declared using PHP 8 constructor property promotion, have these additional array indexes:
* ```php
* 'property_visibility' => string, // The property visibility as declared.
* 'visibility_token' => integer, // The stack pointer to the 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.
Expand All @@ -384,13 +390,15 @@ public static function getProperties(File $phpcsFile, $stackPtr)
* - To allow for backward compatible handling of arrow functions, this method will also accept
* `T_STRING` tokens and examine them to check if these are arrow functions.
* - Support for PHP 8.0 union types.
* - Support for PHP 8.0 constructor property promotion.
*
* @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source.
* @see \PHPCSUtils\BackCompat\BCFile::getMethodParameters() Cross-version compatible version of the original.
*
* @since 1.0.0
* @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
* @since 1.0.0-alpha4 Added support for PHP 8.0 union types.
* @since 1.0.0-alpha4 Added support for PHP 8.0 constructor property promotion.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position in the stack of the function token
Expand Down Expand Up @@ -459,6 +467,7 @@ public static function getParameters(File $phpcsFile, $stackPtr)
$typeHintToken = false;
$typeHintEndToken = false;
$nullableType = false;
$visibilityToken = null;

for ($i = $paramStart; $i <= $closer; $i++) {
// Changed from checking 'code' to 'type' to allow for T_NULLABLE not existing in PHPCS < 2.8.0.
Expand Down Expand Up @@ -502,6 +511,12 @@ public static function getParameters(File $phpcsFile, $stackPtr)
$typeHintEndToken = $i;
break;

case 'T_PUBLIC':
case 'T_PROTECTED':
case 'T_PRIVATE':
$visibilityToken = $i;
break;

case 'T_CLOSE_PARENTHESIS':
case 'T_COMMA':
// If it's null, then there must be no parameters for this
Expand Down Expand Up @@ -534,6 +549,11 @@ 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 ($tokens[$i]['code'] === \T_COMMA) {
$vars[$paramCount]['comma_token'] = $i;
} else {
Expand All @@ -553,6 +573,7 @@ public static function getParameters(File $phpcsFile, $stackPtr)
$typeHintToken = false;
$typeHintEndToken = false;
$nullableType = false;
$visibilityToken = null;

$paramCount++;
break;
Expand Down
32 changes: 32 additions & 0 deletions Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,35 @@ function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {}
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {}

class ConstructorPropertyPromotionNoTypes {
/* testPHP8ConstructorPropertyPromotionNoTypes */
public function __construct(
public $x = 0.0,
protected $y = '',
private $z = null,
) {}
}

class ConstructorPropertyPromotionWithTypes {
/* testPHP8ConstructorPropertyPromotionWithTypes */
public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {}
}

class ConstructorPropertyPromotionAndNormalParams {
/* testPHP8ConstructorPropertyPromotionAndNormalParam */
public function __construct(public int $promotedProp, ?int $normalArg) {}
}

/* testPHP8ConstructorPropertyPromotionGlobalFunction */
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
function globalFunction(private $x) {}

abstract class ConstructorPropertyPromotionAbstractMethod {
/* testPHP8ConstructorPropertyPromotionAbstractMethod */
// Intentional fatal error.
// 1. Property promotion not allowed in abstract method, but that's not the concern of this method.
// 2. Variadic arguments not allowed in property promotion, but that's not the concern of this method.
// 3. The callable type is not supported for properties, but that's not the concern of this method.
abstract public function __construct(public callable $y, private ...$x);
}
249 changes: 249 additions & 0 deletions Tests/Utils/FunctionDeclarations/GetParametersDiffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,252 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
$this->getParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Verify recognition of PHP8 constructor property promotion without type declaration, with defaults.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionNoTypes()
{
$expected = [];
$expected[0] = [
'token' => 8, // Offset from the T_FUNCTION token.
'name' => '$x',
'content' => 'public $x = 0.0',
'default' => '0.0',
'default_token' => 12, // Offset from the T_FUNCTION token.
'default_equal_token' => 10, // Offset from the T_FUNCTION token.
'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' => 6, // Offset from the T_FUNCTION token.
'comma_token' => 13,
];
$expected[1] = [
'token' => 18, // Offset from the T_FUNCTION token.
'name' => '$y',
'content' => 'protected $y = \'\'',
'default' => "''",
'default_token' => 22, // Offset from the T_FUNCTION token.
'default_equal_token' => 20, // Offset from the T_FUNCTION token.
'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' => 'protected',
'visibility_token' => 16, // Offset from the T_FUNCTION token.
'comma_token' => 23,
];
$expected[2] = [
'token' => 28, // Offset from the T_FUNCTION token.
'name' => '$z',
'content' => 'private $z = null',
'default' => 'null',
'default_token' => 32, // Offset from the T_FUNCTION token.
'default_equal_token' => 30, // Offset from the T_FUNCTION token.
'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' => 'private',
'visibility_token' => 26, // Offset from the T_FUNCTION token.
'comma_token' => 33,
];

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

/**
* Verify recognition of PHP8 constructor property promotion with type declarations.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionWithTypes()
{
$expected = [];
$expected[0] = [
'token' => 10, // Offset from the T_FUNCTION token.
'name' => '$x',
'content' => 'protected float|int $x',
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => 'float|int',
'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' => 'protected',
'visibility_token' => 4, // Offset from the T_FUNCTION token.
'comma_token' => 11,
];
$expected[1] = [
'token' => 19, // Offset from the T_FUNCTION token.
'name' => '$y',
'content' => 'public ?string &$y = \'test\'',
'default' => "'test'",
'default_token' => 23, // Offset from the T_FUNCTION token.
'default_equal_token' => 21, // Offset from the T_FUNCTION token.
'pass_by_reference' => true,
'reference_token' => 18, // Offset from the T_FUNCTION token.
'variable_length' => false,
'variadic_token' => false,
'type_hint' => '?string',
'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' => 13, // Offset from the T_FUNCTION token.
'comma_token' => 24,
];
$expected[2] = [
'token' => 30, // Offset from the T_FUNCTION token.
'name' => '$z',
'content' => 'private mixed $z',
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => 'mixed',
'type_hint_token' => 28, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 28, // Offset from the T_FUNCTION token.
'nullable_type' => false,
'property_visibility' => 'private',
'visibility_token' => 26, // Offset from the T_FUNCTION token.
'comma_token' => false,
];

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

/**
* Verify recognition of PHP8 constructor with both property promotion as well as normal parameters.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionAndNormalParam()
{
$expected = [];
$expected[0] = [
'token' => 8, // Offset from the T_FUNCTION token.
'name' => '$promotedProp',
'content' => 'public int $promotedProp',
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => 'int',
'type_hint_token' => 6, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 6, // Offset from the T_FUNCTION token.
'nullable_type' => false,
'property_visibility' => 'public',
'visibility_token' => 4, // Offset from the T_FUNCTION token.
'comma_token' => 9,
];
$expected[1] = [
'token' => 14, // Offset from the T_FUNCTION token.
'name' => '$normalArg',
'content' => '?int $normalArg',
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => '?int',
'type_hint_token' => 12, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 12, // Offset from the T_FUNCTION token.
'nullable_type' => true,
'comma_token' => false,
];

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

/**
* Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionGlobalFunction()
{
$expected = [];
$expected[0] = [
'token' => 6, // Offset from the T_FUNCTION token.
'name' => '$x',
'content' => 'private $x',
'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' => 'private',
'visibility_token' => 4, // Offset from the T_FUNCTION token.
'comma_token' => false,
];

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

/**
* Verify behaviour when an abstract constructor uses PHP 8 property promotion syntax.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionAbstractMethod()
{
$expected = [];
$expected[0] = [
'token' => 8, // Offset from the T_FUNCTION token.
'name' => '$y',
'content' => 'public callable $y',
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => 'callable',
'type_hint_token' => 6, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 6, // Offset from the T_FUNCTION token.
'nullable_type' => false,
'property_visibility' => 'public',
'visibility_token' => 4, // Offset from the T_FUNCTION token.
'comma_token' => 9,
];
$expected[1] = [
'token' => 14, // Offset from the T_FUNCTION token.
'name' => '$x',
'content' => 'private ...$x',
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => true,
'variadic_token' => 13, // Offset from the T_FUNCTION token.
'type_hint' => '',
'type_hint_token' => false,
'type_hint_end_token' => false,
'nullable_type' => false,
'property_visibility' => 'private',
'visibility_token' => 11, // Offset from the T_FUNCTION token.
'comma_token' => false,
];

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

/**
* Test helper.
*
Expand Down Expand Up @@ -422,6 +668,9 @@ protected function getParametersTestHelper($commentString, $expected, $targetTyp
if (isset($param['default_equal_token'])) {
$expected[$key]['default_equal_token'] += $target;
}
if (isset($param['visibility_token'])) {
$expected[$key]['visibility_token'] += $target;
}
}

$this->assertSame($expected, $found);
Expand Down