Skip to content

Commit 672543f

Browse files
authored
Merge pull request #226 from PHPCSStandards/feature/getmethodparam-constr-prop-promotion-sync-with-phpcs
BCFile::getMethodParameters(): sync with PHPCS / constructor property promotion support
2 parents 846c93b + 8a86e13 commit 672543f

File tree

7 files changed

+306
-325
lines changed

7 files changed

+306
-325
lines changed

PHPCSUtils/BackCompat/BCFile.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ public static function getDeclarationName(File $phpcsFile, $stackPtr)
219219
* 'default_equal_token' => integer, // The stack pointer to the equals sign.
220220
* ```
221221
*
222+
* Parameters declared using PHP 8 constructor property promotion, have these additional array indexes:
223+
* ```php
224+
* 'property_visibility' => string, // The property visibility as declared.
225+
* 'visibility_token' => integer, // The stack pointer to the visibility modifier token.
226+
* ```
227+
*
222228
* PHPCS cross-version compatible version of the `File::getMethodParameters()` method.
223229
*
224230
* Changelog for the PHPCS native function:
@@ -258,6 +264,7 @@ public static function getDeclarationName(File $phpcsFile, $stackPtr)
258264
* - PHPCS 3.5.3: Added support for PHP 7.4 `T_FN` arrow functions.
259265
* - PHPCS 3.5.7: Added support for namespace operators in type declarations. PHPCS#3066.
260266
* - PHPCS 3.6.0: Added support for PHP 8.0 union types. PHPCS#3032.
267+
* - PHPCS 3.6.0: Added support for PHP 8.0 constructor property promotion. PHPCS#3152.
261268
*
262269
* @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source.
263270
* @see \PHPCSUtils\Utils\FunctionDeclarations::getParameters() PHPCSUtils native improved version.
@@ -327,6 +334,7 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
327334
$typeHintToken = false;
328335
$typeHintEndToken = false;
329336
$nullableType = false;
337+
$visibilityToken = null;
330338

331339
for ($i = $paramStart; $i <= $closer; $i++) {
332340
// Check to see if this token has a parenthesis or bracket opener. If it does
@@ -445,6 +453,13 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
445453
$typeHintEndToken = $i;
446454
}
447455
break;
456+
case 'T_PUBLIC':
457+
case 'T_PROTECTED':
458+
case 'T_PRIVATE':
459+
if ($defaultStart === null) {
460+
$visibilityToken = $i;
461+
}
462+
break;
448463
case 'T_CLOSE_PARENTHESIS':
449464
case 'T_COMMA':
450465
// If it's null, then there must be no parameters for this
@@ -473,6 +488,11 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
473488
$vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken;
474489
$vars[$paramCount]['nullable_type'] = $nullableType;
475490

491+
if ($visibilityToken !== null) {
492+
$vars[$paramCount]['property_visibility'] = $tokens[$visibilityToken]['content'];
493+
$vars[$paramCount]['visibility_token'] = $visibilityToken;
494+
}
495+
476496
if ($tokens[$i]['code'] === T_COMMA) {
477497
$vars[$paramCount]['comma_token'] = $i;
478498
} else {
@@ -492,6 +512,7 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
492512
$typeHintToken = false;
493513
$typeHintEndToken = false;
494514
$nullableType = false;
515+
$visibilityToken = null;
495516

496517
$paramCount++;
497518
break;

PHPCSUtils/Utils/FunctionDeclarations.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,6 @@ public static function getProperties(File $phpcsFile, $stackPtr)
399399
* - Clearer exception message when a non-closure use token was passed to the function.
400400
* - To allow for backward compatible handling of arrow functions, this method will also accept
401401
* `T_STRING` tokens and examine them to check if these are arrow functions.
402-
* - Support for PHP 8.0 constructor property promotion.
403402
* - Support for PHP 8.0 identifier name tokens in parameter types, cross-version PHP & PHPCS.
404403
*
405404
* @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source.

Tests/BackCompat/BCFile/GetMethodParametersTest.inc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,38 @@ function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {}
179179
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
180180
function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {}
181181

182+
class ConstructorPropertyPromotionNoTypes {
183+
/* testPHP8ConstructorPropertyPromotionNoTypes */
184+
public function __construct(
185+
public $x = 0.0,
186+
protected $y = '',
187+
private $z = null,
188+
) {}
189+
}
190+
191+
class ConstructorPropertyPromotionWithTypes {
192+
/* testPHP8ConstructorPropertyPromotionWithTypes */
193+
public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {}
194+
}
195+
196+
class ConstructorPropertyPromotionAndNormalParams {
197+
/* testPHP8ConstructorPropertyPromotionAndNormalParam */
198+
public function __construct(public int $promotedProp, ?int $normalArg) {}
199+
}
200+
201+
/* testPHP8ConstructorPropertyPromotionGlobalFunction */
202+
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
203+
function globalFunction(private $x) {}
204+
205+
abstract class ConstructorPropertyPromotionAbstractMethod {
206+
/* testPHP8ConstructorPropertyPromotionAbstractMethod */
207+
// Intentional fatal error.
208+
// 1. Property promotion not allowed in abstract method, but that's not the concern of this method.
209+
// 2. Variadic arguments not allowed in property promotion, but that's not the concern of this method.
210+
// 3. The callable type is not supported for properties, but that's not the concern of this method.
211+
abstract public function __construct(public callable $y, private ...$x);
212+
}
213+
182214
/* testFunctionCallFnPHPCS353-354 */
183215
$value = $obj->fn(true);
184216

Tests/BackCompat/BCFile/GetMethodParametersTest.php

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,6 +1621,252 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
16211621
$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
16221622
}
16231623

1624+
/**
1625+
* Verify recognition of PHP8 constructor property promotion without type declaration, with defaults.
1626+
*
1627+
* @return void
1628+
*/
1629+
public function testPHP8ConstructorPropertyPromotionNoTypes()
1630+
{
1631+
$expected = [];
1632+
$expected[0] = [
1633+
'token' => 8, // Offset from the T_FUNCTION token.
1634+
'name' => '$x',
1635+
'content' => 'public $x = 0.0',
1636+
'default' => '0.0',
1637+
'default_token' => 12, // Offset from the T_FUNCTION token.
1638+
'default_equal_token' => 10, // Offset from the T_FUNCTION token.
1639+
'pass_by_reference' => false,
1640+
'reference_token' => false,
1641+
'variable_length' => false,
1642+
'variadic_token' => false,
1643+
'type_hint' => '',
1644+
'type_hint_token' => false,
1645+
'type_hint_end_token' => false,
1646+
'nullable_type' => false,
1647+
'property_visibility' => 'public',
1648+
'visibility_token' => 6, // Offset from the T_FUNCTION token.
1649+
'comma_token' => 13,
1650+
];
1651+
$expected[1] = [
1652+
'token' => 18, // Offset from the T_FUNCTION token.
1653+
'name' => '$y',
1654+
'content' => 'protected $y = \'\'',
1655+
'default' => "''",
1656+
'default_token' => 22, // Offset from the T_FUNCTION token.
1657+
'default_equal_token' => 20, // Offset from the T_FUNCTION token.
1658+
'pass_by_reference' => false,
1659+
'reference_token' => false,
1660+
'variable_length' => false,
1661+
'variadic_token' => false,
1662+
'type_hint' => '',
1663+
'type_hint_token' => false,
1664+
'type_hint_end_token' => false,
1665+
'nullable_type' => false,
1666+
'property_visibility' => 'protected',
1667+
'visibility_token' => 16, // Offset from the T_FUNCTION token.
1668+
'comma_token' => 23,
1669+
];
1670+
$expected[2] = [
1671+
'token' => 28, // Offset from the T_FUNCTION token.
1672+
'name' => '$z',
1673+
'content' => 'private $z = null',
1674+
'default' => 'null',
1675+
'default_token' => 32, // Offset from the T_FUNCTION token.
1676+
'default_equal_token' => 30, // Offset from the T_FUNCTION token.
1677+
'pass_by_reference' => false,
1678+
'reference_token' => false,
1679+
'variable_length' => false,
1680+
'variadic_token' => false,
1681+
'type_hint' => '',
1682+
'type_hint_token' => false,
1683+
'type_hint_end_token' => false,
1684+
'nullable_type' => false,
1685+
'property_visibility' => 'private',
1686+
'visibility_token' => 26, // Offset from the T_FUNCTION token.
1687+
'comma_token' => 33,
1688+
];
1689+
1690+
$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
1691+
}
1692+
1693+
/**
1694+
* Verify recognition of PHP8 constructor property promotion with type declarations.
1695+
*
1696+
* @return void
1697+
*/
1698+
public function testPHP8ConstructorPropertyPromotionWithTypes()
1699+
{
1700+
$expected = [];
1701+
$expected[0] = [
1702+
'token' => 10, // Offset from the T_FUNCTION token.
1703+
'name' => '$x',
1704+
'content' => 'protected float|int $x',
1705+
'pass_by_reference' => false,
1706+
'reference_token' => false,
1707+
'variable_length' => false,
1708+
'variadic_token' => false,
1709+
'type_hint' => 'float|int',
1710+
'type_hint_token' => 6, // Offset from the T_FUNCTION token.
1711+
'type_hint_end_token' => 8, // Offset from the T_FUNCTION token.
1712+
'nullable_type' => false,
1713+
'property_visibility' => 'protected',
1714+
'visibility_token' => 4, // Offset from the T_FUNCTION token.
1715+
'comma_token' => 11,
1716+
];
1717+
$expected[1] = [
1718+
'token' => 19, // Offset from the T_FUNCTION token.
1719+
'name' => '$y',
1720+
'content' => 'public ?string &$y = \'test\'',
1721+
'default' => "'test'",
1722+
'default_token' => 23, // Offset from the T_FUNCTION token.
1723+
'default_equal_token' => 21, // Offset from the T_FUNCTION token.
1724+
'pass_by_reference' => true,
1725+
'reference_token' => 18, // Offset from the T_FUNCTION token.
1726+
'variable_length' => false,
1727+
'variadic_token' => false,
1728+
'type_hint' => '?string',
1729+
'type_hint_token' => 16, // Offset from the T_FUNCTION token.
1730+
'type_hint_end_token' => 16, // Offset from the T_FUNCTION token.
1731+
'nullable_type' => true,
1732+
'property_visibility' => 'public',
1733+
'visibility_token' => 13, // Offset from the T_FUNCTION token.
1734+
'comma_token' => 24,
1735+
];
1736+
$expected[2] = [
1737+
'token' => 30, // Offset from the T_FUNCTION token.
1738+
'name' => '$z',
1739+
'content' => 'private mixed $z',
1740+
'pass_by_reference' => false,
1741+
'reference_token' => false,
1742+
'variable_length' => false,
1743+
'variadic_token' => false,
1744+
'type_hint' => 'mixed',
1745+
'type_hint_token' => 28, // Offset from the T_FUNCTION token.
1746+
'type_hint_end_token' => 28, // Offset from the T_FUNCTION token.
1747+
'nullable_type' => false,
1748+
'property_visibility' => 'private',
1749+
'visibility_token' => 26, // Offset from the T_FUNCTION token.
1750+
'comma_token' => false,
1751+
];
1752+
1753+
$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
1754+
}
1755+
1756+
/**
1757+
* Verify recognition of PHP8 constructor with both property promotion as well as normal parameters.
1758+
*
1759+
* @return void
1760+
*/
1761+
public function testPHP8ConstructorPropertyPromotionAndNormalParam()
1762+
{
1763+
$expected = [];
1764+
$expected[0] = [
1765+
'token' => 8, // Offset from the T_FUNCTION token.
1766+
'name' => '$promotedProp',
1767+
'content' => 'public int $promotedProp',
1768+
'pass_by_reference' => false,
1769+
'reference_token' => false,
1770+
'variable_length' => false,
1771+
'variadic_token' => false,
1772+
'type_hint' => 'int',
1773+
'type_hint_token' => 6, // Offset from the T_FUNCTION token.
1774+
'type_hint_end_token' => 6, // Offset from the T_FUNCTION token.
1775+
'nullable_type' => false,
1776+
'property_visibility' => 'public',
1777+
'visibility_token' => 4, // Offset from the T_FUNCTION token.
1778+
'comma_token' => 9,
1779+
];
1780+
$expected[1] = [
1781+
'token' => 14, // Offset from the T_FUNCTION token.
1782+
'name' => '$normalArg',
1783+
'content' => '?int $normalArg',
1784+
'pass_by_reference' => false,
1785+
'reference_token' => false,
1786+
'variable_length' => false,
1787+
'variadic_token' => false,
1788+
'type_hint' => '?int',
1789+
'type_hint_token' => 12, // Offset from the T_FUNCTION token.
1790+
'type_hint_end_token' => 12, // Offset from the T_FUNCTION token.
1791+
'nullable_type' => true,
1792+
'comma_token' => false,
1793+
];
1794+
1795+
$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
1796+
}
1797+
1798+
/**
1799+
* Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax.
1800+
*
1801+
* @return void
1802+
*/
1803+
public function testPHP8ConstructorPropertyPromotionGlobalFunction()
1804+
{
1805+
$expected = [];
1806+
$expected[0] = [
1807+
'token' => 6, // Offset from the T_FUNCTION token.
1808+
'name' => '$x',
1809+
'content' => 'private $x',
1810+
'pass_by_reference' => false,
1811+
'reference_token' => false,
1812+
'variable_length' => false,
1813+
'variadic_token' => false,
1814+
'type_hint' => '',
1815+
'type_hint_token' => false,
1816+
'type_hint_end_token' => false,
1817+
'nullable_type' => false,
1818+
'property_visibility' => 'private',
1819+
'visibility_token' => 4, // Offset from the T_FUNCTION token.
1820+
'comma_token' => false,
1821+
];
1822+
1823+
$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
1824+
}
1825+
1826+
/**
1827+
* Verify behaviour when an abstract constructor uses PHP 8 property promotion syntax.
1828+
*
1829+
* @return void
1830+
*/
1831+
public function testPHP8ConstructorPropertyPromotionAbstractMethod()
1832+
{
1833+
$expected = [];
1834+
$expected[0] = [
1835+
'token' => 8, // Offset from the T_FUNCTION token.
1836+
'name' => '$y',
1837+
'content' => 'public callable $y',
1838+
'pass_by_reference' => false,
1839+
'reference_token' => false,
1840+
'variable_length' => false,
1841+
'variadic_token' => false,
1842+
'type_hint' => 'callable',
1843+
'type_hint_token' => 6, // Offset from the T_FUNCTION token.
1844+
'type_hint_end_token' => 6, // Offset from the T_FUNCTION token.
1845+
'nullable_type' => false,
1846+
'property_visibility' => 'public',
1847+
'visibility_token' => 4, // Offset from the T_FUNCTION token.
1848+
'comma_token' => 9,
1849+
];
1850+
$expected[1] = [
1851+
'token' => 14, // Offset from the T_FUNCTION token.
1852+
'name' => '$x',
1853+
'content' => 'private ...$x',
1854+
'pass_by_reference' => false,
1855+
'reference_token' => false,
1856+
'variable_length' => true,
1857+
'variadic_token' => 13, // Offset from the T_FUNCTION token.
1858+
'type_hint' => '',
1859+
'type_hint_token' => false,
1860+
'type_hint_end_token' => false,
1861+
'nullable_type' => false,
1862+
'property_visibility' => 'private',
1863+
'visibility_token' => 11, // Offset from the T_FUNCTION token.
1864+
'comma_token' => false,
1865+
];
1866+
1867+
$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
1868+
}
1869+
16241870
/**
16251871
* Verify handling of a closure.
16261872
*
@@ -1729,6 +1975,9 @@ protected function getMethodParametersTestHelper($commentString, $expected, $tar
17291975
if (isset($param['default_equal_token'])) {
17301976
$expected[$key]['default_equal_token'] += $target;
17311977
}
1978+
if (isset($param['visibility_token'])) {
1979+
$expected[$key]['visibility_token'] += $target;
1980+
}
17321981
}
17331982

17341983
$this->assertSame($expected, $found);

0 commit comments

Comments
 (0)