@@ -893,6 +893,60 @@ protected function tokenize($string)
893893 continue ;
894894 }//end if
895895
896+ /*
897+ PHP 8.0 Attributes
898+ */
899+
900+ if (PHP_VERSION_ID < 80000
901+ && $ token [0 ] === T_COMMENT
902+ && strpos ($ token [1 ], '#[ ' ) === 0
903+ ) {
904+ $ subTokens = $ this ->parsePhpAttribute ($ tokens , $ stackPtr );
905+ if ($ subTokens !== null ) {
906+ array_splice ($ tokens , $ stackPtr , 1 , $ subTokens );
907+ $ numTokens = count ($ tokens );
908+
909+ $ tokenIsArray = true ;
910+ $ token = $ tokens [$ stackPtr ];
911+ }
912+ }
913+
914+ if ($ tokenIsArray === true
915+ && $ token [0 ] === T_ATTRIBUTE
916+ ) {
917+ // Go looking for the close bracket.
918+ $ bracketStack = [$ stackPtr ];
919+ $ bracketCloser = null ;
920+ for ($ x = ($ stackPtr + 1 ); $ x < $ numTokens ; $ x ++) {
921+ if ($ tokens [$ x ] === '[ ' ) {
922+ $ bracketStack [] = $ x ;
923+ } else if ($ tokens [$ x ] === '] ' ) {
924+ array_pop ($ bracketStack );
925+ if (empty ($ bracketStack ) === true ) {
926+ $ bracketCloser = $ x ;
927+ break ;
928+ }
929+ }
930+ }
931+
932+ $ newToken = [];
933+ $ newToken ['code ' ] = T_ATTRIBUTE ;
934+ $ newToken ['type ' ] = 'T_ATTRIBUTE ' ;
935+ $ newToken ['content ' ] = '#[ ' ;
936+ $ finalTokens [$ newStackPtr ] = $ newToken ;
937+
938+ $ tokens [$ bracketCloser ] = [];
939+ $ tokens [$ bracketCloser ][0 ] = T_ATTRIBUTE_END ;
940+ $ tokens [$ bracketCloser ][1 ] = '] ' ;
941+
942+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
943+ echo "\t\t* token $ bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END " .PHP_EOL ;
944+ }
945+
946+ $ newStackPtr ++;
947+ continue ;
948+ }//end if
949+
896950 /*
897951 Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
898952 token and ensure that the colon after it is always T_COLON.
@@ -1700,6 +1754,7 @@ function return types. We want to keep the parenthesis map clean,
17001754 T_CLASS => true ,
17011755 T_EXTENDS => true ,
17021756 T_IMPLEMENTS => true ,
1757+ T_ATTRIBUTE => true ,
17031758 T_NEW => true ,
17041759 T_CONST => true ,
17051760 T_NS_SEPARATOR => true ,
@@ -2500,6 +2555,17 @@ protected function processAdditional()
25002555 $ this ->tokens [$ x ]['code ' ] = T_STRING ;
25012556 $ this ->tokens [$ x ]['type ' ] = 'T_STRING ' ;
25022557 }
2558+ } else if ($ this ->tokens [$ i ]['code ' ] === T_ATTRIBUTE ) {
2559+ for ($ x = ($ i + 1 ); $ x < $ numTokens ; $ x ++) {
2560+ if ($ this ->tokens [$ x ]['code ' ] === T_ATTRIBUTE_END ) {
2561+ break ;
2562+ }
2563+ }
2564+
2565+ $ this ->tokens [$ i ]['attribute_opener ' ] = $ i ;
2566+ $ this ->tokens [$ i ]['attribute_closer ' ] = $ x ;
2567+ $ this ->tokens [$ x ]['attribute_opener ' ] = $ i ;
2568+ $ this ->tokens [$ x ]['attribute_closer ' ] = $ x ;
25032569 }//end if
25042570
25052571 if (($ this ->tokens [$ i ]['code ' ] !== T_CASE
@@ -2837,4 +2903,58 @@ public static function resolveSimpleToken($token)
28372903 }//end resolveSimpleToken()
28382904
28392905
2906+ /**
2907+ * PHP 8 attributes parser for PHP < 8
2908+ * Handles single-line and multiline attributes.
2909+ *
2910+ * @param array $tokens The original array of tokens (as returned by token_get_all)
2911+ * @param int $stackPtr The current position in token array
2912+ *
2913+ * @return array|null The array of parsed attribute tokens
2914+ */
2915+ private function parsePhpAttribute (array &$ tokens , $ stackPtr )
2916+ {
2917+
2918+ $ token = $ tokens [$ stackPtr ];
2919+
2920+ $ commentBody = substr ($ token [1 ], 2 );
2921+ $ subTokens = @token_get_all ('<?php ' .$ commentBody );
2922+ array_splice ($ subTokens , 0 , 1 , [[T_ATTRIBUTE , '#[ ' ]]);
2923+
2924+ // Go looking for the close bracket.
2925+ $ findCloser = static function (array $ tokens , $ start =1 ) {
2926+ $ numTokens = count ($ tokens );
2927+ $ bracketStack = [0 ];
2928+ $ bracketCloser = null ;
2929+ for ($ x = $ start ; $ x < $ numTokens ; $ x ++) {
2930+ if ($ tokens [$ x ] === '[ ' ) {
2931+ $ bracketStack [] = $ x ;
2932+ } else if ($ tokens [$ x ] === '] ' ) {
2933+ array_pop ($ bracketStack );
2934+ if (empty ($ bracketStack ) === true ) {
2935+ $ bracketCloser = $ x ;
2936+ break ;
2937+ }
2938+ }
2939+ }
2940+
2941+ return $ bracketCloser ;
2942+ };
2943+
2944+ $ bracketCloser = $ findCloser ($ subTokens );
2945+ if ($ bracketCloser === null ) {
2946+ $ bracketCloser = $ findCloser ($ tokens , $ stackPtr );
2947+ if ($ bracketCloser === null ) {
2948+ return null ;
2949+ }
2950+
2951+ array_splice ($ subTokens , count ($ subTokens ), 0 , array_slice ($ tokens , ($ stackPtr + 1 ), ($ bracketCloser - $ stackPtr )));
2952+ array_splice ($ tokens , ($ stackPtr + 1 ), ($ bracketCloser - $ stackPtr ));
2953+ }
2954+
2955+ return $ subTokens ;
2956+
2957+ }//end parsePhpAttribute()
2958+
2959+
28402960}//end class
0 commit comments