@@ -526,8 +526,9 @@ protected function tokenize($string)
526526 $ numTokens = count ($ tokens );
527527 $ lastNotEmptyToken = 0 ;
528528
529- $ insideInlineIf = [];
530- $ insideUseGroup = false ;
529+ $ insideInlineIf = [];
530+ $ insideUseGroup = false ;
531+ $ insideConstDeclaration = false ;
531532
532533 $ commentTokenizer = new Comment ();
533534
@@ -608,7 +609,8 @@ protected function tokenize($string)
608609 if ($ tokenIsArray === true
609610 && isset (Util \Tokens::$ contextSensitiveKeywords [$ token [0 ]]) === true
610611 && (isset ($ this ->tstringContexts [$ finalTokens [$ lastNotEmptyToken ]['code ' ]]) === true
611- || $ finalTokens [$ lastNotEmptyToken ]['content ' ] === '& ' )
612+ || $ finalTokens [$ lastNotEmptyToken ]['content ' ] === '& '
613+ || $ insideConstDeclaration === true )
612614 ) {
613615 if (isset ($ this ->tstringContexts [$ finalTokens [$ lastNotEmptyToken ]['code ' ]]) === true ) {
614616 $ preserveKeyword = false ;
@@ -665,6 +667,30 @@ protected function tokenize($string)
665667 }
666668 }//end if
667669
670+ // Types in typed constants should not be touched, but the constant name should be.
671+ if ((isset ($ this ->tstringContexts [$ finalTokens [$ lastNotEmptyToken ]['code ' ]]) === true
672+ && $ finalTokens [$ lastNotEmptyToken ]['code ' ] === T_CONST )
673+ || $ insideConstDeclaration === true
674+ ) {
675+ $ preserveKeyword = true ;
676+
677+ // Find the next non-empty token.
678+ for ($ i = ($ stackPtr + 1 ); $ i < $ numTokens ; $ i ++) {
679+ if (is_array ($ tokens [$ i ]) === true
680+ && isset (Util \Tokens::$ emptyTokens [$ tokens [$ i ][0 ]]) === true
681+ ) {
682+ continue ;
683+ }
684+
685+ break ;
686+ }
687+
688+ if ($ tokens [$ i ] === '= ' || $ tokens [$ i ] === '; ' ) {
689+ $ preserveKeyword = false ;
690+ $ insideConstDeclaration = false ;
691+ }
692+ }//end if
693+
668694 if ($ finalTokens [$ lastNotEmptyToken ]['content ' ] === '& ' ) {
669695 $ preserveKeyword = true ;
670696
@@ -698,6 +724,26 @@ protected function tokenize($string)
698724 }
699725 }//end if
700726
727+ /*
728+ Mark the start of a constant declaration to allow for handling keyword to T_STRING
729+ convertion for constant names using reserved keywords.
730+ */
731+
732+ if ($ tokenIsArray === true && $ token [0 ] === T_CONST ) {
733+ $ insideConstDeclaration = true ;
734+ }
735+
736+ /*
737+ Close an open "inside constant declaration" marker when no keyword convertion was needed.
738+ */
739+
740+ if ($ insideConstDeclaration === true
741+ && $ tokenIsArray === false
742+ && ($ token [0 ] === '= ' || $ token [0 ] === '; ' )
743+ ) {
744+ $ insideConstDeclaration = false ;
745+ }
746+
701747 /*
702748 Special case for `static` used as a function name, i.e. `static()`.
703749 */
@@ -1869,6 +1915,20 @@ protected function tokenize($string)
18691915 $ newToken = [];
18701916 $ newToken ['content ' ] = '? ' ;
18711917
1918+ // For typed constants, we only need to check the token before the ? to be sure.
1919+ if ($ finalTokens [$ lastNotEmptyToken ]['code ' ] === T_CONST ) {
1920+ $ newToken ['code ' ] = T_NULLABLE ;
1921+ $ newToken ['type ' ] = 'T_NULLABLE ' ;
1922+
1923+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
1924+ echo "\t\t* token $ stackPtr changed from ? to T_NULLABLE " .PHP_EOL ;
1925+ }
1926+
1927+ $ finalTokens [$ newStackPtr ] = $ newToken ;
1928+ $ newStackPtr ++;
1929+ continue ;
1930+ }
1931+
18721932 /*
18731933 * Check if the next non-empty token is one of the tokens which can be used
18741934 * in type declarations. If not, it's definitely a ternary.
@@ -2236,7 +2296,30 @@ function return types. We want to keep the parenthesis map clean,
22362296 if ($ tokenIsArray === true && $ token [0 ] === T_STRING ) {
22372297 $ preserveTstring = false ;
22382298
2239- if (isset ($ this ->tstringContexts [$ finalTokens [$ lastNotEmptyToken ]['code ' ]]) === true ) {
2299+ // True/false/parent/self/static in typed constants should be fixed to their own token,
2300+ // but the constant name should not be.
2301+ if ((isset ($ this ->tstringContexts [$ finalTokens [$ lastNotEmptyToken ]['code ' ]]) === true
2302+ && $ finalTokens [$ lastNotEmptyToken ]['code ' ] === T_CONST )
2303+ || $ insideConstDeclaration === true
2304+ ) {
2305+ // Find the next non-empty token.
2306+ for ($ i = ($ stackPtr + 1 ); $ i < $ numTokens ; $ i ++) {
2307+ if (is_array ($ tokens [$ i ]) === true
2308+ && isset (Util \Tokens::$ emptyTokens [$ tokens [$ i ][0 ]]) === true
2309+ ) {
2310+ continue ;
2311+ }
2312+
2313+ break ;
2314+ }
2315+
2316+ if ($ tokens [$ i ] === '= ' ) {
2317+ $ preserveTstring = true ;
2318+ $ insideConstDeclaration = false ;
2319+ }
2320+ } else if (isset ($ this ->tstringContexts [$ finalTokens [$ lastNotEmptyToken ]['code ' ]]) === true
2321+ && $ finalTokens [$ lastNotEmptyToken ]['code ' ] !== T_CONST
2322+ ) {
22402323 $ preserveTstring = true ;
22412324
22422325 // Special case for syntax like: return new self/new parent
@@ -3008,6 +3091,12 @@ protected function processAdditional()
30083091 $ suspectedType = 'return ' ;
30093092 }
30103093
3094+ if ($ this ->tokens [$ x ]['code ' ] === T_EQUAL ) {
3095+ // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3096+ $ suspectedType = 'constant ' ;
3097+ break ;
3098+ }
3099+
30113100 break ;
30123101 }//end for
30133102
@@ -3049,6 +3138,11 @@ protected function processAdditional()
30493138 break ;
30503139 }
30513140
3141+ if ($ suspectedType === 'constant ' && $ this ->tokens [$ x ]['code ' ] === T_CONST ) {
3142+ $ confirmed = true ;
3143+ break ;
3144+ }
3145+
30523146 if ($ suspectedType === 'property or parameter '
30533147 && (isset (Util \Tokens::$ scopeModifiers [$ this ->tokens [$ x ]['code ' ]]) === true
30543148 || $ this ->tokens [$ x ]['code ' ] === T_VAR
0 commit comments