@@ -1535,59 +1535,90 @@ public static function getForLoopForIncrementVariable($stackPtr, $forLoops)
15351535 */
15361536 public static function isConstructorPromotion (File $ phpcsFile , $ stackPtr )
15371537 {
1538+ // If we are not in a function's parameters, this is not promotion.
15381539 $ functionIndex = self ::getFunctionIndexForFunctionParameter ($ phpcsFile , $ stackPtr );
15391540 if (! $ functionIndex ) {
15401541 return false ;
15411542 }
15421543
15431544 $ tokens = $ phpcsFile ->getTokens ();
15441545
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+ }
15491562 return false ;
15501563 }
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+ $ tokens = $ phpcsFile ->getTokens ();
1577+ $ token = $ tokens [$ stackPtr ];
1578+ if ($ token ['code ' ] === 'PHPCS_T_NULLABLE ' ) {
15531579 return true ;
15541580 }
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 ;
1581+ if ($ token ['code ' ] === T_NS_SEPARATOR ) {
1582+ return true ;
15621583 }
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- }
1584+ if ($ token ['code ' ] === T_STRING ) {
1585+ return true ;
15711586 }
1572- $ prev2Token = $ tokens [$ prev2Index ];
1573- if (in_array ($ prev2Token ['code ' ], Tokens::$ scopeModifiers , true )) {
1587+ if (in_array ($ token ['code ' ], Tokens::$ emptyTokens )) {
15741588 return true ;
15751589 }
1590+ return false ;
1591+ }
1592+
1593+ /**
1594+ * Return true if the token is inside a typehint
1595+ *
1596+ * @param File $phpcsFile
1597+ * @param int $stackPtr
1598+ *
1599+ * @return bool
1600+ */
1601+ public static function isTokenPartOfTypehint (File $ phpcsFile , $ stackPtr ) {
1602+ $ tokens = $ phpcsFile ->getTokens ();
1603+ $ token = $ tokens [$ stackPtr ];
15761604
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 )) {
1605+ if (! self ::isTokenPossiblyPartOfTypehint ($ phpcsFile , $ stackPtr )) {
15831606 return false ;
15841607 }
1585- $ prev3Token = $ tokens [$ prev3Index ];
1586- $ wasPreviousReadonly = $ prevToken ['content ' ] === 'readonly ' || $ prev2Token ['content ' ] === 'readonly ' ;
1587- if (in_array ($ prev3Token ['code ' ], Tokens::$ scopeModifiers , true ) && $ wasPreviousReadonly ) {
1588- return true ;
1589- }
15901608
1609+ // Examine every following token, ignoring everything that might be part of
1610+ // a typehint. If we find a variable at the end, this is part of a
1611+ // typehint.
1612+ $ i = $ stackPtr ;
1613+ while (true ) {
1614+ $ i += 1 ;
1615+ if (! isset ($ tokens [$ i ])) {
1616+ return false ;
1617+ }
1618+ if (! self ::isTokenPossiblyPartOfTypehint ($ phpcsFile , $ i )) {
1619+ return ($ tokens [$ i ]['code ' ] === T_VARIABLE );
1620+ }
1621+ }
15911622 return false ;
15921623 }
15931624
0 commit comments