@@ -1535,62 +1535,105 @@ 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+ {
1577+ $ tokens = $ phpcsFile ->getTokens ();
1578+ $ token = $ tokens [$ stackPtr ];
1579+ if ($ token ['code ' ] === 'PHPCS_T_NULLABLE ' ) {
15531580 return true ;
15541581 }
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 ;
15621584 }
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 ;
15711587 }
1572- $ prev2Token = $ tokens [$ prev2Index ];
1573- if (in_array ($ prev2Token ['code ' ], Tokens::$ scopeModifiers , true )) {
1588+ if ($ token ['code ' ] === T_TRUE ) {
15741589 return true ;
15751590 }
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 ;
15841593 }
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 )) {
15881601 return true ;
15891602 }
1590-
15911603 return false ;
15921604 }
15931605
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+
15941637 /**
15951638 * Return true if the token is inside an abstract class.
15961639 *
0 commit comments