Example #1
0
 /**
  * Generate a partial report for a single processed file.
  *
  * Function should return TRUE if it printed or stored data about the file
  * and FALSE if it ignored the file. Returning TRUE indicates that the file and
  * its data should be counted in the grand totals.
  *
  * @param array                 $report      Prepared report data.
  * @param \PHP_CodeSniffer\File $phpcsFile   The file being reported on.
  * @param bool                  $showSources Show sources?
  * @param int                   $width       Maximum allowed line width.
  *
  * @return bool
  */
 public function generateFileReport($report, File $phpcsFile, $showSources = false, $width = 80)
 {
     if ($report['errors'] === 0 && $report['warnings'] === 0) {
         // Nothing to print.
         return false;
     }
     // How many lines to show about and below the error line.
     $surroundingLines = 2;
     $file = $report['filename'];
     $tokens = $phpcsFile->getTokens();
     if (empty($tokens) === true) {
         if (PHP_CODESNIFFER_VERBOSITY === 1) {
             $startTime = microtime(true);
             echo 'CODE report is parsing ' . basename($file) . ' ';
         } else {
             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                 echo "CODE report is forcing parse of {$file}" . PHP_EOL;
             }
         }
         $phpcsFile->parse();
         if (PHP_CODESNIFFER_VERBOSITY === 1) {
             $timeTaken = (microtime(true) - $startTime) * 1000;
             if ($timeTaken < 1000) {
                 $timeTaken = round($timeTaken);
                 echo "DONE in {$timeTaken}ms";
             } else {
                 $timeTaken = round($timeTaken / 1000, 2);
                 echo "DONE in {$timeTaken} secs";
             }
             echo PHP_EOL;
         }
         $tokens = $phpcsFile->getTokens();
     }
     //end if
     // Create an array that maps lines to the first token on the line.
     $lineTokens = array();
     $lastLine = 0;
     foreach ($tokens as $stackPtr => $token) {
         if ($token['line'] !== $lastLine) {
             if ($lastLine > 0) {
                 $lineTokens[$lastLine]['end'] = $stackPtr - 1;
             }
             $lastLine++;
             $lineTokens[$lastLine] = array('start' => $stackPtr, 'end' => null);
         }
     }
     // Make sure the last token in the file sits on an imaginary
     // last line so it is easier to generate code snippets at the
     // end of the file.
     $lineTokens[$lastLine]['end'] = $stackPtr;
     // Determine the longest code line we will be showing.
     $maxSnippetLength = 0;
     $eolLen = strlen($phpcsFile->eolChar);
     foreach ($report['messages'] as $line => $lineErrors) {
         $startLine = max($line - $surroundingLines, 1);
         $endLine = min($line + $surroundingLines, $lastLine);
         $maxLineNumLength = strlen($endLine);
         for ($i = $startLine; $i <= $endLine; $i++) {
             if ($i === 1) {
                 continue;
             }
             $lineLength = $tokens[$lineTokens[$i]['start'] - 1]['column'] + $tokens[$lineTokens[$i]['start'] - 1]['length'] - $eolLen;
             $maxSnippetLength = max($lineLength, $maxSnippetLength);
         }
     }
     $maxSnippetLength += $maxLineNumLength + 8;
     // Determine the longest error message we will be showing.
     $maxErrorLength = 0;
     foreach ($report['messages'] as $line => $lineErrors) {
         foreach ($lineErrors as $column => $colErrors) {
             foreach ($colErrors as $error) {
                 $length = strlen($error['message']);
                 if ($showSources === true) {
                     $length += strlen($error['source']) + 3;
                 }
                 $maxErrorLength = max($maxErrorLength, $length + 1);
             }
         }
     }
     // The padding that all lines will require that are printing an error message overflow.
     if ($report['warnings'] > 0) {
         $typeLength = 7;
     } else {
         $typeLength = 5;
     }
     $errorPadding = str_repeat(' ', $maxLineNumLength + 7);
     $errorPadding .= str_repeat(' ', $typeLength);
     $errorPadding .= ' ';
     if ($report['fixable'] > 0) {
         $errorPadding .= '    ';
     }
     $errorPaddingLength = strlen($errorPadding);
     // The maximum amount of space an error message can use.
     $maxErrorSpace = $width - $errorPaddingLength;
     if ($showSources === true) {
         // Account for the chars used to print colors.
         $maxErrorSpace += 8;
     }
     // Figure out the max report width we need and can use.
     $fileLength = strlen($file);
     $maxWidth = max($fileLength + 6, $maxErrorLength + $errorPaddingLength);
     $width = max(min($width, $maxWidth), $maxSnippetLength);
     if ($width < 70) {
         $width = 70;
     }
     // Print the file header.
     echo PHP_EOL . "FILE: ";
     if ($fileLength <= $width - 6) {
         echo $file;
     } else {
         echo '...' . substr($file, $fileLength - ($width - 6));
     }
     echo "" . PHP_EOL;
     echo str_repeat('-', $width) . PHP_EOL;
     echo "" . 'FOUND ' . $report['errors'] . ' ERROR';
     if ($report['errors'] !== 1) {
         echo 'S';
     }
     if ($report['warnings'] > 0) {
         echo ' AND ' . $report['warnings'] . ' WARNING';
         if ($report['warnings'] !== 1) {
             echo 'S';
         }
     }
     echo ' AFFECTING ' . count($report['messages']) . ' LINE';
     if (count($report['messages']) !== 1) {
         echo 'S';
     }
     echo "" . PHP_EOL;
     foreach ($report['messages'] as $line => $lineErrors) {
         $startLine = max($line - $surroundingLines, 1);
         $endLine = min($line + $surroundingLines, $lastLine);
         $snippet = '';
         for ($i = $lineTokens[$startLine]['start']; $i <= $lineTokens[$endLine]['end']; $i++) {
             $snippetLine = $tokens[$i]['line'];
             if ($lineTokens[$snippetLine]['start'] === $i) {
                 // Starting a new line.
                 if ($snippetLine === $line) {
                     $snippet .= "" . '>> ';
                 } else {
                     $snippet .= '   ';
                 }
                 $snippet .= str_repeat(' ', $maxLineNumLength - strlen($snippetLine));
                 $snippet .= $snippetLine . ':  ';
                 if ($snippetLine === $line) {
                     $snippet .= "";
                 }
             }
             if (isset($tokens[$i]['orig_content']) === true) {
                 $tokenContent = $tokens[$i]['orig_content'];
             } else {
                 $tokenContent = $tokens[$i]['content'];
             }
             if (strpos($tokenContent, "\t") !== false) {
                 $token = $tokens[$i];
                 $token['content'] = $tokenContent;
                 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
                     $tab = "";
                 } else {
                     $tab = "»";
                 }
                 $phpcsFile->tokenizer->replaceTabsInToken($token, $tab, "");
                 $tokenContent = $token['content'];
             }
             $tokenContent = Util\Common::prepareForOutput($tokenContent, array("\r", "\n", "\t"));
             $tokenContent = str_replace("", ' ', $tokenContent);
             $underline = false;
             if ($snippetLine === $line && isset($lineErrors[$tokens[$i]['column']]) === true) {
                 $underline = true;
             }
             // Underline invisible characters as well.
             if ($underline === true && trim($tokenContent) === '') {
                 $snippet .= "" . ' ' . "" . $tokenContent;
             } else {
                 if ($underline === true) {
                     $snippet .= "";
                 }
                 $snippet .= $tokenContent;
                 if ($underline === true) {
                     $snippet .= "";
                 }
             }
         }
         //end for
         echo str_repeat('-', $width) . PHP_EOL;
         foreach ($lineErrors as $column => $colErrors) {
             foreach ($colErrors as $error) {
                 $padding = $maxLineNumLength - strlen($line);
                 echo 'LINE ' . str_repeat(' ', $padding) . $line . ': ';
                 if ($error['type'] === 'ERROR') {
                     echo "ERROR";
                     if ($report['warnings'] > 0) {
                         echo '  ';
                     }
                 } else {
                     echo "WARNING";
                 }
                 echo ' ';
                 if ($report['fixable'] > 0) {
                     echo '[';
                     if ($error['fixable'] === true) {
                         echo 'x';
                     } else {
                         echo ' ';
                     }
                     echo '] ';
                 }
                 $message = $error['message'];
                 $message = str_replace("\n", "\n" . $errorPadding, $message);
                 if ($showSources === true) {
                     $message = "" . $message . "" . ' (' . $error['source'] . ')';
                 }
                 $errorMsg = wordwrap($message, $maxErrorSpace, PHP_EOL . $errorPadding);
                 echo $errorMsg . PHP_EOL;
             }
             //end foreach
         }
         //end foreach
         echo str_repeat('-', $width) . PHP_EOL;
         echo rtrim($snippet) . PHP_EOL;
     }
     //end foreach
     echo str_repeat('-', $width) . PHP_EOL;
     if ($report['fixable'] > 0) {
         echo "" . 'PHPCBF CAN FIX THE ' . $report['fixable'] . ' MARKED SNIFF VIOLATIONS AUTOMATICALLY' . "" . PHP_EOL;
         echo str_repeat('-', $width) . PHP_EOL;
     }
     return true;
 }
Example #2
0
File: JS.php Project: thekabal/tki
 /**
  * Performs additional processing after main tokenizing.
  *
  * This additional processing looks for properties, closures, labels and objects.
  *
  * @return void
  */
 public function processAdditional()
 {
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t*** START ADDITIONAL JS PROCESSING ***" . PHP_EOL;
     }
     $numTokens = count($this->tokens);
     $classStack = array();
     for ($i = 0; $i < $numTokens; $i++) {
         if (PHP_CODESNIFFER_VERBOSITY > 1) {
             $type = $this->tokens[$i]['type'];
             $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
             echo str_repeat("\t", count($classStack));
             echo "\tProcess token {$i}: {$type} => {$content}" . PHP_EOL;
         }
         // Looking for functions that are actually closures.
         if ($this->tokens[$i]['code'] === T_FUNCTION && isset($this->tokens[$i]['scope_opener']) === true) {
             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[$i]['code'] = T_CLOSURE;
                 $this->tokens[$i]['type'] = 'T_CLOSURE';
                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                     $line = $this->tokens[$i]['line'];
                     echo str_repeat("\t", count($classStack));
                     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 str_repeat("\t", count($classStack));
                         echo "\t\t* cleaned {$x} ({$type}) *" . PHP_EOL;
                     }
                 }
             }
             //end if
             continue;
         } else {
             if ($this->tokens[$i]['code'] === T_OPEN_CURLY_BRACKET && isset($this->tokens[$i]['scope_condition']) === false) {
                 $classStack[] = $i;
                 $closer = $this->tokens[$i]['bracket_closer'];
                 $this->tokens[$i]['code'] = T_OBJECT;
                 $this->tokens[$i]['type'] = 'T_OBJECT';
                 $this->tokens[$closer]['code'] = T_CLOSE_OBJECT;
                 $this->tokens[$closer]['type'] = 'T_CLOSE_OBJECT';
                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                     echo str_repeat("\t", count($classStack));
                     echo "\t* token {$i} converted from T_OPEN_CURLY_BRACKET to T_OBJECT *" . PHP_EOL;
                     echo str_repeat("\t", count($classStack));
                     echo "\t* token {$closer} converted from T_CLOSE_CURLY_BRACKET to T_CLOSE_OBJECT *" . PHP_EOL;
                 }
                 for ($x = $i + 1; $x < $closer; $x++) {
                     $this->tokens[$x]['conditions'][$i] = T_OBJECT;
                     ksort($this->tokens[$x]['conditions'], SORT_NUMERIC);
                     if (PHP_CODESNIFFER_VERBOSITY > 1) {
                         $type = $this->tokens[$x]['type'];
                         echo str_repeat("\t", count($classStack));
                         echo "\t\t* added T_OBJECT condition to {$x} ({$type}) *" . PHP_EOL;
                     }
                 }
             } else {
                 if ($this->tokens[$i]['code'] === T_CLOSE_OBJECT) {
                     $opener = array_pop($classStack);
                 } else {
                     if ($this->tokens[$i]['code'] === T_COLON) {
                         // If it is a scope opener, it belongs to a
                         // DEFAULT or CASE statement.
                         if (isset($this->tokens[$i]['scope_condition']) === true) {
                             continue;
                         }
                         // Make sure this is not part of an inline IF statement.
                         for ($x = $i - 1; $x >= 0; $x--) {
                             if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
                                 $this->tokens[$i]['code'] = T_INLINE_ELSE;
                                 $this->tokens[$i]['type'] = 'T_INLINE_ELSE';
                                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                     echo str_repeat("\t", count($classStack));
                                     echo "\t* token {$i} converted from T_COLON to T_INLINE_THEN *" . PHP_EOL;
                                 }
                                 continue 2;
                             } else {
                                 if ($this->tokens[$x]['line'] < $this->tokens[$i]['line']) {
                                     break;
                                 }
                             }
                         }
                         // The string to the left of the colon is either a property or label.
                         for ($label = $i - 1; $label >= 0; $label--) {
                             if (isset(Util\Tokens::$emptyTokens[$this->tokens[$label]['code']]) === false) {
                                 break;
                             }
                         }
                         if ($this->tokens[$label]['code'] !== T_STRING && $this->tokens[$label]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
                             continue;
                         }
                         if (empty($classStack) === false) {
                             $this->tokens[$label]['code'] = T_PROPERTY;
                             $this->tokens[$label]['type'] = 'T_PROPERTY';
                             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                 echo str_repeat("\t", count($classStack));
                                 echo "\t* token {$label} converted from T_STRING to T_PROPERTY *" . PHP_EOL;
                             }
                         } else {
                             $this->tokens[$label]['code'] = T_LABEL;
                             $this->tokens[$label]['type'] = 'T_LABEL';
                             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                 echo str_repeat("\t", count($classStack));
                                 echo "\t* token {$label} converted from T_STRING to T_LABEL *" . PHP_EOL;
                             }
                         }
                         //end if
                     }
                 }
             }
         }
         //end if
     }
     //end for
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t*** END ADDITIONAL JS PROCESSING ***" . PHP_EOL;
     }
 }
Example #3
0
 /**
  * Reverts the previous fix made to a token.
  *
  * @param int $stackPtr The position of the token in the token stack.
  *
  * @return bool If a change was reverted.
  */
 public function revertToken($stackPtr)
 {
     if (isset($this->fixedTokens[$stackPtr]) === false) {
         return false;
     }
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         $bt = debug_backtrace();
         if ($bt[1]['class'] === 'PHP_CodeSniffer_Fixer') {
             $sniff = $bt[2]['class'];
             $line = $bt[1]['line'];
         } else {
             $sniff = $bt[1]['class'];
             $line = $bt[0]['line'];
         }
         $tokens = $this->currentFile->getTokens();
         $type = $tokens[$stackPtr]['type'];
         $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]);
         $newContent = Common::prepareForOutput($this->fixedTokens[$stackPtr]);
         if (trim($this->tokens[$stackPtr]) === '' && isset($tokens[$stackPtr + 1]) === true) {
             // Add some context for whitespace only changes.
             $append = Common::prepareForOutput($this->tokens[$stackPtr + 1]);
             $oldContent .= $append;
             $newContent .= $append;
         }
     }
     //end if
     $this->tokens[$stackPtr] = $this->fixedTokens[$stackPtr];
     unset($this->fixedTokens[$stackPtr]);
     $this->numFixes--;
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         $indent = "\t";
         if (empty($this->changeset) === false) {
             $indent .= "\tR: ";
         }
         @ob_end_clean();
         echo "{$indent}{$sniff} (line {$line}) reverted token {$stackPtr} ({$type}) \"{$oldContent}\" => \"{$newContent}\"" . PHP_EOL;
         ob_start();
     }
     return true;
 }
Example #4
0
File: CSS.php Project: thekabal/tki
 /**
  * Creates an array of tokens when given some CSS code.
  *
  * Uses the PHP tokenizer to do all the tricky work
  *
  * @param string $string The string to tokenize.
  *
  * @return array
  */
 public function tokenize($string)
 {
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t*** START CSS TOKENIZING 1ST PASS ***" . PHP_EOL;
     }
     // If the content doesn't have an EOL char on the end, add one so
     // the open and close tags we add are parsed correctly.
     $eolAdded = false;
     if (substr($string, strlen($this->eolChar) * -1) !== $this->eolChar) {
         $string .= $this->eolChar;
         $eolAdded = true;
     }
     $string = str_replace('<?php', '^PHPCS_CSS_T_OPEN_TAG^', $string);
     $string = str_replace('?>', '^PHPCS_CSS_T_CLOSE_TAG^', $string);
     $tokens = parent::tokenize('<?php ' . $string . '?>');
     $finalTokens = array();
     $finalTokens[0] = array('code' => T_OPEN_TAG, 'type' => 'T_OPEN_TAG', 'content' => '');
     $newStackPtr = 1;
     $numTokens = count($tokens);
     $multiLineComment = false;
     for ($stackPtr = 1; $stackPtr < $numTokens; $stackPtr++) {
         $token = $tokens[$stackPtr];
         // CSS files don't have lists, breaks etc, so convert these to
         // standard strings early so they can be converted into T_STYLE
         // tokens and joined with other strings if needed.
         if ($token['code'] === T_BREAK || $token['code'] === T_LIST || $token['code'] === T_DEFAULT || $token['code'] === T_SWITCH || $token['code'] === T_FOR || $token['code'] === T_FOREACH || $token['code'] === T_WHILE) {
             $token['type'] = 'T_STRING';
             $token['code'] = T_STRING;
         }
         if (PHP_CODESNIFFER_VERBOSITY > 1) {
             $type = $token['type'];
             $content = Util\Common::prepareForOutput($token['content']);
             echo "\tProcess token {$stackPtr}: {$type} => {$content}" . PHP_EOL;
         }
         if ($token['code'] === T_BITWISE_XOR && $tokens[$stackPtr + 1]['content'] === 'PHPCS_CSS_T_OPEN_TAG') {
             $content = '<?php';
             for ($stackPtr = $stackPtr + 3; $stackPtr < $numTokens; $stackPtr++) {
                 if ($tokens[$stackPtr]['code'] === T_BITWISE_XOR && $tokens[$stackPtr + 1]['content'] === 'PHPCS_CSS_T_CLOSE_TAG') {
                     // Add the end tag and ignore the * we put at the end.
                     $content .= '?>';
                     $stackPtr += 2;
                     break;
                 } else {
                     $content .= $tokens[$stackPtr]['content'];
                 }
             }
             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                 echo "\t\t=> Found embedded PHP code: ";
                 $cleanContent = Util\Common::prepareForOutput($content);
                 echo $cleanContent . PHP_EOL;
             }
             $finalTokens[$newStackPtr] = array('type' => 'T_EMBEDDED_PHP', 'code' => T_EMBEDDED_PHP, 'content' => $content);
             $newStackPtr++;
             continue;
         }
         //end if
         if ($token['code'] === T_GOTO_LABEL) {
             // Convert these back to T_STRING followed by T_COLON so we can
             // more easily process style definitions.
             $finalTokens[$newStackPtr] = array('type' => 'T_STRING', 'code' => T_STRING, 'content' => substr($token['content'], 0, -1));
             $newStackPtr++;
             $finalTokens[$newStackPtr] = array('type' => 'T_COLON', 'code' => T_COLON, 'content' => ':');
             $newStackPtr++;
             continue;
         }
         if ($token['code'] === T_FUNCTION) {
             // There are no functions in CSS, so convert this to a string.
             $finalTokens[$newStackPtr] = array('type' => 'T_STRING', 'code' => T_STRING, 'content' => $token['content']);
             $newStackPtr++;
             continue;
         }
         if ($token['code'] === T_COMMENT && substr($token['content'], 0, 2) === '/*') {
             // Multi-line comment. Record it so we can ignore other
             // comment tags until we get out of this one.
             $multiLineComment = true;
         }
         if ($token['code'] === T_COMMENT && $multiLineComment === false && (substr($token['content'], 0, 2) === '//' || $token['content'][0] === '#')) {
             $content = ltrim($token['content'], '#/');
             // Guard against PHP7+ syntax errors by stripping
             // leading zeros so the content doesn't look like an invalid int.
             $leadingZero = false;
             if ($content[0] === '0') {
                 $content = '1' . $content;
                 $leadingZero = true;
             }
             $commentTokens = parent::tokenize('<?php ' . $content . '?>');
             // The first and last tokens are the open/close tags.
             array_shift($commentTokens);
             array_pop($commentTokens);
             if ($leadingZero === true) {
                 $commentTokens[0]['content'] = substr($commentTokens[0]['content'], 1);
                 $content = substr($content, 1);
             }
             if ($token['content'][0] === '#') {
                 // The # character is not a comment in CSS files, so
                 // determine what it means in this context.
                 $firstContent = $commentTokens[0]['content'];
                 // If the first content is just a number, it is probably a
                 // colour like 8FB7DB, which PHP splits into 8 and FB7DB.
                 if (($commentTokens[0]['code'] === T_LNUMBER || $commentTokens[0]['code'] === T_DNUMBER) && $commentTokens[1]['code'] === T_STRING) {
                     $firstContent .= $commentTokens[1]['content'];
                     array_shift($commentTokens);
                 }
                 // If the first content looks like a colour and not a class
                 // definition, join the tokens together.
                 if (preg_match('/^[ABCDEF0-9]+$/i', $firstContent) === 1 && $commentTokens[1]['content'] !== '-') {
                     array_shift($commentTokens);
                     // Work out what we trimmed off above and remember to re-add it.
                     $trimmed = substr($token['content'], 0, strlen($token['content']) - strlen($content));
                     $finalTokens[$newStackPtr] = array('type' => 'T_COLOUR', 'code' => T_COLOUR, 'content' => $trimmed . $firstContent);
                 } else {
                     $finalTokens[$newStackPtr] = array('type' => 'T_HASH', 'code' => T_HASH, 'content' => '#');
                 }
             } else {
                 $finalTokens[$newStackPtr] = array('type' => 'T_STRING', 'code' => T_STRING, 'content' => '//');
             }
             //end if
             $newStackPtr++;
             array_splice($tokens, $stackPtr, 1, $commentTokens);
             $numTokens = count($tokens);
             $stackPtr--;
             continue;
         }
         //end if
         if ($token['code'] === T_COMMENT && substr($token['content'], -2) === '*/') {
             // Multi-line comment is done.
             $multiLineComment = false;
         }
         $finalTokens[$newStackPtr] = $token;
         $newStackPtr++;
     }
     //end for
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t*** END CSS TOKENIZING 1ST PASS ***" . PHP_EOL;
         echo "\t*** START CSS TOKENIZING 2ND PASS ***" . PHP_EOL;
     }
     // A flag to indicate if we are inside a style definition,
     // which is defined using curly braces.
     $inStyleDef = false;
     // A flag to indicate if an At-rule like "@media" is used, which will result
     // in nested curly brackets.
     $asperandStart = false;
     $numTokens = count($finalTokens);
     for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
         $token = $finalTokens[$stackPtr];
         if (PHP_CODESNIFFER_VERBOSITY > 1) {
             $type = $token['type'];
             $content = Util\Common::prepareForOutput($token['content']);
             echo "\tProcess token {$stackPtr}: {$type} => {$content}" . PHP_EOL;
         }
         switch ($token['code']) {
             case T_OPEN_CURLY_BRACKET:
                 // Opening curly brackets for an At-rule do not start a style
                 // definition. We also reset the asperand flag here because the next
                 // opening curly bracket could be indeed the start of a style
                 // definition.
                 if ($asperandStart === true) {
                     if (PHP_CODESNIFFER_VERBOSITY > 1) {
                         if ($inStyleDef === true) {
                             echo "\t\t* style definition closed *" . PHP_EOL;
                         }
                         if ($asperandStart === true) {
                             echo "\t\t* at-rule definition closed *" . PHP_EOL;
                         }
                     }
                     $inStyleDef = false;
                     $asperandStart = false;
                 } else {
                     $inStyleDef = true;
                     if (PHP_CODESNIFFER_VERBOSITY > 1) {
                         echo "\t\t* style definition opened *" . PHP_EOL;
                     }
                 }
                 break;
             case T_CLOSE_CURLY_BRACKET:
                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                     if ($inStyleDef === true) {
                         echo "\t\t* style definition closed *" . PHP_EOL;
                     }
                     if ($asperandStart === true) {
                         echo "\t\t* at-rule definition closed *" . PHP_EOL;
                     }
                 }
                 $inStyleDef = false;
                 $asperandStart = false;
                 break;
             case T_MINUS:
                 // Minus signs are often used instead of spaces inside
                 // class names, IDs and styles.
                 if ($finalTokens[$stackPtr + 1]['code'] === T_STRING) {
                     if ($finalTokens[$stackPtr - 1]['code'] === T_STRING) {
                         $newContent = $finalTokens[$stackPtr - 1]['content'] . '-' . $finalTokens[$stackPtr + 1]['content'];
                         if (PHP_CODESNIFFER_VERBOSITY > 1) {
                             echo "\t\t* token is a string joiner; ignoring this and previous token" . PHP_EOL;
                             $old = Util\Common::prepareForOutput($finalTokens[$stackPtr + 1]['content']);
                             $new = Util\Common::prepareForOutput($newContent);
                             echo "\t\t=> token " . ($stackPtr + 1) . " content changed from \"{$old}\" to \"{$new}\"" . PHP_EOL;
                         }
                         $finalTokens[$stackPtr + 1]['content'] = $newContent;
                         unset($finalTokens[$stackPtr]);
                         unset($finalTokens[$stackPtr - 1]);
                     } else {
                         $newContent = '-' . $finalTokens[$stackPtr + 1]['content'];
                         $finalTokens[$stackPtr + 1]['content'] = $newContent;
                         unset($finalTokens[$stackPtr]);
                     }
                 } else {
                     if ($finalTokens[$stackPtr + 1]['code'] === T_LNUMBER) {
                         // They can also be used to provide negative numbers.
                         if (PHP_CODESNIFFER_VERBOSITY > 1) {
                             echo "\t\t* token is part of a negative number; adding content to next token and ignoring *" . PHP_EOL;
                             $content = Util\Common::prepareForOutput($finalTokens[$stackPtr + 1]['content']);
                             echo "\t\t=> token " . ($stackPtr + 1) . " content changed from \"{$content}\" to \"-{$content}\"" . PHP_EOL;
                         }
                         $finalTokens[$stackPtr + 1]['content'] = '-' . $finalTokens[$stackPtr + 1]['content'];
                         unset($finalTokens[$stackPtr]);
                     }
                 }
                 //end if
                 break;
             case T_COLON:
                 // Only interested in colons that are defining styles.
                 if ($inStyleDef === false) {
                     break;
                 }
                 for ($x = $stackPtr - 1; $x >= 0; $x--) {
                     if (isset(Util\Tokens::$emptyTokens[$finalTokens[$x]['code']]) === false) {
                         break;
                     }
                 }
                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                     $type = $finalTokens[$x]['type'];
                     echo "\t\t=> token {$x} changed from {$type} to T_STYLE" . PHP_EOL;
                 }
                 $finalTokens[$x]['type'] = 'T_STYLE';
                 $finalTokens[$x]['code'] = T_STYLE;
                 break;
             case T_STRING:
                 if (strtolower($token['content']) === 'url') {
                     // Find the next content.
                     for ($x = $stackPtr + 1; $x < $numTokens; $x++) {
                         if (isset(Util\Tokens::$emptyTokens[$finalTokens[$x]['code']]) === false) {
                             break;
                         }
                     }
                     // Needs to be in the format "url(" for it to be a URL.
                     if ($finalTokens[$x]['code'] !== T_OPEN_PARENTHESIS) {
                         continue;
                     }
                     // Make sure the content isn't empty.
                     for ($y = $x + 1; $y < $numTokens; $y++) {
                         if (isset(Util\Tokens::$emptyTokens[$finalTokens[$y]['code']]) === false) {
                             break;
                         }
                     }
                     if ($finalTokens[$y]['code'] === T_CLOSE_PARENTHESIS) {
                         continue;
                     }
                     if (PHP_CODESNIFFER_VERBOSITY > 1) {
                         for ($i = $stackPtr + 1; $i <= $y; $i++) {
                             $type = $finalTokens[$i]['type'];
                             $content = Util\Common::prepareForOutput($finalTokens[$i]['content']);
                             echo "\tProcess token {$i}: {$type} => {$content}" . PHP_EOL;
                         }
                         echo "\t\t* token starts a URL *" . PHP_EOL;
                     }
                     // Join all the content together inside the url() statement.
                     $newContent = '';
                     for ($i = $x + 2; $i < $numTokens; $i++) {
                         if ($finalTokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
                             break;
                         }
                         $newContent .= $finalTokens[$i]['content'];
                         if (PHP_CODESNIFFER_VERBOSITY > 1) {
                             $content = Util\Common::prepareForOutput($finalTokens[$i]['content']);
                             echo "\t\t=> token {$i} added to URL string and ignored: {$content}" . PHP_EOL;
                         }
                         unset($finalTokens[$i]);
                     }
                     $stackPtr = $i;
                     // If the content inside the "url()" is in double quotes
                     // there will only be one token and so we don't have to do
                     // anything except change its type. If it is not empty,
                     // we need to do some token merging.
                     $finalTokens[$x + 1]['type'] = 'T_URL';
                     $finalTokens[$x + 1]['code'] = T_URL;
                     if ($newContent !== '') {
                         $finalTokens[$x + 1]['content'] .= $newContent;
                         if (PHP_CODESNIFFER_VERBOSITY > 1) {
                             $content = Util\Common::prepareForOutput($finalTokens[$x + 1]['content']);
                             echo "\t\t=> token content changed to: {$content}" . PHP_EOL;
                         }
                     }
                 }
                 //end if
                 break;
             case T_ASPERAND:
                 $asperandStart = true;
                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                     echo "\t\t* at-rule definition opened *" . PHP_EOL;
                 }
                 break;
             default:
                 // Nothing special to be done with this token.
                 break;
         }
         //end switch
     }
     //end for
     // Reset the array keys to avoid gaps.
     $finalTokens = array_values($finalTokens);
     $numTokens = count($finalTokens);
     // Blank out the content of the end tag.
     $finalTokens[$numTokens - 1]['content'] = '';
     if ($eolAdded === true) {
         // Strip off the extra EOL char we added for tokenizing.
         $finalTokens[$numTokens - 2]['content'] = substr($finalTokens[$numTokens - 2]['content'], 0, strlen($this->eolChar) * -1);
         if ($finalTokens[$numTokens - 2]['content'] === '') {
             unset($finalTokens[$numTokens - 2]);
             $finalTokens = array_values($finalTokens);
             $numTokens = count($finalTokens);
         }
     }
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t*** END CSS TOKENIZING 2ND PASS ***" . PHP_EOL;
     }
     return $finalTokens;
 }
Example #5
0
File: PHP.php Project: thekabal/tki
 /**
  * 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 = array();
     $newStackPtr = 0;
     $numTokens = count($tokens);
     $lastNotEmptyToken = 0;
     $insideInlineIf = array();
     $insideUseGroup = false;
     $commentTokenizer = new Comment();
     for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
         $token = (array) $tokens[$stackPtr];
         $tokenIsArray = isset($token[1]);
         if (PHP_CODESNIFFER_VERBOSITY > 1) {
             if ($tokenIsArray === true) {
                 $type = token_name($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 "\\n";
                     }
                 }
                 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) {
             $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] = array('code' => T_BINARY_CAST, 'type' => 'T_BINARY_CAST', 'content' => 'b');
                 $newStackPtr++;
             }
             $tokenContent = '"';
             $nestedVars = array();
             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 = array();
             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
         /*
             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 ($token[1][3] === "'") {
                 $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 = array();
             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';
                 $nowdoc = true;
             }
             $newStackPtr++;
             // Continue, as we're done with this token.
             continue;
         }
         //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 = array();
             $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 = array();
             $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 = array();
             $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 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 = array();
             $newToken['code'] = T_COALESCE;
             $newToken['type'] = 'T_COALESCE';
             $newToken['content'] = '??';
             $finalTokens[$newStackPtr] = $newToken;
             $newStackPtr++;
             $stackPtr++;
             continue;
         }
         /*
             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 = array();
             $newToken['code'] = T_SPACESHIP;
             $newToken['type'] = 'T_SPACESHIP';
             $newToken['content'] = '<=>';
             $finalTokens[$newStackPtr] = $newToken;
             $newStackPtr++;
             $stackPtr++;
             continue;
         }
         /*
             Emulate traits in PHP versions less than 5.4.
         */
         if ($tokenIsArray === true && $token[0] === T_STRING && strtolower($token[1]) === 'trait' && $tokens[$stackPtr - 1][0] !== T_OBJECT_OPERATOR) {
             $finalTokens[$newStackPtr] = array('content' => $token[1], 'code' => T_TRAIT, 'type' => 'T_TRAIT');
             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                 echo "\t\t* token {$stackPtr} changed from T_STRING to T_TRAIT" . PHP_EOL;
             }
             $newStackPtr++;
             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 = array(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] = array('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
         /*
             HHVM 3.5 tokenizes "else[\s]+if" as a T_ELSEIF token while PHP
             proper only tokenizes "elseif" as a T_ELSEIF token. So split
             up the HHVM token to make it looks like proper PHP.
         */
         if ($tokenIsArray === true && $token[0] === T_ELSEIF && strtolower($token[1]) !== 'elseif') {
             $finalTokens[$newStackPtr] = array('content' => substr($token[1], 0, 4), 'code' => T_ELSE, 'type' => 'T_ELSE');
             $newStackPtr++;
             $finalTokens[$newStackPtr] = array('content' => substr($token[1], 4, -2), 'code' => T_WHITESPACE, 'type' => 'T_WHITESPACE');
             $newStackPtr++;
             $finalTokens[$newStackPtr] = array('content' => substr($token[1], -2), 'code' => T_IF, 'type' => 'T_IF');
             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                 echo "\t\t* token {$stackPtr} changed from T_ELSEIF to T_ELSE/T_WHITESPACE/T_IF" . PHP_EOL;
             }
             $newStackPtr++;
             continue;
         }
         //end if
         /*
             HHVM 3.5 and 3.6 tokenizes a hashbang line such as #!/usr/bin/php
             as T_HASHBANG while PHP proper uses T_INLINE_HTML.
         */
         if ($tokenIsArray === true && token_name($token[0]) === 'T_HASHBANG') {
             $finalTokens[$newStackPtr] = array('content' => $token[1], 'code' => T_INLINE_HTML, 'type' => 'T_INLINE_HTML');
             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                 echo "\t\t* token {$stackPtr} changed from T_HASHBANG to T_INLINE_HTML" . PHP_EOL;
             }
             $newStackPtr++;
             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 = array('type' => token_name($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 = array(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) {
                     $finalTokens[$newStackPtr] = array('content' => $token[1], 'code' => T_STRING, 'type' => 'T_STRING');
                     $newStackPtr++;
                     continue;
                 }
             }
             //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 ($newToken['code'] === T_INLINE_THEN) {
                 $insideInlineIf[] = $stackPtr;
             } else {
                 if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
                     array_pop($insideInlineIf);
                     $newToken['code'] = T_INLINE_ELSE;
                     $newToken['type'] = 'T_INLINE_ELSE';
                 }
             }
             // 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_ARRAY_HINT.
             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_ARRAY_HINT;
                             $newToken['type'] = 'T_ARRAY_HINT';
                             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 && $finalTokens[$newStackPtr - 1]['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) {
                 $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;
 }
Example #6
0
 /**
  * Constructs the level map.
  *
  * The level map adds a 'level' index to each token which indicates the
  * depth that a token within a set of scope blocks. It also adds a
  * 'condition' index which is an array of the scope conditions that opened
  * each of the scopes - position 0 being the first scope opener.
  *
  * @return void
  */
 private function createLevelMap()
 {
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t*** START LEVEL MAP ***" . PHP_EOL;
     }
     $this->numTokens = count($this->tokens);
     $level = 0;
     $conditions = array();
     $lastOpener = null;
     $openers = array();
     for ($i = 0; $i < $this->numTokens; $i++) {
         if (PHP_CODESNIFFER_VERBOSITY > 1) {
             $type = $this->tokens[$i]['type'];
             $line = $this->tokens[$i]['line'];
             $len = $this->tokens[$i]['length'];
             $col = $this->tokens[$i]['column'];
             $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
             echo str_repeat("\t", $level + 1);
             echo "Process token {$i} on line {$line} [col:{$col};len:{$len};lvl:{$level};";
             if (empty($conditions) !== true) {
                 $condString = 'conds;';
                 foreach ($conditions as $condition) {
                     $condString .= token_name($condition) . ',';
                 }
                 echo rtrim($condString, ',') . ';';
             }
             echo "]: {$type} => {$content}" . PHP_EOL;
         }
         //end if
         $this->tokens[$i]['level'] = $level;
         $this->tokens[$i]['conditions'] = $conditions;
         if (isset($this->tokens[$i]['scope_condition']) === true) {
             // Check to see if this token opened the scope.
             if ($this->tokens[$i]['scope_opener'] === $i) {
                 $stackPtr = $this->tokens[$i]['scope_condition'];
                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                     $type = $this->tokens[$stackPtr]['type'];
                     echo str_repeat("\t", $level + 1);
                     echo "=> Found scope opener for {$stackPtr}:{$type}" . PHP_EOL;
                 }
                 $stackPtr = $this->tokens[$i]['scope_condition'];
                 // If we find a scope opener that has a shared closer,
                 // then we need to go back over the condition map that we
                 // just created and fix ourselves as we just added some
                 // conditions where there was none. This happens for T_CASE
                 // statements that are using the same break statement.
                 if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $this->tokens[$i]['scope_closer']) {
                     // This opener shares its closer with the previous opener,
                     // but we still need to check if the two openers share their
                     // closer with each other directly (like CASE and DEFAULT)
                     // or if they are just sharing because one doesn't have a
                     // closer (like CASE with no BREAK using a SWITCHes closer).
                     $thisType = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
                     $opener = $this->tokens[$lastOpener]['scope_condition'];
                     $isShared = isset($this->scopeOpeners[$thisType]['with'][$this->tokens[$opener]['code']]);
                     reset($this->scopeOpeners[$thisType]['end']);
                     reset($this->scopeOpeners[$this->tokens[$opener]['code']]['end']);
                     $sameEnd = current($this->scopeOpeners[$thisType]['end']) === current($this->scopeOpeners[$this->tokens[$opener]['code']]['end']);
                     if ($isShared === true && $sameEnd === true) {
                         $badToken = $opener;
                         if (PHP_CODESNIFFER_VERBOSITY > 1) {
                             $type = $this->tokens[$badToken]['type'];
                             echo str_repeat("\t", $level + 1);
                             echo "* shared closer, cleaning up {$badToken}:{$type} *" . PHP_EOL;
                         }
                         for ($x = $this->tokens[$i]['scope_condition']; $x <= $i; $x++) {
                             $oldConditions = $this->tokens[$x]['conditions'];
                             $oldLevel = $this->tokens[$x]['level'];
                             $this->tokens[$x]['level']--;
                             unset($this->tokens[$x]['conditions'][$badToken]);
                             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                 $type = $this->tokens[$x]['type'];
                                 $oldConds = '';
                                 foreach ($oldConditions as $condition) {
                                     $oldConds .= token_name($condition) . ',';
                                 }
                                 $oldConds = rtrim($oldConds, ',');
                                 $newConds = '';
                                 foreach ($this->tokens[$x]['conditions'] as $condition) {
                                     $newConds .= token_name($condition) . ',';
                                 }
                                 $newConds = rtrim($newConds, ',');
                                 $newLevel = $this->tokens[$x]['level'];
                                 echo str_repeat("\t", $level + 1);
                                 echo "* cleaned {$x}:{$type} *" . PHP_EOL;
                                 echo str_repeat("\t", $level + 2);
                                 echo "=> level changed from {$oldLevel} to {$newLevel}" . PHP_EOL;
                                 echo str_repeat("\t", $level + 2);
                                 echo "=> conditions changed from {$oldConds} to {$newConds}" . PHP_EOL;
                             }
                             //end if
                         }
                         //end for
                         unset($conditions[$badToken]);
                         if (PHP_CODESNIFFER_VERBOSITY > 1) {
                             $type = $this->tokens[$badToken]['type'];
                             echo str_repeat("\t", $level + 1);
                             echo "* token {$badToken}:{$type} removed from conditions array *" . PHP_EOL;
                         }
                         unset($openers[$lastOpener]);
                         $level--;
                         if (PHP_CODESNIFFER_VERBOSITY > 1) {
                             echo str_repeat("\t", $level + 2);
                             echo '* level decreased *' . PHP_EOL;
                         }
                     }
                     //end if
                 }
                 //end if
                 $level++;
                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                     echo str_repeat("\t", $level + 1);
                     echo '* level increased *' . PHP_EOL;
                 }
                 $conditions[$stackPtr] = $this->tokens[$stackPtr]['code'];
                 if (PHP_CODESNIFFER_VERBOSITY > 1) {
                     $type = $this->tokens[$stackPtr]['type'];
                     echo str_repeat("\t", $level + 1);
                     echo "* token {$stackPtr}:{$type} added to conditions array *" . PHP_EOL;
                 }
                 $lastOpener = $this->tokens[$i]['scope_opener'];
                 if ($lastOpener !== null) {
                     $openers[$lastOpener] = $lastOpener;
                 }
             } else {
                 if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $i) {
                     foreach (array_reverse($openers) as $opener) {
                         if ($this->tokens[$opener]['scope_closer'] === $i) {
                             $oldOpener = array_pop($openers);
                             if (empty($openers) === false) {
                                 $lastOpener = array_pop($openers);
                                 $openers[$lastOpener] = $lastOpener;
                             } else {
                                 $lastOpener = null;
                             }
                             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                 $type = $this->tokens[$oldOpener]['type'];
                                 echo str_repeat("\t", $level + 1);
                                 echo "=> Found scope closer for {$oldOpener}:{$type}" . PHP_EOL;
                             }
                             $oldCondition = array_pop($conditions);
                             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                 echo str_repeat("\t", $level + 1);
                                 echo '* token ' . token_name($oldCondition) . ' removed from conditions array *' . PHP_EOL;
                             }
                             // Make sure this closer actually belongs to us.
                             // Either the condition also has to think this is the
                             // closer, or it has to allow sharing with us.
                             $condition = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
                             if ($condition !== $oldCondition) {
                                 if (isset($this->scopeOpeners[$oldCondition]['with'][$condition]) === false) {
                                     $badToken = $this->tokens[$oldOpener]['scope_condition'];
                                     if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                         $type = token_name($oldCondition);
                                         echo str_repeat("\t", $level + 1);
                                         echo "* scope closer was bad, cleaning up {$badToken}:{$type} *" . PHP_EOL;
                                     }
                                     for ($x = $oldOpener + 1; $x <= $i; $x++) {
                                         $oldConditions = $this->tokens[$x]['conditions'];
                                         $oldLevel = $this->tokens[$x]['level'];
                                         $this->tokens[$x]['level']--;
                                         unset($this->tokens[$x]['conditions'][$badToken]);
                                         if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                             $type = $this->tokens[$x]['type'];
                                             $oldConds = '';
                                             foreach ($oldConditions as $condition) {
                                                 $oldConds .= token_name($condition) . ',';
                                             }
                                             $oldConds = rtrim($oldConds, ',');
                                             $newConds = '';
                                             foreach ($this->tokens[$x]['conditions'] as $condition) {
                                                 $newConds .= token_name($condition) . ',';
                                             }
                                             $newConds = rtrim($newConds, ',');
                                             $newLevel = $this->tokens[$x]['level'];
                                             echo str_repeat("\t", $level + 1);
                                             echo "* cleaned {$x}:{$type} *" . PHP_EOL;
                                             echo str_repeat("\t", $level + 2);
                                             echo "=> level changed from {$oldLevel} to {$newLevel}" . PHP_EOL;
                                             echo str_repeat("\t", $level + 2);
                                             echo "=> conditions changed from {$oldConds} to {$newConds}" . PHP_EOL;
                                         }
                                         //end if
                                     }
                                     //end for
                                 }
                                 //end if
                             }
                             //end if
                             $level--;
                             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                                 echo str_repeat("\t", $level + 2);
                                 echo '* level decreased *' . PHP_EOL;
                             }
                             $this->tokens[$i]['level'] = $level;
                             $this->tokens[$i]['conditions'] = $conditions;
                         }
                         //end if
                     }
                     //end foreach
                 }
             }
             //end if
         }
         //end if
     }
     //end for
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t*** END LEVEL MAP ***" . PHP_EOL;
     }
 }
Example #7
0
 /**
  * Starts the stack traversal and tells listeners when tokens are found.
  *
  * @return void
  */
 public function process()
 {
     if ($this->ignored === true) {
         return;
     }
     $this->errors = array();
     $this->warnings = array();
     $this->errorCount = 0;
     $this->warningCount = 0;
     $this->fixableCount = 0;
     $this->parse();
     $this->fixer->startFile($this);
     if (PHP_CODESNIFFER_VERBOSITY > 2) {
         echo "\t*** START TOKEN PROCESSING ***" . PHP_EOL;
     }
     $foundCode = false;
     $listenerIgnoreTo = array();
     $inTests = defined('PHP_CODESNIFFER_IN_TESTS');
     // Foreach of the listeners that have registered to listen for this
     // token, get them to process it.
     foreach ($this->tokens as $stackPtr => $token) {
         // Check for ignored lines.
         if ($token['code'] === T_COMMENT || $token['code'] === T_DOC_COMMENT_TAG || $inTests === true && $token['code'] === T_INLINE_HTML) {
             if (strpos($token['content'], '@codingStandards') !== false) {
                 if (strpos($token['content'], '@codingStandardsIgnoreFile') !== false) {
                     // Ignoring the whole file, just a little late.
                     $this->errors = array();
                     $this->warnings = array();
                     $this->errorCount = 0;
                     $this->warningCount = 0;
                     $this->fixableCount = 0;
                     return;
                 } else {
                     if (strpos($token['content'], '@codingStandardsChangeSetting') !== false) {
                         $start = strpos($token['content'], '@codingStandardsChangeSetting');
                         $comment = substr($token['content'], $start + 30);
                         $parts = explode(' ', $comment);
                         if ($parts >= 3) {
                             $sniffParts = explode('.', $parts[0]);
                             if ($sniffParts >= 3) {
                                 // If the sniff code is not know to us, it has not been registered in this run.
                                 // But don't throw an error as it could be there for a different standard to use.
                                 if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
                                     $listenerCode = array_shift($parts);
                                     $propertyCode = array_shift($parts);
                                     $propertyValue = rtrim(implode(' ', $parts), " */\r\n");
                                     $listenerClass = $this->ruleset->sniffCodes[$listenerCode];
                                     $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
                                 }
                             }
                         }
                     }
                 }
                 //end if
             }
             //end if
         }
         //end if
         if (PHP_CODESNIFFER_VERBOSITY > 2) {
             $type = $token['type'];
             $content = Util\Common::prepareForOutput($token['content']);
             echo "\t\tProcess token {$stackPtr}: {$type} => {$content}" . PHP_EOL;
         }
         if ($token['code'] !== T_INLINE_HTML) {
             $foundCode = true;
         }
         if (isset($this->ruleset->tokenListeners[$token['code']]) === false) {
             continue;
         }
         foreach ($this->ruleset->tokenListeners[$token['code']] as $listenerData) {
             if (isset($this->ignoredListeners[$listenerData['class']]) === true || isset($listenerIgnoreTo[$listenerData['class']]) === true && $listenerIgnoreTo[$listenerData['class']] > $stackPtr) {
                 // This sniff is ignoring past this token, or the whole file.
                 continue;
             }
             // Make sure this sniff supports the tokenizer
             // we are currently using.
             $class = $listenerData['class'];
             if (isset($listenerData['tokenizers'][$this->tokenizerType]) === false) {
                 continue;
             }
             // If the file path matches one of our ignore patterns, skip it.
             // While there is support for a type of each pattern
             // (absolute or relative) we don't actually support it here.
             foreach ($listenerData['ignore'] as $pattern) {
                 // We assume a / directory separator, as do the exclude rules
                 // most developers write, so we need a special case for any system
                 // that is different.
                 if (DIRECTORY_SEPARATOR === '\\') {
                     $pattern = str_replace('/', '\\\\', $pattern);
                 }
                 $pattern = '`' . $pattern . '`i';
                 if (preg_match($pattern, $this->path) === 1) {
                     $this->ignoredListeners[$class] = true;
                     continue 2;
                 }
             }
             // If the file path does not match one of our include patterns, skip it.
             // While there is support for a type of each pattern
             // (absolute or relative) we don't actually support it here.
             foreach ($listenerData['include'] as $pattern) {
                 // We assume a / directory separator, as do the exclude rules
                 // most developers write, so we need a special case for any system
                 // that is different.
                 if (DIRECTORY_SEPARATOR === '\\') {
                     $pattern = str_replace('/', '\\\\', $pattern);
                 }
                 $pattern = '`' . $pattern . '`i';
                 if (preg_match($pattern, $this->path) !== 1) {
                     $this->ignoredListeners[$class] = true;
                     continue 2;
                 }
             }
             $this->activeListener = $class;
             if (PHP_CODESNIFFER_VERBOSITY > 2) {
                 $startTime = microtime(true);
                 echo "\t\t\tProcessing " . $this->activeListener . '... ';
             }
             $ignoreTo = $this->ruleset->sniffs[$class]->process($this, $stackPtr);
             if ($ignoreTo !== null) {
                 $listenerIgnoreTo[$this->activeListener] = $ignoreTo;
             }
             if (PHP_CODESNIFFER_VERBOSITY > 2) {
                 $timeTaken = microtime(true) - $startTime;
                 if (isset($this->listenerTimes[$this->activeListener]) === false) {
                     $this->listenerTimes[$this->activeListener] = 0;
                 }
                 $this->listenerTimes[$this->activeListener] += $timeTaken;
                 $timeTaken = round($timeTaken, 4);
                 echo "DONE in {$timeTaken} seconds" . PHP_EOL;
             }
             $this->activeListener = '';
         }
         //end foreach
     }
     //end foreach
     // If short open tags are off but the file being checked uses
     // short open tags, the whole content will be inline HTML
     // and nothing will be checked. So try and handle this case.
     if ($foundCode === false && $this->tokenizerType === 'PHP') {
         $shortTags = (bool) ini_get('short_open_tag');
         if ($shortTags === false) {
             $error = 'No PHP code was found in this file and short open tags are not allowed by this install of PHP. This file may be using short open tags but PHP does not allow them.';
             $this->addWarning($error, null, 'Internal.NoCodeFound');
         }
     }
     if (PHP_CODESNIFFER_VERBOSITY > 2) {
         echo "\t*** END TOKEN PROCESSING ***" . PHP_EOL;
         echo "\t*** START SNIFF PROCESSING REPORT ***" . PHP_EOL;
         asort($this->listenerTimes, SORT_NUMERIC);
         $this->listenerTimes = array_reverse($this->listenerTimes, true);
         foreach ($this->listenerTimes as $listener => $timeTaken) {
             echo "\t{$listener}: " . round($timeTaken, 4) . ' secs' . PHP_EOL;
         }
         echo "\t*** END SNIFF PROCESSING REPORT ***" . PHP_EOL;
     }
     $this->fixedCount += $this->fixer->getFixCount();
 }
Example #8
0
 /**
  * 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.
  * @param string $eolChar  The EOL character to use for splitting strings.
  * @param int    $stackPtr The position of the first token in the file.
  *
  * @return array
  */
 public function tokenizeString($string, $eolChar, $stackPtr)
 {
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t\t*** START COMMENT TOKENIZING ***" . PHP_EOL;
     }
     $tokens = array();
     $numChars = strlen($string);
     /*
         Doc block comments start with /*, but typically contain an
         extra star when they are used for function and class comments.
     */
     $char = $numChars - strlen(ltrim($string, '/*'));
     $openTag = substr($string, 0, $char);
     $string = ltrim($string, '/*');
     $tokens[$stackPtr] = array('content' => $openTag, 'code' => T_DOC_COMMENT_OPEN_TAG, 'type' => 'T_DOC_COMMENT_OPEN_TAG', 'comment_tags' => array());
     $openPtr = $stackPtr;
     $stackPtr++;
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         $content = Util\Common::prepareForOutput($openTag);
         echo "\t\tCreate comment token: T_DOC_COMMENT_OPEN_TAG => {$content}" . PHP_EOL;
     }
     /*
         Strip off the close tag so it doesn't interfere with any
         of our comment line processing. The token will be added to the
         stack just before we return it.
     */
     $closeTag = array('content' => substr($string, strlen(rtrim($string, '/*'))), 'code' => T_DOC_COMMENT_CLOSE_TAG, 'type' => 'T_DOC_COMMENT_CLOSE_TAG', 'comment_opener' => $openPtr);
     $string = rtrim($string, '/*');
     /*
         Process each line of the comment.
     */
     $lines = explode($eolChar, $string);
     $numLines = count($lines);
     foreach ($lines as $lineNum => $string) {
         if ($lineNum !== $numLines - 1) {
             $string .= $eolChar;
         }
         $char = 0;
         $numChars = strlen($string);
         // We've started a new line, so process the indent.
         $space = $this->collectWhitespace($string, $char, $numChars);
         if ($space !== null) {
             $tokens[$stackPtr] = $space;
             $stackPtr++;
             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                 $content = Util\Common::prepareForOutput($space['content']);
                 echo "\t\tCreate comment token: T_DOC_COMMENT_WHITESPACE => {$content}" . PHP_EOL;
             }
             $char += strlen($space['content']);
             if ($char === $numChars) {
                 break;
             }
         }
         if ($string === '') {
             continue;
         }
         if ($string[$char] === '*') {
             // This is a function or class doc block line.
             $char++;
             $tokens[$stackPtr] = array('content' => '*', 'code' => T_DOC_COMMENT_STAR, 'type' => 'T_DOC_COMMENT_STAR');
             $stackPtr++;
             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                 echo "\t\tCreate comment token: T_DOC_COMMENT_STAR => *" . PHP_EOL;
             }
         }
         // Now we are ready to process the actual content of the line.
         $lineTokens = $this->processLine($string, $eolChar, $char, $numChars);
         foreach ($lineTokens as $lineToken) {
             $tokens[$stackPtr] = $lineToken;
             if (PHP_CODESNIFFER_VERBOSITY > 1) {
                 $content = Util\Common::prepareForOutput($lineToken['content']);
                 $type = $lineToken['type'];
                 echo "\t\tCreate comment token: {$type} => {$content}" . PHP_EOL;
             }
             if ($lineToken['code'] === T_DOC_COMMENT_TAG) {
                 $tokens[$openPtr]['comment_tags'][] = $stackPtr;
             }
             $stackPtr++;
         }
     }
     //end foreach
     $tokens[$stackPtr] = $closeTag;
     $tokens[$openPtr]['comment_closer'] = $stackPtr;
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         $content = Util\Common::prepareForOutput($closeTag['content']);
         echo "\t\tCreate comment token: T_DOC_COMMENT_CLOSE_TAG => {$content}" . PHP_EOL;
     }
     if (PHP_CODESNIFFER_VERBOSITY > 1) {
         echo "\t\t*** END COMMENT TOKENIZING ***" . PHP_EOL;
     }
     return $tokens;
 }