@@ -1535,62 +1535,105 @@ public static function getForLoopForIncrementVariable($stackPtr, $forLoops)
1535
1535
*/
1536
1536
public static function isConstructorPromotion (File $ phpcsFile , $ stackPtr )
1537
1537
{
1538
+ // If we are not in a function's parameters, this is not promotion.
1538
1539
$ functionIndex = self ::getFunctionIndexForFunctionParameter ($ phpcsFile , $ stackPtr );
1539
1540
if (! $ functionIndex ) {
1540
1541
return false ;
1541
1542
}
1542
1543
1543
1544
$ tokens = $ phpcsFile ->getTokens ();
1544
1545
1545
- // If the previous token is a visibility keyword, this is constructor
1546
- // promotion. eg: `public $foobar`.
1547
- $ prevIndex = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ stackPtr - 1 ), $ functionIndex , true );
1548
- if (! is_int ($ prevIndex )) {
1546
+ // Move backwards from the token, ignoring whitespace, typehints, and the
1547
+ // 'readonly' keyword, and return true if the previous token is a
1548
+ // visibility keyword (eg: `public`).
1549
+ for ($ i = $ stackPtr - 1 ; $ i > $ functionIndex ; $ i --) {
1550
+ if (in_array ($ tokens [$ i ]['code ' ], Tokens::$ scopeModifiers , true )) {
1551
+ return true ;
1552
+ }
1553
+ if (in_array ($ tokens [$ i ]['code ' ], Tokens::$ emptyTokens , true )) {
1554
+ continue ;
1555
+ }
1556
+ if ($ tokens [$ i ]['content ' ] === 'readonly ' ) {
1557
+ continue ;
1558
+ }
1559
+ if (self ::isTokenPartOfTypehint ($ phpcsFile , $ i )) {
1560
+ continue ;
1561
+ }
1549
1562
return false ;
1550
1563
}
1551
- $ prevToken = $ tokens [$ prevIndex ];
1552
- if (in_array ($ prevToken ['code ' ], Tokens::$ scopeModifiers , true )) {
1564
+ return false ;
1565
+ }
1566
+
1567
+ /**
1568
+ * Return false if the token is definitely not part of a typehint
1569
+ *
1570
+ * @param File $phpcsFile
1571
+ * @param int $stackPtr
1572
+ *
1573
+ * @return bool
1574
+ */
1575
+ private static function isTokenPossiblyPartOfTypehint (File $ phpcsFile , $ stackPtr )
1576
+ {
1577
+ $ tokens = $ phpcsFile ->getTokens ();
1578
+ $ token = $ tokens [$ stackPtr ];
1579
+ if ($ token ['code ' ] === 'PHPCS_T_NULLABLE ' ) {
1553
1580
return true ;
1554
1581
}
1555
-
1556
- // If the previous token is not a visibility keyword, but the one before it
1557
- // is, the previous token was probably a typehint and this is constructor
1558
- // promotion. eg: `public boolean $foobar`.
1559
- $ prev2Index = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ prevIndex - 1 ), $ functionIndex , true );
1560
- if (! is_int ($ prev2Index )) {
1561
- return false ;
1582
+ if ($ token ['code ' ] === T_NS_SEPARATOR ) {
1583
+ return true ;
1562
1584
}
1563
- $ prev2Token = $ tokens [$ prev2Index ];
1564
- // If the token that might be a visibility keyword is a nullable typehint,
1565
- // ignore it and move back one token further eg: `public ?boolean $foobar`.
1566
- if ($ prev2Token ['code ' ] === 'PHPCS_T_NULLABLE ' ) {
1567
- $ prev2Index = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ prev2Index - 1 ), $ functionIndex , true );
1568
- if (! is_int ($ prev2Index )) {
1569
- return false ;
1570
- }
1585
+ if ($ token ['code ' ] === T_STRING ) {
1586
+ return true ;
1571
1587
}
1572
- $ prev2Token = $ tokens [$ prev2Index ];
1573
- if (in_array ($ prev2Token ['code ' ], Tokens::$ scopeModifiers , true )) {
1588
+ if ($ token ['code ' ] === T_TRUE ) {
1574
1589
return true ;
1575
1590
}
1576
-
1577
- // If the previous token is not a visibility keyword, but the one two
1578
- // before it is, and one of the tokens is `readonly`, the previous token
1579
- // was probably a typehint and this is constructor promotion. eg: `public
1580
- // readonly boolean $foobar`.
1581
- $ prev3Index = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ prev2Index - 1 ), $ functionIndex , true );
1582
- if (! is_int ($ prev3Index )) {
1583
- return false ;
1591
+ if ($ token ['code ' ] === T_FALSE ) {
1592
+ return true ;
1584
1593
}
1585
- $ prev3Token = $ tokens [$ prev3Index ];
1586
- $ wasPreviousReadonly = $ prevToken ['content ' ] === 'readonly ' || $ prev2Token ['content ' ] === 'readonly ' ;
1587
- if (in_array ($ prev3Token ['code ' ], Tokens::$ scopeModifiers , true ) && $ wasPreviousReadonly ) {
1594
+ if ($ token ['code ' ] === T_NULL ) {
1595
+ return true ;
1596
+ }
1597
+ if ($ token ['content ' ] === '| ' ) {
1598
+ return true ;
1599
+ }
1600
+ if (in_array ($ token ['code ' ], Tokens::$ emptyTokens )) {
1588
1601
return true ;
1589
1602
}
1590
-
1591
1603
return false ;
1592
1604
}
1593
1605
1606
+ /**
1607
+ * Return true if the token is inside a typehint
1608
+ *
1609
+ * @param File $phpcsFile
1610
+ * @param int $stackPtr
1611
+ *
1612
+ * @return bool
1613
+ */
1614
+ public static function isTokenPartOfTypehint (File $ phpcsFile , $ stackPtr )
1615
+ {
1616
+ $ tokens = $ phpcsFile ->getTokens ();
1617
+
1618
+ if (! self ::isTokenPossiblyPartOfTypehint ($ phpcsFile , $ stackPtr )) {
1619
+ return false ;
1620
+ }
1621
+
1622
+ // Examine every following token, ignoring everything that might be part of
1623
+ // a typehint. If we find a variable at the end, this is part of a
1624
+ // typehint.
1625
+ $ i = $ stackPtr ;
1626
+ while (true ) {
1627
+ $ i += 1 ;
1628
+ if (! isset ($ tokens [$ i ])) {
1629
+ return false ;
1630
+ }
1631
+ if (! self ::isTokenPossiblyPartOfTypehint ($ phpcsFile , $ i )) {
1632
+ return ($ tokens [$ i ]['code ' ] === T_VARIABLE );
1633
+ }
1634
+ }
1635
+ }
1636
+
1594
1637
/**
1595
1638
* Return true if the token is inside an abstract class.
1596
1639
*
0 commit comments