Chris@0: Chris@0: * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) Chris@0: * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence Chris@0: * @link http://pear.php.net/package/PHP_CodeSniffer Chris@0: */ Chris@0: Chris@0: /** Chris@0: * A helper class for fixing errors. Chris@0: * Chris@0: * Provides helper functions that act upon a token array and modify the file Chris@0: * content. Chris@0: * Chris@0: * @category PHP Chris@0: * @package PHP_CodeSniffer Chris@0: * @author Greg Sherwood Chris@0: * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) Chris@0: * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence Chris@0: * @version Release: @package_version@ Chris@0: * @link http://pear.php.net/package/PHP_CodeSniffer Chris@0: */ Chris@0: class PHP_CodeSniffer_Fixer Chris@0: { Chris@0: Chris@0: /** Chris@0: * Is the fixer enabled and fixing a file? Chris@0: * Chris@0: * Sniffs should check this value to ensure they are not Chris@0: * doing extra processing to prepare for a fix when fixing is Chris@0: * not required. Chris@0: * Chris@0: * @var boolean Chris@0: */ Chris@0: public $enabled = false; Chris@0: Chris@0: /** Chris@0: * The number of times we have looped over a file. Chris@0: * Chris@0: * @var int Chris@0: */ Chris@0: public $loops = 0; Chris@0: Chris@0: /** Chris@0: * The file being fixed. Chris@0: * Chris@0: * @var PHP_CodeSniffer_File Chris@0: */ Chris@0: private $_currentFile = null; Chris@0: Chris@0: /** Chris@0: * The list of tokens that make up the file contents. Chris@0: * Chris@0: * This is a simplified list which just contains the token content and nothing Chris@0: * else. This is the array that is updated as fixes are made, not the file's Chris@0: * token array. Imploding this array will give you the file content back. Chris@0: * Chris@0: * @var array(int => string) Chris@0: */ Chris@0: private $_tokens = array(); Chris@0: Chris@0: /** Chris@0: * A list of tokens that have already been fixed. Chris@0: * Chris@0: * We don't allow the same token to be fixed more than once each time Chris@0: * through a file as this can easily cause conflicts between sniffs. Chris@0: * Chris@0: * @var array(int) Chris@0: */ Chris@0: private $_fixedTokens = array(); Chris@0: Chris@0: /** Chris@0: * The last value of each fixed token. Chris@0: * Chris@0: * If a token is being "fixed" back to its last value, the fix is Chris@0: * probably conflicting with another. Chris@0: * Chris@0: * @var array(int => string) Chris@0: */ Chris@0: private $_oldTokenValues = array(); Chris@0: Chris@0: /** Chris@0: * A list of tokens that have been fixed during a changeset. Chris@0: * Chris@0: * All changes in changeset must be able to be applied, or else Chris@0: * the entire changeset is rejected. Chris@0: * Chris@0: * @var array() Chris@0: */ Chris@0: private $_changeset = array(); Chris@0: Chris@0: /** Chris@0: * Is there an open changeset. Chris@0: * Chris@0: * @var boolean Chris@0: */ Chris@0: private $_inChangeset = false; Chris@0: Chris@0: /** Chris@0: * Is the current fixing loop in conflict? Chris@0: * Chris@0: * @var boolean Chris@0: */ Chris@0: private $_inConflict = false; Chris@0: Chris@0: /** Chris@0: * The number of fixes that have been performed. Chris@0: * Chris@0: * @var int Chris@0: */ Chris@0: private $_numFixes = 0; Chris@0: Chris@0: Chris@0: /** Chris@0: * Starts fixing a new file. Chris@0: * Chris@0: * @param PHP_CodeSniffer_File $phpcsFile The file being fixed. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: public function startFile($phpcsFile) Chris@0: { Chris@0: $this->_currentFile = $phpcsFile; Chris@0: $this->_numFixes = 0; Chris@0: $this->_fixedTokens = array(); Chris@0: Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $this->_tokens = array(); Chris@0: foreach ($tokens as $index => $token) { Chris@0: if (isset($token['orig_content']) === true) { Chris@0: $this->_tokens[$index] = $token['orig_content']; Chris@0: } else { Chris@0: $this->_tokens[$index] = $token['content']; Chris@0: } Chris@0: } Chris@0: Chris@0: }//end startFile() Chris@0: Chris@0: Chris@0: /** Chris@0: * Attempt to fix the file by processing it until no fixes are made. Chris@0: * Chris@0: * @return boolean Chris@0: */ Chris@0: public function fixFile() Chris@0: { Chris@0: $fixable = $this->_currentFile->getFixableCount(); Chris@0: if ($fixable === 0) { Chris@0: // Nothing to fix. Chris@0: return false; Chris@0: } Chris@0: Chris@0: $stdin = false; Chris@0: $cliValues = $this->_currentFile->phpcs->cli->getCommandLineValues(); Chris@0: if (empty($cliValues['files']) === true) { Chris@0: $stdin = true; Chris@0: } Chris@0: Chris@0: $this->enabled = true; Chris@0: Chris@0: $this->loops = 0; Chris@0: while ($this->loops < 50) { Chris@0: ob_start(); Chris@0: Chris@0: // Only needed once file content has changed. Chris@0: $contents = $this->getContents(); Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 2) { Chris@0: @ob_end_clean(); Chris@0: echo '---START FILE CONTENT---'.PHP_EOL; Chris@0: $lines = explode($this->_currentFile->eolChar, $contents); Chris@0: $max = strlen(count($lines)); Chris@0: foreach ($lines as $lineNum => $line) { Chris@0: $lineNum++; Chris@0: echo str_pad($lineNum, $max, ' ', STR_PAD_LEFT).'|'.$line.PHP_EOL; Chris@0: } Chris@0: Chris@0: echo '--- END FILE CONTENT ---'.PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: $this->_inConflict = false; Chris@0: $this->_currentFile->refreshTokenListeners(); Chris@0: $this->_currentFile->start($contents); Chris@0: ob_end_clean(); Chris@0: Chris@0: $this->loops++; Chris@0: Chris@0: if (PHP_CODESNIFFER_CBF === true && $stdin === false) { Chris@0: echo "\r".str_repeat(' ', 80)."\r"; Chris@0: echo "\t=> Fixing file: $this->_numFixes/$fixable violations remaining [made $this->loops pass"; Chris@0: if ($this->loops > 1) { Chris@0: echo 'es'; Chris@0: } Chris@0: Chris@0: echo ']... '; Chris@0: } Chris@0: Chris@0: if ($this->_numFixes === 0 && $this->_inConflict === false) { Chris@0: // Nothing left to do. Chris@0: break; Chris@0: } else if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: echo "\t* fixed $this->_numFixes violations, starting loop ".($this->loops + 1).' *'.PHP_EOL; Chris@0: } Chris@0: }//end while Chris@0: Chris@0: $this->enabled = false; Chris@0: Chris@0: if ($this->_numFixes > 0) { Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: @ob_end_clean(); Chris@0: echo "\t*** Reached maximum number of loops with $this->_numFixes violations left unfixed ***".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: return true; Chris@0: Chris@0: }//end fixFile() Chris@0: Chris@0: Chris@0: /** Chris@0: * Generates a text diff of the original file and the new content. Chris@0: * Chris@0: * @param string $filePath Optional file path to diff the file against. Chris@0: * If not specified, the original version of the Chris@0: * file will be used. Chris@0: * @param boolean $colors Print colored output or not. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function generateDiff($filePath=null, $colors=true) Chris@0: { Chris@0: if ($filePath === null) { Chris@0: $filePath = $this->_currentFile->getFilename(); Chris@0: } Chris@0: Chris@0: $cwd = getcwd().DIRECTORY_SEPARATOR; Chris@0: if (strpos($filePath, $cwd) === 0) { Chris@0: $filename = substr($filePath, strlen($cwd)); Chris@0: } else { Chris@0: $filename = $filePath; Chris@0: } Chris@0: Chris@0: $contents = $this->getContents(); Chris@0: Chris@0: if (function_exists('sys_get_temp_dir') === true) { Chris@0: // This is needed for HHVM support, but only available from 5.2.1. Chris@0: $tempName = tempnam(sys_get_temp_dir(), 'phpcs-fixer'); Chris@0: $fixedFile = fopen($tempName, 'w'); Chris@0: } else { Chris@0: $fixedFile = tmpfile(); Chris@0: $data = stream_get_meta_data($fixedFile); Chris@0: $tempName = $data['uri']; Chris@0: } Chris@0: Chris@0: fwrite($fixedFile, $contents); Chris@0: Chris@0: // We must use something like shell_exec() because whitespace at the end Chris@0: // of lines is critical to diff files. Chris@0: $filename = escapeshellarg($filename); Chris@0: $cmd = "diff -u -L$filename -LPHP_CodeSniffer $filename \"$tempName\""; Chris@0: Chris@0: $diff = shell_exec($cmd); Chris@0: Chris@0: fclose($fixedFile); Chris@0: if (is_file($tempName) === true) { Chris@0: unlink($tempName); Chris@0: } Chris@0: Chris@0: if ($colors === false) { Chris@0: return $diff; Chris@0: } Chris@0: Chris@0: $diffLines = explode(PHP_EOL, $diff); Chris@0: if (count($diffLines) === 1) { Chris@0: // Seems to be required for cygwin. Chris@0: $diffLines = explode("\n", $diff); Chris@0: } Chris@0: Chris@0: $diff = array(); Chris@0: foreach ($diffLines as $line) { Chris@0: if (isset($line[0]) === true) { Chris@0: switch ($line[0]) { Chris@0: case '-': Chris@0: $diff[] = "\033[31m$line\033[0m"; Chris@0: break; Chris@0: case '+': Chris@0: $diff[] = "\033[32m$line\033[0m"; Chris@0: break; Chris@0: default: Chris@0: $diff[] = $line; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: $diff = implode(PHP_EOL, $diff); Chris@0: Chris@0: return $diff; Chris@0: Chris@0: }//end generateDiff() Chris@0: Chris@0: Chris@0: /** Chris@0: * Get a count of fixes that have been performed on the file. Chris@0: * Chris@0: * This value is reset every time a new file is started, or an existing Chris@0: * file is restarted. Chris@0: * Chris@0: * @return int Chris@0: */ Chris@0: public function getFixCount() Chris@0: { Chris@0: return $this->_numFixes; Chris@0: Chris@0: }//end getFixCount() Chris@0: Chris@0: Chris@0: /** Chris@0: * Get the current content of the file, as a string. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getContents() Chris@0: { Chris@0: $contents = implode($this->_tokens); Chris@0: return $contents; Chris@0: Chris@0: }//end getContents() Chris@0: Chris@0: Chris@0: /** Chris@0: * Get the current fixed content of a token. Chris@0: * Chris@0: * This function takes changesets into account so should be used Chris@0: * instead of directly accessing the token array. Chris@0: * Chris@0: * @param int $stackPtr The position of the token in the token stack. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getTokenContent($stackPtr) Chris@0: { Chris@0: if ($this->_inChangeset === true Chris@0: && isset($this->_changeset[$stackPtr]) === true Chris@0: ) { Chris@0: return $this->_changeset[$stackPtr]; Chris@0: } else { Chris@0: return $this->_tokens[$stackPtr]; Chris@0: } Chris@0: Chris@0: }//end getTokenContent() Chris@0: Chris@0: Chris@0: /** Chris@0: * Start recording actions for a changeset. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: public function beginChangeset() Chris@0: { Chris@0: if ($this->_inConflict === true) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: $bt = debug_backtrace(); Chris@0: $sniff = $bt[1]['class']; Chris@0: $line = $bt[0]['line']; Chris@0: Chris@0: @ob_end_clean(); Chris@0: echo "\t=> Changeset started by $sniff (line $line)".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: $this->_changeset = array(); Chris@0: $this->_inChangeset = true; Chris@0: Chris@0: }//end beginChangeset() Chris@0: Chris@0: Chris@0: /** Chris@0: * Stop recording actions for a changeset, and apply logged changes. Chris@0: * Chris@0: * @return boolean Chris@0: */ Chris@0: public function endChangeset() Chris@0: { Chris@0: if ($this->_inConflict === true) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $this->_inChangeset = false; Chris@0: Chris@0: $success = true; Chris@0: $applied = array(); Chris@0: foreach ($this->_changeset as $stackPtr => $content) { Chris@0: $success = $this->replaceToken($stackPtr, $content); Chris@0: if ($success === false) { Chris@0: break; Chris@0: } else { Chris@0: $applied[] = $stackPtr; Chris@0: } Chris@0: } Chris@0: Chris@0: if ($success === false) { Chris@0: // Rolling back all changes. Chris@0: foreach ($applied as $stackPtr) { Chris@0: $this->revertToken($stackPtr); Chris@0: } Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: @ob_end_clean(); Chris@0: echo "\t=> Changeset failed to apply".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: } else if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: $fixes = count($this->_changeset); Chris@0: @ob_end_clean(); Chris@0: echo "\t=> Changeset ended: $fixes changes applied".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: $this->_changeset = array(); Chris@0: Chris@0: }//end endChangeset() Chris@0: Chris@0: Chris@0: /** Chris@0: * Stop recording actions for a changeset, and discard logged changes. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: public function rollbackChangeset() Chris@0: { Chris@0: $this->_inChangeset = false; Chris@0: $this->_inConflict = false; Chris@0: Chris@0: if (empty($this->_changeset) === false) { Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: $bt = debug_backtrace(); Chris@0: if ($bt[1]['class'] === 'PHP_CodeSniffer_Fixer') { Chris@0: $sniff = $bt[2]['class']; Chris@0: $line = $bt[1]['line']; Chris@0: } else { Chris@0: $sniff = $bt[1]['class']; Chris@0: $line = $bt[0]['line']; Chris@0: } Chris@0: Chris@0: $numChanges = count($this->_changeset); Chris@0: Chris@0: @ob_end_clean(); Chris@0: echo "\t\tR: $sniff (line $line) rolled back the changeset ($numChanges changes)".PHP_EOL; Chris@0: echo "\t=> Changeset rolled back".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: $this->_changeset = array(); Chris@0: }//end if Chris@0: Chris@0: }//end rollbackChangeset() Chris@0: Chris@0: Chris@0: /** Chris@0: * Replace the entire contents of a token. Chris@0: * Chris@0: * @param int $stackPtr The position of the token in the token stack. Chris@0: * @param string $content The new content of the token. Chris@0: * Chris@0: * @return bool If the change was accepted. Chris@0: */ Chris@0: public function replaceToken($stackPtr, $content) Chris@0: { Chris@0: if ($this->_inConflict === true) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: if ($this->_inChangeset === false Chris@0: && isset($this->_fixedTokens[$stackPtr]) === true Chris@0: ) { Chris@0: $indent = "\t"; Chris@0: if (empty($this->_changeset) === false) { Chris@0: $indent .= "\t"; Chris@0: } Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: @ob_end_clean(); Chris@0: echo "$indent* token $stackPtr has already been modified, skipping *".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: $bt = debug_backtrace(); Chris@0: if ($bt[1]['class'] === 'PHP_CodeSniffer_Fixer') { Chris@0: $sniff = $bt[2]['class']; Chris@0: $line = $bt[1]['line']; Chris@0: } else { Chris@0: $sniff = $bt[1]['class']; Chris@0: $line = $bt[0]['line']; Chris@0: } Chris@0: Chris@0: $tokens = $this->_currentFile->getTokens(); Chris@0: $type = $tokens[$stackPtr]['type']; Chris@0: $oldContent = PHP_CodeSniffer::prepareForOutput($this->_tokens[$stackPtr]); Chris@0: $newContent = PHP_CodeSniffer::prepareForOutput($content); Chris@0: if (trim($this->_tokens[$stackPtr]) === '' && isset($this->_tokens[($stackPtr + 1)]) === true) { Chris@0: // Add some context for whitespace only changes. Chris@0: $append = PHP_CodeSniffer::prepareForOutput($this->_tokens[($stackPtr + 1)]); Chris@0: $oldContent .= $append; Chris@0: $newContent .= $append; Chris@0: } Chris@0: }//end if Chris@0: Chris@0: if ($this->_inChangeset === true) { Chris@0: $this->_changeset[$stackPtr] = $content; Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: @ob_end_clean(); Chris@0: echo "\t\tQ: $sniff (line $line) replaced token $stackPtr ($type) \"$oldContent\" => \"$newContent\"".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: if (isset($this->_oldTokenValues[$stackPtr]) === false) { Chris@0: $this->_oldTokenValues[$stackPtr] = array( Chris@0: 'curr' => $content, Chris@0: 'prev' => $this->_tokens[$stackPtr], Chris@0: 'loop' => $this->loops, Chris@0: ); Chris@0: } else { Chris@0: if ($this->_oldTokenValues[$stackPtr]['prev'] === $content Chris@0: && $this->_oldTokenValues[$stackPtr]['loop'] === ($this->loops - 1) Chris@0: ) { Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: $indent = "\t"; Chris@0: if (empty($this->_changeset) === false) { Chris@0: $indent .= "\t"; Chris@0: } Chris@0: Chris@0: $loop = $this->_oldTokenValues[$stackPtr]['loop']; Chris@0: Chris@0: @ob_end_clean(); Chris@0: echo "$indent**** $sniff (line $line) has possible conflict with another sniff on loop $loop; caused by the following change ****".PHP_EOL; Chris@0: echo "$indent**** replaced token $stackPtr ($type) \"$oldContent\" => \"$newContent\" ****".PHP_EOL; Chris@0: } Chris@0: Chris@0: if ($this->_oldTokenValues[$stackPtr]['loop'] >= ($this->loops - 1)) { Chris@0: $this->_inConflict = true; Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: echo "$indent**** ignoring all changes until next loop ****".PHP_EOL; Chris@0: } Chris@0: } Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: return false; Chris@0: }//end if Chris@0: Chris@0: $this->_oldTokenValues[$stackPtr]['prev'] = $this->_oldTokenValues[$stackPtr]['curr']; Chris@0: $this->_oldTokenValues[$stackPtr]['curr'] = $content; Chris@0: $this->_oldTokenValues[$stackPtr]['loop'] = $this->loops; Chris@0: }//end if Chris@0: Chris@0: $this->_fixedTokens[$stackPtr] = $this->_tokens[$stackPtr]; Chris@0: $this->_tokens[$stackPtr] = $content; Chris@0: $this->_numFixes++; Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: $indent = "\t"; Chris@0: if (empty($this->_changeset) === false) { Chris@0: $indent .= "\tA: "; Chris@0: } Chris@0: Chris@0: @ob_end_clean(); Chris@0: echo "$indent$sniff (line $line) replaced token $stackPtr ($type) \"$oldContent\" => \"$newContent\"".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: return true; Chris@0: Chris@0: }//end replaceToken() Chris@0: Chris@0: Chris@0: /** Chris@0: * Reverts the previous fix made to a token. Chris@0: * Chris@0: * @param int $stackPtr The position of the token in the token stack. Chris@0: * Chris@0: * @return bool If a change was reverted. Chris@0: */ Chris@0: public function revertToken($stackPtr) Chris@0: { Chris@0: if (isset($this->_fixedTokens[$stackPtr]) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: $bt = debug_backtrace(); Chris@0: if ($bt[1]['class'] === 'PHP_CodeSniffer_Fixer') { Chris@0: $sniff = $bt[2]['class']; Chris@0: $line = $bt[1]['line']; Chris@0: } else { Chris@0: $sniff = $bt[1]['class']; Chris@0: $line = $bt[0]['line']; Chris@0: } Chris@0: Chris@0: $tokens = $this->_currentFile->getTokens(); Chris@0: $type = $tokens[$stackPtr]['type']; Chris@0: $oldContent = PHP_CodeSniffer::prepareForOutput($this->_tokens[$stackPtr]); Chris@0: $newContent = PHP_CodeSniffer::prepareForOutput($this->_fixedTokens[$stackPtr]); Chris@0: if (trim($this->_tokens[$stackPtr]) === '' && isset($tokens[($stackPtr + 1)]) === true) { Chris@0: // Add some context for whitespace only changes. Chris@0: $append = PHP_CodeSniffer::prepareForOutput($this->_tokens[($stackPtr + 1)]); Chris@0: $oldContent .= $append; Chris@0: $newContent .= $append; Chris@0: } Chris@0: }//end if Chris@0: Chris@0: $this->_tokens[$stackPtr] = $this->_fixedTokens[$stackPtr]; Chris@0: unset($this->_fixedTokens[$stackPtr]); Chris@0: $this->_numFixes--; Chris@0: Chris@0: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@0: $indent = "\t"; Chris@0: if (empty($this->_changeset) === false) { Chris@0: $indent .= "\tR: "; Chris@0: } Chris@0: Chris@0: @ob_end_clean(); Chris@0: echo "$indent$sniff (line $line) reverted token $stackPtr ($type) \"$oldContent\" => \"$newContent\"".PHP_EOL; Chris@0: ob_start(); Chris@0: } Chris@0: Chris@0: return true; Chris@0: Chris@0: }//end revertToken() Chris@0: Chris@0: Chris@0: /** Chris@0: * Replace the content of a token with a part of its current content. Chris@0: * Chris@0: * @param int $stackPtr The position of the token in the token stack. Chris@0: * @param int $start The first character to keep. Chris@0: * @param int $length The number of chacters to keep. If NULL, the content of Chris@0: * the token from $start to the end of the content is kept. Chris@0: * Chris@0: * @return bool If the change was accepted. Chris@0: */ Chris@0: public function substrToken($stackPtr, $start, $length=null) Chris@0: { Chris@0: $current = $this->getTokenContent($stackPtr); Chris@0: Chris@0: if ($length === null) { Chris@0: $newContent = substr($current, $start); Chris@0: } else { Chris@0: $newContent = substr($current, $start, $length); Chris@0: } Chris@0: Chris@0: return $this->replaceToken($stackPtr, $newContent); Chris@0: Chris@0: }//end substrToken() Chris@0: Chris@0: Chris@0: /** Chris@0: * Adds a newline to end of a token's content. Chris@0: * Chris@0: * @param int $stackPtr The position of the token in the token stack. Chris@0: * Chris@0: * @return bool If the change was accepted. Chris@0: */ Chris@0: public function addNewline($stackPtr) Chris@0: { Chris@0: $current = $this->getTokenContent($stackPtr); Chris@0: return $this->replaceToken($stackPtr, $current.$this->_currentFile->eolChar); Chris@0: Chris@0: }//end addNewline() Chris@0: Chris@0: Chris@0: /** Chris@0: * Adds a newline to the start of a token's content. Chris@0: * Chris@0: * @param int $stackPtr The position of the token in the token stack. Chris@0: * Chris@0: * @return bool If the change was accepted. Chris@0: */ Chris@0: public function addNewlineBefore($stackPtr) Chris@0: { Chris@0: $current = $this->getTokenContent($stackPtr); Chris@0: return $this->replaceToken($stackPtr, $this->_currentFile->eolChar.$current); Chris@0: Chris@0: }//end addNewlineBefore() Chris@0: Chris@0: Chris@0: /** Chris@0: * Adds content to the end of a token's current content. Chris@0: * Chris@0: * @param int $stackPtr The position of the token in the token stack. Chris@0: * @param string $content The content to add. Chris@0: * Chris@0: * @return bool If the change was accepted. Chris@0: */ Chris@0: public function addContent($stackPtr, $content) Chris@0: { Chris@0: $current = $this->getTokenContent($stackPtr); Chris@0: return $this->replaceToken($stackPtr, $current.$content); Chris@0: Chris@0: }//end addContent() Chris@0: Chris@0: Chris@0: /** Chris@0: * Adds content to the start of a token's current content. Chris@0: * Chris@0: * @param int $stackPtr The position of the token in the token stack. Chris@0: * @param string $content The content to add. Chris@0: * Chris@0: * @return bool If the change was accepted. Chris@0: */ Chris@0: public function addContentBefore($stackPtr, $content) Chris@0: { Chris@0: $current = $this->getTokenContent($stackPtr); Chris@0: return $this->replaceToken($stackPtr, $content.$current); Chris@0: Chris@0: }//end addContentBefore() Chris@0: Chris@0: Chris@0: }//end class