Skip to content

Commit

Permalink
Merge pull request #169 from PHPCSStandards/functiondeclarations/getp…
Browse files Browse the repository at this point in the history
…arameters-php8-constructor-property-promotion

PHP 8.0 | FunctionDeclarations::getParameters(): add support for constructor property promotion
  • Loading branch information
jrfnl committed Jul 17, 2020
2 parents 4d02dd8 + a6297d9 commit ea5c5ac
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 0 deletions.
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

0 comments on commit ea5c5ac

Please sign in to comment.