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

BCFile::getMethodParameters(): sync with PHPCS / constructor property promotion support #226

Merged
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/BackCompat/BCFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ public static function getDeclarationName(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.
* ```
*
* PHPCS cross-version compatible version of the `File::getMethodParameters()` method.
*
* Changelog for the PHPCS native function:
Expand Down Expand Up @@ -258,6 +264,7 @@ public static function getDeclarationName(File $phpcsFile, $stackPtr)
* - PHPCS 3.5.3: Added support for PHP 7.4 `T_FN` arrow functions.
* - PHPCS 3.5.7: Added support for namespace operators in type declarations. PHPCS#3066.
* - PHPCS 3.6.0: Added support for PHP 8.0 union types. PHPCS#3032.
* - PHPCS 3.6.0: Added support for PHP 8.0 constructor property promotion. PHPCS#3152.
*
* @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source.
* @see \PHPCSUtils\Utils\FunctionDeclarations::getParameters() PHPCSUtils native improved version.
Expand Down Expand Up @@ -327,6 +334,7 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
$typeHintToken = false;
$typeHintEndToken = false;
$nullableType = false;
$visibilityToken = null;

for ($i = $paramStart; $i <= $closer; $i++) {
// Check to see if this token has a parenthesis or bracket opener. If it does
Expand Down Expand Up @@ -445,6 +453,13 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
$typeHintEndToken = $i;
}
break;
case 'T_PUBLIC':
case 'T_PROTECTED':
case 'T_PRIVATE':
if ($defaultStart === null) {
$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 @@ -473,6 +488,11 @@ public static function getMethodParameters(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 @@ -492,6 +512,7 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
$typeHintToken = false;
$typeHintEndToken = false;
$nullableType = false;
$visibilityToken = null;

$paramCount++;
break;
Expand Down
1 change: 0 additions & 1 deletion PHPCSUtils/Utils/FunctionDeclarations.php
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,6 @@ public static function getProperties(File $phpcsFile, $stackPtr)
* - Clearer exception message when a non-closure use token was passed to the function.
* - 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 constructor property promotion.
* - Support for PHP 8.0 identifier name tokens in parameter types, cross-version PHP & PHPCS.
*
* @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source.
Expand Down
32 changes: 32 additions & 0 deletions Tests/BackCompat/BCFile/GetMethodParametersTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,38 @@ function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {}
// 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);
}

/* testFunctionCallFnPHPCS353-354 */
$value = $obj->fn(true);

Expand Down
249 changes: 249 additions & 0 deletions Tests/BackCompat/BCFile/GetMethodParametersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,252 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
$this->getMethodParametersTestHelper('/* ' . __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->getMethodParametersTestHelper('/* ' . __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->getMethodParametersTestHelper('/* ' . __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->getMethodParametersTestHelper('/* ' . __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->getMethodParametersTestHelper('/* ' . __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->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Verify handling of a closure.
*
Expand Down Expand Up @@ -1729,6 +1975,9 @@ protected function getMethodParametersTestHelper($commentString, $expected, $tar
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
Loading