@@ -903,6 +903,50 @@ protected function tokenize($string)
903903 continue ;
904904 }//end if
905905
906+ /*
907+ PHP 8.0 Attributes
908+ */
909+
910+ if (PHP_VERSION_ID < 80000
911+ && $ token [0 ] === T_COMMENT
912+ && strpos ($ token [1 ], '#[ ' ) === 0
913+ ) {
914+ $ subTokens = $ this ->parsePhpAttribute ($ tokens , $ stackPtr );
915+ if ($ subTokens !== null ) {
916+ array_splice ($ tokens , $ stackPtr , 1 , $ subTokens );
917+ $ numTokens = count ($ tokens );
918+
919+ $ tokenIsArray = true ;
920+ $ token = $ tokens [$ stackPtr ];
921+ } else {
922+ $ token [0 ] = T_ATTRIBUTE ;
923+ }
924+ }
925+
926+ if ($ tokenIsArray === true
927+ && $ token [0 ] === T_ATTRIBUTE
928+ ) {
929+ // Go looking for the close bracket.
930+ $ bracketCloser = $ this ->findCloser ($ tokens , ($ stackPtr + 1 ), ['[ ' , '#[ ' ], '] ' );
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+
906950 /*
907951 Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
908952 token and ensure that the colon after it is always T_COLON.
@@ -1857,6 +1901,7 @@ function return types. We want to keep the parenthesis map clean,
18571901 T_CLASS => true ,
18581902 T_EXTENDS => true ,
18591903 T_IMPLEMENTS => true ,
1904+ T_ATTRIBUTE => true ,
18601905 T_NEW => true ,
18611906 T_CONST => true ,
18621907 T_NS_SEPARATOR => true ,
@@ -2114,6 +2159,8 @@ protected function processAdditional()
21142159 echo "\t*** START ADDITIONAL PHP PROCESSING *** " .PHP_EOL ;
21152160 }
21162161
2162+ $ this ->createAttributesNestingMap ();
2163+
21172164 $ numTokens = count ($ this ->tokens );
21182165 for ($ i = ($ numTokens - 1 ); $ i >= 0 ; $ i --) {
21192166 // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
@@ -3089,4 +3136,128 @@ public static function resolveSimpleToken($token)
30893136 }//end resolveSimpleToken()
30903137
30913138
3139+ /**
3140+ * Finds a "closer" token (closing parenthesis or square bracket for example)
3141+ * Handle parenthesis balancing while searching for closing token
3142+ *
3143+ * @param array $tokens The list of tokens to iterate searching the closing token (as returned by token_get_all)
3144+ * @param int $start The starting position
3145+ * @param string|string[] $openerTokens The opening character
3146+ * @param string $closerChar The closing character
3147+ *
3148+ * @return int|null The position of the closing token, if found. NULL otherwise.
3149+ */
3150+ private function findCloser (array &$ tokens , $ start , $ openerTokens , $ closerChar )
3151+ {
3152+ $ numTokens = count ($ tokens );
3153+ $ stack = [0 ];
3154+ $ closer = null ;
3155+ $ openerTokens = (array ) $ openerTokens ;
3156+
3157+ for ($ x = $ start ; $ x < $ numTokens ; $ x ++) {
3158+ if (in_array ($ tokens [$ x ], $ openerTokens , true ) === true
3159+ || (is_array ($ tokens [$ x ]) === true && in_array ($ tokens [$ x ][1 ], $ openerTokens , true ) === true )
3160+ ) {
3161+ $ stack [] = $ x ;
3162+ } else if ($ tokens [$ x ] === $ closerChar ) {
3163+ array_pop ($ stack );
3164+ if (empty ($ stack ) === true ) {
3165+ $ closer = $ x ;
3166+ break ;
3167+ }
3168+ }
3169+ }
3170+
3171+ return $ closer ;
3172+
3173+ }//end findCloser()
3174+
3175+
3176+ /**
3177+ * PHP 8 attributes parser for PHP < 8
3178+ * Handles single-line and multiline attributes.
3179+ *
3180+ * @param array $tokens The original array of tokens (as returned by token_get_all)
3181+ * @param int $stackPtr The current position in token array
3182+ *
3183+ * @return array|null The array of parsed attribute tokens
3184+ */
3185+ private function parsePhpAttribute (array &$ tokens , $ stackPtr )
3186+ {
3187+
3188+ $ token = $ tokens [$ stackPtr ];
3189+
3190+ $ commentBody = substr ($ token [1 ], 2 );
3191+ $ subTokens = @token_get_all ('<?php ' .$ commentBody );
3192+
3193+ foreach ($ subTokens as $ i => $ subToken ) {
3194+ if (is_array ($ subToken ) === true
3195+ && $ subToken [0 ] === T_COMMENT
3196+ && strpos ($ subToken [1 ], '#[ ' ) === 0
3197+ ) {
3198+ $ reparsed = $ this ->parsePhpAttribute ($ subTokens , $ i );
3199+ if ($ reparsed !== null ) {
3200+ array_splice ($ subTokens , $ i , 1 , $ reparsed );
3201+ } else {
3202+ $ subToken [0 ] = T_ATTRIBUTE ;
3203+ }
3204+ }
3205+ }
3206+
3207+ array_splice ($ subTokens , 0 , 1 , [[T_ATTRIBUTE , '#[ ' ]]);
3208+
3209+ // Go looking for the close bracket.
3210+ $ bracketCloser = $ this ->findCloser ($ subTokens , 1 , '[ ' , '] ' );
3211+ if ($ bracketCloser === null ) {
3212+ $ bracketCloser = $ this ->findCloser ($ tokens , $ stackPtr , '[ ' , '] ' );
3213+ if ($ bracketCloser === null ) {
3214+ return null ;
3215+ }
3216+
3217+ $ subTokens = array_merge ($ subTokens , array_slice ($ tokens , ($ stackPtr + 1 ), ($ bracketCloser - $ stackPtr )));
3218+ array_splice ($ tokens , ($ stackPtr + 1 ), ($ bracketCloser - $ stackPtr ));
3219+ }
3220+
3221+ return $ subTokens ;
3222+
3223+ }//end parsePhpAttribute()
3224+
3225+
3226+ /**
3227+ * Creates a map for the attributes tokens that surround other tokens.
3228+ *
3229+ * @return void
3230+ */
3231+ private function createAttributesNestingMap ()
3232+ {
3233+ $ map = [];
3234+ for ($ i = 0 ; $ i < $ this ->numTokens ; $ i ++) {
3235+ if (isset ($ this ->tokens [$ i ]['attribute_opener ' ]) === true
3236+ && $ i === $ this ->tokens [$ i ]['attribute_opener ' ]
3237+ ) {
3238+ if (empty ($ map ) === false ) {
3239+ $ this ->tokens [$ i ]['nested_attributes ' ] = $ map ;
3240+ }
3241+
3242+ if (isset ($ this ->tokens [$ i ]['attribute_closer ' ]) === true ) {
3243+ $ map [$ this ->tokens [$ i ]['attribute_opener ' ]]
3244+ = $ this ->tokens [$ i ]['attribute_closer ' ];
3245+ }
3246+ } else if (isset ($ this ->tokens [$ i ]['attribute_closer ' ]) === true
3247+ && $ i === $ this ->tokens [$ i ]['attribute_closer ' ]
3248+ ) {
3249+ array_pop ($ map );
3250+ if (empty ($ map ) === false ) {
3251+ $ this ->tokens [$ i ]['nested_attributes ' ] = $ map ;
3252+ }
3253+ } else {
3254+ if (empty ($ map ) === false ) {
3255+ $ this ->tokens [$ i ]['nested_attributes ' ] = $ map ;
3256+ }
3257+ }//end if
3258+ }//end for
3259+
3260+ }//end createAttributesNestingMap()
3261+
3262+
30923263}//end class
0 commit comments