Mercurial > hg > isophonics-drupal-site
diff vendor/squizlabs/php_codesniffer/src/Tokenizers/PHP.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | |
children | af1871eacc83 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/squizlabs/php_codesniffer/src/Tokenizers/PHP.php Thu Feb 28 13:21:36 2019 +0000 @@ -0,0 +1,2014 @@ +<?php +/** + * Tokenizes PHP code. + * + * @author Greg Sherwood <gsherwood@squiz.net> + * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tokenizers; + +use PHP_CodeSniffer\Util; + +class PHP extends Tokenizer +{ + + + /** + * A list of tokens that are allowed to open a scope. + * + * This array also contains information about what kind of token the scope + * opener uses to open and close the scope, if the token strictly requires + * an opener, if the token can share a scope closer, and who it can be shared + * with. An example of a token that shares a scope closer is a CASE scope. + * + * @var array + */ + public $scopeOpeners = [ + T_IF => [ + 'start' => [ + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_COLON => T_COLON, + ], + 'end' => [ + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_ENDIF => T_ENDIF, + T_ELSE => T_ELSE, + T_ELSEIF => T_ELSEIF, + ], + 'strict' => false, + 'shared' => false, + 'with' => [ + T_ELSE => T_ELSE, + T_ELSEIF => T_ELSEIF, + ], + ], + T_TRY => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_CATCH => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_FINALLY => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_ELSE => [ + 'start' => [ + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_COLON => T_COLON, + ], + 'end' => [ + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_ENDIF => T_ENDIF, + ], + 'strict' => false, + 'shared' => false, + 'with' => [ + T_IF => T_IF, + T_ELSEIF => T_ELSEIF, + ], + ], + T_ELSEIF => [ + 'start' => [ + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_COLON => T_COLON, + ], + 'end' => [ + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_ENDIF => T_ENDIF, + T_ELSE => T_ELSE, + T_ELSEIF => T_ELSEIF, + ], + 'strict' => false, + 'shared' => false, + 'with' => [ + T_IF => T_IF, + T_ELSE => T_ELSE, + ], + ], + T_FOR => [ + 'start' => [ + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_COLON => T_COLON, + ], + 'end' => [ + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_ENDFOR => T_ENDFOR, + ], + 'strict' => false, + 'shared' => false, + 'with' => [], + ], + T_FOREACH => [ + 'start' => [ + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_COLON => T_COLON, + ], + 'end' => [ + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_ENDFOREACH => T_ENDFOREACH, + ], + 'strict' => false, + 'shared' => false, + 'with' => [], + ], + T_INTERFACE => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_FUNCTION => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_CLASS => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_TRAIT => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_USE => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => false, + 'shared' => false, + 'with' => [], + ], + T_DECLARE => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => false, + 'shared' => false, + 'with' => [], + ], + T_NAMESPACE => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => false, + 'shared' => false, + 'with' => [], + ], + T_WHILE => [ + 'start' => [ + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_COLON => T_COLON, + ], + 'end' => [ + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_ENDWHILE => T_ENDWHILE, + ], + 'strict' => false, + 'shared' => false, + 'with' => [], + ], + T_DO => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_SWITCH => [ + 'start' => [ + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_COLON => T_COLON, + ], + 'end' => [ + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_ENDSWITCH => T_ENDSWITCH, + ], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_CASE => [ + 'start' => [ + T_COLON => T_COLON, + T_SEMICOLON => T_SEMICOLON, + ], + 'end' => [ + T_BREAK => T_BREAK, + T_RETURN => T_RETURN, + T_CONTINUE => T_CONTINUE, + T_THROW => T_THROW, + T_EXIT => T_EXIT, + ], + 'strict' => true, + 'shared' => true, + 'with' => [ + T_DEFAULT => T_DEFAULT, + T_CASE => T_CASE, + T_SWITCH => T_SWITCH, + ], + ], + T_DEFAULT => [ + 'start' => [ + T_COLON => T_COLON, + T_SEMICOLON => T_SEMICOLON, + ], + 'end' => [ + T_BREAK => T_BREAK, + T_RETURN => T_RETURN, + T_CONTINUE => T_CONTINUE, + T_THROW => T_THROW, + T_EXIT => T_EXIT, + ], + 'strict' => true, + 'shared' => true, + 'with' => [ + T_CASE => T_CASE, + T_SWITCH => T_SWITCH, + ], + ], + T_START_HEREDOC => [ + 'start' => [T_START_HEREDOC => T_START_HEREDOC], + 'end' => [T_END_HEREDOC => T_END_HEREDOC], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + T_START_NOWDOC => [ + 'start' => [T_START_NOWDOC => T_START_NOWDOC], + 'end' => [T_END_NOWDOC => T_END_NOWDOC], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], + ]; + + /** + * A list of tokens that end the scope. + * + * This array is just a unique collection of the end tokens + * from the _scopeOpeners array. The data is duplicated here to + * save time during parsing of the file. + * + * @var array + */ + public $endScopeTokens = [ + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_ENDIF => T_ENDIF, + T_ENDFOR => T_ENDFOR, + T_ENDFOREACH => T_ENDFOREACH, + T_ENDWHILE => T_ENDWHILE, + T_ENDSWITCH => T_ENDSWITCH, + T_BREAK => T_BREAK, + T_END_HEREDOC => T_END_HEREDOC, + ]; + + /** + * Known lengths of tokens. + * + * @var array<int, int> + */ + public $knownLengths = [ + T_ABSTRACT => 8, + T_AND_EQUAL => 2, + T_ARRAY => 5, + T_AS => 2, + T_BOOLEAN_AND => 2, + T_BOOLEAN_OR => 2, + T_BREAK => 5, + T_CALLABLE => 8, + T_CASE => 4, + T_CATCH => 5, + T_CLASS => 5, + T_CLASS_C => 9, + T_CLONE => 5, + T_CONCAT_EQUAL => 2, + T_CONST => 5, + T_CONTINUE => 8, + T_CURLY_OPEN => 2, + T_DEC => 2, + T_DECLARE => 7, + T_DEFAULT => 7, + T_DIR => 7, + T_DIV_EQUAL => 2, + T_DO => 2, + T_DOLLAR_OPEN_CURLY_BRACES => 2, + T_DOUBLE_ARROW => 2, + T_DOUBLE_COLON => 2, + T_ECHO => 4, + T_ELSE => 4, + T_ELSEIF => 6, + T_EMPTY => 5, + T_ENDDECLARE => 10, + T_ENDFOR => 6, + T_ENDFOREACH => 10, + T_ENDIF => 5, + T_ENDSWITCH => 9, + T_ENDWHILE => 8, + T_EVAL => 4, + T_EXTENDS => 7, + T_FILE => 8, + T_FINAL => 5, + T_FINALLY => 7, + T_FOR => 3, + T_FOREACH => 7, + T_FUNCTION => 8, + T_FUNC_C => 12, + T_GLOBAL => 6, + T_GOTO => 4, + T_HALT_COMPILER => 15, + T_IF => 2, + T_IMPLEMENTS => 10, + T_INC => 2, + T_INCLUDE => 7, + T_INCLUDE_ONCE => 12, + T_INSTANCEOF => 10, + T_INSTEADOF => 9, + T_INTERFACE => 9, + T_ISSET => 5, + T_IS_EQUAL => 2, + T_IS_GREATER_OR_EQUAL => 2, + T_IS_IDENTICAL => 3, + T_IS_NOT_EQUAL => 2, + T_IS_NOT_IDENTICAL => 3, + T_IS_SMALLER_OR_EQUAL => 2, + T_LINE => 8, + T_LIST => 4, + T_LOGICAL_AND => 3, + T_LOGICAL_OR => 2, + T_LOGICAL_XOR => 3, + T_METHOD_C => 10, + T_MINUS_EQUAL => 2, + T_POW_EQUAL => 3, + T_MOD_EQUAL => 2, + T_MUL_EQUAL => 2, + T_NAMESPACE => 9, + T_NS_C => 13, + T_NS_SEPARATOR => 1, + T_NEW => 3, + T_OBJECT_OPERATOR => 2, + T_OPEN_TAG_WITH_ECHO => 3, + T_OR_EQUAL => 2, + T_PLUS_EQUAL => 2, + T_PRINT => 5, + T_PRIVATE => 7, + T_PUBLIC => 6, + T_PROTECTED => 9, + T_REQUIRE => 7, + T_REQUIRE_ONCE => 12, + T_RETURN => 6, + T_STATIC => 6, + T_SWITCH => 6, + T_THROW => 5, + T_TRAIT => 5, + T_TRAIT_C => 9, + T_TRY => 3, + T_UNSET => 5, + T_USE => 3, + T_VAR => 3, + T_WHILE => 5, + T_XOR_EQUAL => 2, + T_YIELD => 5, + T_OPEN_CURLY_BRACKET => 1, + T_CLOSE_CURLY_BRACKET => 1, + T_OPEN_SQUARE_BRACKET => 1, + T_CLOSE_SQUARE_BRACKET => 1, + T_OPEN_PARENTHESIS => 1, + T_CLOSE_PARENTHESIS => 1, + T_COLON => 1, + T_STRING_CONCAT => 1, + T_INLINE_THEN => 1, + T_INLINE_ELSE => 1, + T_NULLABLE => 1, + T_NULL => 4, + T_FALSE => 5, + T_TRUE => 4, + T_SEMICOLON => 1, + T_EQUAL => 1, + T_MULTIPLY => 1, + T_DIVIDE => 1, + T_PLUS => 1, + T_MINUS => 1, + T_MODULUS => 1, + T_POW => 2, + T_SPACESHIP => 3, + T_COALESCE => 2, + T_COALESCE_EQUAL => 3, + T_BITWISE_AND => 1, + T_BITWISE_OR => 1, + T_BITWISE_XOR => 1, + T_SL => 2, + T_SR => 2, + T_SL_EQUAL => 3, + T_SR_EQUAL => 3, + T_GREATER_THAN => 1, + T_LESS_THAN => 1, + T_BOOLEAN_NOT => 1, + T_SELF => 4, + T_PARENT => 6, + T_COMMA => 1, + T_THIS => 4, + T_CLOSURE => 8, + T_BACKTICK => 1, + T_OPEN_SHORT_ARRAY => 1, + T_CLOSE_SHORT_ARRAY => 1, + ]; + + + /** + * A cache of different token types, resolved into arrays. + * + * @var array + * @see standardiseToken() + */ + private static $resolveTokenCache = []; + + + /** + * Creates an array of tokens when given some PHP code. + * + * Starts by using token_get_all() but does a lot of extra processing + * to insert information about the context of the token. + * + * @param string $string The string to tokenize. + * + * @return array + */ + protected function tokenize($string) + { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** START PHP TOKENIZING ***".PHP_EOL; + $isWin = false; + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $isWin = true; + } + } + + $tokens = @token_get_all($string); + $finalTokens = []; + + $newStackPtr = 0; + $numTokens = count($tokens); + $lastNotEmptyToken = 0; + + $insideInlineIf = []; + $insideUseGroup = false; + + $commentTokenizer = new Comment(); + + for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) { + // Special case for tokens we have needed to blank out. + if ($tokens[$stackPtr] === null) { + continue; + } + + $token = (array) $tokens[$stackPtr]; + $tokenIsArray = isset($token[1]); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + if ($tokenIsArray === true) { + $type = Util\Tokens::tokenName($token[0]); + $content = Util\Common::prepareForOutput($token[1]); + } else { + $newToken = self::resolveSimpleToken($token[0]); + $type = $newToken['type']; + $content = Util\Common::prepareForOutput($token[0]); + } + + echo "\tProcess token "; + if ($tokenIsArray === true) { + echo "[$stackPtr]"; + } else { + echo " $stackPtr "; + } + + echo ": $type => $content"; + }//end if + + if ($newStackPtr > 0 && $finalTokens[($newStackPtr - 1)]['code'] !== T_WHITESPACE) { + $lastNotEmptyToken = ($newStackPtr - 1); + } + + /* + If we are using \r\n newline characters, the \r and \n are sometimes + split over two tokens. This normally occurs after comments. We need + to merge these two characters together so that our line endings are + consistent for all lines. + */ + + if ($tokenIsArray === true && substr($token[1], -1) === "\r") { + if (isset($tokens[($stackPtr + 1)]) === true + && is_array($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)][1][0] === "\n" + ) { + $token[1] .= "\n"; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + if ($isWin === true) { + echo '\n'; + } else { + echo "\033[30;1m\\n\033[0m"; + } + } + + if ($tokens[($stackPtr + 1)][1] === "\n") { + // This token's content has been merged into the previous, + // so we can skip it. + $tokens[($stackPtr + 1)] = ''; + } else { + $tokens[($stackPtr + 1)][1] = substr($tokens[($stackPtr + 1)][1], 1); + } + } + }//end if + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo PHP_EOL; + } + + /* + Parse doc blocks into something that can be easily iterated over. + */ + + if ($tokenIsArray === true + && ($token[0] === T_DOC_COMMENT + || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0)) + ) { + $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr); + foreach ($commentTokens as $commentToken) { + $finalTokens[$newStackPtr] = $commentToken; + $newStackPtr++; + } + + continue; + } + + /* + If this is a double quoted string, PHP will tokenize the whole + thing which causes problems with the scope map when braces are + within the string. So we need to merge the tokens together to + provide a single string. + */ + + if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) { + // Binary casts need a special token. + if ($token[0] === 'b"') { + $finalTokens[$newStackPtr] = [ + 'code' => T_BINARY_CAST, + 'type' => 'T_BINARY_CAST', + 'content' => 'b', + ]; + $newStackPtr++; + } + + $tokenContent = '"'; + $nestedVars = []; + for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { + $subToken = (array) $tokens[$i]; + $subTokenIsArray = isset($subToken[1]); + + if ($subTokenIsArray === true) { + $tokenContent .= $subToken[1]; + if ($subToken[1] === '{' + && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE + ) { + $nestedVars[] = $i; + } + } else { + $tokenContent .= $subToken[0]; + if ($subToken[0] === '}') { + array_pop($nestedVars); + } + } + + if ($subTokenIsArray === false + && $subToken[0] === '"' + && empty($nestedVars) === true + ) { + // We found the other end of the double quoted string. + break; + } + }//end for + + $stackPtr = $i; + + // Convert each line within the double quoted string to a + // new token, so it conforms with other multiple line tokens. + $tokenLines = explode($this->eolChar, $tokenContent); + $numLines = count($tokenLines); + $newToken = []; + + for ($j = 0; $j < $numLines; $j++) { + $newToken['content'] = $tokenLines[$j]; + if ($j === ($numLines - 1)) { + if ($tokenLines[$j] === '') { + break; + } + } else { + $newToken['content'] .= $this->eolChar; + } + + $newToken['code'] = T_DOUBLE_QUOTED_STRING; + $newToken['type'] = 'T_DOUBLE_QUOTED_STRING'; + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + } + + // Continue, as we're done with this token. + continue; + }//end if + + /* + Detect binary casting and assign the casts their own token. + */ + + if ($tokenIsArray === true + && $token[0] === T_CONSTANT_ENCAPSED_STRING + && (substr($token[1], 0, 2) === 'b"' + || substr($token[1], 0, 2) === "b'") + ) { + $finalTokens[$newStackPtr] = [ + 'code' => T_BINARY_CAST, + 'type' => 'T_BINARY_CAST', + 'content' => 'b', + ]; + $newStackPtr++; + $token[1] = substr($token[1], 1); + } + + if ($tokenIsArray === true + && $token[0] === T_STRING_CAST + && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1 + ) { + $finalTokens[$newStackPtr] = [ + 'code' => T_BINARY_CAST, + 'type' => 'T_BINARY_CAST', + 'content' => $token[1], + ]; + $newStackPtr++; + continue; + } + + /* + If this is a heredoc, PHP will tokenize the whole + thing which causes problems when heredocs don't + contain real PHP code, which is almost never. + We want to leave the start and end heredoc tokens + alone though. + */ + + if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) { + // Add the start heredoc token to the final array. + $finalTokens[$newStackPtr] = self::standardiseToken($token); + + // Check if this is actually a nowdoc and use a different token + // to help the sniffs. + $nowdoc = false; + if (strpos($token[1], "'") !== false) { + $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC; + $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC'; + $nowdoc = true; + } + + $tokenContent = ''; + for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { + $subTokenIsArray = is_array($tokens[$i]); + if ($subTokenIsArray === true + && $tokens[$i][0] === T_END_HEREDOC + ) { + // We found the other end of the heredoc. + break; + } + + if ($subTokenIsArray === true) { + $tokenContent .= $tokens[$i][1]; + } else { + $tokenContent .= $tokens[$i]; + } + } + + if ($i === $numTokens) { + // We got to the end of the file and never + // found the closing token, so this probably wasn't + // a heredoc. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $finalTokens[$newStackPtr]['type']; + echo "\t\t* failed to find the end of the here/nowdoc".PHP_EOL; + echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL; + } + + $finalTokens[$newStackPtr]['code'] = T_STRING; + $finalTokens[$newStackPtr]['type'] = 'T_STRING'; + $newStackPtr++; + continue; + } + + $stackPtr = $i; + $newStackPtr++; + + // Convert each line within the heredoc to a + // new token, so it conforms with other multiple line tokens. + $tokenLines = explode($this->eolChar, $tokenContent); + $numLines = count($tokenLines); + $newToken = []; + + for ($j = 0; $j < $numLines; $j++) { + $newToken['content'] = $tokenLines[$j]; + if ($j === ($numLines - 1)) { + if ($tokenLines[$j] === '') { + break; + } + } else { + $newToken['content'] .= $this->eolChar; + } + + if ($nowdoc === true) { + $newToken['code'] = T_NOWDOC; + $newToken['type'] = 'T_NOWDOC'; + } else { + $newToken['code'] = T_HEREDOC; + $newToken['type'] = 'T_HEREDOC'; + } + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + }//end for + + // Add the end heredoc token to the final array. + $finalTokens[$newStackPtr] = self::standardiseToken($tokens[$stackPtr]); + + if ($nowdoc === true) { + $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC; + $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC'; + } + + $newStackPtr++; + + // Continue, as we're done with this token. + continue; + }//end if + + /* + Before PHP 7.0, the "yield from" was tokenized as + T_YIELD, T_WHITESPACE and T_STRING. So look for + and change this token in earlier versions. + */ + + if (PHP_VERSION_ID < 70000 + && PHP_VERSION_ID >= 50500 + && $tokenIsArray === true + && $token[0] === T_YIELD + && isset($tokens[($stackPtr + 1)]) === true + && isset($tokens[($stackPtr + 2)]) === true + && $tokens[($stackPtr + 1)][0] === T_WHITESPACE + && $tokens[($stackPtr + 2)][0] === T_STRING + && strtolower($tokens[($stackPtr + 2)][1]) === 'from' + ) { + // Could be multi-line, so just the token stack. + $token[0] = T_YIELD_FROM; + $token[1] = $token[1].$tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1]; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + for ($i = ($stackPtr + 1); $i <= ($stackPtr + 2); $i++) { + $type = Util\Tokens::tokenName($tokens[$i][0]); + $content = Util\Common::prepareForOutput($tokens[$i][1]); + echo "\t\t* token $i merged into T_YIELD_FROM; was: $type => $content".PHP_EOL; + } + } + + $tokens[($stackPtr + 1)] = null; + $tokens[($stackPtr + 2)] = null; + } + + /* + Before PHP 5.5, the yield keyword was tokenized as + T_STRING. So look for and change this token in + earlier versions. + Checks also if it is just "yield" or "yield from". + */ + + if (PHP_VERSION_ID < 50500 + && $tokenIsArray === true + && $token[0] === T_STRING + && strtolower($token[1]) === 'yield' + ) { + if (isset($tokens[($stackPtr + 1)]) === true + && isset($tokens[($stackPtr + 2)]) === true + && $tokens[($stackPtr + 1)][0] === T_WHITESPACE + && $tokens[($stackPtr + 2)][0] === T_STRING + && strtolower($tokens[($stackPtr + 2)][1]) === 'from' + ) { + // Could be multi-line, so just just the token stack. + $token[0] = T_YIELD_FROM; + $token[1] = $token[1].$tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1]; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + for ($i = ($stackPtr + 1); $i <= ($stackPtr + 2); $i++) { + $type = Util\Tokens::tokenName($tokens[$i][0]); + $content = Util\Common::prepareForOutput($tokens[$i][1]); + echo "\t\t* token $i merged into T_YIELD_FROM; was: $type => $content".PHP_EOL; + } + } + + $tokens[($stackPtr + 1)] = null; + $tokens[($stackPtr + 2)] = null; + } else { + $newToken = []; + $newToken['code'] = T_YIELD; + $newToken['type'] = 'T_YIELD'; + $newToken['content'] = $token[1]; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + continue; + }//end if + }//end if + + /* + Before PHP 5.6, the ... operator was tokenized as three + T_STRING_CONCAT tokens in a row. So look for and combine + these tokens in earlier versions. + */ + + if ($tokenIsArray === false + && $token[0] === '.' + && isset($tokens[($stackPtr + 1)]) === true + && isset($tokens[($stackPtr + 2)]) === true + && $tokens[($stackPtr + 1)] === '.' + && $tokens[($stackPtr + 2)] === '.' + ) { + $newToken = []; + $newToken['code'] = T_ELLIPSIS; + $newToken['type'] = 'T_ELLIPSIS'; + $newToken['content'] = '...'; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + $stackPtr += 2; + continue; + } + + /* + Before PHP 5.6, the ** operator was tokenized as two + T_MULTIPLY tokens in a row. So look for and combine + these tokens in earlier versions. + */ + + if ($tokenIsArray === false + && $token[0] === '*' + && isset($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)] === '*' + ) { + $newToken = []; + $newToken['code'] = T_POW; + $newToken['type'] = 'T_POW'; + $newToken['content'] = '**'; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + $stackPtr++; + continue; + } + + /* + Before PHP 5.6, the **= operator was tokenized as + T_MULTIPLY followed by T_MUL_EQUAL. So look for and combine + these tokens in earlier versions. + */ + + if ($tokenIsArray === false + && $token[0] === '*' + && isset($tokens[($stackPtr + 1)]) === true + && is_array($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)][1] === '*=' + ) { + $newToken = []; + $newToken['code'] = T_POW_EQUAL; + $newToken['type'] = 'T_POW_EQUAL'; + $newToken['content'] = '**='; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + $stackPtr++; + continue; + } + + /* + Before PHP 7, the ??= operator was tokenized as + T_INLINE_THEN, T_INLINE_THEN, T_EQUAL. + Between PHP 7.0 and 7.2, the ??= operator was tokenized as + T_COALESCE, T_EQUAL. + So look for and combine these tokens in earlier versions. + */ + + if (($tokenIsArray === false + && $token[0] === '?' + && isset($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)][0] === '?' + && isset($tokens[($stackPtr + 2)]) === true + && $tokens[($stackPtr + 2)][0] === '=') + || ($tokenIsArray === true + && $token[0] === T_COALESCE + && isset($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)][0] === '=') + ) { + $newToken = []; + $newToken['code'] = T_COALESCE_EQUAL; + $newToken['type'] = 'T_COALESCE_EQUAL'; + $newToken['content'] = '??='; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + $stackPtr++; + + if ($tokenIsArray === false) { + // Pre PHP 7. + $stackPtr++; + } + + continue; + } + + /* + Before PHP 7, the ?? operator was tokenized as + T_INLINE_THEN followed by T_INLINE_THEN. + So look for and combine these tokens in earlier versions. + */ + + if ($tokenIsArray === false + && $token[0] === '?' + && isset($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)][0] === '?' + ) { + $newToken = []; + $newToken['code'] = T_COALESCE; + $newToken['type'] = 'T_COALESCE'; + $newToken['content'] = '??'; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + $stackPtr++; + continue; + } + + /* + Convert ? to T_NULLABLE OR T_INLINE_THEN + */ + + if ($tokenIsArray === false && $token[0] === '?') { + $newToken = []; + $newToken['content'] = '?'; + + $prevNonEmpty = null; + for ($i = ($stackPtr - 1); $i >= 0; $i--) { + if (is_array($tokens[$i]) === true) { + $tokenType = $tokens[$i][0]; + } else { + $tokenType = $tokens[$i]; + } + + if ($prevNonEmpty === null + && isset(Util\Tokens::$emptyTokens[$tokenType]) === false + ) { + // Found the previous non-empty token. + if ($tokenType === ':' || $tokenType === ',') { + $newToken['code'] = T_NULLABLE; + $newToken['type'] = 'T_NULLABLE'; + break; + } + + $prevNonEmpty = $tokenType; + } + + if ($tokenType === T_FUNCTION) { + $newToken['code'] = T_NULLABLE; + $newToken['type'] = 'T_NULLABLE'; + break; + } else if (in_array($tokenType, [T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';']) === true) { + $newToken['code'] = T_INLINE_THEN; + $newToken['type'] = 'T_INLINE_THEN'; + + $insideInlineIf[] = $stackPtr; + break; + } + }//end for + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + continue; + }//end if + + /* + Tokens after a double colon may be look like scope openers, + such as when writing code like Foo::NAMESPACE, but they are + only ever variables or strings. + */ + + if ($stackPtr > 1 + && (is_array($tokens[($stackPtr - 1)]) === true + && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM) + && $tokenIsArray === true + && $token[0] !== T_STRING + && $token[0] !== T_VARIABLE + && $token[0] !== T_DOLLAR + && isset(Util\Tokens::$emptyTokens[$token[0]]) === false + ) { + $newToken = []; + $newToken['code'] = T_STRING; + $newToken['type'] = 'T_STRING'; + $newToken['content'] = $token[1]; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + continue; + } + + /* + The string-like token after a function keyword should always be + tokenized as T_STRING even if it appears to be a different token, + such as when writing code like: function default(): foo + so go forward and change the token type before it is processed. + */ + + if ($tokenIsArray === true + && $token[0] === T_FUNCTION + && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE + ) { + for ($x = ($stackPtr + 1); $x < $numTokens; $x++) { + if (is_array($tokens[$x]) === false + || isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false + ) { + // Non-empty content. + break; + } + } + + if ($x < $numTokens && is_array($tokens[$x]) === true) { + $tokens[$x][0] = T_STRING; + } + + /* + This is a special condition for T_ARRAY tokens used for + function return types. We want to keep the parenthesis map clean, + so let's tag these tokens as T_STRING. + */ + + // Go looking for the colon to start the return type hint. + // Start by finding the closing parenthesis of the function. + $parenthesisStack = []; + $parenthesisCloser = false; + for ($x = ($stackPtr + 1); $x < $numTokens; $x++) { + if (is_array($tokens[$x]) === false && $tokens[$x] === '(') { + $parenthesisStack[] = $x; + } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') { + array_pop($parenthesisStack); + if (empty($parenthesisStack) === true) { + $parenthesisCloser = $x; + break; + } + } + } + + if ($parenthesisCloser !== false) { + for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) { + if (is_array($tokens[$x]) === false + || isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false + ) { + // Non-empty content. + if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) { + // Found a use statements, so search ahead for the closing parenthesis. + for ($x += 1; $x < $numTokens; $x++) { + if (is_array($tokens[$x]) === false && $tokens[$x] === ')') { + continue(2); + } + } + } + + break; + } + } + + if (isset($tokens[$x]) === true + && is_array($tokens[$x]) === false + && $tokens[$x] === ':' + ) { + $allowed = [ + T_STRING => T_STRING, + T_ARRAY => T_ARRAY, + T_CALLABLE => T_CALLABLE, + T_SELF => T_SELF, + T_PARENT => T_PARENT, + T_NS_SEPARATOR => T_NS_SEPARATOR, + ]; + + $allowed += Util\Tokens::$emptyTokens; + + // Find the start of the return type. + for ($x += 1; $x < $numTokens; $x++) { + if (is_array($tokens[$x]) === true + && isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === true + ) { + // Whitespace or coments before the return type. + continue; + } + + if (is_array($tokens[$x]) === false && $tokens[$x] === '?') { + // Found a nullable operator, so skip it. + // But also covert the token to save the tokenizer + // a bit of time later on. + $tokens[$x] = [ + T_NULLABLE, + '?', + ]; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token $x changed from ? to T_NULLABLE".PHP_EOL; + } + + continue; + } + + break; + }//end for + + // Any T_ARRAY tokens we find between here and the next + // token that can't be part of the return type need to be + // converted to T_STRING tokens. + for ($x; $x < $numTokens; $x++) { + if (is_array($tokens[$x]) === false || isset($allowed[$tokens[$x][0]]) === false) { + break; + } else if ($tokens[$x][0] === T_ARRAY) { + $tokens[$x][0] = T_STRING; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token $x changed from T_ARRAY to T_STRING".PHP_EOL; + } + } + } + }//end if + }//end if + }//end if + + /* + Before PHP 7, the <=> operator was tokenized as + T_IS_SMALLER_OR_EQUAL followed by T_GREATER_THAN. + So look for and combine these tokens in earlier versions. + */ + + if ($tokenIsArray === true + && $token[0] === T_IS_SMALLER_OR_EQUAL + && isset($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)][0] === '>' + ) { + $newToken = []; + $newToken['code'] = T_SPACESHIP; + $newToken['type'] = 'T_SPACESHIP'; + $newToken['content'] = '<=>'; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + $stackPtr++; + continue; + } + + /* + PHP doesn't assign a token to goto labels, so we have to. + These are just string tokens with a single colon after them. Double + colons are already tokenized and so don't interfere with this check. + But we do have to account for CASE statements, that look just like + goto labels. + */ + + if ($tokenIsArray === true + && $token[0] === T_STRING + && isset($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)] === ':' + && $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM + ) { + $stopTokens = [ + T_CASE => true, + T_SEMICOLON => true, + T_OPEN_CURLY_BRACKET => true, + T_INLINE_THEN => true, + ]; + + for ($x = ($newStackPtr - 1); $x > 0; $x--) { + if (isset($stopTokens[$finalTokens[$x]['code']]) === true) { + break; + } + } + + if ($finalTokens[$x]['code'] !== T_CASE + && $finalTokens[$x]['code'] !== T_INLINE_THEN + ) { + $finalTokens[$newStackPtr] = [ + 'content' => $token[1].':', + 'code' => T_GOTO_LABEL, + 'type' => 'T_GOTO_LABEL', + ]; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token $stackPtr changed from T_STRING to T_GOTO_LABEL".PHP_EOL; + echo "\t\t* skipping T_COLON token ".($stackPtr + 1).PHP_EOL; + } + + $newStackPtr++; + $stackPtr++; + continue; + } + }//end if + + /* + If this token has newlines in its content, split each line up + and create a new token for each line. We do this so it's easier + to ascertain where errors occur on a line. + Note that $token[1] is the token's content. + */ + + if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) { + $tokenLines = explode($this->eolChar, $token[1]); + $numLines = count($tokenLines); + $newToken = [ + 'type' => Util\Tokens::tokenName($token[0]), + 'code' => $token[0], + 'content' => '', + ]; + + for ($i = 0; $i < $numLines; $i++) { + $newToken['content'] = $tokenLines[$i]; + if ($i === ($numLines - 1)) { + if ($tokenLines[$i] === '') { + break; + } + } else { + $newToken['content'] .= $this->eolChar; + } + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + } + } else { + if ($tokenIsArray === true && $token[0] === T_STRING) { + // Some T_STRING tokens should remain that way + // due to their context. + $context = [ + T_OBJECT_OPERATOR => true, + T_FUNCTION => true, + T_CLASS => true, + T_EXTENDS => true, + T_IMPLEMENTS => true, + T_NEW => true, + T_CONST => true, + T_NS_SEPARATOR => true, + T_USE => true, + T_NAMESPACE => true, + T_PAAMAYIM_NEKUDOTAYIM => true, + ]; + if (isset($context[$finalTokens[$lastNotEmptyToken]['code']]) === true) { + // Special case for syntax like: return new self + // where self should not be a string. + if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW + && strtolower($token[1]) === 'self' + ) { + $finalTokens[$newStackPtr] = [ + 'content' => $token[1], + 'code' => T_SELF, + 'type' => 'T_SELF', + ]; + } else { + $finalTokens[$newStackPtr] = [ + 'content' => $token[1], + 'code' => T_STRING, + 'type' => 'T_STRING', + ]; + } + + $newStackPtr++; + continue; + }//end if + }//end if + + $newToken = null; + if ($tokenIsArray === false) { + if (isset(self::$resolveTokenCache[$token[0]]) === true) { + $newToken = self::$resolveTokenCache[$token[0]]; + } + } else { + $cacheKey = null; + if ($token[0] === T_STRING) { + $cacheKey = strtolower($token[1]); + } else if ($token[0] !== T_CURLY_OPEN) { + $cacheKey = $token[0]; + } + + if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) { + $newToken = self::$resolveTokenCache[$cacheKey]; + $newToken['content'] = $token[1]; + } + } + + if ($newToken === null) { + $newToken = self::standardiseToken($token); + } + + // Convert colons that are actually the ELSE component of an + // inline IF statement. + if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) { + // Make sure this isn't the return type separator of a closure. + $isReturnType = false; + for ($i = ($stackPtr - 1); $i > 0; $i--) { + if (is_array($tokens[$i]) === false + || ($tokens[$i][0] !== T_DOC_COMMENT + && $tokens[$i][0] !== T_COMMENT + && $tokens[$i][0] !== T_WHITESPACE) + ) { + break; + } + } + + if ($tokens[$i] === ')') { + $parenCount = 1; + for ($i--; $i > 0; $i--) { + if ($tokens[$i] === '(') { + $parenCount--; + if ($parenCount === 0) { + break; + } + } else if ($tokens[$i] === ')') { + $parenCount++; + } + } + + // We've found the open parenthesis, so if the previous + // non-empty token is FUNCTION or USE, this is a closure. + for ($i--; $i > 0; $i--) { + if (is_array($tokens[$i]) === false + || ($tokens[$i][0] !== T_DOC_COMMENT + && $tokens[$i][0] !== T_COMMENT + && $tokens[$i][0] !== T_WHITESPACE) + ) { + break; + } + } + + if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_USE) { + $isReturnType = true; + } + }//end if + + if ($isReturnType === false) { + array_pop($insideInlineIf); + $newToken['code'] = T_INLINE_ELSE; + $newToken['type'] = 'T_INLINE_ELSE'; + } + }//end if + + // This is a special condition for T_ARRAY tokens used for + // type hinting function arguments as being arrays. We want to keep + // the parenthesis map clean, so let's tag these tokens as + // T_STRING. + if ($newToken['code'] === T_ARRAY) { + for ($i = $stackPtr; $i < $numTokens; $i++) { + if ($tokens[$i] === '(') { + break; + } else if ($tokens[$i][0] === T_VARIABLE) { + $newToken['code'] = T_STRING; + $newToken['type'] = 'T_STRING'; + break; + } + } + } + + // This is a special case when checking PHP 5.5+ code in PHP < 5.5 + // where "finally" should be T_FINALLY instead of T_STRING. + if ($newToken['code'] === T_STRING + && strtolower($newToken['content']) === 'finally' + ) { + $newToken['code'] = T_FINALLY; + $newToken['type'] = 'T_FINALLY'; + } + + // This is a special case for the PHP 5.5 classname::class syntax + // where "class" should be T_STRING instead of T_CLASS. + if (($newToken['code'] === T_CLASS + || $newToken['code'] === T_FUNCTION) + && $finalTokens[$lastNotEmptyToken]['code'] === T_DOUBLE_COLON + ) { + $newToken['code'] = T_STRING; + $newToken['type'] = 'T_STRING'; + } + + // This is a special case for PHP 5.6 use function and use const + // where "function" and "const" should be T_STRING instead of T_FUNCTION + // and T_CONST. + if (($newToken['code'] === T_FUNCTION + || $newToken['code'] === T_CONST) + && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true) + ) { + $newToken['code'] = T_STRING; + $newToken['type'] = 'T_STRING'; + } + + // This is a special case for use groups in PHP 7+ where leaving + // the curly braces as their normal tokens would confuse + // the scope map and sniffs. + if ($newToken['code'] === T_OPEN_CURLY_BRACKET + && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR + ) { + $newToken['code'] = T_OPEN_USE_GROUP; + $newToken['type'] = 'T_OPEN_USE_GROUP'; + $insideUseGroup = true; + } + + if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) { + $newToken['code'] = T_CLOSE_USE_GROUP; + $newToken['type'] = 'T_CLOSE_USE_GROUP'; + $insideUseGroup = false; + } + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + }//end if + }//end for + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** END PHP TOKENIZING ***".PHP_EOL; + } + + return $finalTokens; + + }//end tokenize() + + + /** + * Performs additional processing after main tokenizing. + * + * This additional processing checks for CASE statements that are using curly + * braces for scope openers and closers. It also turns some T_FUNCTION tokens + * into T_CLOSURE when they are not standard function definitions. It also + * detects short array syntax and converts those square brackets into new tokens. + * It also corrects some usage of the static and class keywords. It also + * assigns tokens to function return types. + * + * @return void + */ + protected function processAdditional() + { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** START ADDITIONAL PHP PROCESSING ***".PHP_EOL; + } + + $numTokens = count($this->tokens); + for ($i = ($numTokens - 1); $i >= 0; $i--) { + // Check for any unset scope conditions due to alternate IF/ENDIF syntax. + if (isset($this->tokens[$i]['scope_opener']) === true + && isset($this->tokens[$i]['scope_condition']) === false + ) { + $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition']; + } + + if ($this->tokens[$i]['code'] === T_FUNCTION) { + /* + Detect functions that are actually closures and + assign them a different token. + */ + + if (isset($this->tokens[$i]['scope_opener']) === true) { + for ($x = ($i + 1); $x < $numTokens; $x++) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false + && $this->tokens[$x]['code'] !== T_BITWISE_AND + ) { + break; + } + } + + if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) { + $this->tokens[$i]['code'] = T_CLOSURE; + $this->tokens[$i]['type'] = 'T_CLOSURE'; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + echo "\t* token $i on line $line changed from T_FUNCTION to T_CLOSURE".PHP_EOL; + } + + for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) { + if (isset($this->tokens[$x]['conditions'][$i]) === false) { + continue; + } + + $this->tokens[$x]['conditions'][$i] = T_CLOSURE; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $this->tokens[$x]['type']; + echo "\t\t* cleaned $x ($type) *".PHP_EOL; + } + } + } + }//end if + + continue; + } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) { + /* + Detect anonymous classes and assign them a different token. + */ + + for ($x = ($i + 1); $x < $numTokens; $x++) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) { + break; + } + } + + if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS + || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET + || $this->tokens[$x]['code'] === T_EXTENDS + || $this->tokens[$x]['code'] === T_IMPLEMENTS + ) { + $this->tokens[$i]['code'] = T_ANON_CLASS; + $this->tokens[$i]['type'] = 'T_ANON_CLASS'; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + echo "\t* token $i on line $line changed from T_CLASS to T_ANON_CLASS".PHP_EOL; + } + + for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) { + if (isset($this->tokens[$x]['conditions'][$i]) === false) { + continue; + } + + $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $this->tokens[$x]['type']; + echo "\t\t* cleaned $x ($type) *".PHP_EOL; + } + } + } + + continue; + } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) { + if (isset($this->tokens[$i]['bracket_closer']) === false) { + continue; + } + + // Unless there is a variable or a bracket before this token, + // it is the start of an array being defined using the short syntax. + $isShortArray = false; + $allowed = [ + T_CLOSE_SQUARE_BRACKET => T_CLOSE_SQUARE_BRACKET, + T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET, + T_CLOSE_PARENTHESIS => T_CLOSE_PARENTHESIS, + T_VARIABLE => T_VARIABLE, + T_OBJECT_OPERATOR => T_OBJECT_OPERATOR, + T_STRING => T_STRING, + T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, + ]; + + for ($x = ($i - 1); $x >= 0; $x--) { + // If we hit a scope opener, the statement has ended + // without finding anything, so it's probably an array + // using PHP 7.1 short list syntax. + if (isset($this->tokens[$x]['scope_opener']) === true) { + $isShortArray = true; + break; + } + + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) { + if (isset($allowed[$this->tokens[$x]['code']]) === false) { + $isShortArray = true; + } + + break; + } + } + + if ($isShortArray === true) { + $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY; + $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY'; + + $closer = $this->tokens[$i]['bracket_closer']; + $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY; + $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY'; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + echo "\t* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY".PHP_EOL; + $line = $this->tokens[$closer]['line']; + echo "\t* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY".PHP_EOL; + } + } + + continue; + } else if ($this->tokens[$i]['code'] === T_STATIC) { + for ($x = ($i - 1); $x > 0; $x--) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) { + break; + } + } + + if ($this->tokens[$x]['code'] === T_INSTANCEOF) { + $this->tokens[$i]['code'] = T_STRING; + $this->tokens[$i]['type'] = 'T_STRING'; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + echo "\t* token $i on line $line changed from T_STATIC to T_STRING".PHP_EOL; + } + } + + continue; + } else if ($this->tokens[$i]['code'] === T_TRUE + || $this->tokens[$i]['code'] === T_FALSE + || $this->tokens[$i]['code'] === T_NULL + ) { + for ($x = ($i + 1); $i < $numTokens; $x++) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) { + // Non-whitespace content. + break; + } + } + + $context = [ + T_OBJECT_OPERATOR => true, + T_NS_SEPARATOR => true, + T_PAAMAYIM_NEKUDOTAYIM => true, + ]; + if (isset($context[$this->tokens[$x]['code']]) === true) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + $type = $this->tokens[$i]['type']; + echo "\t* token $i on line $line changed from $type to T_STRING".PHP_EOL; + } + + $this->tokens[$i]['code'] = T_STRING; + $this->tokens[$i]['type'] = 'T_STRING'; + } + } else if ($this->tokens[$i]['code'] === T_CONST) { + // Context sensitive keywords support. + for ($x = ($i + 1); $i < $numTokens; $x++) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) { + // Non-whitespace content. + break; + } + } + + if ($this->tokens[$x]['code'] !== T_STRING) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$x]['line']; + $type = $this->tokens[$x]['type']; + echo "\t* token $x on line $line changed from $type to T_STRING".PHP_EOL; + } + + $this->tokens[$x]['code'] = T_STRING; + $this->tokens[$x]['type'] = 'T_STRING'; + } + }//end if + + if (($this->tokens[$i]['code'] !== T_CASE + && $this->tokens[$i]['code'] !== T_DEFAULT) + || isset($this->tokens[$i]['scope_opener']) === false + ) { + // Only interested in CASE and DEFAULT statements from here on in. + continue; + } + + $scopeOpener = $this->tokens[$i]['scope_opener']; + $scopeCloser = $this->tokens[$i]['scope_closer']; + + // If the first char after the opener is a curly brace + // and that brace has been ignored, it is actually + // opening this case statement and the opener and closer are + // probably set incorrectly. + for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) { + // Non-whitespace content. + break; + } + } + + if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) { + // Special case for multiple CASE statements that share the same + // closer. Because we are going backwards through the file, this next + // CASE statement is already fixed, so just use its closer and don't + // worry about fixing anything. + $newCloser = $this->tokens[$x]['scope_closer']; + $this->tokens[$i]['scope_closer'] = $newCloser; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $oldType = $this->tokens[$scopeCloser]['type']; + $newType = $this->tokens[$newCloser]['type']; + $line = $this->tokens[$i]['line']; + echo "\t* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL; + } + + continue; + } + + if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET + || isset($this->tokens[$x]['scope_condition']) === true + ) { + // Not a CASE/DEFAULT with a curly brace opener. + continue; + } + + // The closer for this CASE/DEFAULT should be the closing curly brace and + // not whatever it already is. The opener needs to be the opening curly + // brace so everything matches up. + $newCloser = $this->tokens[$x]['bracket_closer']; + foreach ([$i, $x, $newCloser] as $index) { + $this->tokens[$index]['scope_condition'] = $i; + $this->tokens[$index]['scope_opener'] = $x; + $this->tokens[$index]['scope_closer'] = $newCloser; + } + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + $tokenType = $this->tokens[$i]['type']; + + $oldType = $this->tokens[$scopeOpener]['type']; + $newType = $this->tokens[$x]['type']; + echo "\t* token $i ($tokenType) on line $line opener changed from $scopeOpener ($oldType) to $x ($newType)".PHP_EOL; + + $oldType = $this->tokens[$scopeCloser]['type']; + $newType = $this->tokens[$newCloser]['type']; + echo "\t* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL; + } + + if ($this->tokens[$scopeOpener]['scope_condition'] === $i) { + unset($this->tokens[$scopeOpener]['scope_condition']); + unset($this->tokens[$scopeOpener]['scope_opener']); + unset($this->tokens[$scopeOpener]['scope_closer']); + } + + if ($this->tokens[$scopeCloser]['scope_condition'] === $i) { + unset($this->tokens[$scopeCloser]['scope_condition']); + unset($this->tokens[$scopeCloser]['scope_opener']); + unset($this->tokens[$scopeCloser]['scope_closer']); + } else { + // We were using a shared closer. All tokens that were + // sharing this closer with us, except for the scope condition + // and it's opener, need to now point to the new closer. + $condition = $this->tokens[$scopeCloser]['scope_condition']; + $start = ($this->tokens[$condition]['scope_opener'] + 1); + for ($y = $start; $y < $scopeCloser; $y++) { + if (isset($this->tokens[$y]['scope_closer']) === true + && $this->tokens[$y]['scope_closer'] === $scopeCloser + ) { + $this->tokens[$y]['scope_closer'] = $newCloser; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$y]['line']; + $tokenType = $this->tokens[$y]['type']; + $oldType = $this->tokens[$scopeCloser]['type']; + $newType = $this->tokens[$newCloser]['type']; + echo "\t\t* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL; + } + } + } + }//end if + + unset($this->tokens[$x]['bracket_opener']); + unset($this->tokens[$x]['bracket_closer']); + unset($this->tokens[$newCloser]['bracket_opener']); + unset($this->tokens[$newCloser]['bracket_closer']); + $this->tokens[$scopeCloser]['conditions'][] = $i; + + // Now fix up all the tokens that think they are + // inside the CASE/DEFAULT statement when they are really outside. + for ($x = $newCloser; $x < $scopeCloser; $x++) { + foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) { + if ($oldCond === $this->tokens[$i]['code']) { + $oldConditions = $this->tokens[$x]['conditions']; + unset($this->tokens[$x]['conditions'][$num]); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $this->tokens[$x]['type']; + $oldConds = ''; + foreach ($oldConditions as $condition) { + $oldConds .= Util\Tokens::tokenName($condition).','; + } + + $oldConds = rtrim($oldConds, ','); + + $newConds = ''; + foreach ($this->tokens[$x]['conditions'] as $condition) { + $newConds .= Util\Tokens::tokenName($condition).','; + } + + $newConds = rtrim($newConds, ','); + + echo "\t\t* cleaned $x ($type) *".PHP_EOL; + echo "\t\t\t=> conditions changed from $oldConds to $newConds".PHP_EOL; + } + + break; + }//end if + }//end foreach + }//end for + }//end for + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** END ADDITIONAL PHP PROCESSING ***".PHP_EOL; + } + + }//end processAdditional() + + + /** + * Takes a token produced from <code>token_get_all()</code> and produces a + * more uniform token. + * + * @param string|array $token The token to convert. + * + * @return array The new token. + */ + public static function standardiseToken($token) + { + if (isset($token[1]) === false) { + if (isset(self::$resolveTokenCache[$token[0]]) === true) { + return self::$resolveTokenCache[$token[0]]; + } + } else { + $cacheKey = null; + if ($token[0] === T_STRING) { + $cacheKey = strtolower($token[1]); + } else if ($token[0] !== T_CURLY_OPEN) { + $cacheKey = $token[0]; + } + + if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) { + $newToken = self::$resolveTokenCache[$cacheKey]; + $newToken['content'] = $token[1]; + return $newToken; + } + } + + if (isset($token[1]) === false) { + return self::resolveSimpleToken($token[0]); + } + + if ($token[0] === T_STRING) { + switch ($cacheKey) { + case 'false': + $newToken['type'] = 'T_FALSE'; + break; + case 'true': + $newToken['type'] = 'T_TRUE'; + break; + case 'null': + $newToken['type'] = 'T_NULL'; + break; + case 'self': + $newToken['type'] = 'T_SELF'; + break; + case 'parent': + $newToken['type'] = 'T_PARENT'; + break; + default: + $newToken['type'] = 'T_STRING'; + break; + } + + $newToken['code'] = constant($newToken['type']); + + self::$resolveTokenCache[$cacheKey] = $newToken; + } else if ($token[0] === T_CURLY_OPEN) { + $newToken = [ + 'code' => T_OPEN_CURLY_BRACKET, + 'type' => 'T_OPEN_CURLY_BRACKET', + ]; + } else { + $newToken = [ + 'code' => $token[0], + 'type' => Util\Tokens::tokenName($token[0]), + ]; + + self::$resolveTokenCache[$token[0]] = $newToken; + }//end if + + $newToken['content'] = $token[1]; + return $newToken; + + }//end standardiseToken() + + + /** + * Converts simple tokens into a format that conforms to complex tokens + * produced by token_get_all(). + * + * Simple tokens are tokens that are not in array form when produced from + * token_get_all(). + * + * @param string $token The simple token to convert. + * + * @return array The new token in array format. + */ + public static function resolveSimpleToken($token) + { + $newToken = []; + + switch ($token) { + case '{': + $newToken['type'] = 'T_OPEN_CURLY_BRACKET'; + break; + case '}': + $newToken['type'] = 'T_CLOSE_CURLY_BRACKET'; + break; + case '[': + $newToken['type'] = 'T_OPEN_SQUARE_BRACKET'; + break; + case ']': + $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET'; + break; + case '(': + $newToken['type'] = 'T_OPEN_PARENTHESIS'; + break; + case ')': + $newToken['type'] = 'T_CLOSE_PARENTHESIS'; + break; + case ':': + $newToken['type'] = 'T_COLON'; + break; + case '.': + $newToken['type'] = 'T_STRING_CONCAT'; + break; + case ';': + $newToken['type'] = 'T_SEMICOLON'; + break; + case '=': + $newToken['type'] = 'T_EQUAL'; + break; + case '*': + $newToken['type'] = 'T_MULTIPLY'; + break; + case '/': + $newToken['type'] = 'T_DIVIDE'; + break; + case '+': + $newToken['type'] = 'T_PLUS'; + break; + case '-': + $newToken['type'] = 'T_MINUS'; + break; + case '%': + $newToken['type'] = 'T_MODULUS'; + break; + case '^': + $newToken['type'] = 'T_BITWISE_XOR'; + break; + case '&': + $newToken['type'] = 'T_BITWISE_AND'; + break; + case '|': + $newToken['type'] = 'T_BITWISE_OR'; + break; + case '~': + $newToken['type'] = 'T_BITWISE_NOT'; + break; + case '<': + $newToken['type'] = 'T_LESS_THAN'; + break; + case '>': + $newToken['type'] = 'T_GREATER_THAN'; + break; + case '!': + $newToken['type'] = 'T_BOOLEAN_NOT'; + break; + case ',': + $newToken['type'] = 'T_COMMA'; + break; + case '@': + $newToken['type'] = 'T_ASPERAND'; + break; + case '$': + $newToken['type'] = 'T_DOLLAR'; + break; + case '`': + $newToken['type'] = 'T_BACKTICK'; + break; + default: + $newToken['type'] = 'T_NONE'; + break; + }//end switch + + $newToken['code'] = constant($newToken['type']); + $newToken['content'] = $token; + + self::$resolveTokenCache[$token] = $newToken; + return $newToken; + + }//end resolveSimpleToken() + + +}//end class