From: nicolasconnault Date: Tue, 19 May 2009 15:22:43 +0000 (+0000) Subject: MDL-19247 Added the PHP CodeSniffer tool and lib/thirdpartylibs.xml X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=d35df1b54a2991af336f52b5ca158282e5023b19;p=moodle.git MDL-19247 Added the PHP CodeSniffer tool and lib/thirdpartylibs.xml --- diff --git a/lib/pear/PHP/CodeSniffer.php b/lib/pear/PHP/CodeSniffer.php new file mode 100644 index 0000000000..2c1efaf1fd --- /dev/null +++ b/lib/pear/PHP/CodeSniffer.php @@ -0,0 +1,1665 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +spl_autoload_register(array('PHP_CodeSniffer', 'autoload')); + +if (class_exists('PHP_CodeSniffer_Exception', true) === false) { + throw new Exception('Class PHP_CodeSniffer_Exception not found'); +} + +if (class_exists('PHP_CodeSniffer_File', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found'); +} + +if (class_exists('PHP_CodeSniffer_Tokens', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found'); +} + +if (interface_exists('PHP_CodeSniffer_Sniff', true) === false) { + throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_Sniff not found'); +} + +/** + * PHP_CodeSniffer tokenises PHP code and detects violations of a + * defined set of coding standards. + * + * Standards are specified by classes that implement the PHP_CodeSniffer_Sniff + * interface. A sniff registers what token types it wishes to listen for, then + * PHP_CodeSniffer encounters that token, the sniff is invoked and passed + * information about where the token was found in the stack, and the token stack + * itself. + * + * Sniff files and their containing class must be prefixed with Sniff, and + * have an extension of .php. + * + * Multiple PHP_CodeSniffer operations can be performed by re-calling the + * process function with different parameters. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer +{ + + /** + * The file or directory that is currently being processed. + * + * @var string + */ + protected $file = array(); + + /** + * The directory to search for sniffs in. + * + * @var string + */ + protected $standardDir = ''; + + /** + * The files that have been processed. + * + * @var array(PHP_CodeSniffer_File) + */ + protected $files = array(); + + /** + * The listeners array. + * + * @var array(PHP_CodeSniffer_Sniff) + */ + protected $listeners = array(); + + /** + * An array of patterns to use for skipping files. + * + * @var array() + */ + protected $ignorePatterns = array(); + + /** + * An array of extensions for files we will check. + * + * @var array + */ + public $allowedFileExtensions = array( + 'php' => 'PHP', + 'inc' => 'PHP', + 'js' => 'JS', + ); + + /** + * An array of variable types for param/var we will check. + * + * @var array(string) + */ + public static $allowedTypes = array( + 'array', + 'boolean', + 'float', + 'integer', + 'mixed', + 'object', + 'string', + ); + + + /** + * Constructs a PHP_CodeSniffer object. + * + * @param int $verbosity The verbosity level. + * 1: Print progress information. + * 2: Print developer debug information. + * @param int $tabWidth The number of spaces each tab represents. + * If greater than zero, tabs will be replaced + * by spaces before testing each file. + * + * @see process() + */ + public function __construct($verbosity=0, $tabWidth=0) + { + define('PHP_CODESNIFFER_VERBOSITY', $verbosity); + define('PHP_CODESNIFFER_TAB_WIDTH', $tabWidth); + + // Change into a directory that we know about to stop any + // relative path conflicts. + chdir(dirname(__FILE__).'/CodeSniffer/'); + + }//end __construct() + + + /** + * Autoload static method for loading classes and interfaces. + * + * @param string $className The name of the class or interface. + * + * @return void + */ + public static function autoload($className) + { + if (substr($className, 0, 4) === 'PHP_') { + $newClassName = substr($className, 4); + } else { + $newClassName = $className; + } + + $path = str_replace('_', '/', $newClassName).'.php'; + + if (is_file(dirname(__FILE__).'/'.$path) === true) { + // Check standard file locations based on class name. + include dirname(__FILE__).'/'.$path; + } else if (is_file(dirname(__FILE__).'/CodeSniffer/Standards/'.$path) === true) { + // Check for included sniffs. + include dirname(__FILE__).'/CodeSniffer/Standards/'.$path; + } else { + // Everything else. + @include $path; + } + + }//end autoload() + + + /** + * Sets an array of file extensions that we will allow checking of. + * + * If the extension is one of the defaults, a specific tokenizer + * will be used. Otherwise, the PHP tokenizer will be used for + * all extensions passed. + * + * @param array $extensions An array of file extensions. + * + * @return void + */ + public function setAllowedFileExtensions(array $extensions) + { + $newExtensions = array(); + foreach ($extensions as $ext) { + if (isset($this->allowedFileExtensions[$ext]) === true) { + $newExtensions[$ext] = $this->allowedFileExtensions[$ext]; + } else { + $newExtensions[$ext] = 'PHP'; + } + } + + $this->allowedFileExtensions = $newExtensions; + + }//end setAllowedFileExtensions() + + + /** + * Sets an array of ignore patterns that we use to skip files and folders. + * + * Patterns are not case sensitive. + * + * @param array $patterns An array of ignore patterns. + * + * @return void + */ + public function setIgnorePatterns(array $patterns) + { + $this->ignorePatterns = $patterns; + + }//end setIgnorePatterns() + + + /** + * Adds a file to the list of checked files. + * + * Checked files are used to generate error reports after the run. + * + * @param PHP_CodeSniffer_File $phpcsFile The file to add. + * + * @return void + */ + public function addFile(PHP_CodeSniffer_File $phpcsFile) + { + $this->files[] = $phpcsFile; + + }//end addFile() + + + /** + * Processes the files/directories that PHP_CodeSniffer was constructed with. + * + * @param string|array $files The files and directories to process. For + * directories, each sub directory will also + * be traversed for source files. + * @param string $standard The set of code sniffs we are testing + * against. + * @param array $sniffs The sniff names to restrict the allowed + * listeners to. + * @param boolean $local If true, don't recurse into directories. + * + * @return void + * @throws PHP_CodeSniffer_Exception If files or standard are invalid. + */ + public function process($files, $standard, array $sniffs=array(), $local=false) + { + if (is_array($files) === false) { + if (is_string($files) === false || $files === null) { + throw new PHP_CodeSniffer_Exception('$file must be a string'); + } + + $files = array($files); + } + + if (is_string($standard) === false || $standard === null) { + throw new PHP_CodeSniffer_Exception('$standard must be a string'); + } + + // Reset the members. + $this->listeners = array(); + $this->files = array(); + + if (PHP_CODESNIFFER_VERBOSITY > 0) { + echo "Registering sniffs in $standard standard... "; + if (PHP_CODESNIFFER_VERBOSITY > 2) { + echo PHP_EOL; + } + } + + $this->setTokenListeners($standard, $sniffs); + if (PHP_CODESNIFFER_VERBOSITY > 0) { + $numSniffs = count($this->listeners); + echo "DONE ($numSniffs sniffs registered)".PHP_EOL; + } + + foreach ($files as $file) { + $this->file = $file; + if (is_dir($this->file) === true) { + $this->processFiles($this->file, $local); + } else { + $this->processFile($this->file); + } + } + + }//end process() + + + /** + * Gets installed sniffs in the coding standard being used. + * + * Traverses the standard directory for classes that implement the + * PHP_CodeSniffer_Sniff interface asks them to register. Each of the + * sniff's class names must be exact as the basename of the sniff file. + * + * Returns an array of sniff class names. + * + * @param string $standard The name of the coding standard we are checking. + * @param array $sniffs The sniff names to restrict the allowed + * listeners to. + * + * @return array + * @throws PHP_CodeSniffer_Exception If any of the tests failed in the + * registration process. + */ + public function getTokenListeners($standard, array $sniffs=array()) + { + if (is_dir($standard) === true) { + // This is a custom standard. + $this->standardDir = $standard; + $standard = basename($standard); + } else { + $this->standardDir = realpath(dirname(__FILE__).'/CodeSniffer/Standards/'.$standard); + } + + $files = self::getSniffFiles($this->standardDir, $standard); + + if (empty($sniffs) === false) { + // Convert the allowed sniffs to lower case so + // they are easier to check. + foreach ($sniffs as &$sniff) { + $sniff = strtolower($sniff); + } + } + + $listeners = array(); + + foreach ($files as $file) { + // Work out where the position of /StandardName/Sniffs/... is + // so we can determine what the class will be called. + $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR); + if ($sniffPos === false) { + continue; + } + + $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR); + if ($slashPos === false) { + continue; + } + + $className = substr($file, ($slashPos + 1)); + $className = substr($className, 0, -4); + $className = str_replace(DIRECTORY_SEPARATOR, '_', $className); + + include_once $file; + + // If they have specified a list of sniffs to restrict to, check + // to see if this sniff is allowed. + $allowed = in_array(strtolower($className), $sniffs); + if (empty($sniffs) === false && $allowed === false) { + continue; + } + + $listeners[] = $className; + + if (PHP_CODESNIFFER_VERBOSITY > 2) { + echo "\tRegistered $className".PHP_EOL; + } + }//end foreach + + return $listeners; + + }//end getTokenListeners() + + + /** + * Sets installed sniffs in the coding standard being used. + * + * @param string $standard The name of the coding standard we are checking. + * @param array $sniffs The sniff names to restrict the allowed + * listeners to. + * + * @return null + */ + public function setTokenListeners($standard, array $sniffs=array()) + { + $this->listeners = $this->getTokenListeners($standard, $sniffs); + + }//end setTokenListeners + + + /** + * Return a list of sniffs that a coding standard has defined. + * + * Sniffs are found by recursing the standard directory and also by + * asking the standard for included sniffs. + * + * @param string $dir The directory where to look for the files. + * @param string $standard The name of the coding standard. If NULL, no + * included sniffs will be checked for. + * + * @return array + * @throws PHP_CodeSniffer_Exception If an included or excluded sniff does + * not exist. + */ + public static function getSniffFiles($dir, $standard=null) + { + $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); + + $ownSniffs = array(); + $includedSniffs = array(); + $excludedSniffs = array(); + + foreach ($di as $file) { + // Skip hidden files. + if (substr($file->getFilename(), 0, 1) === '.') { + continue; + } + + // We are only interested in PHP and sniff files. + $fileParts = explode('.', $file); + if (array_pop($fileParts) !== 'php') { + continue; + } + + $basename = basename($file, '.php'); + if (substr($basename, -5) !== 'Sniff') { + continue; + } + + $ownSniffs[] = $file->getPathname(); + }//end foreach + + // Load the standard class and ask it for a list of external + // sniffs to include in the standard. + if ($standard !== null && is_file("$dir/{$standard}CodingStandard.php") === true) { + include_once "$dir/{$standard}CodingStandard.php"; + $standardClassName = "PHP_CodeSniffer_Standards_{$standard}_{$standard}CodingStandard"; + $standardClass = new $standardClassName; + + $included = $standardClass->getIncludedSniffs(); + foreach ($included as $sniff) { + if (is_dir($sniff) === true) { + // Trying to include from a custom standard. + $sniffDir = $sniff; + $sniff = basename($sniff); + } else if (is_file($sniff) === true) { + // Trying to include a custom sniff. + $sniffDir = $sniff; + } else { + $sniffDir = realpath(dirname(__FILE__)."/CodeSniffer/Standards/$sniff"); + if ($sniffDir === false) { + throw new PHP_CodeSniffer_Exception("Included sniff $sniff does not exist"); + } + } + + if (is_dir($sniffDir) === true) { + if (self::isInstalledStandard($sniff) === true) { + // We are including a whole coding standard. + $includedSniffs = array_merge($includedSniffs, self::getSniffFiles($sniffDir, $sniff)); + } else { + // We are including a whole directory of sniffs. + $includedSniffs = array_merge($includedSniffs, self::getSniffFiles($sniffDir)); + } + } else { + if (substr($sniffDir, -9) !== 'Sniff.php') { + throw new PHP_CodeSniffer_Exception("Included sniff $sniff does not exist"); + } + + $includedSniffs[] = $sniffDir; + } + }//end foreach + + $excluded = $standardClass->getExcludedSniffs(); + foreach ($excluded as $sniff) { + if (is_dir($sniff) === true) { + // Trying to exclude from a custom standard. + $sniffDir = $sniff; + $sniff = basename($sniff); + } else if (is_file($sniff) === true) { + // Trying to exclude a custom sniff. + $sniffDir = $sniff; + } else { + $sniffDir = realpath(dirname(__FILE__)."/CodeSniffer/Standards/$sniff"); + if ($sniffDir === false) { + throw new PHP_CodeSniffer_Exception("Excluded sniff $sniff does not exist"); + } + } + + if (is_dir($sniffDir) === true) { + if (self::isInstalledStandard($sniff) === true) { + // We are excluding a whole coding standard. + $excludedSniffs = array_merge($excludedSniffs, self::getSniffFiles($sniffDir, $sniff)); + } else { + // We are excluding a whole directory of sniffs. + $excludedSniffs = array_merge($excludedSniffs, self::getSniffFiles($sniffDir)); + } + } else { + if (substr($sniffDir, -9) !== 'Sniff.php') { + throw new PHP_CodeSniffer_Exception("Excluded sniff $sniff does not exist"); + } + + $excludedSniffs[] = $sniffDir; + } + }//end foreach + }//end if + + // Merge our own sniff list with our exnternally included + // sniff list, but filter out any excluded sniffs. + $files = array(); + foreach (array_merge($ownSniffs, $includedSniffs) as $sniff) { + if (in_array($sniff, $excludedSniffs) === true) { + continue; + } else { + $files[] = $sniff; + } + } + + return $files; + + }//end getSniffFiles() + + + /** + * Run the code sniffs over each file in a given directory. + * + * Recusively reads the specified directory and performs the PHP_CodeSniffer + * sniffs on each source file found within the directories. + * + * @param string $dir The directory to process. + * @param boolean $local If true, only process files in this directory, not + * sub directories. + * + * @return void + * @throws Exception If there was an error opening the directory. + */ + public function processFiles($dir, $local=false) + { + try { + if ($local === true) { + $di = new DirectoryIterator($dir); + } else { + $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); + } + + // MOODLE CODE: If thirdpartylibs.xml is found, add these values to the ignored array + // first iteration to find thirdpartylibs.xml + foreach ($di as $file) { + if ($file->getFileName() == 'thirdpartylibs.xml') { + $xml = simplexml_load_file($file->getPathName()); + foreach ($xml->library as $libobject) { + $this->ignorePatterns[] = (string) $libobject->location; + } + } + } + + foreach ($di as $file) { + $filePath = realpath($file->getPathname()); + + if (is_dir($filePath) === true) { + continue; + } + + // Check that the file's extension is one we are checking. + // Note that because we are doing a whole directory, we + // are strick about checking the extension and we don't + // let files with no extension through. + $fileParts = explode('.', $file); + $extension = array_pop($fileParts); + if ($extension === $file) { + continue; + } + + if (isset($this->allowedFileExtensions[$extension]) === false) { + continue; + } + + $this->processFile($filePath); + }//end foreach + } catch (Exception $e) { + $trace = $e->getTrace(); + $filename = $trace[0]['args'][0]; + $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage(); + + $phpcsFile = new PHP_CodeSniffer_File($filename, $this->listeners, $this->allowedFileExtensions); + $this->addFile($phpcsFile); + $phpcsFile->addError($error, null); + return; + } + + }//end processFiles() + + + /** + * Run the code sniffs over a single given file. + * + * Processes the file and runs the PHP_CodeSniffer sniffs to verify that it + * conforms with the standard. + * + * @param string $file The file to process. + * @param string $contents The contents to parse. If NULL, the content + * is taken from the file system. + * + * @return void + * @throws PHP_CodeSniffer_Exception If the file could not be processed. + */ + public function processFile($file, $contents=null) + { + if (is_null($contents) === true && file_exists($file) === false) { + throw new PHP_CodeSniffer_Exception("Source file $file does not exist"); + } + + // If the file's path matches one of our ignore patterns, skip it. + foreach ($this->ignorePatterns as $pattern) { + $replacements = array( + '\\,' => ',', + '*' => '.*', + ); + + $pattern = strtr($pattern, $replacements); + if (preg_match("|{$pattern}|i", $file) === 1) { + return; + } + } + + if (PHP_CODESNIFFER_VERBOSITY > 0) { + $startTime = time(); + echo 'Processing '.basename($file).' '; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo PHP_EOL; + } + } + + $phpcsFile = new PHP_CodeSniffer_File($file, $this->listeners, $this->allowedFileExtensions); + $this->addFile($phpcsFile); + $phpcsFile->start($contents); + + if (PHP_CODESNIFFER_VERBOSITY > 0) { + $timeTaken = (time() - $startTime); + if ($timeTaken === 0) { + echo 'DONE in < 1 second'; + } else if ($timeTaken === 1) { + echo 'DONE in 1 second'; + } else { + echo "DONE in $timeTaken seconds"; + } + + $errors = $phpcsFile->getErrorCount(); + $warnings = $phpcsFile->getWarningCount(); + echo " ($errors errors, $warnings warnings)".PHP_EOL; + } + + }//end processFile() + + + /** + * Pre-process and package errors and warnings for all files. + * + * Used by error reports to get a packaged list of all errors and + * warnings in each file. + * + * @param boolean $showWarnings Show warnings as well as errors. + * + * @return array + */ + public function prepareErrorReport($showWarnings=true) + { + $report = array( + 'totals' => array( + 'warnings' => 0, + 'errors' => 0, + ), + 'files' => array(), + ); + + foreach ($this->files as $file) { + $warnings = $file->getWarnings(); + $errors = $file->getErrors(); + $numWarnings = $file->getWarningCount(); + $numErrors = $file->getErrorCount(); + $filename = $file->getFilename(); + + $report['files'][$filename] = array( + 'errors' => 0, + 'warnings' => 0, + 'messages' => array(), + ); + + if ($numErrors === 0 && $numWarnings === 0) { + // Prefect score! + continue; + } + + if ($numErrors === 0 && $showWarnings === false) { + // Prefect score (sort of). + continue; + } + + $report['files'][$filename]['errors'] = $numErrors; + if ($showWarnings === true) { + $report['files'][$filename]['warnings'] = $numWarnings; + } else { + $report['files'][$filename]['warnings'] = 0; + } + + $report['totals']['errors'] += $numErrors; + if ($showWarnings === true) { + $report['totals']['warnings'] += $numWarnings; + } + + // Merge errors and warnings. + foreach ($errors as $line => $lineErrors) { + foreach ($lineErrors as $column => $colErrors) { + $newErrors = array(); + foreach ($colErrors as $message) { + $newErrors[] = array( + 'message' => $message, + 'type' => 'ERROR', + ); + } + + $errors[$line][$column] = $newErrors; + } + }//end foreach + + if ($showWarnings === true) { + foreach ($warnings as $line => $lineWarnings) { + foreach ($lineWarnings as $column => $colWarnings) { + $newWarnings = array(); + foreach ($colWarnings as $message) { + $newWarnings[] = array( + 'message' => $message, + 'type' => 'WARNING', + ); + } + + if (isset($errors[$line]) === false) { + $errors[$line] = array(); + } + + if (isset($errors[$line][$column]) === true) { + $errors[$line][$column] = array_merge($newWarnings, $errors[$line][$column]); + } else { + $errors[$line][$column] = $newWarnings; + } + } + }//end foreach + }//end if + + ksort($errors); + + $report['files'][$filename]['messages'] = $errors; + + }//end foreach + + return $report; + + }//end prepareErrorReport() + + + /** + * Prints all errors and warnings for each file processed, in an XML format. + * + * Errors and warnings are displayed together, grouped by file. + * + * @param boolean $showWarnings Show warnings as well as errors. + * + * @return int The number of error and warning messages shown. + */ + public function printXMLErrorReport($showWarnings=true) + { + echo ''.PHP_EOL; + echo ''.PHP_EOL; + + $errorsShown = 0; + + $report = $this->prepareErrorReport($showWarnings); + foreach ($report['files'] as $filename => $file) { + if (empty($file['messages']) === true) { + continue; + } + + echo ' '.PHP_EOL; + + foreach ($file['messages'] as $line => $lineErrors) { + foreach ($lineErrors as $column => $colErrors) { + foreach ($colErrors as $error) { + $error['type'] = strtolower($error['type']); + echo ' <'.$error['type'].' line="'.$line.'" column="'.$column.'">'; + echo htmlspecialchars($error['message']).''.PHP_EOL; + $errorsShown++; + } + } + }//end foreach + + echo ' '.PHP_EOL; + + }//end foreach + + echo ''.PHP_EOL; + + return $errorsShown; + + }//end printXMLErrorReport() + + + /** + * Prints all errors and warnings for each file processed, in a Checkstyle XML format. + * + * Errors and warnings are displayed together, grouped by file. + * + * @param boolean $showWarnings Show warnings as well as errors. + * + * @return int The number of error and warning messages shown. + */ + public function printCheckstyleErrorReport($showWarnings=true) + { + echo ''.PHP_EOL; + echo ''.PHP_EOL; + + $errorsShown = 0; + + $report = $this->prepareErrorReport($showWarnings); + foreach ($report['files'] as $filename => $file) { + echo ' '.PHP_EOL; + + foreach ($file['messages'] as $line => $lineErrors) { + foreach ($lineErrors as $column => $colErrors) { + foreach ($colErrors as $error) { + $error['type'] = strtolower($error['type']); + echo ' '.PHP_EOL; + $errorsShown++; + } + } + }//end foreach + + echo ' '.PHP_EOL; + + }//end foreach + + echo ''.PHP_EOL; + + return $errorsShown; + + }//end printCheckstyleErrorReport() + + + /** + * Prints all errors and warnings for each file processed, in a CSV format. + * + * @param boolean $showWarnings Show warnings as well as errors. + * + * @return int The number of error and warning messages shown. + */ + public function printCSVErrorReport($showWarnings=true) + { + echo 'File,Line,Column,Severity,Message'.PHP_EOL; + + $errorsShown = 0; + + $report = $this->prepareErrorReport($showWarnings); + foreach ($report['files'] as $filename => $file) { + foreach ($file['messages'] as $line => $lineErrors) { + foreach ($lineErrors as $column => $colErrors) { + foreach ($colErrors as $error) { + $filename = str_replace('"', '\"', $filename); + $message = str_replace('"', '\"', $error['message']); + $type = strtolower($error['type']); + echo "\"$filename\",$line,$column,$type,\"$message\"".PHP_EOL; + $errorsShown++; + } + } + }//end foreach + }//end foreach + + return $errorsShown; + + }//end printCSVErrorReport() + + + /** + * Prints all errors and warnings for each file processed. + * + * Errors and warnings are displayed together, grouped by file. + * + * @param boolean $showWarnings Show warnings as well as errors. + * + * @return int The number of error and warning messages shown. + */ + public function printErrorReport($showWarnings=true) + { + $errorsShown = 0; + + $report = $this->prepareErrorReport($showWarnings); + foreach ($report['files'] as $filename => $file) { + if (empty($file['messages']) === true) { + continue; + } + + echo PHP_EOL.'FILE: '; + if (strlen($filename) <= 71) { + echo $filename; + } else { + echo '...'.substr($filename, (strlen($filename) - 71)); + } + + echo PHP_EOL; + echo str_repeat('-', 80).PHP_EOL; + + echo 'FOUND '.$file['errors'].' ERROR(S) '; + + if ($showWarnings === true) { + echo 'AND '.$file['warnings'].' WARNING(S) '; + } + + echo 'AFFECTING '.count($file['messages']).' LINE(S)'.PHP_EOL; + echo str_repeat('-', 80).PHP_EOL; + + // Work out the max line number for formatting. + $maxLine = 0; + foreach ($file['messages'] as $line => $lineErrors) { + if ($line > $maxLine) { + $maxLine = $line; + } + } + + $maxLineLength = strlen($maxLine); + + // The length of the word ERROR or WARNING; used for padding. + if ($showWarnings === true && $file['warnings'] > 0) { + $typeLength = 7; + } else { + $typeLength = 5; + } + + // The padding that all lines will require that are + // printing an error message overflow. + $paddingLine2 = str_repeat(' ', ($maxLineLength + 1)); + $paddingLine2 .= ' | '; + $paddingLine2 .= str_repeat(' ', $typeLength); + $paddingLine2 .= ' | '; + + // The maxium amount of space an error message can use. + $maxErrorSpace = (80 - strlen($paddingLine2)); + + foreach ($file['messages'] as $line => $lineErrors) { + foreach ($lineErrors as $column => $colErrors) { + foreach ($colErrors as $error) { + // The padding that goes on the front of the line. + $padding = ($maxLineLength - strlen($line)); + $errorMsg = wordwrap($error['message'], $maxErrorSpace, PHP_EOL."$paddingLine2"); + + echo ' '.str_repeat(' ', $padding).$line.' | '.$error['type']; + if ($error['type'] === 'ERROR') { + if ($showWarnings === true && $file['warnings'] > 0) { + echo ' '; + } + } + + echo ' | '.$errorMsg.PHP_EOL; + $errorsShown++; + }//end foreach + }//end foreach + }//end foreach + + echo str_repeat('-', 80).PHP_EOL.PHP_EOL; + + }//end foreach + + return $errorsShown; + + }//end printErrorReport() + + + /** + * Prints a summary of errors and warnings for each file processed. + * + * If verbose output is enabled, results are shown for all files, even if + * they have no errors or warnings. If verbose output is disabled, we only + * show files that have at least one warning or error. + * + * @param boolean $showWarnings Show warnings as well as errors. + * + * @return int The number of error and warning messages shown. + */ + public function printErrorReportSummary($showWarnings=true) + { + $errorFiles = array(); + + foreach ($this->files as $file) { + $numWarnings = $file->getWarningCount(); + $numErrors = $file->getErrorCount(); + $filename = $file->getFilename(); + + // If verbose output is enabled, we show the results for all files, + // but if not, we only show files that had errors or warnings. + if (PHP_CODESNIFFER_VERBOSITY > 0 || $numErrors > 0 || ($numWarnings > 0 && $showWarnings === true)) { + $errorFiles[$filename] = array( + 'warnings' => $numWarnings, + 'errors' => $numErrors, + ); + } + } + + if (empty($errorFiles) === true) { + // Nothing to print. + return 0; + } + + echo PHP_EOL.'PHP CODE SNIFFER REPORT SUMMARY'.PHP_EOL; + echo str_repeat('-', 80).PHP_EOL; + if ($showWarnings === true) { + echo 'FILE'.str_repeat(' ', 60).'ERRORS WARNINGS'.PHP_EOL; + } else { + echo 'FILE'.str_repeat(' ', 70).'ERRORS'.PHP_EOL; + } + + echo str_repeat('-', 80).PHP_EOL; + + $totalErrors = 0; + $totalWarnings = 0; + $totalFiles = 0; + + foreach ($errorFiles as $file => $errors) { + if ($showWarnings === true) { + $padding = (62 - strlen($file)); + } else { + $padding = (72 - strlen($file)); + } + + if ($padding < 0) { + $file = '...'.substr($file, (($padding * -1) + 3)); + $padding = 0; + } + + echo $file.str_repeat(' ', $padding).' '; + echo $errors['errors']; + if ($showWarnings === true) { + echo str_repeat(' ', (8 - strlen((string) $errors['errors']))); + echo $errors['warnings']; + } + + echo PHP_EOL; + + $totalErrors += $errors['errors']; + $totalWarnings += $errors['warnings']; + $totalFiles++; + }//end foreach + + echo str_repeat('-', 80).PHP_EOL; + echo "A TOTAL OF $totalErrors ERROR(S) "; + if ($showWarnings === true) { + echo "AND $totalWarnings WARNING(S) "; + } + + echo "WERE FOUND IN $totalFiles FILE(S)".PHP_EOL; + echo str_repeat('-', 80).PHP_EOL.PHP_EOL; + + return ($totalErrors + $totalWarnings); + + }//end printErrorReportSummary() + + + /** + * Generates documentation for a coding standard. + * + * @param string $standard The standard to generate docs for + * @param array $sniffs A list of sniffs to limit the docs to. + * @param string $generator The name of the generator class to use. + * + * @return void + */ + public function generateDocs($standard, array $sniffs=array(), $generator='Text') + { + include_once 'PHP/CodeSniffer/DocGenerators/'.$generator.'.php'; + + $class = "PHP_CodeSniffer_DocGenerators_$generator"; + $generator = new $class($standard, $sniffs); + + $generator->generate(); + + }//end generateDocs() + + + /** + * Returns the PHP_CodeSniffer file objects. + * + * @return array(PHP_CodeSniffer_File) + */ + public function getFiles() + { + return $this->files; + + }//end getFiles() + + + /** + * Gets the array of PHP_CodeSniffer_Sniff's. + * + * @return array(PHP_CodeSniffer_Sniff) + */ + public function getSniffs() + { + return $this->listeners; + + }//end getSniffs() + + + /** + * Takes a token produced from token_get_all() and produces a + * more uniform token. + * + * Note that this method also resolves T_STRING tokens into more descrete + * types, therefore there is no need to call resolveTstringToken() + * + * @param string|array $token The token to convert. + * + * @return array The new token. + */ + public static function standardiseToken($token) + { + if (is_array($token) === false) { + $newToken = self::resolveSimpleToken($token); + } else { + // Some T_STRING tokens can be more specific. + if ($token[0] === T_STRING) { + $newToken = self::resolveTstringToken($token); + } else { + $newToken = array(); + $newToken['code'] = $token[0]; + $newToken['content'] = $token[1]; + $newToken['type'] = token_name($token[0]); + } + } + + return $newToken; + + }//end standardiseToken() + + + /** + * Converts T_STRING tokens into more usable token names. + * + * The token should be produced using the token_get_all() function. + * Currently, not all T_STRING tokens are converted. + * + * @param string|array $token The T_STRING token to convert as constructed + * by token_get_all(). + * + * @return array The new token. + */ + public static function resolveTstringToken(array $token) + { + $newToken = array(); + switch (strtolower($token[1])) { + case 'false': + $newToken['type'] = 'T_FALSE'; + break; + case 'true': + $newToken['type'] = 'T_TRUE'; + break; + case 'null': + $newToken['type'] = 'T_NULL'; + break; + case 'self': + $newToken['type'] = 'T_SELF'; + break; + case 'parent': + $newToken['type'] = 'T_PARENT'; + break; + default: + $newToken['type'] = 'T_STRING'; + break; + } + + $newToken['code'] = constant($newToken['type']); + $newToken['content'] = $token[1]; + + return $newToken; + + }//end resolveTstringToken() + + + /** + * Converts simple tokens into a format that conforms to complex tokens + * produced by token_get_all(). + * + * Simple tokens are tokens that are not in array form when produced from + * token_get_all(). + * + * @param string $token The simple token to convert. + * + * @return array The new token in array format. + */ + public static function resolveSimpleToken($token) + { + $newToken = array(); + + switch ($token) { + case '{': + $newToken['type'] = 'T_OPEN_CURLY_BRACKET'; + break; + case '}': + $newToken['type'] = 'T_CLOSE_CURLY_BRACKET'; + break; + case '[': + $newToken['type'] = 'T_OPEN_SQUARE_BRACKET'; + break; + case ']': + $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET'; + break; + case '(': + $newToken['type'] = 'T_OPEN_PARENTHESIS'; + break; + case ')': + $newToken['type'] = 'T_CLOSE_PARENTHESIS'; + break; + case ':': + $newToken['type'] = 'T_COLON'; + break; + case '.': + $newToken['type'] = 'T_STRING_CONCAT'; + break; + case '?': + $newToken['type'] = 'T_INLINE_THEN'; + break; + case ';': + $newToken['type'] = 'T_SEMICOLON'; + break; + case '=': + $newToken['type'] = 'T_EQUAL'; + break; + case '*': + $newToken['type'] = 'T_MULTIPLY'; + break; + case '/': + $newToken['type'] = 'T_DIVIDE'; + break; + case '+': + $newToken['type'] = 'T_PLUS'; + break; + case '-': + $newToken['type'] = 'T_MINUS'; + break; + case '%': + $newToken['type'] = 'T_MODULUS'; + break; + case '^': + $newToken['type'] = 'T_POWER'; + break; + case '&': + $newToken['type'] = 'T_BITWISE_AND'; + break; + case '|': + $newToken['type'] = 'T_BITWISE_OR'; + break; + case '<': + $newToken['type'] = 'T_LESS_THAN'; + break; + case '>': + $newToken['type'] = 'T_GREATER_THAN'; + break; + case '!': + $newToken['type'] = 'T_BOOLEAN_NOT'; + break; + case ',': + $newToken['type'] = 'T_COMMA'; + break; + default: + $newToken['type'] = 'T_NONE'; + break; + + }//end switch + + $newToken['code'] = constant($newToken['type']); + $newToken['content'] = $token; + + return $newToken; + + }//end resolveSimpleToken() + + + /** + * Returns true if the specified string is in the camel caps format. + * + * @param string $string The string the verify. + * @param boolean $classFormat If true, check to see if the string is in the + * class format. Class format strings must start + * with a capital letter and contain no + * underscores. + * @param boolean $public If true, the first character in the string + * must be an a-z character. If false, the + * character must be an underscore. This + * argument is only applicable if $classFormat + * is false. + * @param boolean $strict If true, the string must not have two captial + * letters next to each other. If false, a + * relaxed camel caps policy is used to allow + * for acronyms. + * + * @return boolean + */ + public static function isCamelCaps($string, $classFormat=false, $public=true, $strict=true) + { + // Check the first character first. + if ($classFormat === false) { + if ($public === false) { + $legalFirstChar = '[_][a-z]'; + } else { + $legalFirstChar = '[a-z]'; + } + } else { + $legalFirstChar = '[A-Z]'; + } + + if (preg_match("|^$legalFirstChar|", $string) === 0) { + return false; + } + + // Check that the name only contains legal characters. + if ($classFormat === false) { + $legalChars = 'a-zA-Z0-9'; + } else { + $legalChars = 'a-zA-Z'; + } + + if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) { + return false; + } + + if ($strict === true) { + // Check that there are not two captial letters next to each other. + $length = strlen($string); + $lastCharWasCaps = $classFormat; + + for ($i = 1; $i < $length; $i++) { + $ascii = ord($string{$i}); + if ($ascii >= 48 && $ascii <= 57) { + // The character is a number, so it cant be a captial. + $isCaps = false; + } else { + if (strtoupper($string{$i}) === $string{$i}) { + $isCaps = true; + } else { + $isCaps = false; + } + } + + if ($isCaps === true && $lastCharWasCaps === true) { + return false; + } + + $lastCharWasCaps = $isCaps; + } + }//end if + + return true; + + }//end isCamelCaps() + + + /** + * Returns true if the specified string is in the underscore caps format. + * + * @param string $string The string to verify. + * + * @return boolean + */ + public static function isUnderscoreName($string) + { + // If there are space in the name, it can't be valid. + if (strpos($string, ' ') !== false) { + return false; + } + + $validName = true; + $nameBits = explode('_', $string); + + if (preg_match('|^[A-Z]|', $string) === 0) { + // Name does not begin with a capital letter. + $validName = false; + } else { + foreach ($nameBits as $bit) { + if ($bit{0} !== strtoupper($bit{0})) { + $validName = false; + break; + } + } + } + + return $validName; + + }//end isUnderscoreName() + + + /** + * Returns a valid variable type for param/var tag. + * + * If type is not one of the standard type, it must be a custom type. + * Returns the correct type name suggestion if type name is invalid. + * + * @param string $varType The variable type to process. + * + * @return string + */ + public static function suggestType($varType) + { + if ($varType === '') { + return ''; + } + + if (in_array($varType, self::$allowedTypes) === true) { + return $varType; + } else { + $lowerVarType = strtolower($varType); + switch ($lowerVarType) { + case 'bool': + return 'boolean'; + case 'double': + case 'real': + return 'float'; + case 'int': + return 'integer'; + case 'array()': + return 'array'; + }//end switch + + if (strpos($lowerVarType, 'array(') !== false) { + // Valid array declaration: + // array, array(type), array(type1 => type2). + $matches = array(); + $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i'; + if (preg_match($pattern, $varType, $matches) !== 0) { + $type1 = ''; + if (isset($matches[1]) === true) { + $type1 = $matches[1]; + } + + $type2 = ''; + if (isset($matches[3]) === true) { + $type2 = $matches[3]; + } + + $type1 = self::suggestType($type1); + $type2 = self::suggestType($type2); + if ($type2 !== '') { + $type2 = ' => '.$type2; + } + + return "array($type1$type2)"; + } else { + return 'array'; + }//end if + } else if (in_array($lowerVarType, self::$allowedTypes) === true) { + // A valid type, but not lower cased. + return $lowerVarType; + } else { + // Must be a custom type name. + return $varType; + }//end if + }//end if + + }//end suggestType() + + + /** + * Get a list of all coding standards installed. + * + * Coding standards are directories located in the + * CodeSniffer/Standards directory. Valid coding standards + * include a Sniffs subdirectory. + * + * @param boolean $includeGeneric If true, the special "Generic" + * coding standard will be included + * if installed. + * @param string $standardsDir A specific directory to look for standards + * in. If not specified, PHP_CodeSniffer will + * look in its default location. + * + * @return array + * @see isInstalledStandard() + */ + public static function getInstalledStandards($includeGeneric=false, $standardsDir='') + { + $installedStandards = array(); + + if ($standardsDir === '') { + $standardsDir = dirname(__FILE__).'/CodeSniffer/Standards'; + } + + $di = new DirectoryIterator($standardsDir); + foreach ($di as $file) { + if ($file->isDir() === true && $file->isDot() === false) { + $filename = $file->getFilename(); + + // Ignore the special "Generic" standard. + if ($includeGeneric === false && $filename === 'Generic') { + continue; + } + + // Valid coding standard dirs include a standard class. + $csFile = $file->getPathname()."/{$filename}CodingStandard.php"; + if (is_file($csFile) === true) { + // We found a coding standard directory. + $installedStandards[] = $filename; + } + } + } + + return $installedStandards; + + }//end getInstalledStandards() + + + /** + * Determine if a standard is installed. + * + * Coding standards are directories located in the + * CodeSniffer/Standards directory. Valid coding standards + * include a Sniffs subdirectory. + * + * @param string $standard The name of the coding standard. + * + * @return boolean + * @see getInstalledStandards() + */ + public static function isInstalledStandard($standard) + { + $standardDir = dirname(__FILE__); + $standardDir .= '/CodeSniffer/Standards/'.$standard; + if (is_file("$standardDir/{$standard}CodingStandard.php") === true) { + return true; + } else { + // This could be a custom standard, installed outside our + // standards directory. + $standardFile = rtrim($standard, ' /\\').DIRECTORY_SEPARATOR.basename($standard).'CodingStandard.php'; + return (is_file($standardFile) === true); + } + + }//end isInstalledStandard() + + + /** + * Get a single config value. + * + * Config data is stored in the data dir, in a file called + * CodeSniffer.conf. It is a simple PHP array. + * + * @param string $key The name of the config value. + * + * @return string + * @see setConfigData() + * @see getAllConfigData() + */ + public static function getConfigData($key) + { + $phpCodeSnifferConfig = self::getAllConfigData(); + + if ($phpCodeSnifferConfig === null) { + return null; + } + + if (isset($phpCodeSnifferConfig[$key]) === false) { + return null; + } + + return $phpCodeSnifferConfig[$key]; + + }//end getConfigData() + + + /** + * Set a single config value. + * + * Config data is stored in the data dir, in a file called + * CodeSniffer.conf. It is a simple PHP array. + * + * @param string $key The name of the config value. + * @param string|null $value The value to set. If null, the config + * entry is deleted, reverting it to the + * default value. + * @param boolean $temp Set this config data temporarily for this + * script run. This will not write the config + * data to the config file. + * + * @return boolean + * @see getConfigData() + * @throws PHP_CodeSniffer_Exception If the config file can not be written. + */ + public static function setConfigData($key, $value, $temp=false) + { + if ($temp === false) { + $configFile = dirname(__FILE__).'/CodeSniffer.conf'; + if (is_file($configFile) === false) { + $configFile = '/usr/lib/php/data/PHP_CodeSniffer/CodeSniffer.conf'; + } + + if (is_file($configFile) === true && is_writable($configFile) === false) { + $error = "Config file $configFile is not writable"; + throw new PHP_CodeSniffer_Exception($error); + } + } + + $phpCodeSnifferConfig = self::getAllConfigData(); + + if ($value === null) { + if (isset($phpCodeSnifferConfig[$key]) === true) { + unset($phpCodeSnifferConfig[$key]); + } + } else { + $phpCodeSnifferConfig[$key] = $value; + } + + if ($temp === false) { + $output = '<'.'?php'."\n".' $phpCodeSnifferConfig = '; + $output .= var_export($phpCodeSnifferConfig, true); + $output .= "\n?".'>'; + + if (file_put_contents($configFile, $output) === false) { + return false; + } + } + + $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig; + + return true; + + }//end setConfigData() + + + /** + * Get all config data in an array. + * + * @return string + * @see getConfigData() + */ + public static function getAllConfigData() + { + if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']) === true) { + return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']; + } + + $configFile = dirname(__FILE__).'/CodeSniffer.conf'; + if (is_file($configFile) === false) { + $configFile = '/usr/lib/php/data/PHP_CodeSniffer/CodeSniffer.conf'; + } + + if (is_file($configFile) === false) { + return null; + } + + include $configFile; + $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig; + return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']; + + }//end getAllConfigData() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CLI.php b/lib/pear/PHP/CodeSniffer/CLI.php new file mode 100644 index 0000000000..b13bd63ab6 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CLI.php @@ -0,0 +1,473 @@ + + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (is_file(dirname(__FILE__).'/../CodeSniffer.php') === true) { + include_once dirname(__FILE__).'/../CodeSniffer.php'; +} else { + include_once 'PHP/CodeSniffer.php'; +} + +/** + * A class to process command line phpcs scripts. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CLI +{ + + + /** + * Exits if the minimum requirements of PHP_CodSniffer are not met. + * + * @return array + */ + public function checkRequirements() + { + // Check the PHP version. + if (version_compare(PHP_VERSION, '5.1.2') === -1) { + echo 'ERROR: PHP_CodeSniffer requires PHP version 5.1.2 or greater.'.PHP_EOL; + exit(2); + } + + if (extension_loaded('tokenizer') === false) { + echo 'ERROR: PHP_CodeSniffer requires the tokenizer extension to be enabled.'.PHP_EOL; + exit(2); + } + + }//end checkRequirements() + + + /** + * Get a list of default values for all possible command line arguments. + * + * @return array + */ + public function getDefaults() + { + // The default values for config settings. + $defaults['files'] = array(); + $defaults['standard'] = null; + $defaults['verbosity'] = 0; + $defaults['local'] = false; + $defaults['extensions'] = array(); + $defaults['ignored'] = array(); + $defaults['generator'] = ''; + + $defaults['report'] = PHP_CodeSniffer::getConfigData('report_format'); + if ($defaults['report'] === null) { + $defaults['report'] = 'full'; + } + + $showWarnings = PHP_CodeSniffer::getConfigData('show_warnings'); + if ($showWarnings === null) { + $defaults['showWarnings'] = true; + } else { + $defaults['showWarnings'] = (bool) $showWarnings; + } + + $tabWidth = PHP_CodeSniffer::getConfigData('tab_width'); + if ($tabWidth === null) { + $defaults['tabWidth'] = 0; + } else { + $defaults['tabWidth'] = (int) $tabWidth; + } + + return $defaults; + + }//end getDefaults() + + + /** + * Process the command line arguments and returns the values. + * + * @return array + */ + public function getCommandLineValues() + { + $values = $this->getDefaults(); + + for ($i = 1; $i < $_SERVER['argc']; $i++) { + $arg = $_SERVER['argv'][$i]; + if ($arg{0} === '-') { + if ($arg{1} === '-') { + $values = $this->processLongArgument(substr($arg, 2), $i, $values); + } else { + $switches = str_split($arg); + foreach ($switches as $switch) { + if ($switch === '-') { + continue; + } + + $values = $this->processShortArgument($switch, $values); + } + } + } else { + $values = $this->processUnknownArgument($arg, $i, $values); + } + }//end for + + return $values; + + }//end getCommandLineValues() + + + /** + * Processes a sort (-e) command line argument. + * + * @param string $arg The command line argument. + * @param array $values An array of values determined from CLI args. + * + * @return array The updated CLI values. + * @see getCommandLineValues() + */ + public function processShortArgument($arg, $values) + { + switch ($arg) { + case 'h': + case '?': + $this->printUsage(); + exit(0); + break; + case 'i' : + $this->printInstalledStandards(); + exit(0); + break; + case 'v' : + $values['verbosity']++; + break; + case 'l' : + $values['local'] = true; + break; + case 'n' : + $values['showWarnings'] = false; + break; + case 'w' : + $values['showWarnings'] = true; + break; + default: + $values = $this->processUnknownArgument('-'.$arg, $values); + }//end switch + + return $values; + + }//end processShortArgument() + + + /** + * Processes a long (--example) command line argument. + * + * @param string $arg The command line argument. + * @param int $pos The position of the argument on the command line. + * @param array $values An array of values determined from CLI args. + * + * @return array The updated CLI values. + * @see getCommandLineValues() + */ + public function processLongArgument($arg, $pos, $values) + { + switch ($arg) { + case 'help': + $this->printUsage(); + exit(0); + break; + case 'version': + echo 'PHP_CodeSniffer version 1.1.0 (stable) '; + echo 'by Squiz Pty Ltd. (http://www.squiz.net)'.PHP_EOL; + exit(0); + break; + case 'config-set': + $key = $_SERVER['argv'][($pos + 1)]; + $value = $_SERVER['argv'][($pos + 2)]; + PHP_CodeSniffer::setConfigData($key, $value); + exit(0); + break; + case 'config-delete': + $key = $_SERVER['argv'][($pos + 1)]; + PHP_CodeSniffer::setConfigData($key, null); + exit(0); + break; + case 'config-show': + $data = PHP_CodeSniffer::getAllConfigData(); + print_r($data); + exit(0); + break; + default: + if (substr($arg, 0, 7) === 'report=') { + $values['report'] = substr($arg, 7); + $validReports = array( + 'full', + 'xml', + 'checkstyle', + 'csv', + 'summary', + ); + + if (in_array($values['report'], $validReports) === false) { + echo 'ERROR: Report type "'.$report.'" not known.'.PHP_EOL; + exit(2); + } + } else if (substr($arg, 0, 9) === 'standard=') { + $values['standard'] = substr($arg, 9); + } else if (substr($arg, 0, 11) === 'extensions=') { + $values['extensions'] = explode(',', substr($arg, 11)); + } else if (substr($arg, 0, 7) === 'ignore=') { + // Split the ignore string on commas, unless the comma is escaped + // using 1 or 3 slashes (\, or \\\,). + $values['ignored'] = preg_split('/(?<=(?processUnknownArgument('--'.$arg, $values); + }//end if + + break; + }//end switch + + return $values; + + }//end processLongArgument() + + + /** + * Processes an unknown command line argument. + * + * Assumes all unknown arguments are files and folders to check. + * + * @param string $arg The command line argument. + * @param int $pos The position of the argument on the command line. + * @param array $values An array of values determined from CLI args. + * + * @return array The updated CLI values. + * @see getCommandLineValues() + */ + public function processUnknownArgument($arg, $pos, $values) + { + // We don't know about any additional switches; just files. + if ($arg{0} === '-') { + echo 'ERROR: option "'.$arg.'" not known.'.PHP_EOL.PHP_EOL; + $this->printUsage(); + exit(2); + } + + $file = realpath($arg); + if (file_exists($file) === false) { + echo 'ERROR: The file "'.$arg.'" does not exist.'.PHP_EOL.PHP_EOL; + $this->printUsage(); + exit(2); + } else { + $values['files'][] = $file; + } + + return $values; + + }//end processUnknownArgument() + + + /** + * Runs PHP_CodeSniffer over files are directories. + * + * @param array $values An array of values determined from CLI args. + * + * @return int The number of error and warning messages shown. + * @see getCommandLineValues() + */ + public function process($values=array()) + { + if (empty($values) === true) { + $values = $this->getCommandLineValues(); + } + + if ($values['generator'] !== '') { + $phpcs = new PHP_CodeSniffer($values['verbosity']); + $phpcs->generateDocs($values['standard'], $values['files'], $values['generator']); + exit(0); + } + + if (empty($values['files']) === true) { + echo 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL; + $this->printUsage(); + exit(2); + } + + $values['standard'] = $this->validateStandard($values['standard']); + if (PHP_CodeSniffer::isInstalledStandard($values['standard']) === false) { + // They didn't select a valid coding standard, so help them + // out by letting them know which standards are installed. + echo 'ERROR: the "'.$values['standard'].'" coding standard is not installed. '; + $this->printInstalledStandards(); + exit(2); + } + + $phpcs = new PHP_CodeSniffer($values['verbosity'], $values['tabWidth']); + + // Set file extensions if they were specified. Otherwise, + // let PHP_CodeSniffer decide on the defaults. + if (empty($values['extensions']) === false) { + $phpcs->setAllowedFileExtensions($values['extensions']); + } + + // Set ignore patterns if they were specified. + if (empty($values['ignored']) === false) { + $phpcs->setIgnorePatterns($values['ignored']); + } + + $phpcs->process($values['files'], $values['standard'], array(), $values['local']); + return $this->printErrorReport($phpcs, $values['report'], $values['showWarnings']); + + }//end process() + + + /** + * Prints the error report. + * + * @param PHP_CodeSniffer $phpcs The PHP_CodeSniffer object containing + * the errors. + * @param string $report The type of report to print. + * @param bool $showWarnings TRUE if warnings should also be printed. + * + * @return int The number of error and warning messages shown. + */ + public function printErrorReport($phpcs, $report, $showWarnings) + { + switch ($report) { + case 'xml': + $numErrors = $phpcs->printXMLErrorReport($showWarnings); + break; + case 'checkstyle': + $numErrors = $phpcs->printCheckstyleErrorReport($showWarnings); + break; + case 'csv': + $numErrors = $phpcs->printCSVErrorReport($showWarnings); + break; + case 'summary': + $numErrors = $phpcs->printErrorReportSummary($showWarnings); + break; + default: + $numErrors = $phpcs->printErrorReport($showWarnings); + break; + } + + return $numErrors; + + }//end printErrorReport() + + + /** + * Convert the passed standard into a valid standard. + * + * Checks things like default values and case. + * + * @param string $standard The standard to validate. + * + * @return string + */ + public function validateStandard($standard) + { + if ($standard === null) { + // They did not supply a standard to use. + // Try to get the default from the config system. + $standard = PHP_CodeSniffer::getConfigData('default_standard'); + if ($standard === null) { + $standard = 'Moodle'; + } + } + + // Check if the standard name is valid. If not, check that the case + // was not entered incorrectly. + if (PHP_CodeSniffer::isInstalledStandard($standard) === false) { + $installedStandards = PHP_CodeSniffer::getInstalledStandards(); + foreach ($installedStandards as $validStandard) { + if (strtolower($standard) === strtolower($validStandard)) { + $standard = $validStandard; + break; + } + } + } + + return $standard; + + }//end validateStandard() + + + /** + * Prints out the usage information for this script. + * + * @return void + */ + public function printUsage() + { + echo 'Usage: phpcs [-nwlvi] [--report=]'.PHP_EOL; + echo ' [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL; + echo ' [--generator=] [--extensions=]'.PHP_EOL; + echo ' [--ignore=] [--tab-width=] ...'.PHP_EOL; + echo ' -n Do not print warnings'.PHP_EOL; + echo ' -w Print both warnings and errors (on by default)'.PHP_EOL; + echo ' -l Local directory only, no recursion'.PHP_EOL; + echo ' -v[v][v] Print verbose output'.PHP_EOL; + echo ' -i Show a list of installed coding standards'.PHP_EOL; + echo ' --help Print this help message'.PHP_EOL; + echo ' --version Print version information'.PHP_EOL; + echo ' One or more files and/or directories to check'.PHP_EOL; + echo ' A comma separated list of file extensions to check'.PHP_EOL; + echo ' (only valid if checking a directory)'.PHP_EOL; + echo ' A comma separated list of patterns that are used'.PHP_EOL; + echo ' to ignore directories and files'.PHP_EOL; + echo ' The number of spaces each tab represents'.PHP_EOL; + echo ' The name of a doc generator to use'.PHP_EOL; + echo ' (forces doc generation instead of checking)'.PHP_EOL; + echo ' Print either the "full", "xml", "checkstyle",'.PHP_EOL; + echo ' "csv" or "summary" report'.PHP_EOL; + echo ' (the "full" report is printed by default)'.PHP_EOL; + + }//end printUsage() + + + /** + * Prints out a list of installed coding standards. + * + * @return void + */ + public function printInstalledStandards() + { + $installedStandards = PHP_CodeSniffer::getInstalledStandards(); + $numStandards = count($installedStandards); + + if ($numStandards === 0) { + echo 'No coding standards are installed.'.PHP_EOL; + } else { + $lastStandard = array_pop($installedStandards); + if ($numStandards === 1) { + echo "The only coding standard installed is $lastStandard".PHP_EOL; + } else { + $standardList = implode(', ', $installedStandards); + $standardList .= ' and '.$lastStandard; + echo 'The installed coding standards are '.$standardList.PHP_EOL; + } + } + + }//end printInstalledStandards() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/AbstractDocElement.php b/lib/pear/PHP/CodeSniffer/CommentParser/AbstractDocElement.php new file mode 100644 index 0000000000..d508deafd2 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/AbstractDocElement.php @@ -0,0 +1,327 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (interface_exists('PHP_CodeSniffer_CommentParser_DocElement', true) === false) { + $error = 'Interface PHP_CodeSniffer_CommentParser_DocElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * A class to handle most of the parsing operations of a doc comment element. + * + * Extending classes should implement the getSubElements method to return + * a list of elements that the doc comment element contains, in the order that + * they appear in the element. For example a function parameter element has a + * type, a variable name and a comment. It should therefore implement the method + * as follows: + * + * + * protected function getSubElements() + * { + * return array( + * 'type', + * 'variable', + * 'comment', + * ); + * } + * + * + * The processSubElement will be called for each of the sub elements to allow + * the extending class to process them. So for the parameter element we would + * have: + * + * + * protected function processSubElement($name, $content, $whitespaceBefore) + * { + * if ($name === 'type') { + * echo 'The name of the variable was '.$content; + * } + * // Process other tags. + * } + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +abstract class PHP_CodeSniffer_CommentParser_AbstractDocElement implements PHP_CodeSniffer_CommentParser_DocElement +{ + + /** + * The element previous to this element. + * + * @var PHP_CodeSniffer_CommentParser_DocElement + */ + protected $previousElement = null; + + /** + * The element proceeding this element. + * + * @var PHP_CodeSniffer_CommentParser_DocElement + */ + protected $nextElement = null; + + /** + * The whitespace the occurs after this element and its sub elements. + * + * @var string + */ + protected $afterWhitespace = ''; + + /** + * The tokens that comprise this element. + * + * @var array(string) + */ + protected $tokens = array(); + + /** + * The file this element is in. + * + * @var array(string) + */ + protected $phpcsFile = null; + + /** + * The tag that this element represents (omiting the @ symbol). + * + * @var string + */ + protected $tag = ''; + + + /** + * Constructs a Doc Element. + * + * @param PHP_CodeSniffer_CommentParser_DocElement $previousElement The element + * that ocurred + * before this. + * @param array $tokens The tokens of + * this element. + * @param string $tag The doc + * element tag + * this element + * represents. + * @param PHP_CodeSniffer_File $phpcsFile The file that + * this element + * is in. + * + * @throws Exception If $previousElement in not a DocElement or if + * getSubElements() does not return an array. + */ + public function __construct($previousElement, array $tokens, $tag, PHP_CodeSniffer_File $phpcsFile) + { + if ($previousElement !== null && ($previousElement instanceof PHP_CodeSniffer_CommentParser_DocElement) === false) { + $error = '$previousElement must be an instance of DocElement'; + throw new Exception($error); + } + + $this->phpcsFile = $phpcsFile; + + $this->previousElement = $previousElement; + if ($previousElement !== null) { + $this->previousElement->nextElement = $this; + } + + $this->tag = $tag; + $this->tokens = $tokens; + + $subElements = $this->getSubElements(); + + if (is_array($subElements) === false) { + throw new Exception('getSubElements() must return an array'); + } + + $whitespace = ''; + $currElem = 0; + $lastElement = ''; + $lastElementWhitespace = null; + $numSubElements = count($subElements); + + foreach ($this->tokens as $token) { + if (trim($token) === '') { + $whitespace .= $token; + } else { + if ($currElem < ($numSubElements - 1)) { + $element = $subElements[$currElem]; + $this->processSubElement($element, $token, $whitespace); + $whitespace = ''; + $currElem++; + } else { + if ($lastElementWhitespace === null) { + $lastElementWhitespace = $whitespace; + } + + $lastElement .= $whitespace.$token; + $whitespace = ''; + } + } + }//end foreach + + $lastElement = ltrim($lastElement); + $lastElementName = $subElements[($numSubElements - 1)]; + + // Process the last element in this tag. + $this->processSubElement($lastElementName, $lastElement, $lastElementWhitespace); + $this->afterWhitespace = $whitespace; + + }//end __construct() + + + /** + * Returns the element that exists before this. + * + * @return PHP_CodeSniffer_CommentParser_DocElement + */ + public function getPreviousElement() + { + return $this->previousElement; + + }//end getPreviousElement() + + + /** + * Returns the element that exists after this. + * + * @return PHP_CodeSniffer_CommentParser_DocElement + */ + public function getNextElement() + { + return $this->nextElement; + + }//end getNextElement() + + + /** + * Returns the whitespace that exists before this element. + * + * @return string + */ + public function getWhitespaceBefore() + { + if ($this->previousElement !== null) { + return $this->previousElement->getWhitespaceAfter(); + } + + return ''; + + }//end getWhitespaceBefore() + + + /** + * Returns the whitespace that exists after this element. + * + * @return string + */ + public function getWhitespaceAfter() + { + return $this->afterWhitespace; + + }//end getWhitespaceAfter() + + + /** + * Returns the order that this element appears in the comment. + * + * @return int + */ + public function getOrder() + { + if ($this->previousElement === null) { + return 1; + } else { + return ($this->previousElement->getOrder() + 1); + } + + }//end getOrder() + + + /** + * Returns the tag that this element represents, ommiting the @ symbol. + * + * @return string + */ + public function getTag() + { + return $this->tag; + + }//end getTag() + + + /** + * Returns the raw content of this element, ommiting the tag. + * + * @return string + */ + public function getRawContent() + { + return implode('', $this->tokens); + + }//end getRawContent() + + + /** + * Returns the line in which this element first occured. + * + * @return int + */ + public function getLine() + { + if ($this->previousElement === null) { + // First element is on line one. + return 1; + } else { + $previousContent = $this->previousElement->getRawContent(); + $previousLine = $this->previousElement->getLine(); + + return ($previousLine + substr_count($previousContent, $this->phpcsFile->eolChar)); + } + + }//end getLine() + + + /** + * Returns the sub element names that make up this element in the order they + * appear in the element. + * + * @return array(string) + * @see processSubElement() + */ + abstract protected function getSubElements(); + + + /** + * Called to process each sub element as sepcified in the return value + * of getSubElements(). + * + * @param string $name The name of the element to process. + * @param string $content The content of the the element. + * @param string $whitespaceBefore The whitespace found before this element. + * + * @return void + * @see getSubElements() + */ + abstract protected function processSubElement($name, $content, $whitespaceBefore); + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/AbstractParser.php b/lib/pear/PHP/CodeSniffer/CommentParser/AbstractParser.php new file mode 100644 index 0000000000..53a402416b --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/AbstractParser.php @@ -0,0 +1,586 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_SingleElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_SingleElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +if (class_exists('PHP_CodeSniffer_CommentParser_CommentElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_CommentElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +if (class_exists('PHP_CodeSniffer_CommentParser_ParserException', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_ParserException not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * Parses doc comments. + * + * This abstract parser handles the following tags: + * + *
    + *
  • The short description and the long description
  • + *
  • @see
  • + *
  • @link
  • + *
  • @deprecated
  • + *
  • @since
  • + *
+ * + * Extending classes should implement the getAllowedTags() method to return the + * tags that they wish to process, ommiting the tags that this base class + * processes. When one of these tags in encountered, the process<tag_name> + * method is called on that class. For example, if a parser's getAllowedTags() + * method returns \@param as one of its tags, the processParam method will be + * called so that the parser can process such a tag. + * + * The method is passed the tokens that comprise this tag. The tokens array + * includes the whitespace that exists between the tokens, as seperate tokens. + * It's up to the method to create a element that implements the DocElement + * interface, which should be returned. The AbstractDocElement class is a helper + * class that can be used to handle most of the parsing of the tokens into their + * individual sub elements. It requires that you construct it with the element + * previous to the element currently being processed, which can be acquired + * with the protected $previousElement class member of this class. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +abstract class PHP_CodeSniffer_CommentParser_AbstractParser +{ + + /** + * The comment element that appears in the doc comment. + * + * @var PHP_CodeSniffer_CommentParser_CommentElement + */ + protected $comment = null; + + /** + * The string content of the comment. + * + * @var string + */ + protected $commentString = ''; + + /** + * The file that the comment exists in. + * + * @var PHP_CodeSniffer_File + */ + protected $phpcsFile = null; + + /** + * The word tokens that appear in the comment. + * + * Whitespace tokens also appear in this stack, but are separate tokens + * from words. + * + * @var array(string) + */ + protected $words = array(); + + /** + * The previous doc element that was processed. + * + * null if the current element being processed is the first element in the + * doc comment. + * + * @var PHP_CodeSniffer_CommentParser_DocElement + */ + protected $previousElement = null; + + /** + * A list of see elements that appear in this doc comment. + * + * @var array(PHP_CodeSniffer_CommentParser_SingleElement) + */ + protected $sees = array(); + + /** + * A list of see elements that appear in this doc comment. + * + * @var array(PHP_CodeSniffer_CommentParser_SingleElement) + */ + protected $deprecated = null; + + /** + * A list of see elements that appear in this doc comment. + * + * @var array(PHP_CodeSniffer_CommentParser_SingleElement) + */ + protected $links = array(); + + /** + * A element to represent \@since tags. + * + * @var PHP_CodeSniffer_CommentParser_SingleElement + */ + protected $since = null; + + /** + * True if the comment has been parsed. + * + * @var boolean + */ + private $_hasParsed = false; + + /** + * The tags that this class can process. + * + * @var array(string) + */ + private static $_tags = array( + 'see' => false, + 'link' => false, + 'deprecated' => true, + 'since' => true, + ); + + /** + * An array of unknown tags. + * + * @var array(string) + */ + public $unknown = array(); + + /** + * The order of tags. + * + * @var array(string) + */ + public $orders = array(); + + + /** + * Constructs a Doc Comment Parser. + * + * @param string $comment The comment to parse. + * @param PHP_CodeSniffer_File $phpcsFile The file that this comment is in. + */ + public function __construct($comment, PHP_CodeSniffer_File $phpcsFile) + { + $this->commentString = $comment; + $this->phpcsFile = $phpcsFile; + + }//end __construct() + + + /** + * Initiates the parsing of the doc comment. + * + * @return void + * @throws PHP_CodeSniffer_CommentParser_ParserException If the parser finds a + * problem with the + * comment. + */ + public function parse() + { + if ($this->_hasParsed === false) { + $this->_parse($this->commentString); + } + + }//end parse() + + + /** + * Parse the comment. + * + * @param string $comment The doc comment to parse. + * + * @return void + * @see _parseWords() + */ + private function _parse($comment) + { + // Firstly, remove the comment tags and any stars from the left side. + $lines = split($this->phpcsFile->eolChar, $comment); + foreach ($lines as &$line) { + $line = trim($line); + + if ($line !== '') { + if (substr($line, 0, 3) === '/**') { + $line = substr($line, 3); + } else if (substr($line, -2, 2) === '*/') { + $line = substr($line, 0, -2); + } else if ($line{0} === '*') { + $line = substr($line, 1); + } + + // Add the words to the stack, preserving newlines. Other parsers + // might be interested in the spaces between words, so tokenize + // spaces as well as separate tokens. + $flags = (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $words = preg_split('|(\s+)|', $line.$this->phpcsFile->eolChar, -1, $flags); + $this->words = array_merge($this->words, $words); + } + }//end foreach + + $this->_parseWords(); + + }//end _parse() + + + /** + * Parses each word within the doc comment. + * + * @return void + * @see _parse() + * @throws PHP_CodeSniffer_CommentParser_ParserException If more than the allowed + * number of occurances of + * a tag is found. + */ + private function _parseWords() + { + $allowedTags = (self::$_tags + $this->getAllowedTags()); + $allowedTagNames = array_keys($allowedTags); + $foundTags = array(); + $prevTagPos = false; + $wordWasEmpty = true; + + foreach ($this->words as $wordPos => $word) { + + if (trim($word) !== '') { + $wordWasEmpty = false; + } + + if ($word{0} === '@') { + + $tag = substr($word, 1); + + // Filter out @ tags in the comment description. + // A real comment tag should have whitespace and a newline before it. + if (isset($this->words[($wordPos - 1)]) === false || trim($this->words[($wordPos - 1)]) !== '') { + continue; + } + + if (isset($this->words[($wordPos - 2)]) === false || $this->words[($wordPos - 2)] !== $this->phpcsFile->eolChar) { + continue; + } + + $foundTags[] = $tag; + + if ($prevTagPos !== false) { + // There was a tag before this so let's process it. + $prevTag = substr($this->words[$prevTagPos], 1); + $this->parseTag($prevTag, $prevTagPos, ($wordPos - 1)); + } else { + // There must have been a comment before this tag, so + // let's process that. + $this->parseTag('comment', 0, ($wordPos - 1)); + } + + $prevTagPos = $wordPos; + + if (in_array($tag, $allowedTagNames) === false) { + // This is not a tag that we process, but let's check to + // see if it is a tag we know about. If we don't know about it, + // we add it to a list of unknown tags. + $knownTags = array( + 'abstract', + 'access', + 'example', + 'filesource', + 'global', + 'ignore', + 'internal', + 'name', + 'static', + 'staticvar', + 'todo', + 'tutorial', + 'uses', + 'package_version@', + ); + + if (in_array($tag, $knownTags) === false) { + $this->unknown[] = array( + 'tag' => $tag, + 'line' => $this->getLine($wordPos), + ); + } + + }//end if + + }//end if + }//end foreach + + // Only process this tag if there was something to process. + if ($wordWasEmpty === false) { + if ($prevTagPos === false) { + // There must only be a comment in this doc comment. + $this->parseTag('comment', 0, count($this->words)); + } else { + // Process the last tag element. + $prevTag = substr($this->words[$prevTagPos], 1); + $this->parseTag($prevTag, $prevTagPos, count($this->words)); + } + } + + }//end _parseWords() + + + /** + * Returns the line that the token exists on in the doc comment. + * + * @param int $tokenPos The position in the words stack to find the line + * number for. + * + * @return int + */ + protected function getLine($tokenPos) + { + $newlines = 0; + for ($i = 0; $i < $tokenPos; $i++) { + $newlines += substr_count($this->phpcsFile->eolChar, $this->words[$i]); + } + + return $newlines; + + }//end getLine() + + + /** + * Parses see tag element within the doc comment. + * + * @param array(string) $tokens The word tokens that comprise this element. + * + * @return DocElement The element that represents this see comment. + */ + protected function parseSee($tokens) + { + $see = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'see', $this->phpcsFile); + $this->sees[] = $see; + return $see; + + }//end parseSee() + + + /** + * Parses the comment element that appears at the top of the doc comment. + * + * @param array(string) $tokens The word tokens that comprise tihs element. + * + * @return DocElement The element that represents this comment element. + */ + protected function parseComment($tokens) + { + $this->comment = new PHP_CodeSniffer_CommentParser_CommentElement($this->previousElement, $tokens, $this->phpcsFile); + return $this->comment; + + }//end parseComment() + + + /** + * Parses \@deprecated tags. + * + * @param array(string) $tokens The word tokens that comprise tihs element. + * + * @return DocElement The element that represents this deprecated tag. + */ + protected function parseDeprecated($tokens) + { + $this->deprecated = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'deprecated', $this->phpcsFile); + return $this->deprecated; + + }//end parseDeprecated() + + + /** + * Parses \@since tags. + * + * @param array(string) $tokens The word tokens that comprise this element. + * + * @return SingleElement The element that represents this since tag. + */ + protected function parseSince($tokens) + { + $this->since = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'since', $this->phpcsFile); + return $this->since; + + }//end parseSince() + + + /** + * Parses \@link tags. + * + * @param array(string) $tokens The word tokens that comprise this element. + * + * @return SingleElement The element that represents this link tag. + */ + protected function parseLink($tokens) + { + $link = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'link', $this->phpcsFile); + $this->links[] = $link; + return $link; + + }//end parseLink() + + + /** + * Returns the see elements that appear in this doc comment. + * + * @return array(SingleElement) + */ + public function getSees() + { + return $this->sees; + + }//end getSees() + + + /** + * Returns the comment element that appears at the top of this doc comment. + * + * @return CommentElement + */ + public function getComment() + { + return $this->comment; + + }//end getComment() + + + /** + * Returns the link elements found in this comment. + * + * Returns an empty array if no links are found in the comment. + * + * @return array(SingleElement) + */ + public function getLinks() + { + return $this->links; + + }//end getLinks() + + + /** + * Returns the deprecated element found in this comment. + * + * Returns null if no element exists in the comment. + * + * @return SingleElement + */ + public function getDeprecated() + { + return $this->deprecated; + + }//end getDeprecated() + + + /** + * Returns the since element found in this comment. + * + * Returns null if no element exists in the comment. + * + * @return SingleElement + */ + public function getSince() + { + return $this->since; + + }//end getSince() + + + /** + * Parses the specified tag. + * + * @param string $tag The tag name to parse (omitting the @ sybmol from + * the tag) + * @param int $start The position in the word tokens where this element + * started. + * @param int $end The position in the word tokens where this element + * ended. + * + * @return void + * @throws Exception If the process method for the tag cannot be found. + */ + protected function parseTag($tag, $start, $end) + { + $tokens = array_slice($this->words, ($start + 1), ($end - $start)); + + $allowedTags = (self::$_tags + $this->getAllowedTags()); + $allowedTagNames = array_keys($allowedTags); + if ($tag === 'comment' || in_array($tag, $allowedTagNames) === true) { + $method = 'parse'.$tag; + if (method_exists($this, $method) === false) { + $error = 'Method '.$method.' must be implemented to process '.$tag.' tags'; + throw new Exception($error); + } + + $this->previousElement = $this->$method($tokens); + } else { + $this->previousElement = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, $tag, $this->phpcsFile); + } + + $this->orders[] = $tag; + + if ($this->previousElement === null || ($this->previousElement instanceof PHP_CodeSniffer_CommentParser_DocElement) === false) { + throw new Exception('Parse method must return a DocElement'); + } + + }//end parseTag() + + + /** + * Returns a list of tags that this comment parser allows for it's comment. + * + * Each tag should indicate if only one entry of this tag can exist in the + * comment by specifying true as the array value, or false if more than one + * is allowed. Each tag should ommit the @ symbol. Only tags other than + * the standard tags should be returned. + * + * @return array(string => boolean) + */ + protected abstract function getAllowedTags(); + + + /** + * Returns the tag orders (index => tagName). + * + * @return array + */ + public function getTagOrders() + { + return $this->orders; + + }//end getTagOrders() + + + /** + * Returns the unknown tags. + * + * @return array + */ + public function getUnknown() + { + return $this->unknown; + + }//end getUnknown() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/ClassCommentParser.php b/lib/pear/PHP/CodeSniffer/CommentParser/ClassCommentParser.php new file mode 100644 index 0000000000..50e784bec9 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/ClassCommentParser.php @@ -0,0 +1,300 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_AbstractParser', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_AbstractParser not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * Parses Class doc comments. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CommentParser_ClassCommentParser extends PHP_CodeSniffer_CommentParser_AbstractParser +{ + + /** + * The package element of this class. + * + * @var SingleElement + */ + private $_package = null; + + /** + * The subpackage element of this class. + * + * @var SingleElement + */ + private $_subpackage = null; + + /** + * The version element of this class. + * + * @var SingleElement + */ + private $_version = null; + + /** + * The category element of this class. + * + * @var SingleElement + */ + private $_category = null; + + /** + * The copyright elements of this class. + * + * @var array(SingleElement) + */ + private $_copyrights = array(); + + /** + * The licence element of this class. + * + * @var PairElement + */ + private $_license = null; + + /** + * The author elements of this class. + * + * @var array(SingleElement) + */ + private $_authors = array(); + + + /** + * Returns the allowed tags withing a class comment. + * + * @return array(string => int) + */ + protected function getAllowedTags() + { + return array( + 'category' => false, + 'package' => true, + 'subpackage' => true, + 'author' => false, + 'copyright' => true, + 'license' => false, + 'version' => true, + ); + + }//end getAllowedTags() + + + /** + * Parses the license tag of this class comment. + * + * @param array $tokens The tokens that comprise this tag. + * + * @return PHP_CodeSniffer_CommentParser_PairElement + */ + protected function parseLicense($tokens) + { + $this->_license = new PHP_CodeSniffer_CommentParser_PairElement($this->previousElement, $tokens, 'license', $this->phpcsFile); + return $this->_license; + + }//end parseLicense() + + + /** + * Parses the copyright tags of this class comment. + * + * @param array $tokens The tokens that comprise this tag. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + protected function parseCopyright($tokens) + { + $copyright = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'copyright', $this->phpcsFile); + $this->_copyrights[] = $copyright; + return $copyright; + + }//end parseCopyright() + + + /** + * Parses the category tag of this class comment. + * + * @param array $tokens The tokens that comprise this tag. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + protected function parseCategory($tokens) + { + $this->_category = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'category', $this->phpcsFile); + return $this->_category; + + }//end parseCategory() + + + /** + * Parses the author tag of this class comment. + * + * @param array $tokens The tokens that comprise this tag. + * + * @return array(PHP_CodeSniffer_CommentParser_SingleElement) + */ + protected function parseAuthor($tokens) + { + $author = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'author', $this->phpcsFile); + $this->_authors[] = $author; + return $author; + + }//end parseAuthor() + + + /** + * Parses the version tag of this class comment. + * + * @param array $tokens The tokens that comprise this tag. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + protected function parseVersion($tokens) + { + $this->_version = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'version', $this->phpcsFile); + return $this->_version; + + }//end parseVersion() + + + /** + * Parses the package tag found in this test. + * + * @param array $tokens The tokens that comprise this var. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + protected function parsePackage($tokens) + { + $this->_package = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'package', $this->phpcsFile); + return $this->_package; + + }//end parsePackage() + + + /** + * Parses the package tag found in this test. + * + * @param array $tokens The tokens that comprise this var. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + protected function parseSubpackage($tokens) + { + $this->_subpackage = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'subpackage', $this->phpcsFile); + return $this->_subpackage; + + }//end parseSubpackage() + + + /** + * Returns the authors of this class comment. + * + * @return array(PHP_CodeSniffer_CommentParser_SingleElement) + */ + public function getAuthors() + { + return $this->_authors; + + }//end getAuthors() + + + /** + * Returns the version of this class comment. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + public function getVersion() + { + return $this->_version; + + }//end getVersion() + + + /** + * Returns the license of this class comment. + * + * @return PHP_CodeSniffer_CommentParser_PairElement + */ + public function getLicense() + { + return $this->_license; + + }//end getLicense() + + + /** + * Returns the copyrights of this class comment. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + public function getCopyrights() + { + return $this->_copyrights; + + }//end getCopyrights() + + + /** + * Returns the category of this class comment. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + public function getCategory() + { + return $this->_category; + + }//end getCategory() + + + /** + * Returns the package that this class belongs to. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + public function getPackage() + { + return $this->_package; + + }//end getPackage() + + + /** + * Returns the subpackage that this class belongs to. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + public function getSubpackage() + { + return $this->_subpackage; + + }//end getSubpackage() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/CommentElement.php b/lib/pear/PHP/CodeSniffer/CommentParser/CommentElement.php new file mode 100644 index 0000000000..8ee383244c --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/CommentElement.php @@ -0,0 +1,240 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_SingleElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_SingleElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * A class to represent Comments of a doc comment. + * + * Comments are in the following format. + * + * /** <--this is the start of the comment. + * * This is a short comment description + * * + * * This is a long comment description + * * <-- this is the end of the comment + * * @return something + * {@/} + * + * + * Note that the sentence before two newlines is assumed + * the short comment description. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CommentParser_CommentElement extends PHP_CodeSniffer_CommentParser_SingleElement +{ + + + /** + * Constructs a PHP_CodeSniffer_CommentParser_CommentElement. + * + * @param PHP_CodeSniffer_CommentParser_DocElemement $previousElement The element + * that + * appears + * before this + * element. + * @param array $tokens The tokens + * that make + * up this + * element. + * @param PHP_CodeSniffer_File $phpcsFile The file + * that this + * element is + * in. + */ + public function __construct($previousElement, $tokens, PHP_CodeSniffer_File $phpcsFile) + { + parent::__construct($previousElement, $tokens, 'comment', $phpcsFile); + + }//end __construct() + + + /** + * Returns the short comment description. + * + * @return string + * @see getLongComment() + */ + public function getShortComment() + { + $pos = $this->_getShortCommentEndPos(); + if ($pos === -1) { + return ''; + } + + return implode('', array_slice($this->tokens, 0, ($pos + 1))); + + }//end getShortComment() + + + /** + * Returns the last token position of the short comment description. + * + * @return int The last token position of the short comment description + * @see _getLongCommentStartPos() + */ + private function _getShortCommentEndPos() + { + $found = false; + $whiteSpace = array( + ' ', + "\t", + ); + + foreach ($this->tokens as $pos => $token) { + $token = str_replace($whiteSpace, '', $token); + if ($token === $this->phpcsFile->eolChar) { + if ($found === false) { + // Include newlines before short description. + continue; + } else { + if (isset($this->tokens[($pos + 1)]) === true) { + if ($this->tokens[($pos + 1)] === $this->phpcsFile->eolChar) { + return ($pos - 1); + } + } else { + return $pos; + } + } + } else { + $found = true; + } + }//end foreach + + return (count($this->tokens) - 1); + + }//end _getShortCommentEndPos() + + + /** + * Returns the long comment description. + * + * @return string + * @see getShortComment + */ + public function getLongComment() + { + $start = $this->_getLongCommentStartPos(); + if ($start === -1) { + return ''; + } + + return implode('', array_slice($this->tokens, $start)); + + }//end getLongComment() + + + /** + * Returns the start position of the long comment description. + * + * Returns -1 if there is no long comment. + * + * @return int The start position of the long comment description. + * @see _getShortCommentEndPos() + */ + private function _getLongCommentStartPos() + { + $pos = ($this->_getShortCommentEndPos() + 1); + if ($pos === (count($this->tokens) - 1)) { + return -1; + } + + $count = count($this->tokens); + for ($i = $pos; $i < $count; $i++) { + $content = trim($this->tokens[$i]); + if ($content !== '') { + if ($content{0} === '@') { + return -1; + } + + return $i; + } + } + + return -1; + + }//end _getLongCommentStartPos() + + + /** + * Returns the whitespace that exists between + * the short and the long comment description. + * + * @return string + */ + public function getWhiteSpaceBetween() + { + $endShort = ($this->_getShortCommentEndPos() + 1); + $startLong = ($this->_getLongCommentStartPos() - 1); + if ($startLong === -1) { + return ''; + } + + return implode('', array_slice($this->tokens, $endShort, ($startLong - $endShort))); + + }//end getWhiteSpaceBetween() + + + /** + * Returns the number of newlines that exist before the tags. + * + * @return int + */ + public function getNewlineAfter() + { + $long = $this->getLongComment(); + if ($long !== '') { + $long = rtrim($long, ' '); + $long = strrev($long); + $newlines = strspn($long, $this->phpcsFile->eolChar); + } else { + $endShort = ($this->_getShortCommentEndPos() + 1); + $after = implode('', array_slice($this->tokens, $endShort)); + $after = trim($after, ' '); + $newlines = strspn($after, $this->phpcsFile->eolChar); + } + + return ($newlines / strlen($this->phpcsFile->eolChar)); + + }//end getNewlineAfter() + + + /** + * Returns true if there is no comment. + * + * @return boolean + */ + public function isEmpty() + { + return (trim($this->getContent()) === ''); + + }//end isEmpty() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/DocElement.php b/lib/pear/PHP/CodeSniffer/CommentParser/DocElement.php new file mode 100644 index 0000000000..2648b8a728 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/DocElement.php @@ -0,0 +1,105 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * A DocElement represents a logical element within a Doc Comment. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +interface PHP_CodeSniffer_CommentParser_DocElement +{ + + + /** + * Returns the name of the tag this element represents, omitting the @ symbol. + * + * @return string + */ + public function getTag(); + + + /** + * Returns the whitespace that exists before this element. + * + * @return string + * @see getWhitespaceAfter() + */ + public function getWhitespaceBefore(); + + + /** + * Returns the whitespace that exists after this element. + * + * @return string + * @see getWhitespaceBefore() + */ + public function getWhitespaceAfter(); + + + /** + * Returns the order that this element appears in the doc comment. + * + * The first element in the comment should have an order of 1. + * + * @return int + */ + public function getOrder(); + + + /** + * Returns the element that appears before this element. + * + * @return PHP_CodeSniffer_CommentParser_DocElement + * @see getNextElement() + */ + public function getPreviousElement(); + + + /** + * Returns the element that appears after this element. + * + * @return PHP_CodeSniffer_CommentParser_DocElement + * @see getPreviousElement() + */ + public function getNextElement(); + + + /** + * Returns the line that this element started on. + * + * @return int + */ + public function getLine(); + + + /** + * Returns the raw content of this element, ommiting the tag. + * + * @return string + */ + public function getRawContent(); + + +}//end interface + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/FunctionCommentParser.php b/lib/pear/PHP/CodeSniffer/CommentParser/FunctionCommentParser.php new file mode 100644 index 0000000000..a8616cc781 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/FunctionCommentParser.php @@ -0,0 +1,196 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_AbstractParser', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_AbstractParser not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +if (class_exists('PHP_CodeSniffer_CommentParser_ParameterElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_ParameterElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +if (class_exists('PHP_CodeSniffer_CommentParser_PairElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_PairElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +if (class_exists('PHP_CodeSniffer_CommentParser_SingleElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_SingleElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * Parses function doc comments. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CommentParser_FunctionCommentParser extends PHP_CodeSniffer_CommentParser_AbstractParser +{ + + /** + * The parameter elements within this function comment. + * + * @var array(PHP_CodeSniffer_CommentParser_ParameterElement) + */ + private $_params = array(); + + /** + * The return element in this function comment. + * + * @var PHP_CodeSniffer_CommentParser_PairElement. + */ + private $_return = null; + + /** + * The throws element list for this function comment. + * + * @var array(PHP_CodeSniffer_CommentParser_PairElement) + */ + private $_throws = array(); + + + /** + * Constructs a PHP_CodeSniffer_CommentParser_FunctionCommentParser. + * + * @param string $comment The comment to parse. + * @param PHP_CodeSniffer_File $phpcsFile The file that this comment is in. + */ + public function __construct($comment, PHP_CodeSniffer_File $phpcsFile) + { + parent::__construct($comment, $phpcsFile); + + }//end __construct() + + + /** + * Parses parameter elements. + * + * @param array(string) $tokens The tokens that conmpise this sub element. + * + * @return PHP_CodeSniffer_CommentParser_ParameterElement + */ + protected function parseParam($tokens) + { + $param = new PHP_CodeSniffer_CommentParser_ParameterElement($this->previousElement, $tokens, $this->phpcsFile); + $this->_params[] = $param; + return $param; + + }//end parseParam() + + + /** + * Parses return elements. + * + * @param array(string) $tokens The tokens that conmpise this sub element. + * + * @return PHP_CodeSniffer_CommentParser_PairElement + */ + protected function parseReturn($tokens) + { + $return = new PHP_CodeSniffer_CommentParser_PairElement($this->previousElement, $tokens, 'return', $this->phpcsFile); + $this->_return = $return; + return $return; + + }//end parseReturn() + + + /** + * Parses throws elements. + * + * @param array(string) $tokens The tokens that conmpise this sub element. + * + * @return PHP_CodeSniffer_CommentParser_PairElement + */ + protected function parseThrows($tokens) + { + $throws = new PHP_CodeSniffer_CommentParser_PairElement($this->previousElement, $tokens, 'throws', $this->phpcsFile); + $this->_throws[] = $throws; + return $throws; + + }//end parseThrows() + + + /** + * Returns the parameter elements that this function comment contains. + * + * Returns an empty array if no parameter elements are contained within + * this function comment. + * + * @return array(PHP_CodeSniffer_CommentParser_ParameterElement) + */ + public function getParams() + { + return $this->_params; + + }//end getParams() + + + /** + * Returns the return element in this fucntion comment. + * + * Returns null if no return element exists in the comment. + * + * @return PHP_CodeSniffer_CommentParser_PairElement + */ + public function getReturn() + { + return $this->_return; + + }//end getReturn() + + + /** + * Returns the throws elements in this fucntion comment. + * + * Returns empty array if no throws elements in the comment. + * + * @return array(PHP_CodeSniffer_CommentParser_PairElement) + */ + public function getThrows() + { + return $this->_throws; + + }//end getThrows() + + + /** + * Returns the allowed tags that can exist in a function comment. + * + * @return array(string => boolean) + */ + protected function getAllowedTags() + { + return array( + 'param' => false, + 'return' => true, + 'throws' => false, + ); + + }//end getAllowedTags() + + +}//end class + +?> \ No newline at end of file diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/MemberCommentParser.php b/lib/pear/PHP/CodeSniffer/CommentParser/MemberCommentParser.php new file mode 100644 index 0000000000..24825e2b61 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/MemberCommentParser.php @@ -0,0 +1,86 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_ClassCommentParser', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_ClassCommentParser not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * Parses class member comments. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CommentParser_MemberCommentParser extends PHP_CodeSniffer_CommentParser_ClassCommentParser +{ + + /** + * Represents a \@var tag in a member comment. + * + * @var PHP_CodeSniffer_CommentParser_SingleElement + */ + private $_var = null; + + + /** + * Parses Var tags. + * + * @param array $tokens The tokens that represent this tag. + * + * @return PHP_CodeSniffer_CommentParser_SingleElement + */ + protected function parseVar($tokens) + { + $this->_var = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'var', $this->phpcsFile); + return $this->_var; + + }//end parseVar() + + + /** + * Returns the var tag found in the member comment. + * + * @return PHP_CodeSniffer_CommentParser_PairElement + */ + public function getVar() + { + return $this->_var; + + }//end getVar() + + + /** + * Returns the allowed tags for this parser. + * + * @return array + */ + protected function getAllowedTags() + { + return array('var' => true); + + }//end getAllowedTags() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/PairElement.php b/lib/pear/PHP/CodeSniffer/CommentParser/PairElement.php new file mode 100644 index 0000000000..7917f7cddb --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/PairElement.php @@ -0,0 +1,168 @@ + comment format. + * + * PHP version 5 + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_AbstractDocElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_AbstractDocElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * A class to represent elements that have a value => comment format. + * + * An example of a pair element tag is the \@throws as it has an exception type + * and a comment on the circumstance of when the exception is thrown. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CommentParser_PairElement extends PHP_CodeSniffer_CommentParser_AbstractDocElement +{ + + /** + * The value of the tag. + * + * @var string + */ + private $_value = ''; + + /** + * The comment of the tag. + * + * @var string + */ + private $_comment = ''; + + /** + * The whitespace that exists before the value elem. + * + * @var string + */ + private $_valueWhitespace = ''; + + /** + * The whitespace that exists before the comment elem. + * + * @var string + */ + private $_commentWhitespace = ''; + + + /** + * Constructs a PHP_CodeSniffer_CommentParser_PairElement doc tag. + * + * @param PHP_CodeSniffer_CommentParser_DocElement $previousElement The element + * before this + * one. + * @param array $tokens The tokens + * that comprise + * this element. + * @param string $tag The tag that + * this element + * represents. + * @param PHP_CodeSniffer_File $phpcsFile The file that + * this element + * is in. + */ + public function __construct($previousElement, $tokens, $tag, PHP_CodeSniffer_File $phpcsFile) + { + parent::__construct($previousElement, $tokens, $tag, $phpcsFile); + + }//end __construct() + + + /** + * Returns the element names that this tag is comprised of, in the order + * that they appear in the tag. + * + * @return array(string) + * @see processSubElement() + */ + protected function getSubElements() + { + return array( + 'value', + 'comment', + ); + + }//end getSubElements() + + + /** + * Processes the sub element with the specified name. + * + * @param string $name The name of the sub element to process. + * @param string $content The content of this sub element. + * @param string $whitespaceBefore The whitespace that exists before the + * sub element. + * + * @return void + * @see getSubElements() + */ + protected function processSubElement($name, $content, $whitespaceBefore) + { + $element = '_'.$name; + $whitespace = $element.'Whitespace'; + $this->$element = $content; + $this->$whitespace = $whitespaceBefore; + + }//end processSubElement() + + + /** + * Returns the value of the tag. + * + * @return string + */ + public function getValue() + { + return $this->_value; + + }//end getValue() + + + /** + * Returns the comment associated with the value of this tag. + * + * @return string + */ + public function getComment() + { + return $this->_comment; + + }//end getComment() + + + /** + * Returns the witespace before the content of this tag. + * + * @return string + */ + public function getWhitespaceBeforeValue() + { + return $this->_valueWhitespace; + + }//end getWhitespaceBeforeValue() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/ParameterElement.php b/lib/pear/PHP/CodeSniffer/CommentParser/ParameterElement.php new file mode 100644 index 0000000000..a9e4491697 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/ParameterElement.php @@ -0,0 +1,326 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_AbstractDocElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_AbstractDocElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * A class to represent param tags within a function comment. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CommentParser_ParameterElement extends PHP_CodeSniffer_CommentParser_AbstractDocElement +{ + + /** + * The variable name of this parameter name, including the $ sign. + * + * @var string + */ + private $_varName = ''; + + /** + * The comment of this parameter tag. + * + * @var string + */ + private $_comment = ''; + + /** + * The variable type of this parameter tag. + * + * @var string + */ + private $_type = ''; + + /** + * The whitespace that exists before the variable name. + * + * @var string + */ + private $_varNameWhitespace = ''; + + /** + * The whitespace that exists before the comment. + * + * @var string + */ + private $_commentWhitespace = null; + + /** + * The whitespace that exists before the variable type. + * + * @var string + */ + private $_typeWhitespace = ''; + + + /** + * Constructs a PHP_CodeSniffer_CommentParser_ParameterElement. + * + * @param PHP_CodeSniffer_CommentParser_DocElement $previousElement The element + * previous to + * this one. + * @param array $tokens The tokens + * that make up + * this element. + * @param PHP_CodeSniffer_File $phpcsFile The file that + * this element + * is in. + */ + public function __construct($previousElement, $tokens, PHP_CodeSniffer_File $phpcsFile) + { + parent::__construct($previousElement, $tokens, 'param', $phpcsFile); + + // Handle special variable type: array(x => y). + $type = strtolower($this->_type); + if ($this->_varName === '=>' && strpos($type, 'array(') !== false) { + $rawContent = $this->getRawContent(); + $matches = array(); + if (preg_match('/^(\s+)(array\(.*\))(\s+)(\$\S*)(\s+)(.*)/i', $rawContent, $matches) !== 0) { + // Process the sub elements correctly for this special case. + if (count($matches) === 7) { + $this->processSubElement('type', $matches[2], $matches[1]); + $this->processSubElement('varName', $matches[4], $matches[3]); + $this->processSubElement('comment', $matches[6], $matches[5]); + } + } + } + + }//end __construct() + + + /** + * Returns the element names that this tag is comprised of, in the order + * that they appear in the tag. + * + * @return array(string) + * @see processSubElement() + */ + protected function getSubElements() + { + return array( + 'type', + 'varName', + 'comment', + ); + + }//end getSubElements() + + + /** + * Processes the sub element with the specified name. + * + * @param string $name The name of the sub element to process. + * @param string $content The content of this sub element. + * @param string $beforeWhitespace The whitespace that exists before the + * sub element. + * + * @return void + * @see getSubElements() + */ + protected function processSubElement($name, $content, $beforeWhitespace) + { + $element = '_'.$name; + $whitespace = $element.'Whitespace'; + $this->$element = $content; + $this->$whitespace = $beforeWhitespace; + + }//end processSubElement() + + + /** + * Returns the variable name that this parameter tag represents. + * + * @return string + */ + public function getVarName() + { + return $this->_varName; + + }//end getVarName() + + + /** + * Returns the variable type that this string represents. + * + * @return string + */ + public function getType() + { + return $this->_type; + + }//end getType() + + + /** + * Returns the comment of this comment for this parameter. + * + * @return string + */ + public function getComment() + { + return $this->_comment; + + }//end getComment() + + + /** + * Returns the whitespace before the variable type. + * + * @return stirng + * @see getWhiteSpaceBeforeVarName() + * @see getWhiteSpaceBeforeComment() + */ + public function getWhiteSpaceBeforeType() + { + return $this->_typeWhitespace; + + }//end getWhiteSpaceBeforeType() + + + /** + * Returns the whitespace before the variable name. + * + * @return string + * @see getWhiteSpaceBeforeComment() + * @see getWhiteSpaceBeforeType() + */ + public function getWhiteSpaceBeforeVarName() + { + return $this->_varNameWhitespace; + + }//end getWhiteSpaceBeforeVarName() + + + /** + * Returns the whitespace before the comment. + * + * @return string + * @see getWhiteSpaceBeforeVarName() + * @see getWhiteSpaceBeforeType() + */ + public function getWhiteSpaceBeforeComment() + { + return $this->_commentWhitespace; + + }//end getWhiteSpaceBeforeComment() + + + /** + * Returns the postition of this parameter are it appears in the comment. + * + * This method differs from getOrder as it is only relative to method + * parameters. + * + * @return int + */ + public function getPosition() + { + if (($this->getPreviousElement() instanceof PHP_CodeSniffer_CommentParser_ParameterElement) === false) { + return 1; + } else { + return ($this->getPreviousElement()->getPosition() + 1); + } + + }//end getPosition() + + + /** + * Returns true if this parameter's variable aligns with the other's. + * + * @param PHP_CodeSniffer_CommentParser_ParameterElement $other The other param + * to check + * alignment with. + * + * @return boolean + */ + public function alignsVariableWith(PHP_CodeSniffer_CommentParser_ParameterElement $other) + { + // Format is: + // @param type $variable Comment. + // @param <-a-><---b----> + // Compares the index before param variable. + $otherVar = (strlen($other->_type) + strlen($other->_varNameWhitespace)); + $thisVar = (strlen($this->_type) + strlen($this->_varNameWhitespace)); + if ($otherVar !== $thisVar) { + return false; + } + + return true; + + }//end alignsVariableWith() + + + /** + * Returns true if this parameter's comment aligns with the other's. + * + * @param PHP_CodeSniffer_CommentParser_ParameterElement $other The other param + * to check + * alignment with. + * + * @return boolean + */ + public function alignsCommentWith(PHP_CodeSniffer_CommentParser_ParameterElement $other) + { + // Compares the index before param comment. + $otherComment = (strlen($other->_varName) + strlen($other->_commentWhitespace)); + $thisComment = (strlen($this->_varName) + strlen($this->_commentWhitespace)); + if ($otherComment !== $thisComment) { + return false; + } + + return true; + + }//end alignsCommentWith() + + + /** + * Returns true if this parameter aligns with the other paramter. + * + * @param PHP_CodeSniffer_CommentParser_ParameterElement $other The other param + * to check + * alignment with. + * + * @return boolean + */ + public function alignsWith(PHP_CodeSniffer_CommentParser_ParameterElement $other) + { + if ($this->alignsVariableWith($other) === false) { + return false; + } + + if ($this->alignsCommentWith($other) === false) { + return false; + } + + return true; + + }//end alignsWith() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/ParserException.php b/lib/pear/PHP/CodeSniffer/CommentParser/ParserException.php new file mode 100644 index 0000000000..a690a6b842 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/ParserException.php @@ -0,0 +1,72 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * An exception to be thrown when a DocCommentParser finds an anomilty in a + * doc comment. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CommentParser_ParserException extends Exception +{ + + /** + * The line where the exception occured, in relation to the doc comment. + * + * @var int + */ + private $_line = 0; + + + /** + * Constructs a DocCommentParserException. + * + * @param string $message The message of the exception. + * @param int $line The position in comment where the error occured. + * A position of 0 indicates that the error occured + * at the opening line of the doc comment. + */ + public function __construct($message, $line) + { + parent::__construct($message); + $this->_line = $line; + + }//end __construct() + + + /** + * Returns the line number within the comment where the exception occured. + * + * @return int + */ + public function getLineWithinComment() + { + return $this->_line; + + }//end getLineWithinComment() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/CommentParser/SingleElement.php b/lib/pear/PHP/CodeSniffer/CommentParser/SingleElement.php new file mode 100644 index 0000000000..519acd68b1 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/CommentParser/SingleElement.php @@ -0,0 +1,161 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_AbstractDocElement', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_AbstractDocElement not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * A class to represent single element doc tags. + * + * A single element doc tag contains only one value after the tag itself. An + * example would be the \@package tag. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_CommentParser_SingleElement extends PHP_CodeSniffer_CommentParser_AbstractDocElement +{ + + /** + * The content that exists after the tag. + * + * @var string + * @see getContent() + */ + protected $content = ''; + + /** + * The whitespace that exists before the content. + * + * @var string + * @see getWhitespaceBeforeContent() + */ + protected $contentWhitespace = ''; + + + /** + * Constructs a SingleElement doc tag. + * + * @param PHP_CodeSniffer_CommentParser_DocElement $previousElement The element + * before this + * one. + * @param array $tokens The tokens + * that comprise + * this element. + * @param string $tag The tag that + * this element + * represents. + * @param PHP_CodeSniffer_File $phpcsFile The file that + * this element + * is in. + */ + public function __construct($previousElement, $tokens, $tag, PHP_CodeSniffer_File $phpcsFile) + { + parent::__construct($previousElement, $tokens, $tag, $phpcsFile); + + }//end __construct() + + + /** + * Returns the element names that this tag is comprised of, in the order + * that they appear in the tag. + * + * @return array(string) + * @see processSubElement() + */ + protected function getSubElements() + { + return array('content'); + + }//end getSubElements() + + + /** + * Processes the sub element with the specified name. + * + * @param string $name The name of the sub element to process. + * @param string $content The content of this sub element. + * @param string $whitespaceBefore The whitespace that exists before the + * sub element. + * + * @return void + * @see getSubElements() + */ + protected function processSubElement($name, $content, $whitespaceBefore) + { + $this->content = $content; + $this->contentWhitespace = $whitespaceBefore; + + }//end processSubElement() + + + /** + * Returns the content of this tag. + * + * @return string + */ + public function getContent() + { + return $this->content; + + }//end getContent() + + + /** + * Returns the witespace before the content of this tag. + * + * @return string + */ + public function getWhitespaceBeforeContent() + { + return $this->contentWhitespace; + + }//end getWhitespaceBeforeContent() + + + /** + * Processes a content check for single doc element. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $commentStart The line number where + * the error occurs. + * @param string $docBlock Whether this is a file or + * class comment doc. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $commentStart, $docBlock) + { + if ($this->content === '') { + $errorPos = ($commentStart + $this->getLine()); + $error = "Content missing for $this->tag tag in $docBlock comment"; + $phpcsFile->addError($error, $errorPos); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/DocGenerators/Generator.php b/lib/pear/PHP/CodeSniffer/DocGenerators/Generator.php new file mode 100644 index 0000000000..d295846a86 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/DocGenerators/Generator.php @@ -0,0 +1,191 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * The base class for all PHP_CodeSniffer documentation generators. + * + * Documentation generators are used to print documentation about code sniffs + * in a standard. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_DocGenerators_Generator +{ + + /** + * The name of the coding standard we are generating docs for. + * + * @var string + */ + private $_standard = ''; + + /** + * An array of sniffs that we are limiting the generated docs to. + * + * If this array is empty, docs are generated for all sniffs in the + * supplied coding standard. + * + * @var string + */ + private $_sniffs = array(); + + + /** + * Constructs a PHP_CodeSniffer_DocGenerators_Generator object. + * + * @param string $standard The name of the coding standard to generate + * docs for. + * @param array $sniffs An array of sniffs that we are limiting the + * generated docs to. + * + * @see generate() + */ + public function __construct($standard, array $sniffs=array()) + { + $this->_standard = $standard; + $this->_sniffs = $sniffs; + + }//end __construct() + + + /** + * Retrieves the title of the sniff from the DOMNode supplied. + * + * @param DOMNode $doc The DOMNode object for the sniff. + * It represents the "documentation" tag in the XML + * standard file. + * + * @return string + */ + protected function getTitle(DOMNode $doc) + { + return $doc->getAttribute('title'); + + }//end getTitle() + + + /** + * Retrieves the name of the standard we are generating docs for. + * + * @return string + */ + protected function getStandard() + { + return $this->_standard; + + }//end getStandard() + + + /** + * Generates the documentation for a standard. + * + * It's probably wise for doc generators to override this method so they + * have control over how the docs are produced. Otherwise, the processSniff + * method should be overridden to output content for each sniff. + * + * @return void + * @see processSniff() + */ + public function generate() + { + $standardFiles = $this->getStandardFiles(); + + foreach ($standardFiles as $standard) { + $doc = new DOMDocument(); + $doc->load($standard); + $documentation = $doc->getElementsByTagName('documentation')->item(0); + $this->processSniff($documentation); + } + + }//end generate() + + + /** + * Returns a list of paths to XML standard files for all sniffs in a standard. + * + * Any sniffs that do not have an XML standard file are obviously not included + * in the returned array. If documentation is only being generated for some + * sniffs (ie. $this->_sniffs is not empty) then all others sniffs will + * be filtered from the results as well. + * + * @return array(string) + */ + protected function getStandardFiles() + { + if (is_dir($this->_standard) === true) { + // This is a custom standard. + $standardDir = $this->_standard; + $standard = basename($this->_standard); + } else { + $standardDir = realpath(dirname(__FILE__).'/../Standards/'.$this->_standard); + $standard = $this->_standard; + } + + $sniffs = PHP_CodeSniffer::getSniffFiles($standardDir, $standard); + + $standardFiles = array(); + foreach ($sniffs as $sniff) { + if (empty($this->_sniffs) === false) { + // We are limiting the docs to certain sniffs only, so filter + // out any unwanted sniffs. + $sniffName = substr($sniff, (strrpos($sniff, '/') + 1)); + $sniffName = substr($sniffName, 0, -9); + if (in_array($sniffName, $this->_sniffs) === false) { + continue; + } + } + + $standardFile = str_replace(DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR.'Docs'.DIRECTORY_SEPARATOR, $sniff); + $standardFile = str_replace('Sniff.php', 'Standard.xml', $standardFile); + + if (is_file($standardFile) === true) { + $standardFiles[] = $standardFile; + } + } + + return $standardFiles; + + }//end getStandardFiles() + + + /** + * Process the documentation for a single sniff. + * + * Doc generators should override this function to produce output. + * + * @param DOMNode $doc The DOMNode object for the sniff. + * It represents the "documentation" tag in the XML + * standard file. + * + * @return void + * @see generate() + */ + protected function processSniff(DOMNode $doc) + { + + }//end processSniff() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/DocGenerators/HTML.php b/lib/pear/PHP/CodeSniffer/DocGenerators/HTML.php new file mode 100644 index 0000000000..a5f844cff6 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/DocGenerators/HTML.php @@ -0,0 +1,291 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +require_once 'PHP/CodeSniffer/DocGenerators/Generator.php'; + +/** + * A doc generator that outputs documentation in one big HTML file. + * + * Output is in one large HTML file and is designed for you to style with + * your own stylesheet. It contains a table of contents at the top with anchors + * to each sniff. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_DocGenerators_HTML extends PHP_CodeSniffer_DocGenerators_Generator +{ + + + /** + * Generates the documentation for a standard. + * + * @return void + * @see processSniff() + */ + public function generate() + { + ob_start(); + $this->printHeader(); + + $standardFiles = $this->getStandardFiles(); + $this->printToc($standardFiles); + + foreach ($standardFiles as $standard) { + $doc = new DOMDocument(); + $doc->load($standard); + $documentation = $doc->getElementsByTagName('documentation')->item(0); + $this->processSniff($documentation); + } + + $this->printFooter(); + + $content = ob_get_contents(); + ob_end_clean(); + + echo $content; + + }//end generate() + + + /** + * Print the header of the HTML page. + * + * @return void + */ + protected function printHeader() + { + $standard = $this->getStandard(); + echo ''.PHP_EOL; + echo ' '.PHP_EOL; + echo " $standard Coding Standards".PHP_EOL; + echo ' '.PHP_EOL; + echo ' '.PHP_EOL; + echo ' '.PHP_EOL; + echo "

$standard Coding Standards

".PHP_EOL; + + }//end printHeader() + + + /** + * Print the table of contents for the standard. + * + * The TOC is just an unordered list of bookmarks to sniffs on the page. + * + * @param array $standardFiles An array of paths to the XML standard files. + * + * @return void + */ + protected function printToc($standardFiles) + { + echo '

Table of Contents

'.PHP_EOL; + echo '
    '.PHP_EOL; + + foreach ($standardFiles as $standard) { + $doc = new DOMDocument(); + $doc->load($standard); + $documentation = $doc->getElementsByTagName('documentation')->item(0); + $title = $this->getTitle($documentation); + echo '
  • $title
  • ".PHP_EOL; + } + + echo '
'.PHP_EOL; + + }//end printToc() + + + /** + * Print the footer of the HTML page. + * + * @return void + */ + protected function printFooter() + { + // Turn off strict errors so we don't get timezone warnings if people + // don't have their timezone set. + error_reporting(E_ALL); + echo '
'; + echo 'Documentation generated on '.date('r'); + echo ' by PHP_CodeSniffer 1.1.0'; + echo '
'.PHP_EOL; + error_reporting(E_ALL | E_STRICT); + + echo ' '.PHP_EOL; + echo ''.PHP_EOL; + + }//end printFooter() + + + /** + * Process the documentation for a single sniff. + * + * @param DOMNode $doc The DOMNode object for the sniff. + * It represents the "documentation" tag in the XML + * standard file. + * + * @return void + */ + public function processSniff(DOMNode $doc) + { + $title = $this->getTitle($doc); + echo ' '.PHP_EOL; + echo "

$title

".PHP_EOL; + + foreach ($doc->childNodes as $node) { + if ($node->nodeName === 'standard') { + $this->printTextBlock($node); + } else if ($node->nodeName === 'code_comparison') { + $this->printCodeComparisonBlock($node); + } + } + + }//end processSniff() + + + /** + * Print a text block found in a standard. + * + * @param DOMNode $node The DOMNode object for the text block. + * + * @return void + */ + protected function printTextBlock($node) + { + $content = trim($node->nodeValue); + $content = htmlspecialchars($content); + + // Allow em tags only. + $content = str_replace('<em>', '', $content); + $content = str_replace('</em>', '', $content); + + echo "

$content

".PHP_EOL; + + }//end printTextBlock() + + + /** + * Print a code comparison block found in a standard. + * + * @param DOMNode $node The DOMNode object for the code comparison block. + * + * @return void + */ + protected function printCodeComparisonBlock($node) + { + $codeBlocks = $node->getElementsByTagName('code'); + + $firstTitle = $codeBlocks->item(0)->getAttribute('title'); + $first = trim($codeBlocks->item(0)->nodeValue); + $first = str_replace("\n", '
', $first); + $first = str_replace(' ', ' ', $first); + $first = str_replace('', '', $first); + $first = str_replace('', '', $first); + + $secondTitle = $codeBlocks->item(1)->getAttribute('title'); + $second = trim($codeBlocks->item(1)->nodeValue); + $second = str_replace("\n", '
', $second); + $second = str_replace(' ', ' ', $second); + $second = str_replace('', '', $second); + $second = str_replace('', '', $second); + + echo ' '.PHP_EOL; + echo ' '.PHP_EOL; + echo " ".PHP_EOL; + echo " ".PHP_EOL; + echo ' '.PHP_EOL; + echo ' '.PHP_EOL; + echo " ".PHP_EOL; + echo " ".PHP_EOL; + echo ' '.PHP_EOL; + echo '
$firstTitle$secondTitle
$first$second
'.PHP_EOL; + + }//end printCodeComparisonBlock() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/DocGenerators/Text.php b/lib/pear/PHP/CodeSniffer/DocGenerators/Text.php new file mode 100644 index 0000000000..36c7018059 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/DocGenerators/Text.php @@ -0,0 +1,266 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +require_once 'PHP/CodeSniffer/DocGenerators/Generator.php'; + +/** + * A doc generator that outputs text-based documentation. + * + * Output is designed to be displayed in a terminal and is wrapped to 100 characters. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_DocGenerators_Text extends PHP_CodeSniffer_DocGenerators_Generator +{ + + + /** + * Process the documentation for a single sniff. + * + * @param DOMNode $doc The DOMNode object for the sniff. + * It represents the "documentation" tag in the XML + * standard file. + * + * @return void + */ + public function processSniff(DOMNode $doc) + { + $this->printTitle($doc); + + foreach ($doc->childNodes as $node) { + if ($node->nodeName === 'standard') { + $this->printTextBlock($node); + } else if ($node->nodeName === 'code_comparison') { + $this->printCodeComparisonBlock($node); + } + } + + }//end processSniff() + + + /** + * Prints the title area for a single sniff. + * + * @param DOMNode $doc The DOMNode object for the sniff. + * It represents the "documentation" tag in the XML + * standard file. + * + * @return void + */ + protected function printTitle(DOMNode $doc) + { + $title = $this->getTitle($doc); + $standard = $this->getStandard(); + + echo PHP_EOL; + echo str_repeat('-', (strlen("$standard CODING STANDARD: $title") + 4)); + echo strtoupper(PHP_EOL."| $standard CODING STANDARD: $title |".PHP_EOL); + echo str_repeat('-', (strlen("$standard CODING STANDARD: $title") + 4)); + echo PHP_EOL.PHP_EOL; + + }//end printTitle() + + + /** + * Print a text block found in a standard. + * + * @param DOMNode $node The DOMNode object for the text block. + * + * @return void + */ + protected function printTextBlock($node) + { + $text = trim($node->nodeValue); + $text = str_replace('', '*', $text); + $text = str_replace('', '*', $text); + + $lines = array(); + $tempLine = ''; + $words = explode(' ', $text); + + foreach ($words as $word) { + if (strlen($tempLine.$word) >= 99) { + if (strlen($tempLine.$word) === 99) { + // Adding the extra space will push us to the edge + // so we are done. + $lines[] = $tempLine.$word; + $tempLine = ''; + } else if (strlen($tempLine.$word) === 100) { + // We are already at the edge, so we are done. + $lines[] = $tempLine.$word; + $tempLine = ''; + } else { + $lines[] = rtrim($tempLine); + $tempLine = $word.' '; + } + } else { + $tempLine .= $word.' '; + } + }//end foreach + + if ($tempLine !== '') { + $lines[] = rtrim($tempLine); + } + + echo implode(PHP_EOL, $lines).PHP_EOL.PHP_EOL; + + }//end printTextBlock() + + + /** + * Print a code comparison block found in a standard. + * + * @param DOMNode $node The DOMNode object for the code comparison block. + * + * @return void + */ + protected function printCodeComparisonBlock($node) + { + $codeBlocks = $node->getElementsByTagName('code'); + $first = trim($codeBlocks->item(0)->nodeValue); + $firstTitle = $codeBlocks->item(0)->getAttribute('title'); + + $firstTitleLines = array(); + $tempTitle = ''; + $words = explode(' ', $firstTitle); + + foreach ($words as $word) { + if (strlen($tempTitle.$word) >= 45) { + if (strlen($tempTitle.$word) === 45) { + // Adding the extra space will push us to the edge + // so we are done. + $firstTitleLines[] = $tempTitle.$word; + $tempTitle = ''; + } else if (strlen($tempTitle.$word) === 46) { + // We are already at the edge, so we are done. + $firstTitleLines[] = $tempTitle.$word; + $tempTitle = ''; + } else { + $firstTitleLines[] = $tempTitle; + $tempTitle = $word; + } + } else { + $tempTitle .= $word.' '; + } + }//end foreach + + if ($tempTitle !== '') { + $firstTitleLines[] = $tempTitle; + } + + $first = str_replace('', '', $first); + $first = str_replace('', '', $first); + $firstLines = explode("\n", $first); + + $second = trim($codeBlocks->item(1)->nodeValue); + $secondTitle = $codeBlocks->item(1)->getAttribute('title'); + + $secondTitleLines = array(); + $tempTitle = ''; + $words = explode(' ', $secondTitle); + + foreach ($words as $word) { + if (strlen($tempTitle.$word) >= 45) { + if (strlen($tempTitle.$word) === 45) { + // Adding the extra space will push us to the edge + // so we are done. + $secondTitleLines[] = $tempTitle.$word; + $tempTitle = ''; + } else if (strlen($tempTitle.$word) === 46) { + // We are already at the edge, so we are done. + $secondTitleLines[] = $tempTitle.$word; + $tempTitle = ''; + } else { + $secondTitleLines[] = $tempTitle; + $tempTitle = $word; + } + } else { + $tempTitle .= $word.' '; + } + }//end foreach + + if ($tempTitle !== '') { + $secondTitleLines[] = $tempTitle; + } + + $second = str_replace('', '', $second); + $second = str_replace('', '', $second); + $secondLines = explode("\n", $second); + + $maxCodeLines = max(count($firstLines), count($secondLines)); + $maxTitleLines = max(count($firstTitleLines), count($secondTitleLines)); + + echo str_repeat('-', 41); + echo ' CODE COMPARISON '; + echo str_repeat('-', 42).PHP_EOL; + + for ($i = 0; $i < $maxTitleLines; $i++) { + if (isset($firstTitleLines[$i]) === true) { + $firstLineText = $firstTitleLines[$i]; + } else { + $firstLineText = ''; + } + + if (isset($secondTitleLines[$i]) === true) { + $secondLineText = $secondTitleLines[$i]; + } else { + $secondLineText = ''; + } + + echo '| '; + echo $firstLineText.str_repeat(' ', (46 - strlen($firstLineText))); + echo ' | '; + echo $secondLineText.str_repeat(' ', (47 - strlen($secondLineText))); + echo ' |'.PHP_EOL; + }//end for + + echo str_repeat('-', 100).PHP_EOL; + + for ($i = 0; $i < $maxCodeLines; $i++) { + if (isset($firstLines[$i]) === true) { + $firstLineText = $firstLines[$i]; + } else { + $firstLineText = ''; + } + + if (isset($secondLines[$i]) === true) { + $secondLineText = $secondLines[$i]; + } else { + $secondLineText = ''; + } + + echo '| '; + echo $firstLineText.str_repeat(' ', (47 - strlen($firstLineText))); + echo '| '; + echo $secondLineText.str_repeat(' ', (48 - strlen($secondLineText))); + echo '|'.PHP_EOL; + }//end for + + echo str_repeat('-', 100).PHP_EOL.PHP_EOL; + + }//end printCodeComparisonBlock() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Exception.php b/lib/pear/PHP/CodeSniffer/Exception.php new file mode 100644 index 0000000000..5828ba24b1 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Exception.php @@ -0,0 +1,36 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +require_once 'PEAR/Exception.php'; + +/** + * An exception thrown by PHP_CodeSniffer when it encounters an unrecoverable error. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_Exception extends PEAR_Exception +{ + +}//end class + +?> \ No newline at end of file diff --git a/lib/pear/PHP/CodeSniffer/File.php b/lib/pear/PHP/CodeSniffer/File.php new file mode 100644 index 0000000000..9611d402d1 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/File.php @@ -0,0 +1,2000 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * A PHP_CodeSniffer_File object represents a PHP source file and the tokens + * associated with it. + * + * It provides a means for traversing the token stack, along with + * other token related operations. If a PHP_CodeSniffer_Sniff finds and error or + * warning within a PHP_CodeSniffer_File, you can raise an error using the + * addError() or addWarning() methods. + * + * Token Information + * + * Each token within the stack contains information about itself: + * + * + * array( + * 'code' => 301, // the token type code (see token_get_all()) + * 'content' => 'if', // the token content + * 'type' => 'T_IF', // the token name + * 'line' => 56, // the line number when the token is located + * 'column' => 12, // the column in the line where this token + * // starts (starts from 1) + * 'level' => 2 // the depth a token is within the scopes open + * 'conditions' => array( // a list of scope condition token + * // positions => codes that + * 2 => 50, // openened the scopes that this token exists + * 9 => 353, // in (see conditional tokens section below) + * ), + * ); + * + * + * Conditional Tokens + * + * In addition to the standard token fields, conditions contain information to + * determine where their scope begins and ends: + * + * + * array( + * 'scope_condition' => 38, // the token position of the condition + * 'scope_opener' => 41, // the token position that started the scope + * 'scope_closer' => 70, // the token position that ended the scope + * ); + * + * + * The condition, the scope opener and the scope closer each contain this + * information. + * + * Parenthesis Tokens + * + * Each parenthesis token (T_OPEN_PARENTHESIS and T_CLOSE_PARENTHESIS) has a + * reference to their opening and closing parenthesis, one being itself, the + * other being its oposite. + * + * + * array( + * 'parenthesis_opener' => 34, + * 'parenthesis_closer' => 40, + * ); + * + * + * Some tokens can "own" a set of parethesis. For example a T_FUNCTION token + * has parenthesis around its argument list. These tokens also have the + * parenthesis_opener and and parenthesis_closer indicies. Not all parethesis + * have owners, for example parenthesis used for arithmetic operations and + * function calls. The parenthesis tokens that have an owner have the following + * auxilery array indicies. + * + * + * array( + * 'parenthesis_opener' => 34, + * 'parenthesis_closer' => 40, + * 'parenthesis_owner' => 33, + * ); + * + * + * Each token within a set of parenthesis also has an array indicy + * 'nested_parenthesis' which is an array of the + * left parenthesis => right parenthesis token positions. + * + * + * 'nested_parentheisis' => array( + * 12 => 15 + * 11 => 14 + * ); + * + * + * Extended Tokens + * + * PHP_CodeSniffer extends and augments some of the tokens created by + * token_get_all(). A full list of these tokens can be seen in the + * Tokens.php file. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_File +{ + + /** + * The absolute path to the file associated with this object. + * + * @var string + */ + private $_file = ''; + + /** + * The EOL character this file uses. + * + * @var string + */ + public $eolChar = ''; + + /** + * The tokenizer being used for this file. + * + * @var object + */ + public $tokenizer = null; + + /** + * The tokenizer being used for this file. + * + * @var string + */ + public $tokenizerType = 'PHP'; + + /** + * The number of tokens in this file. + * + * Stored here to save calling count() everywhere. + * + * @var int + */ + public $numTokens = 0; + + /** + * The tokens stack map. + * + * Note that the tokens in this array differ in format to the tokens + * produced by token_get_all(). Tokens are initially produced with + * token_get_all(), then augmented so that it's easier to process them. + * + * @var array() + * @see Tokens.php + */ + private $_tokens = array(); + + /** + * The errors raised from PHP_CodeSniffer_Sniffs. + * + * @var array() + * @see getErrors() + */ + private $_errors = array(); + + /** + * The warnings raised form PHP_CodeSniffer_Sniffs. + * + * @var array() + * @see getWarnings() + */ + private $_warnings = array(); + + /** + * The total number of errors raised. + * + * @var int + */ + private $_errorCount = 0; + + /** + * The total number of warnings raised. + * + * @var int + */ + private $_warningCount = 0; + + /** + * An array of sniffs listening to this file's processing. + * + * @var array(PHP_CodeSniffer_Sniff) + */ + private $_listeners = array(); + + /** + * A constant to represent an error in PHP_CodeSniffer. + * + * @var int + */ + const ERROR = 0; + + /** + * A constant to represent a warning in PHP_CodeSniffer. + * + * @var int + */ + const WARNING = 1; + + /** + * An array of extensions mapping to the tokenizer to use. + * + * This value gets set by PHP_CodeSniffer when the object is created. + * + * @var array + */ + protected $tokenizers = array(); + + + /** + * Constructs a PHP_CodeSniffer_File. + * + * @param string $file The absolute path to the file + * to process. + * @param array(string) $listeners The initial listeners listening + * to processing of this file. + * @param array $tokenizers An array of extensions mapping + * to the tokenizer to use. + * + * @throws PHP_CodeSniffer_Exception If the register() method does + * not return an array. + */ + public function __construct($file, array $listeners, array $tokenizers) + { + foreach ($listeners as $listenerClass) { + $listener = new $listenerClass(); + $tokens = $listener->register(); + + if (is_array($tokens) === false) { + $msg = "Sniff $listenerClass register() method must return an array"; + throw new PHP_CodeSniffer_Exception($msg); + } + + $this->addTokenListener($listener, $tokens); + } + + $this->_file = trim($file); + $this->tokenizers = $tokenizers; + + }//end __construct() + + + /** + * Adds a listener to the token stack that listens to the specific tokens. + * + * When PHP_CodeSniffer encounters on the the tokens specified in $tokens, it + * invokes the process method of the sniff. + * + * @param PHP_CodeSniffer_Sniff $listener The listener to add to the + * listener stack. + * @param array(int) $tokens The token types the listener wishes to + * listen to. + * + * @return void + */ + public function addTokenListener(PHP_CodeSniffer_Sniff $listener, array $tokens) + { + foreach ($tokens as $token) { + if (isset($this->_listeners[$token]) === false) { + $this->_listeners[$token] = array(); + } + + if (in_array($listener, $this->_listeners[$token], true) === false) { + $this->_listeners[$token][] = $listener; + } + } + + }//end addTokenListener() + + + /** + * Removes a listener from listening from the specified tokens. + * + * @param PHP_CodeSniffer_Sniff $listener The listener to remove from the + * listener stack. + * @param array(int) $tokens The token types the listener wishes to + * stop listen to. + * + * @return void + */ + public function removeTokenListener(PHP_CodeSniffer_Sniff $listener, array $tokens) + { + foreach ($tokens as $token) { + if (isset($this->_listeners[$token]) === false) { + continue; + } + + if (in_array($listener, $this->_listeners[$token]) === true) { + foreach ($this->_listeners[$token] as $pos => $value) { + if ($value === $listener) { + unset($this->_listeners[$token][$pos]); + } + } + } + } + + }//end removeTokenListener() + + + /** + * Returns the token stack for this file. + * + * @return array() + */ + public function getTokens() + { + return $this->_tokens; + + }//end getTokens() + + + /** + * Starts the stack traversal and tells listeners when tokens are found. + * + * @param string $contents The contents to parse. If NULL, the content + * is taken from the file system. + * + * @return void + */ + public function start($contents=null) + { + $this->_parse($contents); + + if (PHP_CODESNIFFER_VERBOSITY > 2) { + echo "\t*** START TOKEN PROCESSING ***".PHP_EOL; + } + + // Foreach of the listeners that have registed to listen for this + // token, get them to process it. + foreach ($this->_tokens as $stackPtr => $token) { + if (PHP_CODESNIFFER_VERBOSITY > 2) { + $type = $token['type']; + $content = str_replace($this->eolChar, '\n', $token['content']); + echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL; + } + + $tokenType = $token['code']; + if (isset($this->_listeners[$tokenType]) === true) { + foreach ($this->_listeners[$tokenType] as $listener) { + // Make sure this sniff supports the tokenizer + // we are currently using. + $vars = get_class_vars(get_class($listener)); + if (isset($vars['supportedTokenizers']) === true) { + if (in_array($this->tokenizerType, $vars['supportedTokenizers']) === false) { + continue; + } + } else { + // The default supported tokenizer is PHP. + if ($this->tokenizerType !== 'PHP') { + continue; + } + } + + if (PHP_CODESNIFFER_VERBOSITY > 2) { + $startTime = microtime(true); + echo "\t\t\tProcessing ".get_class($listener).'... '; + } + + $listener->process($this, $stackPtr); + + if (PHP_CODESNIFFER_VERBOSITY > 2) { + $timeTaken = round((microtime(true) - $startTime), 4); + echo "DONE in $timeTaken seconds".PHP_EOL; + } + }//end foreach + }//end if + }//end foreach + + if (PHP_CODESNIFFER_VERBOSITY > 2) { + echo "\t*** END TOKEN PROCESSING ***".PHP_EOL; + } + + // We don't need the tokens any more, so get rid of them + // to save some memory. + $this->_tokens = null; + $this->_listeners = null; + + }//end start() + + + /** + * Tokenizes the file and preapres it for the test run. + * + * @param string $contents The contents to parse. If NULL, the content + * is taken from the file system. + * + * @return void + */ + private function _parse($contents=null) + { + $this->eolChar = self::detectLineEndings($this->_file, $contents); + + // Determine the tokenizer from the file extension. + $fileParts = explode('.', $this->_file); + $extension = array_pop($fileParts); + if (isset($this->tokenizers[$extension]) === true) { + $tokenizerClass = 'PHP_CodeSniffer_Tokenizers_'.$this->tokenizers[$extension]; + $this->tokenizerType = $this->tokenizers[$extension]; + } else { + // Revert to default. + $tokenizerClass = 'PHP_CodeSniffer_Tokenizers_'.$this->tokenizerType; + } + + $tokenizer = new $tokenizerClass(); + $this->tokenizer = $tokenizer; + + if ($contents === null) { + $contents = file_get_contents($this->_file); + } + + $this->_tokens = self::tokenizeString($contents, $tokenizer, $this->eolChar); + $this->numTokens = count($this->_tokens); + + if (PHP_CODESNIFFER_VERBOSITY > 0) { + if ($this->numTokens === 0) { + $numLines = 0; + } else { + $numLines = $this->_tokens[($this->numTokens - 1)]['line']; + } + + echo "[$this->numTokens tokens in $numLines lines]... "; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo PHP_EOL; + } + } + + }//end _parse() + + + /** + * Opens a file and detects the EOL character being used. + * + * @param string $file The full path to the file. + * @param string $contents The contents to parse. If NULL, the content + * is taken from the file system. + * + * @return string + * @throws PHP_CodeSniffer_Exception If $file could not be opened. + */ + public static function detectLineEndings($file, $contents=null) + { + if ($contents === null) { + // Determine the newline character being used in this file. + // Will be either \r, \r\n or \n. + $handle = fopen($file, 'r'); + if ($handle === false) { + $error = 'File could not be opened; could not auto-detect line endings'; + throw new PHP_CodeSniffer_Exception($error); + } + + $firstLine = fgets($handle); + fclose($handle); + + $eolChar = substr($firstLine, -1); + if ($eolChar === "\n") { + $secondLastChar = substr($firstLine, -2, 1); + if ($secondLastChar === "\r") { + $eolChar = "\r\n"; + } + } + } else { + if (preg_match("/\r\n?|\n/", $contents, $matches) !== 1) { + $error = 'Could not auto-detect line endings from content'; + throw new PHP_CodeSniffer_Exception($error); + } + + $eolChar = $matches[0]; + }//end if + + return $eolChar; + + }//end detectLineEndings() + + + /** + * Adds an error to the error stack. + * + * @param string $error The error message. + * @param int $stackPtr The stack position where the error occured. + * + * @return void + */ + public function addError($error, $stackPtr) + { + if ($stackPtr === null) { + $lineNum = 1; + $column = 1; + } else { + $lineNum = $this->_tokens[$stackPtr]['line']; + $column = $this->_tokens[$stackPtr]['column']; + } + + if (isset($this->_errors[$lineNum]) === false) { + $this->errors[$lineNum] = array(); + } + + if (isset($this->_errors[$lineNum][$column]) === false) { + $this->errors[$lineNum][$column] = array(); + } + + $this->_errors[$lineNum][$column][] = $error; + $this->_errorCount++; + + }//end addError() + + + /** + * Adds an warning to the warning stack. + * + * @param string $warning The error message. + * @param int $stackPtr The stack position where the error occured. + * + * @return void + */ + public function addWarning($warning, $stackPtr) + { + if ($stackPtr === null) { + $lineNum = 1; + $column = 1; + } else { + $lineNum = $this->_tokens[$stackPtr]['line']; + $column = $this->_tokens[$stackPtr]['column']; + } + + if (isset($this->_warnings[$lineNum]) === false) { + $this->_warnings[$lineNum] = array(); + } + + if (isset($this->_warnings[$lineNum][$column]) === false) { + $this->_warnings[$lineNum][$column] = array(); + } + + $this->_warnings[$lineNum][$column][] = $warning; + $this->_warningCount++; + + }//end addWarning() + + + /** + * Returns the number of errors raised. + * + * @return int + */ + public function getErrorCount() + { + return $this->_errorCount; + + }//end getErrorCount() + + + /** + * Returns the number of warnings raised. + * + * @return int + */ + public function getWarningCount() + { + return $this->_warningCount; + + }//end getWarningCount() + + + /** + * Returns the errors raised from processing this file. + * + * @return array + */ + public function getErrors() + { + return $this->_errors; + + }//end getErrors() + + + /** + * Returns the warnings raised from processing this file. + * + * @return array + */ + public function getWarnings() + { + return $this->_warnings; + + }//end getWarnings() + + + /** + * Returns the absolute filename of this file. + * + * @return string + */ + public function getFilename() + { + return $this->_file; + + }//end getFilename() + + + /** + * 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 object $tokenizer A tokenizer class to use to tokenize the string. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return array + */ + public static function tokenizeString($string, $tokenizer, $eolChar='\n') + { + $tokens = $tokenizer->tokenizeString($string, $eolChar); + + self::_createLineMap($tokens, $tokenizer, $eolChar); + self::_createBracketMap($tokens, $tokenizer, $eolChar); + self::_createParenthesisMap($tokens, $tokenizer, $eolChar); + self::_createParenthesisNestingMap($tokens, $tokenizer, $eolChar); + self::_createScopeMap($tokens, $tokenizer, $eolChar); + + // If we know the width of each tab, convert tabs + // into spaces so sniffs can use one method of checking. + if (PHP_CODESNIFFER_TAB_WIDTH > 0) { + self::_convertTabs($tokens, $tokenizer, $eolChar); + } + + // Column map requires the line map to be complete. + self::_createColumnMap($tokens, $tokenizer, $eolChar); + self::_createLevelMap($tokens, $tokenizer, $eolChar); + + return $tokens; + + }//end tokenizeString() + + + /** + * Creates a map of tokens => line numbers for each token. + * + * @param array &$tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return void + */ + private static function _createLineMap(&$tokens, $tokenizer, $eolChar) + { + $lineNumber = 1; + $count = count($tokens); + + for ($i = 0; $i < $count; $i++) { + $tokens[$i]['line'] = $lineNumber; + $lineNumber += substr_count($tokens[$i]['content'], $eolChar); + } + + }//end _createLineMap() + + + /** + * Converts tabs into spaces. + * + * Each tab can represent between 1 and $width spaces, so + * this cannot be a straight string replace. + * + * @param array &$tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return void + */ + private static function _convertTabs(&$tokens, $tokenizer, $eolChar) + { + $currColumn = 1; + $count = count($tokens); + + for ($i = 0; $i < $count; $i++) { + $tokenContent = $tokens[$i]['content']; + + if (strpos($tokenContent, "\t") === false) { + // There are no tabs in this content. + $currColumn += (strlen($tokenContent) - 1); + } else { + // We need to determine the length of each tab. + $tabs = preg_split("|(\t)|", $tokenContent, -1, PREG_SPLIT_DELIM_CAPTURE); + $tabNum = 0; + $adjustedTab = false; + $tabsToSpaces = array(); + $newContent = ''; + + foreach ($tabs as $content) { + if ($content === '') { + continue; + } + + if (strpos($content, "\t") === false) { + // This piece of content is not a tab. + $currColumn += strlen($content); + $newContent .= $content; + } else { + $lastCurrColumn = $currColumn; + $tabNum++; + + // Move the pointer to the next tab stop. + if (($currColumn % PHP_CODESNIFFER_TAB_WIDTH) === 0) { + // This is the first tab, and we are already at a + // tab stop, so this tab counts as a single space. + $currColumn++; + $adjustedTab = true; + } else { + $currColumn++; + while (($currColumn % PHP_CODESNIFFER_TAB_WIDTH) != 0) { + $currColumn++; + } + + $currColumn++; + } + + $newContent .= str_repeat(' ', ($currColumn - $lastCurrColumn)); + }//end if + }//end foreach + + if ($tabNum === 1 && $adjustedTab === true) { + $currColumn--; + } + + $tokens[$i]['content'] = $newContent; + }//end if + + if (isset($tokens[($i + 1)]['line']) === true && $tokens[($i + 1)]['line'] !== $tokens[$i]['line']) { + $currColumn = 1; + } else { + $currColumn++; + } + }//end for + + }//end _convertTabs() + + + /** + * Creates a column map. + * + * The column map indicates where the token started on the line where it + * exists. + * + * @param array &$tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return void + */ + private static function _createColumnMap(&$tokens, $tokenizer, $eolChar) + { + $currColumn = 1; + $count = count($tokens); + + for ($i = 0; $i < $count; $i++) { + $tokens[$i]['column'] = $currColumn; + if (isset($tokens[($i + 1)]['line']) === true && $tokens[($i + 1)]['line'] !== $tokens[$i]['line']) { + $currColumn = 1; + } else { + $currColumn += strlen($tokens[$i]['content']); + } + } + + }//end _createColumnMap() + + + /** + * Creates a map for opening and closing of square brackets. + * + * Each bracket token (T_OPEN_SQUARE_BRACKET and T_CLOSE_SQUARE_BRACKET) + * has a reference to their opening and closing bracket + * (bracket_opener and bracket_closer). + * + * @param array &$tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return void + */ + private static function _createBracketMap(&$tokens, $tokenizer, $eolChar) + { + $openers = array(); + $numTokens = count($tokens); + $owners = array(); + + for ($i = 0; $i < $numTokens; $i++) { + if ($tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) { + $openers[] = $i; + } else if ($tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET) { + if (empty($openers) === false) { + $opener = array_pop($openers); + $tokens[$i]['bracket_opener'] = $opener; + $tokens[$i]['bracket_closer'] = $i; + $tokens[$opener]['bracket_opener'] = $opener; + $tokens[$opener]['bracket_closer'] = $i; + } + } + } + + }//end _createBracketMap() + + + /** + * Creates a map for opening and closing of parenthesis. + * + * Each parenthesis token (T_OPEN_PARENTHESIS and T_CLOSE_PARENTHESIS) has a + * reference to their opening and closing parenthesis (parenthesis_opener + * and parenthesis_closer). + * + * @param array &$tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return void + */ + private static function _createParenthesisMap(&$tokens, $tokenizer, $eolChar) + { + $openers = array(); + $numTokens = count($tokens); + $openOwner = null; + + for ($i = 0; $i < $numTokens; $i++) { + if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$parenthesisOpeners) === true) { + $tokens[$i]['parenthesis_opener'] = null; + $tokens[$i]['parenthesis_closer'] = null; + $tokens[$i]['parenthesis_owner'] = $i; + $openOwner = $i; + } else if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) { + $openers[] = $i; + $tokens[$i]['parenthesis_opener'] = $i; + if ($openOwner !== null) { + $tokens[$openOwner]['parenthesis_opener'] = $i; + $tokens[$i]['parenthesis_owner'] = $openOwner; + $openOwner = null; + } + } else if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS) { + // Did we set an owner for this set of parenthesis? + $numOpeners = count($openers); + if ($numOpeners !== 0) { + $opener = array_pop($openers); + if (isset($tokens[$opener]['parenthesis_owner']) === true) { + $owner = $tokens[$opener]['parenthesis_owner']; + + $tokens[$owner]['parenthesis_closer'] = $i; + $tokens[$i]['parenthesis_owner'] = $owner; + } + + $tokens[$i]['parenthesis_opener'] = $opener; + $tokens[$i]['parenthesis_closer'] = $i; + $tokens[$opener]['parenthesis_closer'] = $i; + } + }//end if + }//end for + + }//end _createParenthesisMap() + + + /** + * Creates a map for the parenthesis tokens that surround other tokens. + * + * @param array &$tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return void + */ + private static function _createParenthesisNestingMap(&$tokens, $tokenizer, $eolChar) + { + $numTokens = count($tokens); + $map = array(); + for ($i = 0; $i < $numTokens; $i++) { + if (isset($tokens[$i]['parenthesis_opener']) === true && $i === $tokens[$i]['parenthesis_opener']) { + if (empty($map) === false) { + $tokens[$i]['nested_parenthesis'] = $map; + } + + if (isset($tokens[$i]['parenthesis_closer']) === true) { + $map[$tokens[$i]['parenthesis_opener']] = $tokens[$i]['parenthesis_closer']; + } + } else if (isset($tokens[$i]['parenthesis_closer']) === true && $i === $tokens[$i]['parenthesis_closer']) { + array_pop($map); + if (empty($map) === false) { + $tokens[$i]['nested_parenthesis'] = $map; + } + } else { + if (empty($map) === false) { + $tokens[$i]['nested_parenthesis'] = $map; + } + } + } + + }//end _createParenthesisNestingMap() + + + /** + * Creates a scope map of tokens that open scopes. + * + * @param array &$tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return void + * @see _recurseScopeMap() + */ + private static function _createScopeMap(&$tokens, $tokenizer, $eolChar) + { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** START SCOPE MAP ***".PHP_EOL; + } + + $numTokens = count($tokens); + for ($i = 0; $i < $numTokens; $i++) { + // Check to see if the current token starts a new scope. + if (isset($tokenizer->scopeOpeners[$tokens[$i]['code']]) === true) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$i]['type']; + $content = str_replace($eolChar, '\n', $tokens[$i]['content']); + echo "\tStart scope map at $i: $type => $content".PHP_EOL; + } + + $i = self::_recurseScopeMap($tokens, $numTokens, $tokenizer, $eolChar, $i); + } + } + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** END SCOPE MAP ***".PHP_EOL; + } + + }//end _createScopeMap() + + + /** + * Recurses though the scope openers to build a scope map. + * + * @param array &$tokens The array of tokens to process. + * @param int $numTokens The size of the tokens array. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * @param int $stackPtr The position in the stack of the token that + * opened the scope (eg. an IF token or FOR token). + * @param int $depth How many scope levels down we are. + * + * @return int The position in the stack that closed the scope. + */ + private static function _recurseScopeMap(&$tokens, $numTokens, $tokenizer, $eolChar, $stackPtr, $depth=1) + { + $opener = null; + $currType = $tokens[$stackPtr]['code']; + $startLine = $tokens[$stackPtr]['line']; + $ignore = false; + + // If the start token for this scope opener is the same as + // the scope token, we have already found our opener. + if ($currType === $tokenizer->scopeOpeners[$currType]['start']) { + $opener = $stackPtr; + } + + for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { + $tokenType = $tokens[$i]['code']; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$i]['type']; + $content = str_replace($eolChar, '\n', $tokens[$i]['content']); + echo str_repeat("\t", $depth); + echo "Process token $i ["; + if ($opener !== null) { + echo "opener:$opener;"; + } + + if ($ignore === true) { + echo 'ignore;'; + } + + echo "]: $type => $content".PHP_EOL; + } + + // Is this an opening condition ? + if (isset($tokenizer->scopeOpeners[$tokenType]) === true) { + if ($opener === null) { + // Found another opening condition but still haven't + // found our opener, so we are never going to find one. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$stackPtr]['type']; + echo str_repeat("\t", $depth); + echo "=> Couldn't find scope opener for $stackPtr ($type), bailing".PHP_EOL; + } + + return $stackPtr; + } + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* token is an opening condition *'.PHP_EOL; + } + + $isShared = ($tokenizer->scopeOpeners[$tokenType]['shared'] === true); + + if (isset($tokens[$i]['scope_condition']) === true) { + // We've been here before. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* already processed, skipping *'.PHP_EOL; + } + + if ($isShared === false && isset($tokens[$i]['scope_closer']) === true) { + $i = $tokens[$i]['scope_closer']; + } + + continue; + } else if ($currType === $tokenType && $isShared === false && $opener === null) { + // We haven't yet found our opener, but we have found another + // scope opener which is the same type as us, and we don't + // share openers, so we will never find one. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* it was another token\'s opener, bailing *'.PHP_EOL; + } + + return $stackPtr; + } else { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* searching for opener *'.PHP_EOL; + } + + $i = self::_recurseScopeMap($tokens, $numTokens, $tokenizer, $eolChar, $i, ($depth + 1)); + }//end if + }//end if start scope + + if ($tokenType === $tokenizer->scopeOpeners[$currType]['start'] && $opener === null) { + if ($tokenType === T_OPEN_CURLY_BRACKET) { + // Make sure this is actually an opener and not a + // string offset (e.g., $var{0}). + for ($x = ($i - 1); $x > 0; $x--) { + if (in_array($tokens[$x]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === true) { + continue; + } else { + // If the first non-whitespace/comment token is a + // variable or object operator then this is an opener + // for a string offset and not a scope. + if ($tokens[$x]['code'] === T_VARIABLE || $tokens[$x]['code'] === T_OBJECT_OPERATOR) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* ignoring curly brace *'.PHP_EOL; + } + + $ignore = true; + }//end if + + break; + }//end if + }//end for + }//end if + + if ($ignore === false) { + // We found the opening scope token for $currType. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$stackPtr]['type']; + echo str_repeat("\t", $depth); + echo "=> Found scope opener for $stackPtr ($type)".PHP_EOL; + } + + $opener = $i; + } + } else if ($tokenType === $tokenizer->scopeOpeners[$currType]['end'] && $opener !== null) { + if ($ignore === true && $tokenType === T_CLOSE_CURLY_BRACKET) { + // The last opening bracket must have been for a string + // offset or alike, so let's ignore it. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* finished ignoring curly brace *'.PHP_EOL; + } + + $ignore = false; + } else { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$stackPtr]['type']; + echo str_repeat("\t", $depth); + echo "=> Found scope closer for $stackPtr ($type)".PHP_EOL; + } + + foreach (array($stackPtr, $opener, $i) as $token) { + $tokens[$token]['scope_condition'] = $stackPtr; + $tokens[$token]['scope_opener'] = $opener; + $tokens[$token]['scope_closer'] = $i; + } + + if ($tokenizer->scopeOpeners[$tokens[$stackPtr]['code']]['shared'] === true) { + return $opener; + } else { + return $i; + } + }//end if + } else if ($tokenType === T_OPEN_PARENTHESIS) { + if (isset($tokens[$i]['parenthesis_owner']) === true) { + $owner = $tokens[$i]['parenthesis_owner']; + if (in_array($tokens[$owner]['code'], PHP_CodeSniffer_Tokens::$scopeOpeners) === true && isset($tokens[$i]['parenthesis_closer']) === true) { + // If we get into here, then we opened a parenthesis for + // a scope (eg. an if or else if). We can just skip to + // the closing parenthesis. + $i = $tokens[$i]['parenthesis_closer']; + + // Update the start of the line so that when we check to see + // if the closing parenthesis is more than 3 lines away from + // the statement, we check from the closing parenthesis. + $startLine = $tokens[$tokens[$i]['parenthesis_closer']]['line']; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* skipping parenthesis *'.PHP_EOL; + } + } + } + } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) { + // We opened something that we don't have a scope opener for. + // Examples of this are curly brackets for string offsets etc. + // We want to ignore this so that we don't have an invalid scope + // map. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* ignoring curly brace *'.PHP_EOL; + } + + $ignore = true; + } else if ($opener === null && isset($tokenizer->scopeOpeners[$currType]) === true) { + // If we still haven't found the opener after 3 lines, + // we're not going to find it, unless we know it requires + // an opener, in which case we better keep looking. + if ($tokens[$i]['line'] >= ($startLine + 3)) { + if ($tokenizer->scopeOpeners[$currType]['strict'] === true) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$stackPtr]['type']; + $lines = ($tokens[$i]['line'] - $startLine); + echo str_repeat("\t", $depth); + echo "=> Still looking for $stackPtr ($type) scope opener after $lines lines".PHP_EOL; + } + } else { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$stackPtr]['type']; + echo str_repeat("\t", $depth); + echo "=> Couldn't find scope opener for $stackPtr ($type), bailing".PHP_EOL; + } + + return $stackPtr; + } + } + } else if ($opener !== null && $tokenType !== T_BREAK && in_array($tokenType, $tokenizer->endScopeTokens) === true) { + if (isset($tokens[$i]['scope_condition']) === false) { + if ($ignore === true) { + // We found the end token for the opener we were ignoring. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", $depth); + echo '* finished ignoring curly brace *'.PHP_EOL; + } + + $ignore = false; + } else { + // We found a token that closes the scope but it doesn't + // have a condition, so it belongs to another token and + // our token doesn't have a closer, so pretend this is + // the closer. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$stackPtr]['type']; + echo str_repeat("\t", $depth); + echo "=> Found (unexpected) scope closer for $stackPtr ($type)".PHP_EOL; + } + + foreach (array($stackPtr, $opener) as $token) { + $tokens[$token]['scope_condition'] = $stackPtr; + $tokens[$token]['scope_opener'] = $opener; + $tokens[$token]['scope_closer'] = $i; + } + + return ($i - 1); + }//end if + }//end if + }//end if + }//end for + + return $stackPtr; + + }//end _recurseScopeMap() + + + /** + * Constructs the level map. + * + * The level map adds a 'level' indice to each token which indicates the + * depth that a token within a set of scope blocks. It also adds a + * 'condition' indice which is an array of the scope conditions that opened + * each of the scopes - position 0 being the first scope opener. + * + * @param array &$tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character to use for splitting strings. + * + * @return void + */ + private static function _createLevelMap(&$tokens, $tokenizer, $eolChar) + { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** START LEVEL MAP ***".PHP_EOL; + } + + $numTokens = count($tokens); + $level = 0; + $conditions = array(); + $lastOpener = null; + $openers = array(); + + for ($i = 0; $i < $numTokens; $i++) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$i]['type']; + $line = $tokens[$i]['line']; + $content = str_replace($eolChar, '\n', $tokens[$i]['content']); + echo str_repeat("\t", ($level + 1)); + echo "Process token $i on line $line [lvl:$level;"; + if (empty($conditions) !== true) { + $condString = 'conds;'; + foreach ($conditions as $condition) { + $condString .= token_name($condition).','; + } + + echo rtrim($condString, ',').';'; + } + + echo "]: $type => $content".PHP_EOL; + } + + $tokens[$i]['level'] = $level; + $tokens[$i]['conditions'] = $conditions; + + if (isset($tokens[$i]['scope_condition']) === true) { + // Check to see if this token opened the scope. + if ($tokens[$i]['scope_opener'] === $i) { + $stackPtr = $tokens[$i]['scope_condition']; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$stackPtr]['type']; + echo str_repeat("\t", ($level + 1)); + echo "=> Found scope opener for $stackPtr ($type)".PHP_EOL; + } + + $stackPtr = $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 && $tokens[$lastOpener]['scope_closer'] === $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 = $tokens[$tokens[$i]['scope_condition']]['code']; + $opener = $tokens[$lastOpener]['scope_condition']; + if (in_array($tokens[$opener]['code'], $tokenizer->scopeOpeners[$thisType]['with']) === true) { + $badToken = $tokens[$lastOpener]['scope_condition']; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$badToken]['type']; + echo str_repeat("\t", ($level + 1)); + echo "* shared closer, cleaning up $badToken ($type) *".PHP_EOL; + } + + for ($x = $tokens[$i]['scope_condition']; $x <= $i; $x++) { + $oldConditions = $tokens[$x]['conditions']; + $oldLevel = $tokens[$x]['level']; + $tokens[$x]['level']--; + unset($tokens[$x]['conditions'][$badToken]); + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$x]['type']; + $oldConds = ''; + foreach ($oldConditions as $condition) { + $oldConds .= token_name($condition).','; + } + + $oldConds = rtrim($oldConds, ','); + + $newConds = ''; + foreach ($tokens[$x]['conditions'] as $condition) { + $newConds .= token_name($condition).','; + } + + $newConds = rtrim($newConds, ','); + + $newLevel = $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 = $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] = $tokens[$stackPtr]['code']; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $tokens[$stackPtr]['type']; + echo str_repeat("\t", ($level + 1)); + echo "* token $stackPtr ($type) added to conditions array *".PHP_EOL; + } + + $lastOpener = $tokens[$i]['scope_opener']; + if ($lastOpener !== null) { + $openers[$lastOpener] = $lastOpener; + } + } else if ($tokens[$i]['scope_closer'] === $i) { + $removedCondition = false; + foreach (array_reverse($openers) as $opener) { + if ($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 = $tokens[$oldOpener]['type']; + echo str_repeat("\t", ($level + 1)); + echo "=> Found scope closer for $oldOpener ($type)".PHP_EOL; + } + + if ($removedCondition === false) { + $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; + } + + $level--; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", ($level + 2)); + echo '* level decreased *'.PHP_EOL; + } + } + + $tokens[$i]['level'] = $level; + $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; + } + + }//end _createLevelMap() + + + /** + * Returns the declaration names for T_CLASS, T_INTERFACE and T_FUNCTION tokens. + * + * @param int $stackPtr The position of the declaration token which + * declared the class, interface or function. + * + * @return string The name of the class, interface or function. + * @throws PHP_CodeSniffer_Exception If the specified token is not of type + * T_FUNCTION, T_CLASS or T_INTERFACE. + */ + public function getDeclarationName($stackPtr) + { + $tokenCode = $this->_tokens[$stackPtr]['code']; + if ($tokenCode !== T_FUNCTION && $tokenCode !== T_CLASS && $tokenCode !== T_INTERFACE) { + throw new PHP_CodeSniffer_Exception('Token type is not T_FUNCTION, T_CLASS OR T_INTERFACE'); + } + + $token = $this->findNext(T_STRING, $stackPtr); + return $this->_tokens[$token]['content']; + + }//end getDeclarationName() + + + /** + * Returns the method parameters for the specified T_FUNCTION token. + * + * Each parameter is in the following format: + * + * + * 0 => array( + * 'name' => '$var', // The variable name. + * 'pass_by_reference' => false, // Passed by reference. + * 'type_hint' => string, // Type hint for array or custom type + * ) + * + * + * Parameters with default values have and additional array indice of + * 'default' with the value of the default as a string. + * + * @param int $stackPtr The position in the stack of the T_FUNCTION token + * to acquire the parameters for. + * + * @return array() + * @throws PHP_CodeSniffer_Exception If the specified $stackPtr is not of + * type T_FUNCTION. + */ + public function getMethodParameters($stackPtr) + { + if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION) { + throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION'); + } + + $opener = $this->_tokens[$stackPtr]['parenthesis_opener']; + $closer = $this->_tokens[$stackPtr]['parenthesis_closer']; + + $vars = array(); + $currVar = null; + $defaultStart = null; + $paramCount = 0; + $passByReference = false; + $typeHint = ''; + + for ($i = ($opener + 1); $i <= $closer; $i++) { + // Check to see if this token has a parenthesis opener. If it does + // its likely to be an array, which might have arguments in it, which + // we cause problems in our parsing below, so lets just skip to the + // end of it. + if (isset($this->_tokens[$i]['parenthesis_opener']) === true) { + // Don't do this if its the close parenthesis for the method. + if ($i !== $this->_tokens[$i]['parenthesis_closer']) { + $i = ($this->_tokens[$i]['parenthesis_closer'] + 1); + } + } + + switch ($this->_tokens[$i]['code']) { + case T_BITWISE_AND: + $passByReference = true; + break; + case T_VARIABLE: + $currVar = $i; + break; + case T_ARRAY_HINT: + $typeHint = $this->_tokens[$i]['content']; + break; + case T_STRING: + // This is a string, so it may be a type hint, but it could + // also be a constant used as a default value. + $prevComma = $this->findPrevious(T_COMMA, $i, $opener); + if ($prevComma !== false) { + $nextEquals = $this->findNext(T_EQUAL, $prevComma, $i); + if ($nextEquals !== false) { + break; + } + } + + $typeHint = $this->_tokens[$i]['content']; + break; + case T_CLOSE_PARENTHESIS: + case T_COMMA: + // If it's null, then there must be no parameters for this + // method. + if ($currVar === null) { + continue; + } + + $vars[$paramCount] = array(); + $vars[$paramCount]['name'] = $this->_tokens[$currVar]['content']; + + if ($defaultStart !== null) { + $vars[$paramCount]['default'] = $this->getTokensAsString($defaultStart, ($i - $defaultStart)); + } + + $vars[$paramCount]['pass_by_reference'] = $passByReference; + $vars[$paramCount]['type_hint'] = $typeHint; + + // Reset the vars, as we are about to process the next parameter. + $defaultStart = null; + $passByReference = false; + $typeHint = ''; + + $paramCount++; + break; + case T_EQUAL: + $defaultStart = ($i + 1); + break; + }//end switch + }//end for + + return $vars; + + }//end getMethodParameters() + + + /** + * Returns the visibility and implementation properies of a method. + * + * The format of the array is: + * + * array( + * 'scope' => 'public', // public private or protected + * 'scope_specified' => true, // true is scope keyword was found. + * 'is_abstract' => false, // true if the abstract keyword was found. + * 'is_final' => false, // true if the final keyword was found. + * 'is_static' => false, // true if the static keyword was found. + * ); + * + * + * @param int $stackPtr The position in the stack of the T_FUNCTION token to + * acquire the properties for. + * + * @return array + * @throws PHP_CodeSniffer_Exception If the specified position is not a + * T_FUNCTION token. + */ + public function getMethodProperties($stackPtr) + { + if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION) { + throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION'); + } + + $valid = array( + T_PUBLIC, + T_PRIVATE, + T_PROTECTED, + T_STATIC, + T_FINAL, + T_ABSTRACT, + T_WHITESPACE, + T_COMMENT, + T_DOC_COMMENT, + ); + + $scope = 'public'; + $scopeSpecified = false; + $isAbstract = false; + $isFinal = false; + $isStatic = false; + + for ($i = ($stackPtr - 1); $i > 0; $i--) { + if (in_array($this->_tokens[$i]['code'], $valid) === false) { + break; + } + + switch ($this->_tokens[$i]['code']) { + case T_PUBLIC: + $scope = 'public'; + $scopeSpecified = true; + break; + case T_PRIVATE: + $scope = 'private'; + $scopeSpecified = true; + break; + case T_PROTECTED: + $scope = 'protected'; + $scopeSpecified = true; + break; + case T_ABSTRACT: + $isAbstract = true; + break; + case T_FINAL: + $isFinal = true; + break; + case T_STATIC: + $isStatic = true; + break; + }//end switch + }//end for + + return array( + 'scope' => $scope, + 'scope_specified' => $scopeSpecified, + 'is_abstract' => $isAbstract, + 'is_final' => $isFinal, + 'is_static' => $isStatic, + ); + + }//end getMethodProperties() + + + /** + * Returns the visibility and implementation properies of the class member + * variable found at the specified position in the stack. + * + * The format of the array is: + * + * + * array( + * 'scope' => 'public', // public private or protected + * 'is_static' => false, // true if the static keyword was found. + * ); + * + * + * @param int $stackPtr The position in the stack of the T_VARIABLE token to + * acquire the properties for. + * + * @return array + * @throws PHP_CodeSniffer_Exception If the specified position is not a + * T_VARIABLE token, or if the position is not + * a class member variable. + */ + public function getMemberProperties($stackPtr) + { + if ($this->_tokens[$stackPtr]['code'] !== T_VARIABLE) { + throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_VARIABLE'); + } + + end($this->_tokens[$stackPtr]['conditions']); + $ptr = key($this->_tokens[$stackPtr]['conditions']); + if (isset($this->_tokens[$ptr]) === false || $this->_tokens[$ptr]['code'] !== T_CLASS) { + if (isset($this->_tokens[$ptr]) === true && $this->_tokens[$ptr]['code'] === T_INTERFACE) { + $error = 'Possible parse error: interfaces may not include member vars'; + $this->addWarning($error, $stackPtr); + return array(); + } else { + throw new PHP_CodeSniffer_Exception('$stackPtr is not a class member var'); + } + } + + $valid = array( + T_PUBLIC, + T_PRIVATE, + T_PROTECTED, + T_STATIC, + T_WHITESPACE, + T_COMMENT, + T_DOC_COMMENT, + ); + + $scope = 'public'; + $scopeSpecified = false; + $isStatic = false; + + for ($i = ($stackPtr - 1); $i > 0; $i--) { + if (in_array($this->_tokens[$i]['code'], $valid) === false) { + break; + } + + switch ($this->_tokens[$i]['code']) { + case T_PUBLIC: + $scope = 'public'; + $scopeSpecified = true; + break; + case T_PRIVATE: + $scope = 'private'; + $scopeSpecified = true; + break; + case T_PROTECTED: + $scope = 'protected'; + $scopeSpecified = true; + break; + case T_STATIC: + $isStatic = true; + break; + } + }//end for + + return array( + 'scope' => $scope, + 'scope_specified' => $scopeSpecified, + 'is_static' => $isStatic, + ); + + }//end getMemberProperties() + + + /** + * Determine if the passed token is a reference operator. + * + * Returns true if the specified token position represents a reference. + * Returns false if the token represents a bitwise operator. + * + * @param int $stackPtr The position of the T_BITWISE_AND token. + * + * @return boolean + */ + public function isReference($stackPtr) + { + if ($this->_tokens[$stackPtr]['code'] !== T_BITWISE_AND) { + return false; + } + + $tokenBefore = $this->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); + + if ($this->_tokens[$tokenBefore]['code'] === T_FUNCTION) { + // Function returns a reference. + return true; + } + + if ($this->_tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) { + // Inside a foreach loop, this is a reference. + return true; + } + + if ($this->_tokens[$tokenBefore]['code'] === T_AS) { + // Inside a foreach loop, this is a reference. + return true; + } + + if (in_array($this->_tokens[$tokenBefore]['code'], PHP_CodeSniffer_Tokens::$assignmentTokens) === true) { + // This is directly after an assignment. It's a reference. Even if + // it is part of an operation, the other tests will handle it. + return true; + } + + if (isset($this->_tokens[$stackPtr]['nested_parenthesis']) === true) { + $brackets = $this->_tokens[$stackPtr]['nested_parenthesis']; + $lastBracket = array_pop($brackets); + if (isset($this->_tokens[$lastBracket]['parenthesis_owner']) === true) { + $owner = $this->_tokens[$this->_tokens[$lastBracket]['parenthesis_owner']]; + if ($owner['code'] === T_FUNCTION) { + // Inside a function declaration, this is a reference. + return true; + } + } + } + + return false; + + }//end isReference() + + + /** + * Returns the content of the tokens from the specified start position in + * the token stack for the specified legnth. + * + * @param int $start The position to start from in the token stack. + * @param int $length The length of tokens to traverse from the start pos. + * + * @return string The token contents. + */ + public function getTokensAsString($start, $length) + { + $str = ''; + $end = ($start + $length); + for ($i = $start; $i < $end; $i++) { + $str .= $this->_tokens[$i]['content']; + } + + return $str; + + }//end getTokensAsString() + + + /** + * Returns the position of the next specified token(s). + * + * If a value is specified, the next token of the specified type(s) + * containing the specified value will be returned. + * + * Returns false if no token can be found. + * + * @param int|array $types The type(s) of tokens to search for. + * @param int $start The position to start searching from in the + * token stack. + * @param int $end The end position to fail if no token is found. + * if not specified or null, end will default to + * the start of the token stack. + * @param bool $exclude If true, find the next token that are NOT of + * the types specified in $types. + * @param string $value The value that the token(s) must be equal to. + * If value is ommited, tokens with any value will + * be returned. + * @param bool $local If true, tokens outside the current statement + * will not be cheked. IE. checking will stop + * at the next semi-colon found. + * + * @return int | bool + * @see findNext() + */ + public function findPrevious($types, $start, $end=null, $exclude=false, $value=null, $local=false) + { + $types = (array) $types; + + if ($end === null) { + $end = 0; + } + + for ($i = $start; $i >= $end; $i--) { + $found = (bool) $exclude; + foreach ($types as $type) { + if ($this->_tokens[$i]['code'] === $type) { + $found = !$exclude; + break; + } + } + + if ($found === true) { + if ($value === null) { + return $i; + } else if ($this->_tokens[$i]['content'] === $value) { + return $i; + } + } + + if ($local === true && $this->_tokens[$i]['code'] === T_SEMICOLON) { + break; + } + }//end for + + return false; + + }//end findPrevious() + + + /** + * Returns the position of the next specified token(s). + * + * If a value is specified, the next token of the specified type(s) + * containing the specified value will be returned. + * + * Returns false if no token can be found. + * + * @param int|array $types The type(s) of tokens to search for. + * @param int $start The position to start searching from in the + * token stack. + * @param int $end The end position to fail if no token is found. + * if not specified or null, end will default to + * the end of the token stack. + * @param bool $exclude If true, find the next token that is NOT of + * a type specified in $types. + * @param string $value The value that the token(s) must be equal to. + * If value is ommited, tokens with any value will + * be returned. + * @param bool $local If true, tokens outside the current statement + * will not be cheked. IE. checking will stop + * at the next semi-colon found. + * + * @return int | bool + * @see findPrevious() + */ + public function findNext($types, $start, $end=null, $exclude=false, $value=null, $local=false) + { + $types = (array) $types; + + if ($end === null || $end > $this->numTokens) { + $end = $this->numTokens; + } + + for ($i = $start; $i < $end; $i++) { + $found = (bool) $exclude; + foreach ($types as $type) { + if ($this->_tokens[$i]['code'] === $type) { + $found = !$exclude; + break; + } + } + + if ($found === true) { + if ($value === null) { + return $i; + } else if ($this->_tokens[$i]['content'] === $value) { + return $i; + } + } + + if ($local === true && $this->_tokens[$i]['code'] === T_SEMICOLON) { + break; + } + }//end for + + return false; + + }//end findNext() + + + /** + * Returns the position of the first token on a line, matching given type. + * + * Returns false if no token can be found. + * + * @param int|array $types The type(s) of tokens to search for. + * @param int $start The position to start searching from in the + * token stack. The first token matching on + * this line before this token will be returned. + * @param bool $exclude If true, find the token that is NOT of + * the types specified in $types. + * @param string $value The value that the token must be equal to. + * If value is ommited, tokens with any value will + * be returned. + * + * @return int | bool + */ + public function findFirstOnLine($types, $start, $exclude=false, $value=null) + { + if (is_array($types) === false) { + $types = array($types); + } + + $foundToken = false; + + for ($i = $start; $i >= 0; $i--) { + if ($this->_tokens[$i]['line'] < $this->_tokens[$start]['line']) { + break; + } + + $found = $exclude; + foreach ($types as $type) { + if ($exclude === false) { + if ($this->_tokens[$i]['code'] === $type) { + $found = true; + break; + } + } else { + if ($this->_tokens[$i]['code'] === $type) { + $found = false; + break; + } + } + } + + if ($found === true) { + if ($value === null) { + $foundToken = $i; + } else if ($this->_tokens[$i]['content'] === $value) { + $foundToken = $i; + } + } + }//end for + + return $foundToken; + + }//end findFirstOnLine() + + + /** + * Determine if the passed token has a condition of one of the passed types. + * + * @param int $stackPtr The position of the token we are checking. + * @param int|array $types The type(s) of tokens to search for. + * + * @return boolean + */ + public function hasCondition($stackPtr, $types) + { + // Check for the existence of the token. + if (isset($this->_tokens[$stackPtr]) === false) { + return false; + } + + // Make sure the token has conditions. + if (isset($this->_tokens[$stackPtr]['conditions']) === false) { + return false; + } + + $types = (array) $types; + $conditions = $this->_tokens[$stackPtr]['conditions']; + + foreach ($types as $type) { + if (in_array($type, $conditions) === true) { + // We found a token with the required type. + return true; + } + } + + return false; + + }//end hasCondition() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Sniff.php b/lib/pear/PHP/CodeSniffer/Sniff.php new file mode 100644 index 0000000000..2c97582bfe --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Sniff.php @@ -0,0 +1,94 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Represents a PHP_CodeSniffer sniff for sniffing coding standards. + * + * A sniff registers what token types it wishes to listen for, then, when + * PHP_CodeSniffer encounters that token, the sniff is invoked and passed + * information about where the token was found in the stack, and the + * PHP_CodeSniffer file in which the token was found. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +interface PHP_CodeSniffer_Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * An example return value for a sniff that wants to listen for whitespace + * and any comments would be: + * + * + * return array( + * T_WHITESPACE, + * T_DOC_COMMENT, + * T_COMMENT, + * ); + * + * + * @return array(int) + * @see Tokens.php + */ + public function register(); + + + /** + * Called when one of the token types that this sniff is listening for + * is found. + * + * The stackPtr variable indicates where in the stack the token was found. + * A sniff can acquire information this token, along with all the other + * tokens within the stack by first acquiring the token stack: + * + * + * $tokens = $phpcsFile->getTokens(); + * echo 'Encountered a '.$tokens[$stackPtr]['type'].' token'; + * echo 'token information: '; + * print_r($tokens[$stackPtr]); + * + * + * If the sniff discovers an anomilty in the code, they can raise an error + * by calling addError() on the PHP_CodeSniffer_File object, specifying an error + * message and the position of the offending token: + * + * + * $phpcsFile->addError('Encountered an error', $stackPtr); + * + * + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $stackPtr The position in the PHP_CodeSniffer + * file's token stack where the token + * was found. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr); + + +}//end interface + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/AbstractPatternSniff.php b/lib/pear/PHP/CodeSniffer/Standards/AbstractPatternSniff.php new file mode 100644 index 0000000000..31b7a86891 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/AbstractPatternSniff.php @@ -0,0 +1,779 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_Standards_IncorrectPatternException', true) === false) { + $error = 'Class PHP_CodeSniffer_Standards_IncorrectPatternException not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * Processes pattern strings and checks that the code conforms to the pattern. + * + * This test essentially checks that code is correctly formatted with whitespace. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +abstract class PHP_CodeSniffer_Standards_AbstractPatternSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * The parsed patterns array. + * + * @var array + */ + private $_parsedPatterns = array(); + + /** + * Tokens that wish this sniff wishes to process outside of the patterns. + * + * @var array(int) + * @see registerSupplementary() + * @see processSupplementary() + */ + private $_supplementaryTokens = array(); + + /** + * If true, comments will be ignored if they are found in the code. + * + * @var boolean + */ + private $_ignoreComments = false; + + /** + * Positions in the stack where errors have occured. + * + * @var array() + */ + private $_errorPos = array(); + + + /** + * Constructs a PHP_CodeSniffer_Standards_AbstractPatternSniff. + * + * @param boolean $ignoreComments If true, comments will be ignored. + */ + public function __construct($ignoreComments=false) + { + $this->_ignoreComments = $ignoreComments; + $this->_supplementaryTokens = $this->registerSupplementary(); + + }//end __construct() + + + /** + * Registers the tokens to listen to. + * + * Classes extending AbstractPatternTest should implement the + * getPatterns() method to register the patterns they wish to test. + * + * @return array(int) + * @see process() + */ + public final function register() + { + $listenTypes = array(); + $patterns = $this->getPatterns(); + + foreach ($patterns as $pattern) { + + $parsedPattern = $this->_parse($pattern); + + // Find a token position in the pattern that we can use for a listener + // token. + $pos = $this->_getListenerTokenPos($parsedPattern); + $tokenType = $parsedPattern[$pos]['token']; + $listenTypes[] = $tokenType; + + $patternArray = array( + 'listen_pos' => $pos, + 'pattern' => $parsedPattern, + 'pattern_code' => $pattern, + ); + + if (isset($this->_parsedPatterns[$tokenType]) === false) { + $this->_parsedPatterns[$tokenType] = array(); + } + + $this->_parsedPatterns[$tokenType][] = $patternArray; + + }//end foreach + + return array_unique(array_merge($listenTypes, $this->_supplementaryTokens)); + + }//end register() + + + /** + * Returns the token types that the specified pattern is checking for. + * + * Returned array is in the format: + * + * array( + * T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token + * // should occur in the pattern. + * ); + * + * + * @param array $pattern The parsed pattern to find the acquire the token + * types from. + * + * @return array(int => int) + */ + private function _getPatternTokenTypes($pattern) + { + $tokenTypes = array(); + foreach ($pattern as $pos => $patternInfo) { + if ($patternInfo['type'] === 'token') { + if (isset($tokenTypes[$patternInfo['token']]) === false) { + $tokenTypes[$patternInfo['token']] = $pos; + } + } + } + + return $tokenTypes; + + }//end _getPatternTokenTypes() + + + /** + * Returns the position in the pattern that this test should register as + * a listener for the pattern. + * + * @param array $pattern The pattern to acquire the listener for. + * + * @return int The postition in the pattern that this test should register + * as the listener. + * @throws PHP_CodeSniffer_Exception If we could not determine a token + * to listen for. + */ + private function _getListenerTokenPos($pattern) + { + $tokenTypes = $this->_getPatternTokenTypes($pattern); + $tokenCodes = array_keys($tokenTypes); + $token = PHP_CodeSniffer_Tokens::getHighestWeightedToken($tokenCodes); + + // If we could not get a token. + if ($token === false) { + $error = 'Could not determine a token to listen for'; + throw new PHP_CodeSniffer_Exception($error); + } + + return $tokenTypes[$token]; + + }//end _getListenerTokenPos() + + + /** + * Processes the test. + * + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the + * token occured. + * @param int $stackPtr The postion in the tokens stack + * where the listening token type was + * found. + * + * @return void + * @see register() + */ + public final function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (in_array($tokens[$stackPtr]['code'], $this->_supplementaryTokens) === true) { + $this->processSupplementary($phpcsFile, $stackPtr); + } + + $type = $tokens[$stackPtr]['code']; + + // If the type is not set, then it must have been a token registered + // with registerSupplementary(). + if (isset($this->_parsedPatterns[$type]) === false) { + return; + } + + $allErrors = array(); + + // Loop over each pattern that is listening to the current token type + // that we are processing. + foreach ($this->_parsedPatterns[$type] as $patternInfo) { + + // If processPattern returns false, then the pattern that we are + // checking the code with must not be design to check that code. + $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr); + if ($errors === false) { + // The pattern didn't match. + continue; + } else if (empty($errors) === true) { + // The pattern matched, but there were no errors. + break; + } + + foreach ($errors as $stackPtr => $error) { + if (isset($this->_errorPos[$stackPtr]) === false) { + $this->_errorPos[$stackPtr] = true; + $allErrors[$stackPtr] = $error; + } + } + } + + foreach ($allErrors as $stackPtr => $error) { + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + + /** + * Processes the pattern and verifies the code at $stackPtr. + * + * @param array $patternInfo Information about the pattern used + * for checking, which includes are + * parsed token representation of the + * pattern. + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the + * token occured. + * @param int $stackPtr The postion in the tokens stack where + * the listening token type was found. + * + * @return array(errors) + */ + protected function processPattern($patternInfo, PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $pattern = $patternInfo['pattern']; + $patternCode = $patternInfo['pattern_code']; + $errors = array(); + $found = ''; + + $ignoreTokens = array(T_WHITESPACE); + + if ($this->_ignoreComments === true) { + $ignoreTokens = array_merge($ignoreTokens, PHP_CodeSniffer_Tokens::$commentTokens); + } + + $origStackPtr = $stackPtr; + $hasError = false; + + if ($patternInfo['listen_pos'] > 0) { + $stackPtr--; + + for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) { + + if ($pattern[$i]['type'] === 'token') { + + if ($pattern[$i]['token'] === T_WHITESPACE) { + + if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { + $found = $tokens[$stackPtr]['content'].$found; + } + + // Only check the size of the whitespace if this is not + // not the first token. We don't care about the size of + // leading whitespace, just that there is some. + if ($i !== 0) { + if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) { + $hasError = true; + } + } + + } else { + + // Check to see if this important token is the same as the + // previous important token in the pattern. If it is not, + // then the pattern cannot be for this piece of code. + $prev = $phpcsFile->findPrevious($ignoreTokens, $stackPtr, null, true); + if ($prev === false || $tokens[$prev]['code'] !== $pattern[$i]['token']) { + return false; + } + + // If we skipped past some whitespace tokens, then add them + // to the found string. + if (($stackPtr - $prev) > 1) { + for ($j = ($stackPtr - 1); $j > $prev; $j--) { + $found = $tokens[$j]['content'].$found; + } + } + + $found = $tokens[$prev]['content'].$found; + + if (isset($pattern[($i - 1)]) === true && $pattern[($i - 1)]['type'] === 'skip') { + $stackPtr = $prev; + } else { + $stackPtr = ($prev - 1); + } + + }//end if + } else if ($pattern[$i]['type'] === 'skip') { + // Skip to next piece of relevant code. + if ($pattern[$i]['to'] === 'parenthesis_closer') { + $to = 'parenthesis_opener'; + } else { + $to = 'scope_opener'; + } + + // Find the previous opener. + $next = $phpcsFile->findPrevious($ignoreTokens, $stackPtr, null, true); + if ($next === false || isset($tokens[$next][$to]) === false) { + // If there was not opener, then we must be + // using the wrong pattern. + return false; + } + + if ($to === 'parenthesis_opener') { + $found = '{'.$found; + } else { + $found = '('.$found; + } + + $found = '...'.$found; + + // Skip to the opening token. + $stackPtr = ($tokens[$next][$to] - 1); + } else if ($pattern[$i]['type'] === 'string') { + $found = 'abc'; + } else if ($pattern[$i]['type'] === 'newline') { + $found = 'EOL'; + }//end if + }//end for + }//end if + + $stackPtr = $origStackPtr; + $lastAddedStackPtr = null; + $patternLen = count($pattern); + + for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) { + + if ($pattern[$i]['type'] === 'token') { + + if ($pattern[$i]['token'] === T_WHITESPACE) { + + if ($this->_ignoreComments === true) { + // If we are ignoring comments, check to see if this current + // token is a comment. If so skip it. + if (in_array($tokens[$stackPtr]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) { + continue; + } + + // If the next token is a comment, the we need to skip the + // current token as we should allow a space before a + // comment for readability. + if (in_array($tokens[($stackPtr + 1)]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) { + continue; + } + } + + $tokenContent = ''; + if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { + if (isset($pattern[($i + 1)]) === false) { + // This is the last token in the pattern, so just compare + // the next token of content. + $tokenContent = $tokens[$stackPtr]['content']; + } else { + // Get all the whitespace to the next token. + $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr, null, true); + $tokenContent = $phpcsFile->getTokensAsString($stackPtr, ($next - $stackPtr)); + $lastAddedStackPtr = $stackPtr; + $stackPtr = $next; + } + + if ($stackPtr !== $lastAddedStackPtr) { + $found .= $tokenContent; + } + } else { + if ($stackPtr !== $lastAddedStackPtr) { + $found .= $tokens[$stackPtr]['content']; + $lastAddedStackPtr = $stackPtr; + } + }//end if + + if (isset($pattern[($i + 1)]) === true && $pattern[($i + 1)]['type'] === 'skip') { + // The next token is a skip token, so we just need to make + // sure the whitespace we found has *at least* the + // whitespace required. + if (strpos($tokenContent, $pattern[$i]['value']) !== 0) { + $hasError = true; + } + } else { + if ($tokenContent !== $pattern[$i]['value']) { + $hasError = true; + } + } + + } else { + // Check to see if this important token is the same as the + // next important token in the pattern. If it is not, then + // the pattern cannot be for this piece of code. + $next = $phpcsFile->findNext($ignoreTokens, $stackPtr, null, true); + if ($next === false || $tokens[$next]['code'] !== $pattern[$i]['token']) { + return false; + } + + // If we skipped past some whitespace tokens, then add them + // to the found string. + if (($next - $stackPtr) > 0) { + $hasComment = false; + for ($j = $stackPtr; $j < $next; $j++) { + $found .= $tokens[$j]['content']; + if (in_array($tokens[$j]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) { + $hasComment = true; + } + } + + // If we are not ignoring comments, this additional + // whitespace or comment is not allowed. If we are + // ignoring comments, there needs to be at least one + // comment for this to be allowed. + if ($this->_ignoreComments === false || ($this->_ignoreComments === true && $hasComment === false)) { + $hasError = true; + } + + // Even when ignoring comments, we are not allowed to include + // newlines without the pattern specifying them, so + // everything should be on the same line. + if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { + $hasError = true; + } + }//end if + + if ($next !== $lastAddedStackPtr) { + $found .= $tokens[$next]['content']; + $lastAddedStackPtr = $next; + } + + if (isset($pattern[($i + 1)]) === true && $pattern[($i + 1)]['type'] === 'skip') { + $stackPtr = $next; + } else { + $stackPtr = ($next + 1); + } + }//end if + + } else if ($pattern[$i]['type'] === 'skip') { + // Find the previous opener. + $next = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$blockOpeners, $stackPtr, null); + if ($next === false || isset($tokens[$next][$pattern[$i]['to']]) === false) { + // If there was not opener, then we must + // be using the wrong pattern. + return false; + } + + $found .= '...'; + if ($pattern[$i]['to'] === 'parenthesis_closer') { + $found .= ')'; + } else { + $found .= '}'; + } + + // Skip to the closing token. + $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1); + } else if ($pattern[$i]['type'] === 'string') { + if ($tokens[$stackPtr]['code'] !== T_STRING) { + $hasError = true; + } + + if ($stackPtr !== $lastAddedStackPtr) { + $found .= 'abc'; + $lastAddedStackPtr = $stackPtr; + } + + $stackPtr++; + } else if ($pattern[$i]['type'] === 'newline') { + // Find the next token that contains a newline character. + $newline = 0; + for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) { + if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) { + $newline = $j; + break; + } + } + + if ($newline === 0) { + // We didn't find a newline character in the rest of the file. + $next = ($phpcsFile->numTokens - 1); + $hasError = true; + } else { + if ($this->_ignoreComments === false) { + // The newline character cannot be part of a comment. + if (in_array($tokens[$newline]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) { + $hasError = true; + } + } + + if ($newline === $stackPtr) { + $next = ($stackPtr + 1); + } else { + // Check that there were no significant tokens that we + // skipped over to find our newline character. + $next = $phpcsFile->findNext($ignoreTokens, $stackPtr, null, true); + if ($next < $newline) { + // We skipped a non-ignored token. + $hasError = true; + } else { + $next = ($newline + 1); + } + } + }//end if + + if ($stackPtr !== $lastAddedStackPtr) { + $found .= $phpcsFile->getTokensAsString($stackPtr, ($next - $stackPtr)); + $diff = ($next - $stackPtr); + $lastAddedStackPtr = ($next - 1); + } + + $stackPtr = $next; + }//end if + }//end for + + if ($hasError === true) { + $error = $this->prepareError($found, $patternCode); + $errors[$origStackPtr] = $error; + } + + return $errors; + + }//end processPattern() + + + /** + * Prepares an error for the specified patternCode. + * + * @param string $found The actual found string in the code. + * @param string $patternCode The expected pattern code. + * + * @return string The error message. + */ + protected function prepareError($found, $patternCode) + { + $found = str_replace("\r\n", '\n', $found); + $found = str_replace("\n", '\n', $found); + $found = str_replace("\r", '\n', $found); + $found = str_replace('EOL', '\n', $found); + $expected = str_replace('EOL', '\n', $patternCode); + + $error = "Expected \"$expected\"; found \"$found\""; + + return $error; + + }//end prepareError() + + + /** + * Returns the patterns that should be checked. + * + * @return array(string) + */ + protected abstract function getPatterns(); + + + /** + * Registers any supplementary tokens that this test might wish to process. + * + * A sniff may wish to register supplementary tests when it wishes to group + * an arbitary validation that cannot be performed using a pattern, with + * other pattern tests. + * + * @return array(int) + * @see processSupplementary() + */ + protected function registerSupplementary() + { + return array(); + + }//end registerSupplementary() + + + /** + * Processes any tokens registered with registerSupplementary(). + * + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where to + * process the skip. + * @param int $stackPtr The position in the tokens stack to + * process. + * + * @return void + * @see registerSupplementary() + */ + protected function processSupplementary(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + return; + + }//end processSupplementary() + + + /** + * Parses a pattern string into an array of pattern steps. + * + * @param string $pattern The pattern to parse. + * + * @return array The parsed pattern array. + * @see _createSkipPattern() + * @see _createTokenPattern() + */ + private function _parse($pattern) + { + $patterns = array(); + $length = strlen($pattern); + $lastToken = 0; + $firstToken = 0; + + for ($i = 0; $i < $length; $i++) { + + $specialPattern = false; + $isLastChar = ($i === ($length - 1)); + $oldFirstToken = $firstToken; + + if (substr($pattern, $i, 3) === '...') { + // It's a skip pattern. The skip pattern requires the + // content of the token in the "from" position and the token + // to skip to. + $specialPattern = $this->_createSkipPattern($pattern, ($i - 1)); + $lastToken = ($i - $firstToken); + $firstToken = ($i + 4); + $i = ($i + 3); + } else if (substr($pattern, $i, 3) === 'abc') { + $specialPattern = array('type' => 'string'); + $lastToken = ($i - $firstToken); + $firstToken = ($i + 3); + $i = ($i + 2); + } else if (substr($pattern, $i, 3) === 'EOL') { + $specialPattern = array('type' => 'newline'); + $lastToken = ($i - $firstToken); + $firstToken = ($i + 3); + $i = ($i + 2); + } + + if ($specialPattern !== false || $isLastChar === true) { + + // If we are at the end of the string, don't worry about a limit. + if ($isLastChar === true) { + // Get the string from the end of the last skip pattern, if any, + // to the end of the pattern string. + $str = substr($pattern, $oldFirstToken); + } else { + // Get the string from the end of the last special pattern, + // if any, to the start of this special pattern. + $str = substr($pattern, $oldFirstToken, $lastToken); + } + + $tokenPatterns = $this->_createTokenPattern($str); + + // Make sure we don't skip the last token. + if ($isLastChar === false && $i === ($length - 1)) { + $i--; + } + + foreach ($tokenPatterns as $tokenPattern) { + $patterns[] = $tokenPattern; + } + }//end if + + // Add the skip pattern *after* we have processed + // all the tokens from the end of the last skip pattern + // to the start of this skip pattern. + if ($specialPattern !== false) { + $patterns[] = $specialPattern; + } + + }//end for + + return $patterns; + + }//end _parse() + + + /** + * Creates a skip pattern. + * + * @param string $pattern The pattern being parsed. + * @param string $from The token content that the skip pattern starts from. + * + * @return array The pattern step. + * @see _createTokenPattern() + * @see _parse() + */ + private function _createSkipPattern($pattern, $from) + { + $skip = array('type' => 'skip'); + + for ($from; $from >= 0; $from--) { + switch ($pattern[$from]) { + case '(': + $skip['to'] = 'parenthesis_closer'; + break; + case '{': + $skip['to'] = 'scope_closer'; + break; + } + + if (isset($skip['to']) === true) { + break; + } + } + + if (isset($skip['to']) === false) { + $skip['to'] = 'unknown'; + } + + return $skip; + + }//end _createSkipPattern() + + + /** + * Creates a token pattern. + * + * @param string $str The tokens string that the pattern should match. + * + * @return array The pattern step. + * @see _createSkipPattern() + * @see _parse() + */ + private function _createTokenPattern($str) + { + // Don't add a space after the closing php tag as it will add a new + // whitespace token. + $tokens = token_get_all(''); + + // Remove the 'token', + 'token' => $patternInfo['code'], + 'value' => $patternInfo['content'], + ); + } + + return $patterns; + + }//end _createTokenPattern() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/AbstractScopeSniff.php b/lib/pear/PHP/CodeSniffer/Standards/AbstractScopeSniff.php new file mode 100644 index 0000000000..f47c47a98f --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/AbstractScopeSniff.php @@ -0,0 +1,213 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * An AbstractScopeTest allows for tests that extend from this class to + * listen for tokens within a particluar scope. + * + * Below is a test that listens to methods that exist only within classes: + * + * class ClassScopeTest extends PHP_CodeSniffer_Standards_AbstractScopeSniff + * { + * public function __construct() + * { + * parent::__construct(array(T_CLASS), array(T_FUNCTION)); + * } + * + * protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $) + * { + * $className = $phpcsFile->getDeclarationName($currScope); + * echo 'encountered a method within class '.$className; + * } + * } + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +abstract class PHP_CodeSniffer_Standards_AbstractScopeSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * The token types that this test wishes to listen to within the scope. + * + * @var array() + */ + private $_tokens = array(); + + /** + * The type of scope opener tokens that this test wishes to listen to. + * + * @var string + */ + private $_scopeTokens = array(); + + /** + * The position in the tokens array that opened the current scope. + * + * @var array() + */ + protected $currScope = null; + + /** + * True if this test should fire on tokens outside of the scope. + * + * @var boolean + */ + private $_listenOutside = false; + + + /** + * Constructs a new AbstractScopeTest. + * + * @param array $scopeTokens The type of scope the test wishes to listen to. + * @param array $tokens The tokens that the test wishes to listen to + * within the scope. + * @param boolean $listenOutside If true this test will also alert the + * extending class when a token is found outside + * the scope, by calling the + * processTokenOutideScope method. + * + * @see PHP_CodeSniffer.getValidScopeTokeners() + * @throws PHP_CodeSniffer_Test_Exception If the specified tokens array is empty. + */ + public function __construct(array $scopeTokens, array $tokens, $listenOutside=false) + { + if (empty($scopeTokens) === true) { + $error = 'The scope tokens list cannot be empty'; + throw new PHP_CodeSniffer_Test_Exception($error); + } + + if (empty($tokens) === true) { + $error = 'The tokens list cannot be empty'; + throw new PHP_CodeSniffer_Test_Exception($error); + } + + $invalidScopeTokens = array_intersect($scopeTokens, $tokens); + if (empty($invalidScopeTokens) === false) { + $invalid = implode(', ', $invalidScopeTokens); + $error = "Scope tokens [$invalid] cant be in the tokens array"; + throw new PHP_CodeSniffer_Test_Exception($error); + } + + $this->_listenOutside = $listenOutside; + $this->_scopeTokens = $scopeTokens; + $this->_tokens = $tokens; + + }//end __construct() + + + /** + * The method that is called to register the tokens this test wishes to + * listen to. + * + * DO NOT OVERRIDE THIS METHOD. Use the constructor of this class to register + * for the desired tokens and scope. + * + * @return array(int) + * @see __constructor() + */ + public final function register() + { + if ($this->_listenOutside === false) { + return $this->_scopeTokens; + } else { + return array_merge($this->_scopeTokens, $this->_tokens); + } + + }//end register() + + + /** + * Processes the tokens that this test is listening for. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack where this + * token was found. + * + * @return void + * @see processTokenWithinScope() + */ + public final function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (in_array($tokens[$stackPtr]['code'], $this->_scopeTokens) === true) { + $this->currScope = $stackPtr; + $phpcsFile->addTokenListener($this, $this->_tokens); + } else if ($this->currScope !== null && isset($tokens[$this->currScope]['scope_closer']) === true && $stackPtr > $tokens[$this->currScope]['scope_closer']) { + $this->currScope = null; + if ($this->_listenOutside === true) { + // This is a token outside the current scope, so notify the + // extender as they wish to know about this. + $this->processTokenOutsideScope($phpcsFile, $stackPtr); + } else { + // Don't remove the listener if the extender wants to know about + // tokens that live outside the current scope. + $phpcsFile->removeTokenListener($this, $this->_tokens); + } + } else if ($this->currScope !== null) { + $this->processTokenWithinScope($phpcsFile, $stackPtr, $this->currScope); + } else { + $this->processTokenOutsideScope($phpcsFile, $stackPtr); + } + + }//end process() + + + /** + * Processes a token that is found within the scope that this test is + * listening to. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack where this + * token was found. + * @param int $currScope The position in the tokens array that + * opened the scope that this test is + * listening for. + * + * @return void + */ + protected abstract function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope); + + + /** + * Processes a token that is found within the scope that this test is + * listening to. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. + * @param int $stackPtr The position in the stack where this + * token was found. + * + * @return void + */ + protected function processTokenOutsideScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + return; + + }//end processTokenOutsideScope() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/AbstractVariableSniff.php b/lib/pear/PHP/CodeSniffer/Standards/AbstractVariableSniff.php new file mode 100644 index 0000000000..1ad7a6cdb2 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/AbstractVariableSniff.php @@ -0,0 +1,217 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_Standards_AbstractScopeSniff', true) === false) { + $error = 'Class PHP_CodeSniffer_Standards_AbstractScopeSniff not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * A class to find T_VARIABLE tokens. + * + * This class can distingush between normal T_VARIABLE tokens, and those tokens + * that represent class members. If a class member is encountered, then then + * processMemberVar method is called so the extending class can process it. If + * the token is found to be a normal T_VARIABLE token, then processVariable is + * called. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +abstract class PHP_CodeSniffer_Standards_AbstractVariableSniff extends PHP_CodeSniffer_Standards_AbstractScopeSniff +{ + + /** + * The end token of the current function that we are in. + * + * @var int + */ + private $_endFunction = -1; + + /** + * true if a function is currently open. + * + * @var boolean + */ + private $_functionOpen = false; + + /** + * The current PHP_CodeSniffer file that we are processing. + * + * @var PHP_CodeSniffer_File + */ + protected $currentFile = null; + + + /** + * Constructs an AbstractVariableTest. + */ + public function __construct() + { + $listen = array( + T_CLASS, + T_INTERFACE, + ); + + $scopes = array( + T_FUNCTION, + T_VARIABLE, + T_DOUBLE_QUOTED_STRING, + ); + + parent::__construct($listen, $scopes, true); + + }//end __construct() + + + /** + * Processes the token in the specified PHP_CodeSniffer_File. + * + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this + * token was found. + * @param int $stackPtr The position where the token was found. + * @param array $currScope The current scope opener token. + * + * @return void + */ + protected final function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) + { + if ($this->currentFile !== $phpcsFile) { + $this->currentFile = $phpcsFile; + $this->_functionOpen = false; + $this->_endFunction = -1; + } + + $tokens = $phpcsFile->getTokens(); + + if ($stackPtr > $this->_endFunction) { + $this->_functionOpen = false; + } + + if ($tokens[$stackPtr]['code'] === T_FUNCTION && $this->_functionOpen === false) { + + $this->_functionOpen = true; + + $methodProps = $phpcsFile->getMethodProperties($stackPtr); + + // If the function is abstract, or is in an interface, + // then set the end of the function to it's closing semicolon. + if ($methodProps['is_abstract'] === true || $tokens[$currScope]['code'] === T_INTERFACE) { + $this->_endFunction = $phpcsFile->findNext(array(T_SEMICOLON), $stackPtr); + } else { + if (isset($tokens[$stackPtr]['scope_closer']) === false) { + $error = 'Possible parse error: non-abstract method defined as abstract'; + $phpcsFile->addWarning($error, $stackPtr); + return; + } + + $this->_endFunction = $tokens[$stackPtr]['scope_closer']; + } + + } + + if ($this->_functionOpen === true) { + if ($tokens[$stackPtr]['code'] === T_VARIABLE) { + $this->processVariable($phpcsFile, $stackPtr); + } else if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING) { + // Check to see if this string has a variable in it. + $pattern = '|[^\\\]\$[a-zA-Z0-9_]+|'; + if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) { + $this->processVariableInString($phpcsFile, $stackPtr); + } + } + + return; + } else { + // What if we assign a member variable to another? + // ie. private $_count = $this->_otherCount + 1;. + $this->processMemberVar($phpcsFile, $stackPtr); + } + + }//end processTokenWithinScope() + + + /** + * Processes the token outside the scope in the file. + * + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this + * token was found. + * @param int $stackPtr The position where the token was found. + * + * @return void + */ + protected final function processTokenOutsideScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + // These variables are not member vars. + if ($tokens[$stackPtr]['code'] === T_VARIABLE) { + $this->processVariable($phpcsFile, $stackPtr); + } else { + $this->processVariableInString($phpcsFile, $stackPtr); + } + + }//end processTokenOutsideScope() + + + /** + * Called to process class member vars. + * + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this + * token was found. + * @param int $stackPtr The position where the token was found. + * + * @return void + */ + abstract protected function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr); + + + /** + * Called to process normal member vars. + * + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this + * token was found. + * @param int $stackPtr The position where the token was found. + * + * @return void + */ + abstract protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr); + + + /** + * Called to process variables found in duoble quoted strings. + * + * Note that there may be more than one variable in the string, which will + * result only in one call for the string. + * + * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this + * token was found. + * @param int $stackPtr The position where the double quoted + * string was found. + * + * @return void + */ + abstract protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr); + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/CodingStandard.php b/lib/pear/PHP/CodeSniffer/Standards/CodingStandard.php new file mode 100644 index 0000000000..53d8748680 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/CodingStandard.php @@ -0,0 +1,73 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Base Coding Standard class. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_Standards_CodingStandard +{ + + + /** + * Return a list of external sniffs to include with this standard. + * + * External locations can be single sniffs, a whole directory of sniffs, or + * an entire coding standard. Locations start with the standard name. For + * example: + * PEAR => include all sniffs in this standard + * PEAR/Sniffs/Files => include all sniffs in this dir + * PEAR/Sniffs/Files/LineLengthSniff => include this single sniff + * + * @return array + */ + public function getIncludedSniffs() + { + return array(); + + }//end getIncludedSniffs() + + + /** + * Return a list of external sniffs to exclude from this standard. + * + * External locations can be single sniffs, a whole directory of sniffs, or + * an entire coding standard. Locations start with the standard name. For + * example: + * PEAR => exclude all sniffs in this standard + * PEAR/Sniffs/Files => exclude all sniffs in this dir + * PEAR/Sniffs/Files/LineLengthSniff => exclude this single sniff + * + * @return array + */ + public function getExcludedSniffs() + { + return array(); + + }//end getExcludedSniffs() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/IncorrectPatternException.php b/lib/pear/PHP/CodeSniffer/Standards/IncorrectPatternException.php new file mode 100644 index 0000000000..d7f8e45fe1 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/IncorrectPatternException.php @@ -0,0 +1,36 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * An exception thrown if the pattern being processed is not supposed to be + * validating the code in question. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_Standards_IncorrectPatternException extends Exception +{ + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/MoodleCodingStandard.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/MoodleCodingStandard.php new file mode 100644 index 0000000000..51f135cf55 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/MoodleCodingStandard.php @@ -0,0 +1,38 @@ + + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_Standards_CodingStandard', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_CodingStandard not found'); +} + +/** + * Moodle Coding Standard. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_Standards_Moodle_MoodleCodingStandard extends PHP_CodeSniffer_Standards_CodingStandard { + public function getIncludedSniffs() { + return array(); + } + + public function getExcludedSniffs() { + return array('Moodle/Sniffs/CodeAnalysis'); + } +}//end class +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Classes/ClassDeclarationSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Classes/ClassDeclarationSniff.php new file mode 100644 index 0000000000..8ddc123fd3 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Classes/ClassDeclarationSniff.php @@ -0,0 +1,100 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Class Declaration Test. + * + * Checks the declaration of the class is correct. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Classes_ClassDeclarationSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_CLASS, + T_INTERFACE, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + $error = 'Possible parse error: '; + $error .= $tokens[$stackPtr]['content']; + $error .= ' missing opening or closing brace'; + $phpcsFile->addWarning($error, $stackPtr); + return; + } + + $curlyBrace = $tokens[$stackPtr]['scope_opener']; + $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($curlyBrace - 1), $stackPtr, true); + $classLine = $tokens[$lastContent]['line']; + $braceLine = $tokens[$curlyBrace]['line']; + if ($braceLine != $classLine) { + $error = 'Opening brace of a '; + $error .= $tokens[$stackPtr]['content']; + $error .= ' must be on the same line as the definition'; + $phpcsFile->addError($error, $curlyBrace); + return; + } + + if ($tokens[($curlyBrace - 1)]['code'] === T_WHITESPACE) { + $prevContent = $tokens[($curlyBrace - 1)]['content']; + if ($prevContent !== $phpcsFile->eolChar) { + $blankSpace = substr($prevContent, strpos($prevContent, $phpcsFile->eolChar)); + $spaces = strlen($blankSpace); + if ($spaces !== 1) { + $error = "Expected 1 space before opening brace; $spaces found"; + $phpcsFile->addError($error, $curlyBrace); + } + } + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/EmptyStatementSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/EmptyStatementSniff.php new file mode 100644 index 0000000000..ad2ada4c53 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/EmptyStatementSniff.php @@ -0,0 +1,129 @@ + + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * This sniff class detected empty statement. + * + * This sniff implements the common algorithm for empty statement body detection. + * A body is considered as empty if it is completely empty or it only contains + * whitespace characters and|or comments. + * + * + * stmt { + * // foo + * } + * stmt (conditions) { + * // foo + * } + * + * + * Statements covered by this sniff are catch, do, else, + * elsif, for, foreach, if, switch, try + * and while. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_CodeAnalysis_EmptyStatementSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * List of block tokens that this sniff covers. + * + * The key of this hash identifies the required token while the boolean + * value says mark an error or mark a warning. + * + * @type array + * @var array(integer=>boolean) $_tokens + */ + private $_tokens = array( + T_CATCH => true, + T_DO => false, + T_ELSE => false, + T_ELSEIF => false, + T_FOR => false, + T_FOREACH => false, + T_IF => false, + T_SWITCH => false, + T_TRY => false, + T_WHILE => false, + ); + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return array(integer) + */ + public function register() + { + return array_keys($this->_tokens); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + + // Skip for-statements without body. + if (isset($token['scope_opener']) === false) { + return; + } + + $next = ++$token['scope_opener']; + $end = --$token['scope_closer']; + + $emptyBody = true; + for (; $next <= $end; ++$next) { + if (in_array($tokens[$next]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) { + $emptyBody = false; + break; + } + } + + if ($emptyBody === true) { + // Get token identifier. + $name = $phpcsFile->getTokensAsString($stackPtr, 1); + $error = sprintf('Empty %s statement detected', strtoupper($name)); + if ($this->_tokens[$token['code']] === true) { + $phpcsFile->addError($error, $stackPtr); + } else { + $phpcsFile->addWarning($error, $stackPtr); + } + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/ForLoopShouldBeWhileLoopSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/ForLoopShouldBeWhileLoopSniff.php new file mode 100644 index 0000000000..159e7b3b16 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/ForLoopShouldBeWhileLoopSniff.php @@ -0,0 +1,101 @@ + + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Detects for-loops that can be simplified to a while-loop. + * + * This rule is based on the PMD rule catalog. Detects for-loops that can be + * simplified as a while-loop. + * + * + * class Foo + * { + * public function bar($x) + * { + * for (;true;) true; // No Init or Update part, may as well be: while (true) + * } + * } + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_CodeAnalysis_ForLoopShouldBeWhileLoopSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return array(integer) + */ + public function register() + { + return array(T_FOR); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + + // Skip invalid statement. + if (isset($token['parenthesis_opener']) === false) { + return; + } + + $next = ++$token['parenthesis_opener']; + $end = --$token['parenthesis_closer']; + + $parts = array(0, 0, 0); + $index = 0; + + for (; $next <= $end; ++$next) { + $code = $tokens[$next]['code']; + if ($code === T_SEMICOLON) { + ++$index; + } else if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === false) { + ++$parts[$index]; + } + } + + if ($parts[0] === 0 && $parts[2] === 0 && $parts[1] > 0) { + $error = 'This FOR loop can be simplified to a WHILE loop'; + $phpcsFile->addWarning($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php new file mode 100644 index 0000000000..7977f55897 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php @@ -0,0 +1,114 @@ + + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Detects for-loops that use a function call in the test expression. + * + * This rule is based on the PMD rule catalog. Detects for-loops that use a + * function call in the test expression. + * + * + * class Foo + * { + * public function bar($x) + * { + * $a = array(1, 2, 3, 4); + * for ($i = 0; $i < count($a); $i++) { + * $a[$i] *= $i; + * } + * } + * } + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_CodeAnalysis_ForLoopWithTestFunctionCallSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return array(integer) + */ + public function register() + { + return array(T_FOR); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + + // Skip invalid statement. + if (isset($token['parenthesis_opener']) === false) { + return; + } + + $next = ++$token['parenthesis_opener']; + $end = --$token['parenthesis_closer']; + + $position = 0; + + for (; $next <= $end; ++$next) { + $code = $tokens[$next]['code']; + if ($code === T_SEMICOLON) { + ++$position; + } + + if ($position < 1) { + continue; + } else if ($position > 1) { + break; + } else if ($code !== T_VARIABLE && $code !== T_STRING) { + continue; + } + + // Find next non empty token, if it is a open curly brace we have a + // function call. + $index = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), null, true); + + if ($tokens[$index]['code'] === T_OPEN_PARENTHESIS) { + $error = 'Avoid function calls in a FOR loop test part'; + $phpcsFile->addWarning($error, $stackPtr); + break; + } + }//end for + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/JumbledIncrementerSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/JumbledIncrementerSniff.php new file mode 100644 index 0000000000..d1c08a5e47 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/JumbledIncrementerSniff.php @@ -0,0 +1,148 @@ + + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Detects incrementer jumbling in for loops. + * + * This rule is based on the PMD rule catalog. The jumbling incrementer sniff + * detects the usage of one and the same incrementer into an outer and an inner + * loop. Even it is intended this is confusing code. + * + * + * class Foo + * { + * public function bar($x) + * { + * for ($i = 0; $i < 10; $i++) + * { + * for ($k = 0; $k < 20; $i++) + * { + * echo 'Hello'; + * } + * } + * } + * } + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_CodeAnalysis_JumbledIncrementerSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return array(integer) + */ + public function register() + { + return array(T_FOR); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + + // Skip for-loop without body. + if (isset($token['scope_opener']) === false) { + return; + } + + // Find incrementors for outer loop. + $outer = $this->findIncrementers($tokens, $token); + + // Skip if empty. + if (count($outer) === 0) { + return; + } + + // Find nested for loops. + $start = ++$token['scope_opener']; + $end = --$token['scope_closer']; + + for (; $start <= $end; ++$start) { + if ($tokens[$start]['code'] !== T_FOR) { + continue; + } + + $inner = $this->findIncrementers($tokens, $tokens[$start]); + $diff = array_intersect($outer, $inner); + + if (count($diff) !== 0) { + $error = sprintf('Loop incrementor (%s) jumbling with inner loop', join(', ', $diff)); + $phpcsFile->addWarning($error, $stackPtr); + } + } + + }//end process() + + + /** + * Get all used variables in the incrementer part of a for statement. + * + * @param array(integer=>array) $tokens Array with all code sniffer tokens. + * @param array(string=>mixed) $token Current for loop token + * + * @return array(string) List of all found incrementer variables. + */ + protected function findIncrementers(array $tokens, array $token) + { + // Skip invalid statement. + if (isset($token['parenthesis_opener']) === false) { + return array(); + } + + $start = ++$token['parenthesis_opener']; + $end = --$token['parenthesis_closer']; + + $incrementers = array(); + $semicolons = 0; + for ($next = $start; $next <= $end; ++$next) { + $code = $tokens[$next]['code']; + if ($code === T_SEMICOLON) { + ++$semicolons; + } else if ($semicolons === 2 && $code === T_VARIABLE) { + $incrementers[] = $tokens[$next]['content']; + } + } + + return $incrementers; + + }//end findIncrementers() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php new file mode 100644 index 0000000000..8f0b4639b3 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php @@ -0,0 +1,107 @@ + + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Detects unconditional if- and elseif-statements. + * + * This rule is based on the PMD rule catalog. The Unconditional If Statment + * sniff detects statement conditions that are only set to one of the constant + * values true or false + * + * + * class Foo + * { + * public function close() + * { + * if (true) + * { + * // ... + * } + * } + * } + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_CodeAnalysis_UnconditionalIfStatementSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return array(integer) + */ + public function register() + { + return array( + T_IF, + T_ELSEIF, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + + // Skip for-loop without body. + if (isset($token['parenthesis_opener']) === false) { + return; + } + + $next = ++$token['parenthesis_opener']; + $end = --$token['parenthesis_closer']; + + $goodCondition = false; + for (; $next <= $end; ++$next) { + $code = $tokens[$next]['code']; + + if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === true) { + continue; + } else if ($code !== T_TRUE && $code !== T_FALSE) { + $goodCondition = true; + } + } + + if ($goodCondition === false) { + $error = 'Avoid IF statements that are always true or false'; + $phpcsFile->addWarning($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php new file mode 100644 index 0000000000..9ad3fbc183 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php @@ -0,0 +1,99 @@ + + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Detects unnecessary final modifiers inside of final classes. + * + * This rule is based on the PMD rule catalog. The Unnecessary Final Modifier + * sniff detects the use of the final modifier inside of a final class which + * is unnecessary. + * + * + * final class Foo + * { + * public final function bar() + * { + * } + * } + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_CodeAnalysis_UnnecessaryFinalModifierSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return array(integer) + */ + public function register() + { + return array(T_CLASS); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + + // Skip for-statements without body. + if (isset($token['scope_opener']) === false) { + return; + } + + // Fetch previous token. + $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); + + // Skip for non final class. + if ($prev === false || $tokens[$prev]['code'] !== T_FINAL) { + return; + } + + $next = ++$token['scope_opener']; + $end = --$token['scope_closer']; + + for (; $next <= $end; ++$next) { + if ($tokens[$next]['code'] === T_FINAL) { + $error = 'Unnecessary FINAL modifier in FINAL class'; + $phpcsFile->addWarning($error, $next); + } + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php new file mode 100644 index 0000000000..244aa09cef --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php @@ -0,0 +1,141 @@ + + * @author Manuel Pichler + * @copyright 2009 Nicolas Connault + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Checks the for unused function parameters. + * + * This sniff checks that all function parameters are used in the function body. + * One exception is made for empty function bodies or function bodies that only + * contain comments. This could be usefull for the classes that implement an + * interface that defines multiple methods but the implementation only needs some + * of them. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_CodeAnalysis_UnusedFunctionParameterSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_FUNCTION); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + + // Skip broken function declarations. + if (isset($token['scope_opener']) === false || isset($token['parenthesis_opener']) === false) { + return; + } + + $params = array(); + foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) { + $params[$param['name']] = $stackPtr; + } + + $next = ++$token['scope_opener']; + $end = --$token['scope_closer']; + + $emptyBody = true; + + for (; $next <= $end; ++$next) { + $token = $tokens[$next]; + $code = $token['code']; + + // Ingorable tokens. + if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === true) { + continue; + } else if ($code === T_THROW && $emptyBody === true) { + // Throw statement and an empty body indicate an interface method. + return; + } else if ($code === T_RETURN && $emptyBody === true) { + // Return statement and an empty body indicate an interface method. + $tmp = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), null, true); + if ($tmp === false) { + return; + } + + // There is a return. + if ($tokens[$tmp] === T_SEMICOLON) { + return; + } + + $tmp = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($tmp + 1), null, true); + + // There is a return . + if ($tmp !== false && $tokens[$tmp] === T_SEMICOLON) { + return; + } + }//end if + + $emptyBody = false; + + if ($code === T_VARIABLE && isset($params[$token['content']]) === true) { + unset($params[$token['content']]); + } else if ($code === T_DOUBLE_QUOTED_STRING) { + // Tokenize double quote string. + $strTokens = token_get_all(sprintf('', $token['content'])); + + foreach ($strTokens as $tok) { + if (is_array($tok) === false || $tok[0] !== T_VARIABLE ) { + continue; + } + + if (isset($params[$tok[1]]) === true) { + unset($params[$tok[1]]); + } + } + }//end if + }//end for + + if ($emptyBody === false && count($params) > 0) { + foreach ($params as $paramName => $position) { + $error = 'The method parameter '.$paramName.' is never used'; + $phpcsFile->addWarning($error, $position); + } + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php new file mode 100644 index 0000000000..9dd1a551f1 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php @@ -0,0 +1,182 @@ + + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Detects unnecessary final modifiers inside of final classes. + * + * This rule is based on the PMD rule catalog. The Unnecessary Final Modifier + * sniff detects the use of the final modifier inside of a final class which + * is unnecessary. + * + * + * final class Foo + * { + * public final function bar() + * { + * } + * } + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Manuel Pichler + * @copyright 2007-2008 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_CodeAnalysis_UselessOverridingMethodSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return array(integer) + */ + public function register() + { + return array(T_FUNCTION); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + + // Skip function without body. + if (isset($token['scope_opener']) === false) { + return; + } + + // Get function name. + $methodName = $phpcsFile->getDeclarationName($stackPtr); + + // Get all parameters from method signature. + $signature = array(); + foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) { + $signature[] = $param['name']; + } + + $next = ++$token['scope_opener']; + $end = --$token['scope_closer']; + + for (; $next <= $end; ++$next) { + $code = $tokens[$next]['code']; + + if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === true) { + continue; + } else if ($code === T_RETURN) { + continue; + } + + break; + } + + // Any token except 'parent' indicates correct code. + if ($tokens[$next]['code'] !== T_PARENT) { + return; + } + + // Find next non empty token index, should be double colon. + $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), null, true); + + // Skip for invalid code. + if ($next === false || $tokens[$next]['code'] !== T_DOUBLE_COLON) { + return; + } + + // Find next non empty token index, should be the function name. + $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), null, true); + + // Skip for invalid code or other method. + if ($next === false || $tokens[$next]['content'] !== $methodName) { + return; + } + + // Find next non empty token index, should be the open parenthesis. + $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), null, true); + + // Skip for invalid code. + if ($next === false || $tokens[$next]['code'] !== T_OPEN_PARENTHESIS) { + return; + } + + $validParameterTypes = array( + T_VARIABLE, + T_LNUMBER, + T_CONSTANT_ENCAPSED_STRING, + ); + + $parameters = array(''); + $parenthesisCount = 1; + $count = count($tokens); + for (++$next; $next < $count; ++$next) { + $code = $tokens[$next]['code']; + + if ($code === T_OPEN_PARENTHESIS) { + ++$parenthesisCount; + } else if ($code === T_CLOSE_PARENTHESIS) { + --$parenthesisCount; + } else if ($parenthesisCount === 1 && $code === T_COMMA) { + $parameters[] = ''; + } else if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === false) { + $parameters[(count($parameters) - 1)] .= $tokens[$next]['content']; + } + + if ($parenthesisCount === 0) { + break; + } + }//end for + + $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), null, true); + if ($next === false || $tokens[$next]['code'] !== T_SEMICOLON) { + return; + } + + // Check rest of the scope. + for (++$next; $next <= $end; ++$next) { + $code = $tokens[$next]['code']; + // Skip for any other content. + if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === false) { + return; + } + } + + $parameters = array_map('trim', $parameters); + $parameters = array_filter($parameters); + + if (count($parameters) === count($signature) && $parameters === $signature) { + $phpcsFile->addWarning('Useless method overriding detected', $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/ClassCommentSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/ClassCommentSniff.php new file mode 100644 index 0000000000..9ac8e1413e --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/ClassCommentSniff.php @@ -0,0 +1,224 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_ClassCommentParser', true) === false) { + $error = 'Class PHP_CodeSniffer_CommentParser_ClassCommentParser not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +if (class_exists('Moodle_Sniffs_Commenting_FileCommentSniff', true) === false) { + $error = 'Class Moodle_Sniffs_Commenting_FileCommentSniff not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * Parses and verifies the doc comments for classes. + * + * Verifies that : + *
    + *
  • A doc comment exists.
  • + *
  • There is a blank newline after the short description.
  • + *
  • There is a blank newline between the long and short description.
  • + *
  • There is a blank newline between the long description and tags.
  • + *
  • Check the order of the tags.
  • + *
  • Check the indentation of each tag.
  • + *
  • Check required and optional tags and the format of their content.
  • + *
+ * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Commenting_ClassCommentSniff extends Moodle_Sniffs_Commenting_FileCommentSniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_CLASS, + T_INTERFACE, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + // Modify array of required tags + $this->tags['package']['required'] = false; + $this->tags['copyright']['required'] = false; + $this->tags['author']['required'] = true; + + $this->currentFile = $phpcsFile; + + $tokens = $phpcsFile->getTokens(); + $type = strtolower($tokens[$stackPtr]['content']); + $find = array( + T_ABSTRACT, + T_WHITESPACE, + T_FINAL, + ); + + // Extract the class comment docblock. + $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); + + if ($commentEnd !== false && $tokens[$commentEnd]['code'] === T_COMMENT) { + $phpcsFile->addError("You must use \"/**\" style comments for a $type comment", $stackPtr); + return; + } else if ($commentEnd === false || $tokens[$commentEnd]['code'] !== T_DOC_COMMENT) { + $phpcsFile->addError("Missing $type doc comment", $stackPtr); + return; + } + + $commentStart = ($phpcsFile->findPrevious(T_DOC_COMMENT, ($commentEnd - 1), null, true) + 1); + $commentNext = $phpcsFile->findPrevious(T_WHITESPACE, ($commentEnd + 1), $stackPtr, false, $phpcsFile->eolChar); + + // Distinguish file and class comment. + $prevClassToken = $phpcsFile->findPrevious(T_CLASS, ($stackPtr - 1)); + if ($prevClassToken === false) { + // This is the first class token in this file, need extra checks. + $prevNonComment = $phpcsFile->findPrevious(T_DOC_COMMENT, ($commentStart - 1), null, true); + if ($prevNonComment !== false) { + $prevComment = $phpcsFile->findPrevious(T_DOC_COMMENT, ($prevNonComment - 1)); + if ($prevComment === false) { + // There is only 1 doc comment between open tag and class token. + $newlineToken = $phpcsFile->findNext(T_WHITESPACE, ($commentEnd + 1), $stackPtr, false, $phpcsFile->eolChar); + if ($newlineToken !== false) { + $newlineToken = $phpcsFile->findNext(T_WHITESPACE, ($newlineToken + 1), $stackPtr, false, $phpcsFile->eolChar); + if ($newlineToken !== false) { + // Blank line between the class and the doc block. + // The doc block is most likely a file comment. + $phpcsFile->addError("Missing $type doc comment", ($stackPtr + 1)); + return; + } + }//end if + }//end if + }//end if + }//end if + + $comment = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1)); + + // Parse the class comment.docblock. + try { + $this->commentParser = new PHP_CodeSniffer_CommentParser_ClassCommentParser($comment, $phpcsFile); + $this->commentParser->parse(); + } catch (PHP_CodeSniffer_CommentParser_ParserException $e) { + $line = ($e->getLineWithinComment() + $commentStart); + $phpcsFile->addError($e->getMessage(), $line); + return; + } + + $comment = $this->commentParser->getComment(); + if (is_null($comment) === true) { + $error = ucfirst($type).' doc comment is empty'; + $phpcsFile->addError($error, $commentStart); + return; + } + + // No extra newline before short description. + $short = $comment->getShortComment(); + $newlineCount = 0; + $newlineSpan = strspn($short, $phpcsFile->eolChar); + if ($short !== '' && $newlineSpan > 0) { + $line = ($newlineSpan > 1) ? 'newlines' : 'newline'; + $error = "Extra $line found before $type comment short description"; + $phpcsFile->addError($error, ($commentStart + 1)); + } + + $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1); + + // Exactly one blank line between short and long description. + $long = $comment->getLongComment(); + if (empty($long) === false) { + $between = $comment->getWhiteSpaceBetween(); + $newlineBetween = substr_count($between, $phpcsFile->eolChar); + if ($newlineBetween !== 2) { + $error = "There must be exactly one blank line between descriptions in $type comments"; + $phpcsFile->addError($error, ($commentStart + $newlineCount + 1)); + } + + $newlineCount += $newlineBetween; + } + + // Exactly one blank line before tags. + $tags = $this->commentParser->getTagOrders(); + if (count($tags) > 1) { + $newlineSpan = $comment->getNewlineAfter(); + if ($newlineSpan !== 2) { + $error = "There must be exactly one blank line before the tags in $type comments"; + if ($long !== '') { + $newlineCount += (substr_count($long, $phpcsFile->eolChar) - $newlineSpan + 1); + } + + $phpcsFile->addError($error, ($commentStart + $newlineCount)); + $short = rtrim($short, $phpcsFile->eolChar.' '); + } + } + + // Check each tag. + $this->processTags($commentStart, $commentEnd); + + }//end process() + + + /** + * Process the version tag. + * + * @param int $errorPos The line number where the error occurs. + * + * @return void + */ + protected function processVersion($errorPos) + { + $version = $this->commentParser->getVersion(); + if ($version !== null) { + $content = $version->getContent(); + $matches = array(); + if (empty($content) === true) { + $error = 'Content missing for @version tag in doc comment'; + $this->currentFile->addError($error, $errorPos); + } else if ((strstr($content, 'Release:') === false)) { + $error = "Invalid version \"$content\" in doc comment; consider \"Release: \" instead"; + $this->currentFile->addWarning($error, $errorPos); + } + } + + }//end processVersion() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/FileCommentSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/FileCommentSniff.php new file mode 100644 index 0000000000..caf481f81a --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/FileCommentSniff.php @@ -0,0 +1,716 @@ + + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_ClassCommentParser', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CommentParser_ClassCommentParser not found'); +} + +/** + * Parses and verifies the doc comments for files. + * + * Verifies that : + *
    + *
  • A doc comment exists.
  • + *
  • There is a blank newline after the short description.
  • + *
  • There is a blank newline between the long and short description.
  • + *
  • There is a blank newline between the long description and tags.
  • + *
  • A PHP version is specified.
  • + *
  • Check the order of the tags.
  • + *
  • Check the indentation of each tag.
  • + *
  • Check required and optional tags and the format of their content.
  • + *
+ * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +class Moodle_Sniffs_Commenting_FileCommentSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * The header comment parser for the current file. + * + * @var PHP_CodeSniffer_Comment_Parser_ClassCommentParser + */ + protected $commentParser = null; + + /** + * The current PHP_CodeSniffer_File object we are processing. + * + * @var PHP_CodeSniffer_File + */ + protected $currentFile = null; + + /** + * Tags in correct order and related info. + * + * @var array + */ + protected $tags = array( + 'category' => array( + 'required' => false, + 'allow_multiple' => false, + 'order_text' => 'precedes @package', + ), + 'package' => array( + 'required' => true, + 'allow_multiple' => false, + 'order_text' => 'follows @category', + ), + 'subpackage' => array( + 'required' => false, + 'allow_multiple' => false, + 'order_text' => 'follows @package', + ), + 'author' => array( + 'required' => false, + 'allow_multiple' => true, + 'order_text' => 'follows @subpackage (if used) or @package', + ), + 'copyright' => array( + 'required' => true, + 'allow_multiple' => true, + 'order_text' => 'follows @author', + ), + 'license' => array( + 'required' => true, + 'allow_multiple' => false, + 'order_text' => 'follows @copyright (if used) or @author', + ), + 'version' => array( + 'required' => false, + 'allow_multiple' => false, + 'order_text' => 'follows @licence', + ), + 'link' => array( + 'required' => false, + 'allow_multiple' => true, + 'order_text' => 'follows @version', + ), + 'see' => array( + 'required' => false, + 'allow_multiple' => true, + 'order_text' => 'follows @link', + ), + 'since' => array( + 'required' => false, + 'allow_multiple' => false, + 'order_text' => 'follows @see (if used) or @link', + ), + 'deprecated' => array( + 'required' => false, + 'allow_multiple' => false, + 'order_text' => 'follows @since (if used) or @see (if used) or @link', + ), + ); + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_OPEN_TAG); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $this->currentFile = $phpcsFile; + + // We are only interested if this is the first open tag. + if ($stackPtr !== 0) { + if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) { + return; + } + } + + $tokens = $phpcsFile->getTokens(); + + // Find the next non whitespace token. + $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + + // Look for $Id$ and boilerplate + if ($tokens[$commentStart]['code'] != T_COMMENT) { + $phpcsFile->addError('File must begin with License boilerplate', ($stackPtr + 1)); + return; + } else if (preg_match('|\$Id\$|i', $tokens[$commentStart]['content'])) { + $phpcsFile->addWarning('$Id$ tag is no longer required, please remove.', ($stackPtr + 1)); + return; + } + + // now look for boilerplate, must be immediately after the first line + $boilerplate = '// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see .'; + + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + $boilerplate_lines = preg_split('/[\n\r]+/', $boilerplate); + + if (rtrim($tokens[$nextToken]['content']) != $boilerplate_lines[0]) { + $phpcsFile->addError('You must include the Moodle boilerplate at the top of the file', ($nextToken)); + return; + } + + $boilerplate_index = 0; + + foreach ($boilerplate_lines as $line) { + $nextToken = $phpcsFile->findNext(T_COMMENT, ($nextToken)); + + if (rtrim($tokens[$nextToken]['content']) != $boilerplate_lines[$boilerplate_index]) { + $phpcsFile->addError('Badly formatted boilerplate. Please copy-paste exactly', ($nextToken)); + return; + } + $nextToken++; + $boilerplate_index++; + } + + $filedocToken = $phpcsFile->findNext(T_WHITESPACE, ($nextToken + 1), null, true); + + if ($tokens[$filedocToken]['code'] === T_CLOSE_TAG) { + // We are only interested if this is the first open tag. + return; + } else if ($tokens[$filedocToken]['code'] === T_COMMENT) { + $phpcsFile->addError('You must use "/**" style comments for a file comment', ($filedocToken + 1)); + return; + } else if ($filedocToken === false || $tokens[$filedocToken]['code'] !== T_DOC_COMMENT) { + $phpcsFile->addError('Missing file doc comment', ($filedocToken + 1)); + return; + } else { + + // Extract the header comment docblock. + $commentEnd = ($phpcsFile->findNext(T_DOC_COMMENT, ($filedocToken + 1), null, true) - 1); + + // Check if there is only 1 doc comment between the open tag and class token. + $nextToken = array( + T_ABSTRACT, + T_CLASS, + T_FUNCTION, + T_DOC_COMMENT, + ); + $commentNext = $phpcsFile->findNext($nextToken, ($commentEnd + 1)); + if ($commentNext !== false && $tokens[$commentNext]['code'] !== T_DOC_COMMENT) { + // Found a class token right after comment doc block. + $newlineToken = $phpcsFile->findNext(T_WHITESPACE, ($commentEnd + 1), $commentNext, false, $phpcsFile->eolChar); + if ($newlineToken !== false) { + $newlineToken = $phpcsFile->findNext(T_WHITESPACE, ($newlineToken + 1), $commentNext, false, $phpcsFile->eolChar); + if ($newlineToken === false) { + // No blank line between the class token and the doc block. + // The doc block is most likely a class comment. + $phpcsFile->addError('Missing file doc comment', ($stackPtr + 1)); + return; + } + } + } + + $comment = $phpcsFile->getTokensAsString($filedocToken, ($commentEnd - $filedocToken + 1)); + + // Parse the header comment docblock. + try { + $this->commentParser = new PHP_CodeSniffer_CommentParser_ClassCommentParser($comment, $phpcsFile); + $this->commentParser->parse(); + } catch (PHP_CodeSniffer_CommentParser_ParserException $e) { + $line = ($e->getLineWithinComment() + $filedocToken); + $phpcsFile->addError($e->getMessage(), $line); + return; + } + + $comment = $this->commentParser->getComment(); + if (is_null($comment) === true) { + $error = 'File doc comment is empty'; + $phpcsFile->addError($error, $filedocToken); + return; + } + + // No extra newline before short description. + $short = $comment->getShortComment(); + $newlineCount = 0; + $newlineSpan = strspn($short, $phpcsFile->eolChar); + if ($short !== '' && $newlineSpan > 0) { + $line = ($newlineSpan > 1) ? 'newlines' : 'newline'; + $error = "Extra $line found before file comment short description"; + $phpcsFile->addError($error, ($filedocToken + 1)); + } + + $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1); + + // Exactly one blank line between short and long description. + $long = $comment->getLongComment(); + if (empty($long) === false) { + $between = $comment->getWhiteSpaceBetween(); + $newlineBetween = substr_count($between, $phpcsFile->eolChar); + if ($newlineBetween !== 2) { + $error = 'There must be exactly one blank line between descriptions in file comment'; + $phpcsFile->addError($error, ($filedocToken + $newlineCount + 1)); + } + + $newlineCount += $newlineBetween; + } + + // Exactly one blank line before tags. + $tags = $this->commentParser->getTagOrders(); + if (count($tags) > 1) { + $newlineSpan = $comment->getNewlineAfter(); + if ($newlineSpan !== 2) { + $error = 'There must be exactly one blank line before the tags in file comment'; + if ($long !== '') { + $newlineCount += (substr_count($long, $phpcsFile->eolChar) - $newlineSpan + 1); + } + + $phpcsFile->addError($error, ($filedocToken + $newlineCount)); + $short = rtrim($short, $phpcsFile->eolChar.' '); + } + } + + // Check the PHP Version. + /* + if (strstr(strtolower($long), 'php version') === false) { + $error = 'PHP version not specified'; + $phpcsFile->addWarning($error, $commentEnd); + } + */ + + // Check each tag. + $this->processTags($filedocToken, $commentEnd); + }//end if + + }//end process() + + + /** + * Processes each required or optional tag. + * + * @param int $commentStart The position in the stack where the comment started. + * @param int $commentEnd The position in the stack where the comment ended. + * + * @return void + */ + protected function processTags($commentStart, $commentEnd) + { + $docBlock = (get_class($this) === 'Moodle_Sniffs_Commenting_FileCommentSniff') ? 'file' : 'class'; + $foundTags = $this->commentParser->getTagOrders(); + $orderIndex = 0; + $indentation = array(); + $longestTag = 0; + $errorPos = 0; + + foreach ($this->tags as $tag => $info) { + + // Required tag missing. + if ($info['required'] === true && in_array($tag, $foundTags) === false) { + $error = "Missing @$tag tag in $docBlock comment"; + $this->currentFile->addError($error, $commentEnd); + continue; + } + + // Get the line number for current tag. + $tagName = ucfirst($tag); + if ($info['allow_multiple'] === true) { + $tagName .= 's'; + } + + $getMethod = 'get'.$tagName; + $tagElement = $this->commentParser->$getMethod(); + if (is_null($tagElement) === true || empty($tagElement) === true) { + continue; + } + + $errorPos = $commentStart; + if (is_array($tagElement) === false) { + $errorPos = ($commentStart + $tagElement->getLine()); + } + + // Get the tag order. + $foundIndexes = array_keys($foundTags, $tag); + + if (count($foundIndexes) > 1) { + // Multiple occurance not allowed. + if ($info['allow_multiple'] === false) { + $error = "Only 1 @$tag tag is allowed in a $docBlock comment"; + $this->currentFile->addError($error, $errorPos); + } else { + // Make sure same tags are grouped together. + $i = 0; + $count = $foundIndexes[0]; + foreach ($foundIndexes as $index) { + if ($index !== $count) { + $errorPosIndex = ($errorPos + $tagElement[$i]->getLine()); + $error = "@$tag tags must be grouped together"; + $this->currentFile->addError($error, $errorPosIndex); + } + + $i++; + $count++; + } + } + }//end if + + // Check tag order. + if ($foundIndexes[0] > $orderIndex) { + $orderIndex = $foundIndexes[0]; + } else { + if (is_array($tagElement) === true && empty($tagElement) === false) { + $errorPos += $tagElement[0]->getLine(); + } + + $orderText = $info['order_text']; + $error = "The @$tag tag is in the wrong order; the tag $orderText"; + $this->currentFile->addError($error, $errorPos); + } + + // Store the indentation for checking. + $len = strlen($tag); + if ($len > $longestTag) { + $longestTag = $len; + } + + if (is_array($tagElement) === true) { + foreach ($tagElement as $key => $element) { + $indentation[] = array( + 'tag' => $tag, + 'space' => $this->getIndentation($tag, $element), + 'line' => $element->getLine(), + ); + } + } else { + $indentation[] = array( + 'tag' => $tag, + 'space' => $this->getIndentation($tag, $tagElement), + ); + } + + $method = 'process'.$tagName; + if (method_exists($this, $method) === true) { + // Process each tag if a method is defined. + call_user_func(array($this, $method), $errorPos); + } else { + if (is_array($tagElement) === true) { + foreach ($tagElement as $key => $element) { + $element->process($this->currentFile, $commentStart, $docBlock); + } + } else { + $tagElement->process($this->currentFile, $commentStart, $docBlock); + } + } + }//end foreach + + foreach ($indentation as $indentInfo) { + if ($indentInfo['space'] !== 0 && $indentInfo['space'] !== ($longestTag + 1)) { + $expected = (($longestTag - strlen($indentInfo['tag'])) + 1); + $space = ($indentInfo['space'] - strlen($indentInfo['tag'])); + $error = "@$indentInfo[tag] tag comment indented incorrectly. "; + $error .= "Expected $expected spaces but found $space."; + $getTagMethod = 'get'.ucfirst($indentInfo['tag']); + if ($this->tags[$indentInfo['tag']]['allow_multiple'] === true) { + $line = $indentInfo['line']; + } else { + $tagElem = $this->commentParser->$getTagMethod(); + $line = $tagElem->getLine(); + } + + $this->currentFile->addError($error, ($commentStart + $line)); + } + } + + }//end processTags() + + + /** + * Get the indentation information of each tag. + * + * @param string $tagName The name of the doc comment element. + * @param PHP_CodeSniffer_CommentParser_DocElement $tagElement The doc comment element. + * + * @return void + */ + protected function getIndentation($tagName, $tagElement) + { + if ($tagElement instanceof PHP_CodeSniffer_CommentParser_SingleElement) { + if ($tagElement->getContent() !== '') { + return (strlen($tagName) + substr_count($tagElement->getWhitespaceBeforeContent(), ' ')); + } + } else if ($tagElement instanceof PHP_CodeSniffer_CommentParser_PairElement) { + if ($tagElement->getValue() !== '') { + return (strlen($tagName) + substr_count($tagElement->getWhitespaceBeforeValue(), ' ')); + } + } + + return 0; + + }//end getIndentation() + + + /** + * Process the category tag. + * + * @param int $errorPos The line number where the error occurs. + * + * @return void + */ + protected function processCategory($errorPos) + { + $category = $this->commentParser->getCategory(); + if ($category !== null) { + $content = $category->getContent(); + if ($content !== '') { + if (PHP_CodeSniffer::isUnderscoreName($content) !== true) { + $newContent = str_replace(' ', '_', $content); + $nameBits = explode('_', $newContent); + $firstBit = array_shift($nameBits); + $newName = ucfirst($firstBit).'_'; + foreach ($nameBits as $bit) { + $newName .= ucfirst($bit).'_'; + } + + $validName = trim($newName, '_'); + $error = "Category name \"$content\" is not valid; consider \"$validName\" instead"; + $this->currentFile->addError($error, $errorPos); + } + } else { + $error = '@category tag must contain a name'; + $this->currentFile->addError($error, $errorPos); + } + } + + }//end processCategory() + + + /** + * Process the package tag. + * + * @param int $errorPos The line number where the error occurs. + * + * @return void + */ + protected function processPackage($errorPos) + { + $package = $this->commentParser->getPackage(); + if ($package !== null) { + $content = $package->getContent(); + + if ($content !== '') { + if (!preg_match('/^[a-z\-]*$/', $content)) { + $error = "Package name \"$content\" is not valid; must be lower-case with optional hyphens."; + $this->currentFile->addError($error, $errorPos); + } + } else { + $error = '@package tag must contain a name'; + $this->currentFile->addError($error, $errorPos); + } + } + + }//end processPackage() + + + /** + * Process the subpackage tag. + * + * @param int $errorPos The line number where the error occurs. + * + * @return void + */ + protected function processSubpackage($errorPos) + { + $package = $this->commentParser->getSubpackage(); + if ($package !== null) { + $content = $package->getContent(); + if ($content !== '') { + if (PHP_CodeSniffer::isUnderscoreName($content) !== true) { + $newContent = str_replace(' ', '_', $content); + $nameBits = explode('_', $newContent); + $firstBit = array_shift($nameBits); + $newName = strtoupper($firstBit{0}).substr($firstBit, 1).'_'; + foreach ($nameBits as $bit) { + $newName .= strtoupper($bit{0}).substr($bit, 1).'_'; + } + + $validName = trim($newName, '_'); + $error = "Subpackage name \"$content\" is not valid; consider \"$validName\" instead"; + $this->currentFile->addError($error, $errorPos); + } + } else { + $error = '@subpackage tag must contain a name'; + $this->currentFile->addError($error, $errorPos); + } + } + + }//end processSubpackage() + + + /** + * Process the author tag(s) that this header comment has. + * + * This function is different from other _process functions + * as $authors is an array of SingleElements, so we work out + * the errorPos for each element separately + * + * @param int $commentStart The position in the stack where + * the comment started. + * + * @return void + */ + protected function processAuthors($commentStart) + { + $authors = $this->commentParser->getAuthors(); + // Report missing return. + if (empty($authors) === false) { + foreach ($authors as $author) { + $errorPos = ($commentStart + $author->getLine()); + $content = $author->getContent(); + if ($content !== '') { + $local = '\da-zA-Z-_+'; + // Dot character cannot be the first or last character in the local-part. + $localMiddle = $local.'.\w'; + if (preg_match('/^([^<]*)\s+<(['.$local.']['.$localMiddle.']*['.$local.']@[\da-zA-Z][-.\w]*[\da-zA-Z]\.[a-zA-Z]{2,7})>$/', $content) === 0) { + $error = 'Content of the @author tag must be in the form "Display Name "'; + $this->currentFile->addError($error, $errorPos); + } + } else { + $docBlock = (get_class($this) === 'Moodle_Sniffs_Commenting_FileCommentSniff') ? 'file' : 'class'; + $error = "Content missing for @author tag in $docBlock comment"; + $this->currentFile->addError($error, $errorPos); + } + } + } + + }//end processAuthors() + + + /** + * Process the copyright tags. + * + * @param int $commentStart The position in the stack where + * the comment started. + * + * @return void + */ + protected function processCopyrights($commentStart) + { + $copyrights = $this->commentParser->getCopyrights(); + foreach ($copyrights as $copyright) { + $errorPos = ($commentStart + $copyright->getLine()); + $content = $copyright->getContent(); + if ($content !== '') { + $matches = array(); + if (preg_match('/^([0-9]{4})((.{1})([0-9]{4}))? (.+)$/', $content, $matches) !== 0) { + // Check earliest-latest year order. + if ($matches[3] !== '') { + if ($matches[3] !== '-') { + $error = 'A hyphen must be used between the earliest and latest year'; + $this->currentFile->addError($error, $errorPos); + } + + if ($matches[4] !== '' && $matches[4] < $matches[1]) { + $error = "Invalid year span \"$matches[1]$matches[3]$matches[4]\" found; consider \"$matches[4]-$matches[1]\" instead"; + $this->currentFile->addWarning($error, $errorPos); + } + } + } else { + $error = '@copyright tag must contain a year and the name of the copyright holder'; + $this->currentFile->addError($error, $errorPos); + } + } else { + $error = '@copyright tag must contain a year and the name of the copyright holder'; + $this->currentFile->addError($error, $errorPos); + }//end if + }//end if + + }//end processCopyrights() + + + /** + * Process the license tag. + * + * @param int $errorPos The line number where the error occurs. + * + * @return void + */ + protected function processLicense($errorPos) + { + $license = $this->commentParser->getLicense(); + if ($license !== null) { + $value = $license->getValue(); + $comment = $license->getComment(); + if ($value === '' || $comment === '') { + $error = '@license tag must contain a URL and a license name'; + $this->currentFile->addError($error, $errorPos); + } + if ($comment != 'GNU GPL v3 or later') { + $this->currentFile->addError('License must be "GNU GPL v3 or later", found "'.$comment.'"', $errorPos); + } + if ($value != 'http://www.gnu.org/copyleft/gpl.html') { + $this->currentFile->addError('License must be "GNU GPL v3 or later"', $errorPos); + } + } + + }//end processLicense() + + + /** + * Process the version tag. + * + * @param int $errorPos The line number where the error occurs. + * + * @return void + */ + protected function processVersion($errorPos) + { + $version = $this->commentParser->getVersion(); + if ($version !== null) { + $content = $version->getContent(); + $matches = array(); + if (empty($content) === true) { + $error = 'Content missing for @version tag in file comment'; + $this->currentFile->addError($error, $errorPos); + } else if (strstr($content, 'CVS:') === false && strstr($content, 'SVN:') === false) { + $error = "Invalid version \"$content\" in file comment; consider \"CVS: \" or \"SVN: \" instead"; + $this->currentFile->addWarning($error, $errorPos); + } + } + + }//end processVersion() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/FunctionCommentSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/FunctionCommentSniff.php new file mode 100644 index 0000000000..e4daed6ca2 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/FunctionCommentSniff.php @@ -0,0 +1,462 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_CommentParser_FunctionCommentParser', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CommentParser_FunctionCommentParser not found'); +} + +/** + * Parses and verifies the doc comments for functions. + * + * Verifies that : + *
    + *
  • A comment exists
  • + *
  • There is a blank newline after the short description.
  • + *
  • There is a blank newline between the long and short description.
  • + *
  • There is a blank newline between the long description and tags.
  • + *
  • Parameter names represent those in the method.
  • + *
  • Parameter comments are in the correct order
  • + *
  • Parameter comments are complete
  • + *
  • A space is present before the first and after the last parameter
  • + *
  • A return type exists
  • + *
  • There must be one blank line between body and headline comments.
  • + *
  • Any throw tag must have an exception class.
  • + *
+ * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Commenting_FunctionCommentSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * The name of the method that we are currently processing. + * + * @var string + */ + private $_methodName = ''; + + /** + * The position in the stack where the fucntion token was found. + * + * @var int + */ + private $_functionToken = null; + + /** + * The position in the stack where the class token was found. + * + * @var int + */ + private $_classToken = null; + + /** + * The function comment parser for the current method. + * + * @var PHP_CodeSniffer_Comment_Parser_FunctionCommentParser + */ + protected $commentParser = null; + + /** + * The current PHP_CodeSniffer_File object we are processing. + * + * @var PHP_CodeSniffer_File + */ + protected $currentFile = null; + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_FUNCTION); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $find = array( + T_COMMENT, + T_DOC_COMMENT, + T_CLASS, + T_FUNCTION, + T_OPEN_TAG, + ); + + $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1)); + + if ($commentEnd === false) { + return; + } + + $this->currentFile = $phpcsFile; + $tokens = $phpcsFile->getTokens(); + + // If the token that we found was a class or a function, then this + // function has no doc comment. + $code = $tokens[$commentEnd]['code']; + + if ($code === T_COMMENT) { + $error = 'You must use "/**" style comments for a function comment'; + $phpcsFile->addError($error, $stackPtr); + return; + } else if ($code !== T_DOC_COMMENT) { + $phpcsFile->addError('Missing function doc comment', $stackPtr); + return; + } + + // If there is any code between the function keyword and the doc block + // then the doc block is not for us. + $ignore = PHP_CodeSniffer_Tokens::$scopeModifiers; + $ignore[] = T_STATIC; + $ignore[] = T_WHITESPACE; + $ignore[] = T_ABSTRACT; + $ignore[] = T_FINAL; + $prevToken = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); + if ($prevToken !== $commentEnd) { + $phpcsFile->addError('Missing function doc comment', $stackPtr); + return; + } + + $this->_functionToken = $stackPtr; + + foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condition) { + if ($condition === T_CLASS || $condition === T_INTERFACE) { + $this->_classToken = $condPtr; + break; + } + } + + // If the first T_OPEN_TAG is right before the comment, it is probably + // a file comment. + $commentStart = ($phpcsFile->findPrevious(T_DOC_COMMENT, ($commentEnd - 1), null, true) + 1); + $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($commentStart - 1), null, true); + if ($tokens[$prevToken]['code'] === T_OPEN_TAG) { + // Is this the first open tag? + if ($stackPtr === 0 || $phpcsFile->findPrevious(T_OPEN_TAG, ($prevToken - 1)) === false) { + $phpcsFile->addError('Missing function doc comment', $stackPtr); + return; + } + } + + $comment = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1)); + $this->_methodName = $phpcsFile->getDeclarationName($stackPtr); + + try { + $this->commentParser = new PHP_CodeSniffer_CommentParser_FunctionCommentParser($comment, $phpcsFile); + $this->commentParser->parse(); + } catch (PHP_CodeSniffer_CommentParser_ParserException $e) { + $line = ($e->getLineWithinComment() + $commentStart); + $phpcsFile->addError($e->getMessage(), $line); + return; + } + + $comment = $this->commentParser->getComment(); + if (is_null($comment) === true) { + $error = 'Function doc comment is empty'; + $phpcsFile->addError($error, $commentStart); + return; + } + + $this->processParams($commentStart); + $this->processReturn($commentStart, $commentEnd); + $this->processThrows($commentStart); + + // No extra newline before short description. + $short = $comment->getShortComment(); + $newlineCount = 0; + $newlineSpan = strspn($short, $phpcsFile->eolChar); + if ($short !== '' && $newlineSpan > 0) { + $line = ($newlineSpan > 1) ? 'newlines' : 'newline'; + $error = "Extra $line found before function comment short description"; + $phpcsFile->addError($error, ($commentStart + 1)); + } + + $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1); + + // Exactly one blank line between short and long description. + $long = $comment->getLongComment(); + if (empty($long) === false) { + $between = $comment->getWhiteSpaceBetween(); + $newlineBetween = substr_count($between, $phpcsFile->eolChar); + if ($newlineBetween !== 2) { + $error = 'There must be exactly one blank line between descriptions in function comment'; + $phpcsFile->addError($error, ($commentStart + $newlineCount + 1)); + } + + $newlineCount += $newlineBetween; + } + + // Exactly one blank line before tags. + $params = $this->commentParser->getTagOrders(); + if (count($params) > 1) { + $newlineSpan = $comment->getNewlineAfter(); + if ($newlineSpan !== 2) { + $error = 'There must be exactly one blank line before the tags in function comment'; + if ($long !== '') { + $newlineCount += (substr_count($long, $phpcsFile->eolChar) - $newlineSpan + 1); + } + + $phpcsFile->addError($error, ($commentStart + $newlineCount)); + $short = rtrim($short, $phpcsFile->eolChar.' '); + } + } + + }//end process() + + + /** + * Process any throw tags that this function comment has. + * + * @param int $commentStart The position in the stack where the + * comment started. + * + * @return void + */ + protected function processThrows($commentStart) + { + if (count($this->commentParser->getThrows()) === 0) { + return; + } + + foreach ($this->commentParser->getThrows() as $throw) { + + $exception = $throw->getValue(); + $errorPos = ($commentStart + $throw->getLine()); + + if ($exception === '') { + $error = '@throws tag must contain the exception class name'; + $this->currentFile->addError($error, $errorPos); + } + } + + }//end processThrows() + + + /** + * Process the return comment of this function comment. + * + * @param int $commentStart The position in the stack where the comment started. + * @param int $commentEnd The position in the stack where the comment ended. + * + * @return void + */ + protected function processReturn($commentStart, $commentEnd) + { + // Skip constructor and destructor. + $className = ''; + if ($this->_classToken !== null) { + $className = $this->currentFile->getDeclarationName($this->_classToken); + $className = strtolower(ltrim($className, '_')); + } + + $methodName = strtolower(ltrim($this->_methodName, '_')); + $isSpecialMethod = ($this->_methodName === '__construct' || $this->_methodName === '__destruct'); + + if ($isSpecialMethod === false && $methodName !== $className) { + // Report missing return tag. + if ($this->commentParser->getReturn() === null) { + $error = 'Missing @return tag in function comment'; + $this->currentFile->addError($error, $commentEnd); + } else if (trim($this->commentParser->getReturn()->getRawContent()) === '') { + $error = '@return tag is empty in function comment'; + $errorPos = ($commentStart + $this->commentParser->getReturn()->getLine()); + $this->currentFile->addError($error, $errorPos); + } + } + + }//end processReturn() + + + /** + * Process the function parameter comments. + * + * @param int $commentStart The position in the stack where + * the comment started. + * + * @return void + */ + protected function processParams($commentStart) + { + $realParams = $this->currentFile->getMethodParameters($this->_functionToken); + + $params = $this->commentParser->getParams(); + $foundParams = array(); + + if (empty($params) === false) { + + $lastParm = (count($params) - 1); + if (substr_count($params[$lastParm]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) { + $error = 'Last parameter comment requires a blank newline after it'; + $errorPos = ($params[$lastParm]->getLine() + $commentStart); + $this->currentFile->addError($error, $errorPos); + } + + // Parameters must appear immediately after the comment. + if ($params[0]->getOrder() !== 2) { + $error = 'Parameters must appear immediately after the comment'; + $errorPos = ($params[0]->getLine() + $commentStart); + $this->currentFile->addError($error, $errorPos); + } + + $previousParam = null; + $spaceBeforeVar = 10000; + $spaceBeforeComment = 10000; + $longestType = 0; + $longestVar = 0; + + foreach ($params as $param) { + + $paramComment = trim($param->getComment()); + $errorPos = ($param->getLine() + $commentStart); + + // Make sure that there is only one space before the var type. + if ($param->getWhitespaceBeforeType() !== ' ') { + $error = 'Expected 1 space before variable type'; + $this->currentFile->addError($error, $errorPos); + } + + $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); + if ($spaceCount < $spaceBeforeVar) { + $spaceBeforeVar = $spaceCount; + $longestType = $errorPos; + } + + $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); + + if ($spaceCount < $spaceBeforeComment && $paramComment !== '') { + $spaceBeforeComment = $spaceCount; + $longestVar = $errorPos; + } + + // Make sure they are in the correct order, + // and have the correct name. + $pos = $param->getPosition(); + + $paramName = ($param->getVarName() !== '') ? $param->getVarName() : '[ UNKNOWN ]'; + + if ($previousParam !== null) { + $previousName = ($previousParam->getVarName() !== '') ? $previousParam->getVarName() : 'UNKNOWN'; + + // Check to see if the parameters align properly. + if ($param->alignsVariableWith($previousParam) === false) { + $error = 'The variable names for parameters '.$previousName.' ('.($pos - 1).') and '.$paramName.' ('.$pos.') do not align'; + $this->currentFile->addError($error, $errorPos); + } + + if ($param->alignsCommentWith($previousParam) === false) { + $error = 'The comments for parameters '.$previousName.' ('.($pos - 1).') and '.$paramName.' ('.$pos.') do not align'; + $this->currentFile->addError($error, $errorPos); + } + }//end if + + // Make sure the names of the parameter comment matches the + // actual parameter. + if (isset($realParams[($pos - 1)]) === true) { + $realName = $realParams[($pos - 1)]['name']; + $foundParams[] = $realName; + // Append ampersand to name if passing by reference. + if ($realParams[($pos - 1)]['pass_by_reference'] === true) { + $realName = '&'.$realName; + } + + if ($realName !== $param->getVarName()) { + $error = 'Doc comment var "'.$paramName; + $error .= '" does not match actual variable name "'.$realName; + $error .= '" at position '.$pos; + + $this->currentFile->addError($error, $errorPos); + } + } else { + // We must have an extra parameter comment. + $error = 'Superfluous doc comment at position '.$pos; + $this->currentFile->addError($error, $errorPos); + } + + if ($param->getVarName() === '') { + $error = 'Missing parameter name at position '.$pos; + $this->currentFile->addError($error, $errorPos); + } + + if ($param->getType() === '') { + $error = 'Missing type at position '.$pos; + $this->currentFile->addError($error, $errorPos); + } + + if ($paramComment === '') { + $error = 'Missing comment for param "'.$paramName.'" at position '.$pos; + $this->currentFile->addError($error, $errorPos); + } + + $previousParam = $param; + + }//end foreach + + if ($spaceBeforeVar !== 1 && $spaceBeforeVar !== 10000 && $spaceBeforeComment !== 10000) { + $error = 'Expected 1 space after the longest type'; + $this->currentFile->addError($error, $longestType); + } + + if ($spaceBeforeComment !== 1 && $spaceBeforeComment !== 10000) { + $error = 'Expected 1 space after the longest variable name'; + $this->currentFile->addError($error, $longestVar); + } + + }//end if + + $realNames = array(); + foreach ($realParams as $realParam) { + $realNames[] = $realParam['name']; + } + + // Report and missing comments. + $diff = array_diff($realNames, $foundParams); + foreach ($diff as $neededParam) { + if (count($params) !== 0) { + $errorPos = ($params[(count($params) - 1)]->getLine() + $commentStart); + } else { + $errorPos = $commentStart; + } + + $error = 'Doc comment for "'.$neededParam.'" missing'; + $this->currentFile->addError($error, $errorPos); + } + + }//end processParams() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/InlineCommentSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/InlineCommentSniff.php new file mode 100644 index 0000000000..695a2b05e8 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Commenting/InlineCommentSniff.php @@ -0,0 +1,71 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * PHP_CodeSniffer_Sniffs_Moodle_Commenting_InlineCommentSniff. + * + * Checks that no perl-style comments are used. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Commenting_InlineCommentSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_COMMENT); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['content']{0} === '#') { + $error = 'Perl-style comments are not allowed. Use "// Comment."'; + $error .= ' or "/* comment */" instead.'; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/ControlStructures/ControlSignatureSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/ControlStructures/ControlSignatureSniff.php new file mode 100644 index 0000000000..ae5caf4e40 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/ControlStructures/ControlSignatureSniff.php @@ -0,0 +1,71 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_Standards_AbstractPatternSniff', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractPatternSniff not found'); +} + +/** + * Verifies that control statements conform to their coding standards. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_ControlStructures_ControlSignatureSniff extends PHP_CodeSniffer_Standards_AbstractPatternSniff +{ + + + /** + * Constructs a Moodle_Sniffs_ControlStructures_ControlSignatureSniff. + */ + public function __construct() + { + parent::__construct(true); + + }//end __construct() + + + /** + * Returns the patterns that this test wishes to verify. + * + * @return array(string) + */ + protected function getPatterns() + { + return array( + 'do {EOL...} while (...);EOL', + 'while (...) {EOL', + 'for (...) {EOL', + 'if (...) {EOL', + 'foreach (...) {EOL', + '} else if (...) {EOL', + '} elseif (...) {EOL', + '} else {EOL', + 'do {EOL', + ); + + }//end getPatterns() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/ControlStructures/InlineControlStructureSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/ControlStructures/InlineControlStructureSniff.php new file mode 100644 index 0000000000..63803d9f01 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/ControlStructures/InlineControlStructureSniff.php @@ -0,0 +1,120 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_ControlStructures_InlineControlStructureSniff. + * + * Verifies that inline control statements are not present. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_ControlStructures_InlineControlStructureSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * A list of tokenizers this sniff supports. + * + * @var array + */ + public $supportedTokenizers = array( + 'PHP', + 'JS', + ); + + /** + * If true, an error will be thrown; otherwise a warning. + * + * @var bool + */ + protected $error = true; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_IF, + T_ELSE, + T_FOREACH, + T_WHILE, + T_DO, + T_SWITCH, + T_FOR, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + // Ignore the ELSE in ELSE IF. We'll process the IF part later. + if (($tokens[$stackPtr]['code'] === T_ELSE) && ($tokens[($stackPtr + 2)]['code'] === T_IF)) { + return; + } + + if ($tokens[$stackPtr]['code'] === T_WHILE) { + // This could be from a DO WHILE, which doesn't have an opening brace. + $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$lastContent]['code'] === T_CLOSE_CURLY_BRACKET) { + $brace = $tokens[$lastContent]; + if (isset($brace['scope_condition']) === true) { + $condition = $tokens[$brace['scope_condition']]; + if ($condition['code'] === T_DO) { + return; + } + } + } + } + + // This is a control structure without an opening brace, + // so it is an inline statement. + if ($this->error === true) { + $phpcsFile->addError('Inline control structures are not allowed', $stackPtr); + } else { + $phpcsFile->addWarning('Inline control structures are discouraged', $stackPtr); + } + + return; + }//end if + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/IncludingFileSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/IncludingFileSniff.php new file mode 100644 index 0000000000..35be2b140e --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/IncludingFileSniff.php @@ -0,0 +1,136 @@ + + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Files_IncludingFileSniff. + * + * Checks that the include_once is used in conditional situations, and + * require_once is used elsewhere. Also checks that brackets do not surround + * the file being included. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Files_IncludingFileSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * Conditions that should use include_once + * + * @var array(int) + */ + private static $_conditions = array( + T_IF, + T_ELSE, + T_ELSEIF, + T_SWITCH, + ); + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_INCLUDE_ONCE, + T_REQUIRE_ONCE, + T_REQUIRE, + T_INCLUDE, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS) { + $error = '"'.$tokens[$stackPtr]['content'].'"'; + $error .= ' is a statement, not a function; '; + $error .= 'no parentheses are required'; + $phpcsFile->addError($error, $stackPtr); + } + + $inCondition = (count($tokens[$stackPtr]['conditions']) !== 0) ? true : false; + + // Check to see if this including statement is within the parenthesis of a condition. + // If that's the case then we need to process it as being within a condition, as they + // are checking the return value. + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { + foreach ($tokens[$stackPtr]['nested_parenthesis'] as $left => $right) { + if (isset($tokens[$left]['parenthesis_owner']) === true) { + $inCondition = true; + } + } + } + + // Check to see if they are assigning the return value of this including call. + // If they are then they are probably checking it, so its conditional. + $previous = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if (in_array($tokens[$previous]['code'], PHP_CodeSniffer_Tokens::$assignmentTokens) === true) { + // The have assigned the return value to it, so its conditional. + $inCondition = true; + } + + $tokenCode = $tokens[$stackPtr]['code']; + if ($inCondition === true) { + // We are inside a conditional statement. We need an include_once. + if ($tokenCode === T_REQUIRE_ONCE) { + $error = 'File is being conditionally included; '; + $error .= 'use "include_once" instead'; + $phpcsFile->addError($error, $stackPtr); + } else if ($tokenCode === T_REQUIRE) { + $error = 'File is being conditionally included; '; + $error .= 'use "include" instead'; + $phpcsFile->addError($error, $stackPtr); + } + } else { + // We are unconditionally including, we need a require_once. + if ($tokenCode === T_INCLUDE_ONCE) { + $error = 'File is being unconditionally included; '; + $error .= 'use "require_once" instead'; + $phpcsFile->addError($error, $stackPtr); + } else if ($tokenCode === T_INCLUDE) { + $error = 'File is being unconditionally included; '; + $error .= 'use "require" instead'; + $phpcsFile->addError($error, $stackPtr); + } + }//end if + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/LineEndingsSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/LineEndingsSniff.php new file mode 100644 index 0000000000..8ab212f69d --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/LineEndingsSniff.php @@ -0,0 +1,88 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Files_LineEndingsSniff. + * + * Checks that end of line characters are correct. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Files_LineEndingsSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * The valid EOL character. + * + * @var string + */ + protected $eolChar = "\n"; + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_OPEN_TAG); + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + // We are only interested if this is the first open tag. + if ($stackPtr !== 0) { + if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) { + return; + } + } + + if ($phpcsFile->eolChar !== $this->eolChar) { + $expected = $this->eolChar; + $expected = str_replace("\n", '\n', $expected); + $expected = str_replace("\r", '\r', $expected); + $found = $phpcsFile->eolChar; + $found = str_replace("\n", '\n', $found); + $found = str_replace("\r", '\r', $found); + $error = "End of line character is invalid; expected \"$expected\" but found \"$found\""; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/LineLengthSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/LineLengthSniff.php new file mode 100644 index 0000000000..0e62631b9a --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Files/LineLengthSniff.php @@ -0,0 +1,134 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Files_LineLengthSniff. + * + * Checks all lines in the file, and throws warnings if they are over 80 + * characters in length and errors if they are over 100. Both these + * figures can be changed by extending this sniff in your own standard. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Files_LineLengthSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * The limit that the length of a line should not exceed. + * + * @var int + */ + protected $lineLimit = 80; + + /** + * The limit that the length of a line must not exceed. + * + * Set to zero (0) to disable. + * + * @var int + */ + protected $absoluteLineLimit = 120; + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_OPEN_TAG); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Make sure this is the first open tag. + $previousOpenTag = $phpcsFile->findPrevious(array(T_OPEN_TAG), ($stackPtr - 1)); + if ($previousOpenTag !== false) { + return; + } + + $tokenCount = 0; + $currentLineContent = ''; + $currentLine = 1; + + for (; $tokenCount < $phpcsFile->numTokens; $tokenCount++) { + if ($tokens[$tokenCount]['line'] === $currentLine) { + $currentLineContent .= $tokens[$tokenCount]['content']; + } else { + $currentLineContent = trim($currentLineContent, $phpcsFile->eolChar); + $this->checkLineLength($phpcsFile, ($tokenCount - 1), $currentLineContent); + $currentLineContent = $tokens[$tokenCount]['content']; + $currentLine++; + } + } + + $this->checkLineLength($phpcsFile, ($tokenCount - 1), $currentLineContent); + + }//end process() + + + /** + * Checks if a line is too long. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The token at the end of the line. + * @param string $lineContent The content of the line. + * + * @return void + */ + protected function checkLineLength(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $lineContent) + { + // If the content is a CVS or SVN id in a version tag, or it is + // a license tag with a name and URL, there is nothing the + // developer can do to shorten the line, so don't throw errors. + if (preg_match('|@version[^\$]+\$Id|', $lineContent) === 0 && preg_match('|@license|', $lineContent) === 0) { + $lineLength = strlen($lineContent); + if ($this->absoluteLineLimit > 0 && $lineLength > $this->absoluteLineLimit) { + $error = 'Line exceeds maximum limit of '.$this->absoluteLineLimit." characters; contains $lineLength characters"; + $phpcsFile->addError($error, $stackPtr); + } else if ($lineLength > $this->lineLimit) { + $warning = 'Line exceeds '.$this->lineLimit." characters; contains $lineLength characters"; + $phpcsFile->addWarning($warning, $stackPtr); + } + } + + }//end checkLineLength() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Formatting/SpaceAfterCastSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Formatting/SpaceAfterCastSniff.php new file mode 100644 index 0000000000..4dee2b7b95 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Formatting/SpaceAfterCastSniff.php @@ -0,0 +1,76 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Formatting_SpaceAfterCastSniff. + * + * Ensures there is a single space after cast tokens. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Formatting_SpaceAfterCastSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return PHP_CodeSniffer_Tokens::$castTokens; + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in + * the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + $error = 'A cast statement must be followed by a single space'; + $phpcsFile->addError($error, $stackPtr); + return; + } + + if ($tokens[($stackPtr + 1)]['content'] !== ' ') { + $error = 'A cast statement must be followed by a single space'; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php new file mode 100644 index 0000000000..d559a7a778 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php @@ -0,0 +1,135 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Functions_FunctionCallArgumentSpacingSniff. + * + * Checks that calls to methods and functions are spaced correctly. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Functions_FunctionCallArgumentSpacingSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_STRING); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Skip tokens that are the names of functions or classes + // within their definitions. For example: + // function myFunction... + // "myFunction" is T_STRING but we should skip because it is not a + // function or method *call*. + $functionName = $stackPtr; + $functionKeyword = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($tokens[$functionKeyword]['code'] === T_FUNCTION || $tokens[$functionKeyword]['code'] === T_CLASS) { + return; + } + + // If the next non-whitespace token after the function or method call + // is not an opening parenthesis then it cant really be a *call*. + $openBracket = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($functionName + 1), null, true); + if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) { + return; + } + + $closeBracket = $tokens[$openBracket]['parenthesis_closer']; + + $nextSeperator = $openBracket; + while (($nextSeperator = $phpcsFile->findNext(array(T_COMMA, T_VARIABLE), ($nextSeperator + 1), $closeBracket)) !== false) { + // Make sure the comma or variable belongs directly to this function call, + // and is not inside a nested function call or array. + $brackets = $tokens[$nextSeperator]['nested_parenthesis']; + $lastBracket = array_pop($brackets); + if ($lastBracket !== $closeBracket) { + continue; + } + + if ($tokens[$nextSeperator]['code'] === T_COMMA) { + if ($tokens[($nextSeperator - 1)]['code'] === T_WHITESPACE) { + $error = 'Space found before comma in function call'; + $phpcsFile->addWarning($error, $stackPtr); + } + + if ($tokens[($nextSeperator + 1)]['code'] !== T_WHITESPACE) { + $error = 'No space found after comma in function call'; + $phpcsFile->addWarning($error, $stackPtr); + } else { + // If there is a newline in the space, then the must be formatting + // each argument on a newline, which is valid, so ignore it. + if (strpos($tokens[($nextSeperator + 1)]['content'], $phpcsFile->eolChar) === false) { + $space = strlen($tokens[($nextSeperator + 1)]['content']); + if ($space > 1) { + $error = 'Expected 1 space after comma in function call; '; + $error .= $space.' found'; + $phpcsFile->addWarning($error, $stackPtr); + } + } + } + } else { + // Token is a variable. + $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($nextSeperator + 1), $closeBracket, true); + if ($nextToken !== false) { + if ($tokens[$nextToken]['code'] === T_EQUAL) { + if (($tokens[($nextToken - 1)]['code']) !== T_WHITESPACE) { + $error = 'Expected 1 space before = sign of default value'; + $phpcsFile->addWarning($error, $stackPtr); + } + + if ($tokens[($nextToken + 1)]['code'] !== T_WHITESPACE) { + $error = 'Expected 1 space after = sign of default value'; + $phpcsFile->addWarning($error, $stackPtr); + } + } + } + }//end if + }//end while + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionCallSignatureSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionCallSignatureSniff.php new file mode 100644 index 0000000000..a778096975 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionCallSignatureSniff.php @@ -0,0 +1,125 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Functions_FunctionCallSignatureSniff. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Functions_FunctionCallSignatureSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_STRING); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Find the next non-empty token. + $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true); + + if ($tokens[$next]['code'] !== T_OPEN_PARENTHESIS) { + // Not a function call. + return; + } + + if (isset($tokens[$next]['parenthesis_closer']) === false) { + // Not a function call. + return; + } + + // Find the previous non-empty token. + $previous = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($tokens[$previous]['code'] === T_FUNCTION) { + // It's a function definition, not a function call. + return; + } + + if ($tokens[$previous]['code'] === T_NEW) { + // We are creating an object, not calling a function. + return; + } + + if (($stackPtr + 1) !== $next) { + // Checking this: $value = my_function[*](...). + $error = 'Space before opening parenthesis of function call prohibited'; + $phpcsFile->addError($error, $stackPtr); + } + + if ($tokens[($next + 1)]['code'] === T_WHITESPACE) { + // Checking this: $value = my_function([*]...). + $error = 'Space after opening parenthesis of function call prohibited'; + $phpcsFile->addError($error, $stackPtr); + } + + $closer = $tokens[$next]['parenthesis_closer']; + + if ($tokens[($closer - 1)]['code'] === T_WHITESPACE) { + // Checking this: $value = my_function(...[*]). + $between = $phpcsFile->findNext(T_WHITESPACE, ($next + 1), null, true); + + // Only throw an error if there is some content between the parenthesis. + // IE. Checking for this: $value = my_function(). + // If there is no content, then we would have thrown an error in the + // previous IF statement because it would look like this: + // $value = my_function( ). + if ($between !== $closer) { + $error = 'Space before closing parenthesis of function call prohibited'; + $phpcsFile->addError($error, $closer); + } + } + + $next = $phpcsFile->findNext(T_WHITESPACE, ($closer + 1), null, true); + + if ($tokens[$next]['code'] === T_SEMICOLON) { + if (in_array($tokens[($closer + 1)]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === true) { + $error = 'Space after closing parenthesis of function call prohibited'; + $phpcsFile->addError($error, $closer); + } + } + + }//end process() + + +}//end class +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionDeclarationSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionDeclarationSniff.php new file mode 100644 index 0000000000..76e03a5d0c --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/FunctionDeclarationSniff.php @@ -0,0 +1,54 @@ + + * @copyright 2006 Moodle Pty Ltd (ABN 77 084 670 600) + * @license http://www.gnu.org/copyleft/gpl.html GPL + * @version CVS: $Id: + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_Standards_AbstractPatternSniff', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractPatternSniff not found'); +} + +/** + * Moodle_Sniffs_Functions_FunctionDeclarationSniff. + * + * Checks the function declaration is correct. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * @copyright 2006 Moodle Pty Ltd (ABN 77 084 670 600) + * @license http://www.gnu.org/copyleft/gpl.html GPL + * @version CVS: $Id: + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Functions_FunctionDeclarationSniff extends PHP_CodeSniffer_Standards_AbstractPatternSniff +{ + + + /** + * Returns an array of patterns to check are correct. + * + * @return array + */ + protected function getPatterns() + { + return array( + 'function abc(...) {', + 'abstract function abc(...);' + ); + + }//end getPatterns() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/LowercaseFunctionKeywordsSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/LowercaseFunctionKeywordsSniff.php new file mode 100644 index 0000000000..afbe5065f2 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/LowercaseFunctionKeywordsSniff.php @@ -0,0 +1,77 @@ + + * @copyright 2006 Moodle Pty Ltd (ABN 77 084 670 600) + * @license http://www.gnu.org/copyleft/gpl.html GPL + * @version CVS: $Id: + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Functions_LowercaseFunctionKeywordsSniff. + * + * Ensures all class keywords are lowercase. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * @copyright 2006 Moodle Pty Ltd (ABN 77 084 670 600) + * @license http://www.gnu.org/copyleft/gpl.html GPL + * @version CVS: $Id: + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Functions_LowercaseFunctionKeywordsSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_FUNCTION, + T_PUBLIC, + T_PRIVATE, + T_PROTECTED, + T_STATIC, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in + * the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $content = $tokens[$stackPtr]['content']; + if ($content !== strtolower($content)) { + $type = strtoupper($content); + $expected = strtolower($content); + $error = "$type keyword must be lowercase; expected \"$expected\" but found \"$content\""; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/ValidDefaultValueSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/ValidDefaultValueSniff.php new file mode 100644 index 0000000000..f906472cf8 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Functions/ValidDefaultValueSniff.php @@ -0,0 +1,110 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Functions_ValidDefaultValueSniff. + * + * A Sniff to ensure that parameters defined for a function that have a default + * value come at the end of the function signature. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Functions_ValidDefaultValueSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_FUNCTION); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $argStart = $tokens[$stackPtr]['parenthesis_opener']; + $argEnd = $tokens[$stackPtr]['parenthesis_closer']; + + // Flag for when we have found a default in our arg list. + // If there is a value without a default after this, it is an error. + $defaultFound = false; + + $nextArg = $argStart; + while (($nextArg = $phpcsFile->findNext(T_VARIABLE, ($nextArg + 1), $argEnd)) !== false) { + $argHasDefault = self::_argHasDefault($phpcsFile, $nextArg); + if (($argHasDefault === false) && ($defaultFound === true)) { + $error = 'Arguments with default values must be at the end'; + $error .= ' of the argument list'; + $phpcsFile->addError($error, $nextArg); + return; + } + + if ($argHasDefault === true) { + $defaultFound = true; + } + } + + }//end process() + + + /** + * Returns true if the passed argument has a default value. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $argPtr The position of the argument + * in the stack. + * + * @return bool + */ + private static function _argHasDefault(PHP_CodeSniffer_File $phpcsFile, $argPtr) + { + $tokens = $phpcsFile->getTokens(); + $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($argPtr + 1), null, true); + if ($tokens[$nextToken]['code'] !== T_EQUAL) { + return false; + } + + return true; + + }//end _argHasDefault() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php new file mode 100644 index 0000000000..533ed85f27 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php @@ -0,0 +1,151 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_NamingConventions_UpperCaseConstantNameSniff. + * + * Ensures that constant names are all uppercase. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_NamingConventions_UpperCaseConstantNameSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_STRING); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $constName = $tokens[$stackPtr]['content']; + + // If this token is in a heredoc, ignore it. + if ($phpcsFile->hasCondition($stackPtr, T_START_HEREDOC) === true) { + return; + } + + // If the next non-whitespace token after this token + // is not an opening parenthesis then it is not a function call. + $openBracket = $phpcsFile->findNext(array(T_WHITESPACE), ($stackPtr + 1), null, true); + if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) { + $functionKeyword = $phpcsFile->findPrevious(array(T_WHITESPACE, T_COMMA, T_COMMENT, T_STRING), ($stackPtr - 1), null, true); + + $declarations = array( + T_FUNCTION, + T_CLASS, + T_INTERFACE, + T_IMPLEMENTS, + T_EXTENDS, + T_INSTANCEOF, + T_NEW, + ); + if (in_array($tokens[$functionKeyword]['code'], $declarations) === true) { + // This is just a declaration; no constants here. + return; + } + + if ($tokens[$functionKeyword]['code'] === T_CONST) { + // This is a class constant. + if (strtoupper($constName) !== $constName) { + $error = 'Class constants must be uppercase; expected '.strtoupper($constName)." but found $constName"; + $phpcsFile->addError($error, $stackPtr); + } + + return; + } + + // Is this a class name? + $nextPtr = $phpcsFile->findNext(array(T_WHITESPACE), ($stackPtr + 1), null, true); + if ($tokens[$nextPtr]['code'] === T_DOUBLE_COLON) { + return; + } + + // Is this a type hint? + if ($tokens[$nextPtr]['code'] === T_VARIABLE) { + return; + } else if ($phpcsFile->isReference($nextPtr) === true) { + return; + } + + // Is this a member var name? + $prevPtr = $phpcsFile->findPrevious(array(T_WHITESPACE), ($stackPtr - 1), null, true); + if ($tokens[$prevPtr]['code'] === T_OBJECT_OPERATOR) { + return; + } + + // Is this an instance of declare() + $prevPtr = $phpcsFile->findPrevious(array(T_WHITESPACE, T_OPEN_PARENTHESIS), ($stackPtr - 1), null, true); + if ($tokens[$prevPtr]['code'] === T_DECLARE) { + return; + } + + // This is a real constant. + if (strtoupper($constName) !== $constName) { + $error = 'Constants must be uppercase; expected '.strtoupper($constName)." but found $constName"; + $phpcsFile->addError($error, $stackPtr); + } + + } else if (strtolower($constName) === 'define' || strtolower($constName) === 'constant') { + + /* + This may be a "define" or "constant" function call. + */ + + // The next non-whitespace token must be the constant name. + $constPtr = $phpcsFile->findNext(array(T_WHITESPACE), ($openBracket + 1), null, true); + if ($tokens[$constPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING) { + return; + } + + $constName = $tokens[$constPtr]['content']; + if (strtoupper($constName) !== $constName) { + $error = 'Constants must be uppercase; expected '.strtoupper($constName)." but found $constName"; + $phpcsFile->addError($error, $stackPtr); + } + }//end if + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidClassNameSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidClassNameSniff.php new file mode 100644 index 0000000000..accc3508f0 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidClassNameSniff.php @@ -0,0 +1,80 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_NamingConventions_ValidClassNameSniff. + * + * Ensures class and interface names start with a capital letter + * and use _ separators. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_NamingConventions_ValidClassNameSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_CLASS, + T_INTERFACE, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The current file being processed. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $className = $phpcsFile->findNext(T_STRING, $stackPtr); + $name = trim($tokens[$className]['content']); + + // Make sure that the word is all lowercase + + if (!preg_match('/[a-z]?/', $name)) { + $error = ucfirst($tokens[$stackPtr]['content']).' name is not valid, must be all lower-case'; + $phpcsFile->addError($error, $stackPtr); + }//end if + + }//end process() + + +}//end class + + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php new file mode 100644 index 0000000000..1eb1148f1e --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -0,0 +1,193 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_Standards_AbstractScopeSniff', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractScopeSniff not found'); +} + +/** + * Moodle_Sniffs_NamingConventions_ValidFunctionNameSniff. + * + * Ensures method names are correct depending on whether they are public + * or private, and that functions are named correctly. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_NamingConventions_ValidFunctionNameSniff extends PHP_CodeSniffer_Standards_AbstractScopeSniff +{ + + /** + * A list of all PHP magic methods. + * + * @var array + */ + private $_magicMethods = array( + 'construct', + 'destruct', + 'call', + 'callStatic', + 'get', + 'set', + 'isset', + 'unset', + 'sleep', + 'wakeup', + 'toString', + 'set_state', + 'clone', + ); + + /** + * A list of all PHP magic functions. + * + * @var array + */ + private $_magicFunctions = array( + 'autoload', + ); + + + /** + * Constructs a Moodle_Sniffs_NamingConventions_ValidFunctionNameSniff. + */ + public function __construct() + { + parent::__construct(array(T_CLASS, T_INTERFACE), array(T_FUNCTION), true); + + }//end __construct() + + + /** + * Processes the tokens within the scope. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being processed. + * @param int $stackPtr The position where this token was + * found. + * @param int $currScope The position of the current scope. + * + * @return void + */ + protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) + { + $className = $phpcsFile->getDeclarationName($currScope); + $methodName = $phpcsFile->getDeclarationName($stackPtr); + + // Is this a magic method. IE. is prefixed with "__". + if (preg_match('|^__|', $methodName) !== 0) { + $magicPart = substr($methodName, 2); + if (in_array($magicPart, $this->_magicMethods) === false) { + $error = "Method name \"$className::$methodName\" is invalid; only PHP magic methods should be prefixed with a double underscore"; + $phpcsFile->addError($error, $stackPtr); + } + + return; + } + + // PHP4 constructors are allowed to break our rules. + if ($methodName === $className) { + return; + } + + // PHP4 destructors are allowed to break our rules. + if ($methodName === '_'.$className) { + return; + } + + $methodProps = $phpcsFile->getMethodProperties($stackPtr); + $isPublic = ($methodProps['scope'] === 'private') ? false : true; + $scope = $methodProps['scope']; + $scopeSpecified = $methodProps['scope_specified']; + + // Only lower-case accepted + if (preg_match('/[A-Z]+/', $methodName)) { + if ($scopeSpecified === true) { + $error = ucfirst($scope)." method name \"$className::$methodName\" must be in lower-case letters only"; + } else { + $error = "Method name \"$className::$methodName\" must be in lower-case letters only"; + } + + $phpcsFile->addError($error, $stackPtr); + return; + } + + // No numbers accepted + if (preg_match('/[0-9]+/', $methodName)) { + if ($scopeSpecified === true) { + $error = ucfirst($scope)." method name \"$className::$methodName\" must only contain letters"; + } else { + $error = "Method name \"$className::$methodName\" must only contain letters"; + } + + $phpcsFile->addError($error, $stackPtr); + return; + } + + }//end processTokenWithinScope() + + + /** + * Processes the tokens outside the scope. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being processed. + * @param int $stackPtr The position where this token was + * found. + * + * @return void + */ + protected function processTokenOutsideScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $functionName = $phpcsFile->getDeclarationName($stackPtr); + + // Is this a magic function. IE. is prefixed with "__". + if (preg_match('|^__|', $functionName) !== 0) { + $magicPart = substr($functionName, 2); + if (in_array($magicPart, $this->_magicFunctions) === false) { + $error = "Function name \"$functionName\" is invalid; only PHP magic methods should be prefixed with a double underscore"; + $phpcsFile->addError($error, $stackPtr); + } + + return; + } + + // Only lower-case accepted + if (preg_match('/[A-Z]+/', $functionName)) { + $error = "function name \"$functionName\" must be lower-case letters only"; + + $phpcsFile->addError($error, $stackPtr); + return; + } + + // Only letters accepted + if (preg_match('/[0-9]+/', $functionName)) { + $error = "function name \"$functionName\" must only contain letters"; + + $phpcsFile->addError($error, $stackPtr); + return; + } + + }//end processTokenOutsideScope() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidVariableNameSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidVariableNameSniff.php new file mode 100644 index 0000000000..56ae2b4551 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -0,0 +1,116 @@ + + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_Standards_AbstractVariableSniff', true) === false) { + $error = 'Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found'; + throw new PHP_CodeSniffer_Exception($error); +} + +/** + * Moodle_Sniffs_NamingConventions_ValidVariableNameSniff. + * + * Checks the naming of member variables. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_NamingConventions_ValidVariableNameSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff +{ + + private $allowed_global_vars = array('CFG', 'SESSION', 'USER', 'COURSE', 'SITE', 'PAGE', 'DB', 'THEME'); + + /** + * Processes class member variables. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + protected function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $memberName = ltrim($tokens[$stackPtr]['content'], '$'); + if (preg_match('/[A-Z]+/', $memberName)) { + $error = "Member variable \"$memberName\" must be all lower-case"; + $phpcsFile->addError($error, $stackPtr); + return; + } + + // Must not be preceded by 'var' keyword + $keyword = $phpcsFile->findPrevious(T_VAR, $stackPtr); + if ($tokens[$keyword]['line'] == $tokens[$stackPtr]['line']) { + $error = "The 'var' keyword is not permitted. Visibility must be explicitly declared with public, private or protected"; + $phpcsFile->addError($error, $stackPtr); + return; + } + + }//end processMemberVar() + + + /** + * Processes normal variables. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. + * @param int $stackPtr The position where the token was found. + * + * @return void + */ + protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $memberName = ltrim($tokens[$stackPtr]['content'], '$'); + if (preg_match('/[A-Z]+/', $memberName)) { + if (!in_array($memberName, $this->allowed_global_vars)) { + $error = "Member variable \"$memberName\" must be all lower-case"; + $phpcsFile->addError($error, $stackPtr); + return; + } + } + + }//end processVariable() + + + /** + * Processes variables in double quoted strings. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. + * @param int $stackPtr The position where the token was found. + * + * @return void + */ + protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $memberName = ltrim($tokens[$stackPtr]['content'], '$'); + if (preg_match('/[A-Z]+/', $memberName)) { + $error = "Member variable \"$memberName\" must be all lower-case"; + $phpcsFile->addError($error, $stackPtr); + return; + } + return; + + }//end processVariableInString() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/DisallowShortOpenTagSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/DisallowShortOpenTagSniff.php new file mode 100644 index 0000000000..f61266a2e8 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/DisallowShortOpenTagSniff.php @@ -0,0 +1,91 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_PHP_DisallowShortOpenTagSniff. + * + * Makes sure that shorthand PHP open tags are not used. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_PHP_DisallowShortOpenTagSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + // If short open tags are off, then any short open tags will be converted + // to inline_html tags so we can just ignore them. + // If its on, then we want to ban the use of them. + $option = ini_get('short_open_tag'); + + // Ini_get returns a string "0" if short open tags is off. + if ($option === '0') { + return; + } + + $tokens = $phpcsFile->getTokens(); + $openTag = $tokens[$stackPtr]; + + if ($openTag['content'] === 'addError($error, $stackPtr); + } + + if ($openTag['code'] === T_OPEN_TAG_WITH_ECHO) { + $nextVar = $tokens[$phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true)]; + $error = 'Short PHP opening tag used with echo. Found "'; + $error .= $openTag['content'].' '.$nextVar['content'].' ..." but expected "addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/LowerCaseConstantSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/LowerCaseConstantSniff.php new file mode 100644 index 0000000000..760ad028af --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/LowerCaseConstantSniff.php @@ -0,0 +1,84 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_PHP_LowerCaseConstantSniff. + * + * Checks that all uses of true, false and null are lowerrcase. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_PHP_LowerCaseConstantSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * A list of tokenizers this sniff supports. + * + * @var array + */ + public $supportedTokenizers = array( + 'PHP', + 'JS', + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_TRUE, + T_FALSE, + T_NULL, + ); + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $keyword = $tokens[$stackPtr]['content']; + if (strtolower($keyword) !== $keyword) { + $error = 'TRUE, FALSE and NULL must be lowercase; expected "'.strtolower($keyword).'" but found "'.$keyword.'"'; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/LowercasePHPFunctionsSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/LowercasePHPFunctionsSniff.php new file mode 100644 index 0000000000..b3d305c717 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/PHP/LowercasePHPFunctionsSniff.php @@ -0,0 +1,139 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_PHP_LowercasePHPFunctionsSniff. + * + * Ensures all calls to inbuilt PHP functions are lowercase. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_PHP_LowercasePHPFunctionsSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_ISSET, + T_ECHO, + T_PRINT, + T_RETURN, + T_BREAK, + T_CONTINUE, + T_EMPTY, + T_EVAL, + T_EXIT, + T_LIST, + T_UNSET, + T_INCLUDE, + T_INCLUDE_ONCE, + T_REQUIRE, + T_REQUIRE_ONCE, + T_NEW, + T_DECLARE, + T_STRING, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in + * the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$stackPtr]['code'] !== T_STRING) { + $content = $tokens[$stackPtr]['content']; + if ($content !== strtolower($content)) { + $type = strtoupper($content); + $expected = strtolower($content); + $error = "$type keyword must be lowercase; expected \"$expected\" but found \"$content\""; + $phpcsFile->addError($error, $stackPtr); + } + + return; + } + + // Make sure this is a function call. + $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($next === false) { + // Not a function call. + return; + } + + if ($tokens[$next]['code'] !== T_OPEN_PARENTHESIS) { + // Not a function call. + return; + } + + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$prev]['code'] === T_FUNCTION) { + // Function declaration, not a function call. + return; + } + + if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR) { + // Not an inbuilt function. + return; + } + + if ($tokens[$prev]['code'] === T_DOUBLE_COLON) { + // Not an inbuilt function. + return; + } + + // Make sure it is an inbuilt PHP function. + // PHP_CodeSniffer doesn't include/require any files, so no + // user defined global functions can exist, except for + // PHP_CodeSniffer ones. + $content = $tokens[$stackPtr]['content']; + if (function_exists($content) === false) { + return; + } + + if ($content !== strtolower($content)) { + $expected = strtolower($content); + $error = "Calls to inbuilt PHP functions must be lowercase; expected \"$expected\" but found \"$content\""; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Strings/DoubleQuoteUsageSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Strings/DoubleQuoteUsageSniff.php new file mode 100644 index 0000000000..5500830fb7 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Strings/DoubleQuoteUsageSniff.php @@ -0,0 +1,109 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Strings_DoubleQuoteUsageSniff. + * + * Makes sure that any use of Double Quotes ("") are warranted. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Strings_DoubleQuoteUsageSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array( + T_CONSTANT_ENCAPSED_STRING, + ); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $workingString = $tokens[$stackPtr]['content']; + + // Check if it's a double quoted string. + if (strpos($workingString, '"') === false) { + return; + } + + // Make sure it's not a part of a string started above. + // If it is, then we have already checked it. + if ($workingString[0] !== '"') { + return; + } + + // Work through the following tokens, in case this string is stretched + // over multiple Lines. + for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { + if ($tokens[$i]['type'] !== 'T_CONSTANT_ENCAPSED_STRING') { + break; + } + + $workingString .= $tokens[$i]['content']; + } + + $allowedChars = array( + '\n', + '\r', + '\f', + '\t', + '\v', + '\x', + '\'', + ); + + foreach ($allowedChars as $testChar) { + if (strpos($workingString, $testChar) !== false) { + return; + } + } + + $error = "String $workingString does not require double quotes; use single quotes instead"; + $phpcsFile->addError($error, $stackPtr); + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Strings/EchoedStringsSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Strings/EchoedStringsSniff.php new file mode 100644 index 0000000000..e60a05c921 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/Strings/EchoedStringsSniff.php @@ -0,0 +1,85 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Strings_EchoedStringsSniff. + * + * Makes sure that any strings that are "echoed" are not enclosed in brackets + * like a function call. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_Strings_EchoedStringsSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_ECHO); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $firstContent = $phpcsFile->findNext(array(T_WHITESPACE), ($stackPtr + 1), null, true); + // If the first non-whitespace token is not an opening parenthesis, then we are not concerned. + if ($tokens[$firstContent]['code'] !== T_OPEN_PARENTHESIS) { + return; + } + + $endOfStatement = $phpcsFile->findNext(array(T_SEMICOLON), $stackPtr, null, false); + + // If the token before the semi-colon is not a closing parenthesis, then we are not concerned. + if ($tokens[($endOfStatement - 1)]['code'] !== T_CLOSE_PARENTHESIS) { + return; + } + + if (($phpcsFile->findNext(PHP_CodeSniffer_Tokens::$operators, $stackPtr, $endOfStatement, false)) === false) { + // There are no arithmetic operators in this. + $error = 'Echoed strings should not be bracketed'; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/DisallowTabIndentSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/DisallowTabIndentSniff.php new file mode 100644 index 0000000000..678e921917 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/DisallowTabIndentSniff.php @@ -0,0 +1,86 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_WhiteSpace_DisallowTabIndentSniff. + * + * Throws errors if tabs are used for indentation. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_WhiteSpace_DisallowTabIndentSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * A list of tokenizers this sniff supports. + * + * @var array + */ + public $supportedTokenizers = array( + 'PHP', + 'JS', + ); + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_WHITESPACE); + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document. + * @param int $stackPtr The position of the current token in + * the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Make sure this is whitespace used for indentation. + $line = $tokens[$stackPtr]['line']; + if ($stackPtr > 0 && $tokens[($stackPtr - 1)]['line'] === $line) { + return; + } + + if (strpos($tokens[$stackPtr]['content'], "\t") !== false) { + $error = 'Spaces must be used to indent lines; tabs are not allowed'; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/MemberVarSpacingSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/MemberVarSpacingSniff.php new file mode 100644 index 0000000000..d3a2670770 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/MemberVarSpacingSniff.php @@ -0,0 +1,121 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PHP_CodeSniffer_Standards_AbstractVariableSniff', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found'); +} + +/** + * Verifies that class members are spaced correctly. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_WhiteSpace_MemberVarSpacingSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff +{ + + + /** + * Processes the function tokens within the class. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. + * @param int $stackPtr The position where the token was found. + * + * @return void + */ + protected function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // There needs to be 1 blank line before the var, not counting comments. + $prevLineToken = null; + for ($i = ($stackPtr - 1); $i > 0; $i--) { + if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) { + // Skip comments. + continue; + } else if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) === false) { + // Not the end of the line. + continue; + } else { + // If this is a WHITESPACE token, and the token right before + // it is a DOC_COMMENT, then it is just the newline after the + // member var's comment, and can be skipped. + if ($tokens[$i]['code'] === T_WHITESPACE && in_array($tokens[($i - 1)]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) { + continue; + } + + $prevLineToken = $i; + break; + } + } + + if (is_null($prevLineToken) === true) { + // Never found the previous line, which means + // there are 0 blank lines before the member var. + $foundLines = 0; + } else { + $prevContent = $phpcsFile->findPrevious(array(T_WHITESPACE, T_DOC_COMMENT), $prevLineToken, null, true); + $foundLines = ($tokens[$prevLineToken]['line'] - $tokens[$prevContent]['line']); + }//end if + + if ($foundLines !== 1) { + $phpcsFile->addError("Expected 1 blank line before member var; $foundLines found", $stackPtr); + } + + }//end processMemberVar() + + + /** + * Processes normal variables. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. + * @param int $stackPtr The position where the token was found. + * + * @return void + */ + protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + // We don't care about normal variables. + return; + + }//end processVariable() + + + /** + * Processes variables in double quoted strings. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. + * @param int $stackPtr The position where the token was found. + * + * @return void + */ + protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + // We don't care about normal variables. + return; + + }//end processVariableInString() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php new file mode 100644 index 0000000000..c6144d9a94 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php @@ -0,0 +1,115 @@ + + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Whitespace_ScopeClosingBraceSniff. + * + * Checks that the closing braces of scopes are aligned correctly. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Nicolas Connault + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_WhiteSpace_ScopeClosingBraceSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return PHP_CodeSniffer_Tokens::$scopeOpeners; + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // If this is an inline condition (ie. there is no scope opener), then + // return, as this is not a new scope. + if (isset($tokens[$stackPtr]['scope_closer']) === false) { + return; + } + + // We need to actually find the first piece of content on this line, + // as if this is a method with tokens before it (public, static etc) + // or an if with an else before it, then we need to start the scope + // checking from there, rather than the current token. + $lineStart = ($stackPtr - 1); + for ($lineStart; $lineStart > 0; $lineStart--) { + if (strpos($tokens[$lineStart]['content'], $phpcsFile->eolChar) !== false) { + break; + } + } + + // We found a new line, now go forward and find the first non-whitespace + // token. + $lineStart = $phpcsFile->findNext(array(T_WHITESPACE), ($lineStart + 1), null, true); + + $startColumn = $tokens[$lineStart]['column']; + $scopeStart = $tokens[$stackPtr]['scope_opener']; + $scopeEnd = $tokens[$stackPtr]['scope_closer']; + + // Check that the closing brace is on its own line. + $lastContent = $phpcsFile->findPrevious(array(T_WHITESPACE), ($scopeEnd - 1), $scopeStart, true); + if ($tokens[$lastContent]['line'] === $tokens[$scopeEnd]['line']) { + $error = 'Closing brace must be on a line by itself'; + $phpcsFile->addError($error, $scopeEnd); + return; + } + + // Check now that the closing brace is lined up correctly. + $braceIndent = $tokens[$scopeEnd]['column']; + $isBreakCloser = ($tokens[$scopeEnd]['code'] === T_BREAK); + if (in_array($tokens[$stackPtr]['code'], array(T_CASE, T_DEFAULT)) === true && $isBreakCloser === true) { + // BREAK statements should be indented 4 spaces from the + // CASE or DEFAULT statement. + if ($braceIndent !== ($startColumn + 4)) { + $error = 'Break statement indented incorrectly; expected '.($startColumn + 3).' spaces, found '.($braceIndent - 1); + $phpcsFile->addError($error, $scopeEnd); + } + } else { + if ($braceIndent !== $startColumn) { + $error = 'Closing brace indented incorrectly; expected '.($startColumn - 1).' spaces, found '.($braceIndent - 1); + $phpcsFile->addError($error, $scopeEnd); + } + } + + }//end process() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/ScopeIndentSniff.php b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/ScopeIndentSniff.php new file mode 100644 index 0000000000..ef6fa34fa8 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Standards/Moodle/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -0,0 +1,295 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Moodle_Sniffs_Whitespace_ScopeIndentSniff. + * + * Checks that control structures are structured correctly, and their content + * is indented correctly. This sniff will throw errors if tabs are used + * for indentation rather than spaces. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class Moodle_Sniffs_WhiteSpace_ScopeIndentSniff implements PHP_CodeSniffer_Sniff +{ + + /** + * The number of spaces code should be indented. + * + * @var int + */ + protected $indent = 4; + + /** + * Does the indent need to be exactly right. + * + * If TRUE, indent needs to be exactly $ident spaces. If FALSE, + * indent needs to be at least $ident spaces (but can be more). + * + * @var bool + */ + protected $exact = false; + + /** + * Any scope openers that should not cause an indent. + * + * @var array(int) + */ + protected $nonIndentingScopes = array(T_SWITCH); + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return PHP_CodeSniffer_Tokens::$scopeOpeners; + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // If this is an inline condition (ie. there is no scope opener), then + // return, as this is not a new scope. + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + return; + } + + if ($tokens[$stackPtr]['code'] === T_ELSE) { + $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true); + // We will handle the T_IF token in another call to process. + if ($tokens[$next]['code'] === T_IF) { + return; + } + } + + // Find the first token on this line. + $firstToken = $stackPtr; + for ($i = $stackPtr; $i >= 0; $i--) { + // Record the first code token on the line. + if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) { + $firstToken = $i; + } + + // It's the start of the line, so we've found our first php token. + if ($tokens[$i]['column'] === 1) { + break; + } + } + + // Based on the conditions that surround this token, determine the + // indent that we expect this current content to be. + $expectedIndent = $this->calculateExpectedIndent($tokens, $firstToken); + + if ($tokens[$firstToken]['column'] !== $expectedIndent) { + $error = 'Line indented incorrectly; expected '; + $error .= ($expectedIndent - 1).' spaces, found '; + $error .= ($tokens[$firstToken]['column'] - 1); + $phpcsFile->addError($error, $stackPtr); + } + + $scopeOpener = $tokens[$stackPtr]['scope_opener']; + $scopeCloser = $tokens[$stackPtr]['scope_closer']; + + // Some scopes are expected not to have indents. + if (in_array($tokens[$firstToken]['code'], $this->nonIndentingScopes) === false) { + $indent = ($expectedIndent + $this->indent); + } else { + $indent = $expectedIndent; + } + + $newline = false; + $commentOpen = false; + $inHereDoc = false; + + // Only loop over the content beween the opening and closing brace, not + // the braces themselves. + for ($i = ($scopeOpener + 1); $i < $scopeCloser; $i++) { + + // If this token is another scope, skip it as it will be handled by + // another call to this sniff. + if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$scopeOpeners) === true) { + if (isset($tokens[$i]['scope_opener']) === true) { + $i = $tokens[$i]['scope_closer']; + } else { + // If this token does not have a scope_opener indice, then + // it's probably an inline scope, so let's skip to the next + // semicolon. Inline scopes include inline if's, abstract methods etc. + $nextToken = $phpcsFile->findNext(T_SEMICOLON, $i, $scopeCloser); + if ($nextToken !== false) { + $i = $nextToken; + } + } + + continue; + } + + // If this is a HEREDOC then we need to ignore it as the whitespace + // before the contents within the HEREDOC are considered part of the content. + if ($tokens[$i]['code'] === T_START_HEREDOC) { + $inHereDoc = true; + continue; + } else if ($inHereDoc === true) { + if ($tokens[$i]['code'] === T_END_HEREDOC) { + $inHereDoc = false; + } + + continue; + } + + if ($tokens[$i]['column'] === 1) { + // We started a newline. + $newline = true; + } + + if ($newline === true && $tokens[$i]['code'] !== T_WHITESPACE) { + // If we started a newline and we find a token that is not + // whitespace, then this must be the first token on the line that + // must be indented. + $newline = false; + $firstToken = $i; + + $column = $tokens[$firstToken]['column']; + + // Special case for non-PHP code. + if ($tokens[$firstToken]['code'] === T_INLINE_HTML) { + $trimmedContentLength = strlen(ltrim($tokens[$firstToken]['content'])); + if ($trimmedContentLength === 0) { + continue; + } + + $contentLength = strlen($tokens[$firstToken]['content']); + $column = ($contentLength - $trimmedContentLength + 1); + } + + // Check to see if this constant string spans multiple lines. + // If so, then make sure that the strings on lines other than the + // first line are indented appropriately, based on their whitespace. + if (in_array($tokens[$firstToken]['code'], PHP_CodeSniffer_Tokens::$stringTokens) === true) { + if (in_array($tokens[($firstToken - 1)]['code'], PHP_CodeSniffer_Tokens::$stringTokens) === true) { + // If we find a string that directly follows another string + // then its just a string that spans multiple lines, so we + // don't need to check for indenting. + continue; + } + } + + // This is a special condition for T_DOC_COMMENT and C-style + // comments, which contain whitespace between each line. + if (in_array($tokens[$firstToken]['code'], array(T_COMMENT, T_DOC_COMMENT)) === true) { + + $content = trim($tokens[$firstToken]['content']); + if (preg_match('|^/\*|', $content) !== 0) { + // Check to see if the end of the comment is on the same line + // as the start of the comment. If it is, then we don't + // have to worry about opening a comment. + if (preg_match('|\*/$|', $content) === 0) { + // We don't have to calculate the column for the start + // of the comment as there is a whitespace token before it. + $commentOpen = true; + } + } else if ($commentOpen === true) { + if ($content === '') { + // We are in a comment, but this line has nothing on it + // so let's skip it. + continue; + } + + $contentLength = strlen($tokens[$firstToken]['content']); + $trimmedContentLength = strlen(ltrim($tokens[$firstToken]['content'])); + $column = ($contentLength - $trimmedContentLength + 1); + if (preg_match('|\*/$|', $content) !== 0) { + $commentOpen = false; + } + }//end if + }//end if + + // The token at the start of the line, needs to have its' column + // greater than the relative indent we set above. If it is less, + // an error should be shown. + if ($column !== $indent) { + if ($this->exact === true || $column < $indent) { + $error = 'Line indented incorrectly; expected '; + if ($this->exact === false) { + $error .= 'at least '; + } + + $error .= ($indent - 1).' spaces, found '; + $error .= ($column - 1); + $phpcsFile->addError($error, $firstToken); + } + } + }//end if + }//end for + + }//end process() + + + /** + * Calculates the expected indent of a token. + * + * @param array $tokens The stack of tokens for this file. + * @param int $stackPtr The position of the token to get indent for. + * + * @return int + */ + protected function calculateExpectedIndent(array $tokens, $stackPtr) + { + $conditionStack = array(); + + // Empty conditions array (top level structure). + if (empty($tokens[$stackPtr]['conditions']) === true) { + return 1; + } + + $tokenConditions = $tokens[$stackPtr]['conditions']; + foreach ($tokenConditions as $id => $condition) { + // If it's an indenting scope ie. it's not in our array of + // scopes that don't indent, add it to our condition stack. + if (in_array($condition, $this->nonIndentingScopes) === false) { + $conditionStack[$id] = $condition; + } + } + + return ((count($conditionStack) * $this->indent) + 1); + + }//end calculateExpectedIndent() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Tokenizers/JS.php b/lib/pear/PHP/CodeSniffer/Tokenizers/JS.php new file mode 100644 index 0000000000..74b91aafc1 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Tokenizers/JS.php @@ -0,0 +1,718 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Tokenizes JS code. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_Tokenizers_JS +{ + + /** + * A list of tokens that are allowed to open a scope. + * + * This array also contains information about what kind of token the scope + * opener uses to open and close the scope, if the token strictly requires + * an opener, if the token can share a scope closer, and who it can be shared + * with. An example of a token that shares a scope closer is a CASE scope. + * + * @var array + */ + public $scopeOpeners = array( + T_IF => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_TRY => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_CATCH => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_ELSE => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_FOR => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_FUNCTION => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_WHILE => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_DO => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_SWITCH => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_CASE => array( + 'start' => T_COLON, + 'end' => T_BREAK, + 'strict' => true, + 'shared' => true, + 'with' => array( + T_DEFAULT, + T_CASE, + ), + ), + T_DEFAULT => array( + 'start' => T_COLON, + 'end' => T_BREAK, + 'strict' => true, + 'shared' => true, + 'with' => array(T_CASE), + ), + ); + + /** + * A list of tokens that end the scope. + * + * This array is just a unique collection of the end tokens + * from the _scopeOpeners array. The data is duplicated here to + * save time during parsing of the file. + * + * @var array + */ + public $endScopeTokens = array( + T_CLOSE_CURLY_BRACKET, + T_BREAK, + ); + + /** + * A list of special JS tokens and their types. + * + * @var array + */ + protected $tokenValues = array( + 'function' => 'T_FUNCTION', + 'prototype' => 'T_PROTOTYPE', + 'try' => 'T_TRY', + 'catch' => 'T_CATCH', + 'return' => 'T_RETURN', + 'if' => 'T_IF', + 'else' => 'T_ELSE', + 'do' => 'T_DO', + 'while' => 'T_WHILE', + 'for' => 'T_FOR', + 'var' => 'T_VAR', + 'true' => 'T_TRUE', + 'false' => 'T_FALSE', + 'null' => 'T_NULL', + 'this' => 'T_THIS', + '(' => 'T_OPEN_PARENTHESIS', + ')' => 'T_CLOSE_PARENTHESIS', + '{' => 'T_OPEN_CURLY_BRACKET', + '}' => 'T_CLOSE_CURLY_BRACKET', + '[' => 'T_OPEN_SQUARE_BRACKET', + ']' => 'T_CLOSE_SQUARE_BRACKET', + '?' => 'T_INLINE_THEN', + '.' => 'T_OBJECT_OPERATOR', + '+' => 'T_PLUS', + '-' => 'T_MINUS', + '*' => 'T_MULTIPLY', + '%' => 'T_MODULUS', + '/' => 'T_DIVIDE', + ',' => 'T_COMMA', + ';' => 'T_SEMICOLON', + ':' => 'T_COLON', + '<' => 'T_LESS_THAN', + '>' => 'T_GREATER_THAN', + '<=' => 'T_IS_SMALLER_OR_EQUAL', + '>=' => 'T_IS_GREATER_OR_EQUAL', + '!' => 'T_BOOLEAN_NOT', + '!=' => 'T_IS_NOT_EQUAL', + '!==' => 'T_IS_NOT_IDENTICAL', + '=' => 'T_EQUAL', + '==' => 'T_IS_EQUAL', + '===' => 'T_IS_IDENTICAL', + '-=' => 'T_MINUS_EQUAL', + '+=' => 'T_PLUS_EQUAL', + '++' => 'T_INC', + '--' => 'T_DEC', + '//' => 'T_COMMENT', + '/*' => 'T_COMMENT', + '/**' => 'T_DOC_COMMENT', + '*/' => 'T_COMMENT', + ); + + /** + * A list string delimiters. + * + * @var array + */ + protected $stringTokens = array( + '\'', + '"', + ); + + /** + * A list tokens that start and end comments. + * + * @var array + */ + protected $commentTokens = array( + '//' => null, + '/*' => '*/', + '/**' => '*/', + ); + + + /** + * 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. + * + * @return array + */ + public function tokenizeString($string, $eolChar='\n') + { + $tokenTypes = array_keys($this->tokenValues); + + $maxTokenLength = 0; + foreach ($tokenTypes as $token) { + if (strlen($token) > $maxTokenLength) { + $maxTokenLength = strlen($token); + } + } + + $tokens = array(); + $inString = ''; + $inComment = ''; + $buffer = ''; + $cleanBuffer = false; + + $tokens[] = array( + 'code' => T_OPEN_TAG, + 'type' => 'T_OPEN_TAG', + 'content' => '', + ); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** START TOKENIZING ***".PHP_EOL; + } + + $chars = str_split($string); + foreach ($chars as $i => $char) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $content = str_replace($eolChar, '\n', $char); + if ($inString !== '') { + echo "\t"; + } + + if ($inComment !== '') { + echo "\t"; + } + + echo "Process char $i => $content".PHP_EOL; + } + + if ($inString === '' && $inComment === '' && $buffer !== '') { + // If the buffer only has whitespace and we are about to + // add a character, store the whitespace first. + if (trim($char) !== '' && trim($buffer) === '') { + $tokens[] = array( + 'code' => T_WHITESPACE, + 'type' => 'T_WHITESPACE', + 'content' => $buffer, + ); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $content = str_replace($eolChar, '\n', $buffer); + echo "=> Added token T_WHITESPACE ($content)".PHP_EOL; + } + + $buffer = ''; + } + + // If the buffer is not whitespace and we are about to + // add a whitespace character, store the content first. + if ($inString === '' && $inComment === '' && trim($char) === '' && trim($buffer) !== '') { + $tokens[] = array( + 'code' => T_STRING, + 'type' => 'T_STRING', + 'content' => $buffer, + ); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $content = str_replace($eolChar, '\n', $buffer); + echo "=> Added token T_STRING ($content)".PHP_EOL; + } + + $buffer = ''; + } + }//end if + + // Process strings. + if ($inComment === '' && in_array($char, $this->stringTokens) === true) { + if ($inString === $char) { + // This could be the end of the string, but make sure it + // is not escaped first. + $escapes = 0; + for ($x = ($i - 1); $x >= 0; $x--) { + if ($chars[$x] !== '\\') { + break; + } + + $escapes++; + } + + if ($escapes === 0 || ($escapes % 2) === 0) { + // There is an even number escape chars, + // so this is not escaped, it is the end of the string. + $tokens[] = array( + 'code' => T_CONSTANT_ENCAPSED_STRING, + 'type' => 'T_CONSTANT_ENCAPSED_STRING', + 'content' => $buffer.$char, + ); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t* found end of string *".PHP_EOL; + $content = str_replace($eolChar, '\n', $buffer.$char); + echo "=> Added token T_CONSTANT_ENCAPSED_STRING $content)".PHP_EOL; + } + + $buffer = ''; + $inString = ''; + continue; + } + } else if ($inString === '') { + $inString = $char; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t* looking for string closer *".PHP_EOL; + } + }//end if + }//end if + + $buffer .= $char; + + // We don't look for special tokens inside strings, + // so if we are in a string, we can continue here now + // that the current char is in the buffer. + if ($inString !== '') { + continue; + } + + // Check for known tokens, but ignore tokens found that are not at + // the end of a string, like FOR and this.FORmat. + if (in_array(strtolower($buffer), $tokenTypes) === true && (preg_match('|[a-zA-z0-9_]|', $char) === 0 || preg_match('|[a-zA-z0-9_]|', $chars[($i + 1)]) === 0)) { + $matchedToken = false; + $lookAheadLength = ($maxTokenLength - strlen($buffer)); + + if ($lookAheadLength > 0) { + // The buffer contains a token type, but we need + // to look ahead at the next chars to see if this is + // actually part of a larger token. For example, + // FOR and FOREACH. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t* buffer possibly contains token, looking ahead $lookAheadLength chars *".PHP_EOL; + } + + $charBuffer = $buffer; + for ($x = 1; $x <= $lookAheadLength; $x++) { + if (isset($chars[($i + $x)]) === false) { + break; + } + + $charBuffer .= $chars[($i + $x)]; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $content = str_replace($eolChar, '\n', $charBuffer); + echo "\t=> Looking ahead $x chars => $content".PHP_EOL; + } + + if (in_array(strtolower($charBuffer), $tokenTypes) === true) { + // We've found something larger that matches + // so we can ignore this char. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $this->tokenValues[strtolower($charBuffer)]; + echo "\t* look ahead found more specific token ($type), ignoring $i *".PHP_EOL; + } + + $matchedToken = true; + break; + } + }//end for + }//end if + + if ($matchedToken === false) { + $value = $this->tokenValues[strtolower($buffer)]; + $tokens[] = array( + 'code' => constant($value), + 'type' => $value, + 'content' => $buffer, + ); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + if ($lookAheadLength > 0) { + echo "\t* look ahead found nothing *".PHP_EOL; + } + + $content = str_replace($eolChar, '\n', $buffer); + echo "=> Added token $value ($content)".PHP_EOL; + } + + $cleanBuffer = true; + } + } else if (in_array(strtolower($char), $tokenTypes) === true) { + // No matter what token we end up using, we don't + // need the content in the buffer any more because we have + // found a valid token. + $tokens[] = array( + 'code' => T_STRING, + 'type' => 'T_STRING', + 'content' => substr($buffer, 0, -1), + ); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $content = str_replace($eolChar, '\n', substr($buffer, 0, -1)); + echo "=> Added token T_STRING ($content)".PHP_EOL; + } + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t* char is token, looking ahead ".($maxTokenLength - 1).' chars *'.PHP_EOL; + } + + // The char is a token type, but we need to look ahead at the + // next chars to see if this is actually part of a larger token. + // For example, = and ===. + $charBuffer = $char; + $matchedToken = false; + for ($x = 1; $x <= $maxTokenLength; $x++) { + if (isset($chars[($i + $x)]) === false) { + break; + } + + $charBuffer .= $chars[($i + $x)]; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $content = str_replace($eolChar, '\n', $charBuffer); + echo "\t=> Looking ahead $x chars => $content".PHP_EOL; + } + + if (in_array(strtolower($charBuffer), $tokenTypes) === true) { + // We've found something larger that matches + // so we can ignore this char. + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = $this->tokenValues[strtolower($charBuffer)]; + echo "\t* look ahead found more specific token ($type), ignoring $i *".PHP_EOL; + } + + $matchedToken = true; + break; + } + }//end for + + if ($matchedToken === false) { + $value = $this->tokenValues[strtolower($char)]; + $tokens[] = array( + 'code' => constant($value), + 'type' => $value, + 'content' => $char, + ); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t* look ahead found nothing *".PHP_EOL; + $content = str_replace($eolChar, '\n', $char); + echo "=> Added token $value ($content)".PHP_EOL; + } + + $cleanBuffer = true; + } else { + $buffer = $char; + } + }//end if + + // Keep track of content inside comments. + if ($inComment === '' && array_key_exists($buffer, $this->commentTokens) === true) { + // We have started a comment. + $inComment = $buffer; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t* looking for end of comment *".PHP_EOL; + } + } else if ($inComment !== '') { + if ($this->commentTokens[$inComment] === null) { + // Comment ends at the next newline. + if (strpos($buffer, $eolChar) !== false) { + $inComment = ''; + } + } else { + if ($this->commentTokens[$inComment] === $buffer) { + $inComment = ''; + } + } + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + if ($inComment === '') { + echo "\t* found end of comment *".PHP_EOL; + } + } + }//end if + + if ($cleanBuffer === true) { + $buffer = ''; + $cleanBuffer = false; + } + }//end foreach + + $tokens[] = array( + 'code' => T_CLOSE_TAG, + 'type' => 'T_CLOSE_TAG', + 'content' => '', + ); + + /* + Now that we have done some basic tokenizing, we need to + modify the tokens to join some together and split some apart + so they match what the PHP tokenizer does. + */ + + $finalTokens = array(); + $newStackPtr = 0; + $numTokens = count($tokens); + for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) { + $token = $tokens[$stackPtr]; + + /* + Look for regular expressions and join the tokens together. + */ + + if ($token['code'] === T_DIVIDE) { + $beforeTokens = array( + T_EQUAL, + T_OPEN_PARENTHESIS, + ); + + $afterTokens = array( + T_COMMA, + T_CLOSE_PARENTHESIS, + T_SEMICOLON, + T_WHITESPACE, + ); + + for ($prev = ($stackPtr - 1); $prev >= 0; $prev--) { + if (in_array($tokens[$prev]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) { + break; + } + } + + // Token needs to be one of the standard allowed or the replace() + // method that can be called on string: string.replace(/abc/...). + if (in_array($tokens[$prev]['code'], $beforeTokens) === true || $tokens[$prev]['content'] === 'replace') { + // This might be a regular expression. + $regexTokens = array( + T_STRING, + T_WHITESPACE, + T_OBJECT_OPERATOR, + ); + + for ($next = ($stackPtr + 1); $next < $numTokens; $next++) { + if (in_array($tokens[$next]['code'], $regexTokens) === false) { + break; + } + } + + if ($tokens[$next]['code'] === T_DIVIDE) { + if ($tokens[($next + 1)]['code'] === T_STRING) { + // The token directly after the end of the regex can + // be modifiers like global and case insensitive + // (.e.g, /pattern/gi). + $next++; + } + + $regexEnd = $next; + + for ($next = ($next + 1); $next < $numTokens; $next++) { + if (in_array($tokens[$next]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) { + break; + } else if (strpos($tokens[$next]['content'], $eolChar) !== false) { + // If this is the last token on the line. + break; + } + } + + if (in_array($tokens[$next]['code'], $afterTokens) === true) { + // This is a regular expression, so join all the + // tokens together. + for ($i = ($stackPtr + 1); $i <= $regexEnd; $i++) { + $token['content'] .= $tokens[$i]['content']; + } + + $token['code'] = T_REGULAR_EXPRESSION; + $token['type'] ='T_REGULAR_EXPRESSION'; + $stackPtr = $regexEnd; + } + } + }//end if + }//end if + + /* + Look for comments and join the tokens together. + */ + + if (array_key_exists($token['content'], $this->commentTokens) === true) { + $newContent = ''; + $tokenContent = $token['content']; + $endContent = $this->commentTokens[$tokenContent]; + while ($tokenContent !== $endContent) { + if ($endContent === null && strpos($tokenContent, $eolChar) !== false) { + // A null end token means the comment ends at the end of + // the line so we look for newlines and split the token. + $tokens[$stackPtr]['content'] = substr($tokenContent, (strpos($tokenContent, $eolChar) + strlen($eolChar))); + + $tokenContent = substr($tokenContent, 0, (strpos($tokenContent, $eolChar) + strlen($eolChar))); + + // If the substr failed, skip the token as the content + // will now be blank. + if ($tokens[$stackPtr]['content'] !== false) { + $stackPtr--; + } + + break; + }//end if + + $stackPtr++; + $newContent .= $tokenContent; + if (isset($tokens[$stackPtr]) === false) { + break; + } + + $tokenContent = $tokens[$stackPtr]['content']; + }//end while + + // Save the new content in the current token so + // the code below can chop it up on newlines. + $token['content'] = $newContent.$tokenContent; + }//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 asertain where errors occur on a line. + Note that $token[1] is the token's content. + */ + + if (strpos($token['content'], $eolChar) !== false) { + $tokenLines = explode($eolChar, $token['content']); + $numLines = count($tokenLines); + + for ($i = 0; $i < $numLines; $i++) { + $newToken['content'] = $tokenLines[$i]; + if ($i === ($numLines - 1)) { + if ($tokenLines[$i] === '') { + break; + } + } else { + $newToken['content'] .= $eolChar; + } + + $newToken['type'] = $token['type']; + $newToken['code'] = $token['code']; + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + } + } else { + $finalTokens[$newStackPtr] = $token; + $newStackPtr++; + }//end if + + // Convert numbers, including decimals. + if ($token['code'] === T_STRING || $token['code'] === T_OBJECT_OPERATOR) { + $newContent = ''; + $oldStackPtr = $stackPtr; + while (preg_match('|^[0-9\.]+$|', $tokens[$stackPtr]['content']) !== 0) { + $newContent .= $tokens[$stackPtr]['content']; + $stackPtr++; + } + + if ($newContent !== '' && $newContent !== '.') { + $finalTokens[($newStackPtr - 1)]['content'] = $newContent; + if (ctype_digit($newContent) === true) { + $finalTokens[($newStackPtr - 1)]['code'] = constant('T_LNUMBER'); + $finalTokens[($newStackPtr - 1)]['type'] = 'T_LNUMBER'; + } else { + $finalTokens[($newStackPtr - 1)]['code'] = constant('T_DNUMBER'); + $finalTokens[($newStackPtr - 1)]['type'] = 'T_DNUMBER'; + } + + $stackPtr--; + } else { + $stackPtr = $oldStackPtr; + } + }//end if + }//end for + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t*** END TOKENIZING ***".PHP_EOL; + } + + return $finalTokens; + + }//end tokenizeString() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Tokenizers/PHP.php b/lib/pear/PHP/CodeSniffer/Tokenizers/PHP.php new file mode 100644 index 0000000000..c610fdad32 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Tokenizers/PHP.php @@ -0,0 +1,398 @@ + + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Tokenizes PHP code. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class PHP_CodeSniffer_Tokenizers_PHP +{ + + /** + * A list of tokens that are allowed to open a scope. + * + * This array also contains information about what kind of token the scope + * opener uses to open and close the scope, if the token strictly requires + * an opener, if the token can share a scope closer, and who it can be shared + * with. An example of a token that shares a scope closer is a CASE scope. + * + * @var array + */ + public $scopeOpeners = array( + T_IF => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_TRY => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_CATCH => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_ELSE => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_ELSEIF => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_FOR => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_FOREACH => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_INTERFACE => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_FUNCTION => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_CLASS => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_WHILE => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => false, + 'shared' => false, + 'with' => array(), + ), + T_DO => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_SWITCH => array( + 'start' => T_OPEN_CURLY_BRACKET, + 'end' => T_CLOSE_CURLY_BRACKET, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + T_CASE => array( + 'start' => T_COLON, + 'end' => T_BREAK, + 'strict' => true, + 'shared' => true, + 'with' => array( + T_DEFAULT, + T_CASE, + ), + ), + T_DEFAULT => array( + 'start' => T_COLON, + 'end' => T_BREAK, + 'strict' => true, + 'shared' => true, + 'with' => array(T_CASE), + ), + T_START_HEREDOC => array( + 'start' => T_START_HEREDOC, + 'end' => T_END_HEREDOC, + 'strict' => true, + 'shared' => false, + 'with' => array(), + ), + ); + + /** + * A list of tokens that end the scope. + * + * This array is just a unique collection of the end tokens + * from the _scopeOpeners array. The data is duplicated here to + * save time during parsing of the file. + * + * @var array + */ + public $endScopeTokens = array( + T_CLOSE_CURLY_BRACKET, + T_BREAK, + T_END_HEREDOC, + ); + + + /** + * 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. + * + * @return array + */ + public function tokenizeString($string, $eolChar='\n') + { + $tokens = @token_get_all($string); + $finalTokens = array(); + + $newStackPtr = 0; + $numTokens = count($tokens); + for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) { + $token = $tokens[$stackPtr]; + $tokenIsArray = is_array($token); + + /* + 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 ($tokens[($stackPtr + 1)][1] === "\n") { + // The next token's content has been merged into this token, + // so we can skip it. + $stackPtr++; + } else { + $tokens[($stackPtr + 1)][1] = substr($tokens[($stackPtr + 1)][1], 1); + } + } + }//end if + + /* + If this is a double quoted string, PHP will tokenise 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 === '"') { + $tokenContent = '"'; + $nestedVars = array(); + for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { + $subTokenIsArray = is_array($tokens[$i]); + + if ($subTokenIsArray === true) { + $tokenContent .= $tokens[$i][1]; + if ($tokens[$i][1] === '{') { + $nestedVars[] = $i; + } + } else { + $tokenContent .= $tokens[$i]; + if ($tokens[$i] === '}') { + array_pop($nestedVars); + } + } + + if ($subTokenIsArray === false && $tokens[$i] === '"' && empty($nestedVars) === true) { + // We found the other end of the double quoted string. + break; + } + } + + $stackPtr = $i; + + // Convert each line within the double quoted string to a + // new token, so it conforms with other multiple line tokens. + $tokenLines = explode($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'] .= $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 tokenise 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] = PHP_CodeSniffer::standardiseToken($token); + $newStackPtr++; + + $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]; + } + } + + $stackPtr = $i; + + // Convert each line within the heredoc to a + // new token, so it conforms with other multiple line tokens. + $tokenLines = explode($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'] .= $eolChar; + } + + $newToken['code'] = T_HEREDOC; + $newToken['type'] = 'T_HEREDOC'; + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + } + + // Add the end heredoc token to the final array. + $finalTokens[$newStackPtr] = PHP_CodeSniffer::standardiseToken($tokens[$stackPtr]); + $newStackPtr++; + + // Continue, as we're done with this token. + 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 asertain where errors occur on a line. + Note that $token[1] is the token's content. + */ + + if ($tokenIsArray === true && strpos($token[1], $eolChar) !== false) { + $tokenLines = explode($eolChar, $token[1]); + $numLines = count($tokenLines); + $tokenName = token_name($token[0]); + + for ($i = 0; $i < $numLines; $i++) { + $newToken['content'] = $tokenLines[$i]; + if ($i === ($numLines - 1)) { + if ($tokenLines[$i] === '') { + break; + } + } else { + $newToken['content'] .= $eolChar; + } + + $newToken['type'] = $tokenName; + $newToken['code'] = $token[0]; + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + } + } else { + $newToken = PHP_CodeSniffer::standardiseToken($token); + + // This is a special condition for T_ARRAY tokens use to + // type hint function arguments as being arrays. We want to keep + // the parenthsis map clean, so let's tag these tokens as + // T_ARRAY_HINT. + if ($newToken['code'] === T_ARRAY) { + // Recalculate number of tokens. + $numTokens = count($tokens); + for ($i = $stackPtr; $i < $numTokens; $i++) { + if (is_array($tokens[$i]) === false) { + if ($tokens[$i] === '(') { + break; + } + } else if ($tokens[$i][0] === T_VARIABLE) { + $newToken['code'] = T_ARRAY_HINT; + $newToken['type'] = 'T_ARRAY_HINT'; + break; + } + } + } + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + }//end if + }//end for + + return $finalTokens; + + }//end tokenizeString() + + +}//end class + +?> diff --git a/lib/pear/PHP/CodeSniffer/Tokens.php b/lib/pear/PHP/CodeSniffer/Tokens.php new file mode 100644 index 0000000000..4b96f9f318 --- /dev/null +++ b/lib/pear/PHP/CodeSniffer/Tokens.php @@ -0,0 +1,389 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +define('T_NONE', 0); +define('T_OPEN_CURLY_BRACKET', 1000); +define('T_CLOSE_CURLY_BRACKET', 1001); +define('T_OPEN_SQUARE_BRACKET', 1002); +define('T_CLOSE_SQUARE_BRACKET', 1003); +define('T_OPEN_PARENTHESIS', 1004); +define('T_CLOSE_PARENTHESIS', 1005); +define('T_COLON', 1006); +define('T_STRING_CONCAT', 1007); +define('T_INLINE_THEN', 1008); +define('T_NULL', 1009); +define('T_FALSE', 1010); +define('T_TRUE', 1011); +define('T_SEMICOLON', 1012); +define('T_EQUAL', 1013); +define('T_MULTIPLY', 1015); +define('T_DIVIDE', 1016); +define('T_PLUS', 1017); +define('T_MINUS', 1018); +define('T_MODULUS', 1019); +define('T_POWER', 1020); +define('T_BITWISE_AND', 1021); +define('T_BITWISE_OR', 1022); +define('T_ARRAY_HINT', 1023); +define('T_GREATER_THAN', 1024); +define('T_LESS_THAN', 1025); +define('T_BOOLEAN_NOT', 1026); +define('T_SELF', 1027); +define('T_PARENT', 1028); +define('T_DOUBLE_QUOTED_STRING', 1029); +define('T_COMMA', 1030); +define('T_HEREDOC', 1031); +define('T_PROTOTYPE', 1032); +define('T_THIS', 1033); +define('T_REGULAR_EXPRESSION', 1034); + +/** + * The Tokens class contains weightings for tokens based on their + * probability of occurance in a file. + * + * The less the chance of a high occurance of an abitrary token, the higher + * the weighting. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.1.0 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +final class PHP_CodeSniffer_Tokens +{ + + /** + * The token weightings. + * + * @var array(int => int) + */ + public static $weightings = array( + T_CLASS => 1000, + T_FUNCTION => 100, + + /* + Conditions. + */ + + T_WHILE => 50, + T_FOR => 50, + T_FOREACH => 50, + T_IF => 50, + T_ELSE => 50, + T_ELSEIF => 50, + T_WHILE => 50, + T_DO => 50, + T_TRY => 50, + T_CATCH => 50, + T_SWITCH => 50, + + T_SELF => 25, + T_PARENT => 25, + + /* + Operators and arithmetic. + */ + + T_BITWISE_AND => 8, + T_BITWISE_OR => 8, + + T_MULTIPLY => 5, + T_DIVIDE => 5, + T_PLUS => 5, + T_MINUS => 5, + T_MODULUS => 5, + T_POWER => 5, + + T_EQUAL => 5, + T_AND_EQUAL => 5, + T_CONCAT_EQUAL => 5, + T_DIV_EQUAL => 5, + T_MINUS_EQUAL => 5, + T_MOD_EQUAL => 5, + T_MUL_EQUAL => 5, + T_OR_EQUAL => 5, + T_PLUS_EQUAL => 5, + T_XOR_EQUAL => 5, + + T_BOOLEAN_AND => 5, + T_BOOLEAN_OR => 5, + + /* + Equality. + */ + + T_IS_EQUAL => 5, + T_IS_NOT_EQUAL => 5, + T_IS_IDENTICAL => 5, + T_IS_NOT_IDENTICAL => 5, + T_IS_SMALLER_OR_EQUAL => 5, + T_IS_GREATER_OR_EQUAL => 5, + + T_WHITESPACE => 0, + ); + + /** + * Tokens that represent assignments. + * + * @var array(int) + */ + public static $assignmentTokens = array( + T_EQUAL, + T_AND_EQUAL, + T_CONCAT_EQUAL, + T_DIV_EQUAL, + T_MINUS_EQUAL, + T_MOD_EQUAL, + T_MUL_EQUAL, + T_PLUS_EQUAL, + T_XOR_EQUAL, + ); + + /** + * Tokens that represent equality comparisons. + * + * @var array(int) + */ + public static $equalityTokens = array( + T_IS_EQUAL, + T_IS_NOT_EQUAL, + T_IS_IDENTICAL, + T_IS_NOT_IDENTICAL, + T_IS_SMALLER_OR_EQUAL, + T_IS_GREATER_OR_EQUAL, + ); + + /** + * Tokens that represent comparison operator. + * + * @var array(int) + */ + public static $comparisonTokens = array( + T_IS_EQUAL, + T_IS_IDENTICAL, + T_IS_NOT_EQUAL, + T_IS_NOT_IDENTICAL, + T_LESS_THAN, + T_GREATER_THAN, + T_IS_SMALLER_OR_EQUAL, + T_IS_GREATER_OR_EQUAL, + ); + + /** + * Tokens that represent arithmetic operators. + * + * @var array(int) + */ + public static $arithmeticTokens = array( + T_PLUS, + T_MINUS, + T_MULTIPLY, + T_DIVIDE, + T_MODULUS, + ); + + /** + * Tokens that represent casting. + * + * @var array(int) + */ + public static $castTokens = array( + T_INT_CAST, + T_STRING_CAST, + T_DOUBLE_CAST, + T_ARRAY_CAST, + T_BOOL_CAST, + T_OBJECT_CAST, + T_UNSET_CAST, + ); + + /** + * Token types that open parethesis. + * + * @var array(int) + */ + public static $parenthesisOpeners = array( + T_ARRAY, + T_FUNCTION, + T_WHILE, + T_FOR, + T_FOREACH, + T_SWITCH, + T_IF, + T_ELSEIF, + T_CATCH, + ); + + /** + * Tokens that are allowed to open scopes. + * + * @var array(int) + */ + public static $scopeOpeners = array( + T_CLASS, + T_INTERFACE, + T_FUNCTION, + T_IF, + T_SWITCH, + T_CASE, + T_DEFAULT, + T_WHILE, + T_ELSE, + T_ELSEIF, + T_FOR, + T_FOREACH, + T_DO, + T_TRY, + T_CATCH, + ); + + /** + * Tokens that represent scope modifiers. + * + * @var array(int) + */ + public static $scopeModifiers = array( + T_PRIVATE, + T_PUBLIC, + T_PROTECTED, + ); + + /** + * Tokens that perform operations. + * + * @var array(int) + */ + public static $operators = array( + T_MINUS, + T_PLUS, + T_MULTIPLY, + T_DIVIDE, + T_MODULUS, + T_POWER, + T_BITWISE_AND, + T_BITWISE_OR, + ); + + /** + * Tokens that perform operations. + * + * @var array(int) + */ + public static $blockOpeners = array( + T_OPEN_CURLY_BRACKET, + T_OPEN_SQUARE_BRACKET, + T_OPEN_PARENTHESIS, + ); + + /** + * Tokens that don't represent code. + * + * @var array(int) + */ + public static $emptyTokens = array( + T_WHITESPACE, + T_COMMENT, + T_DOC_COMMENT, + ); + + /** + * Tokens that are comments. + * + * @var array(int) + */ + public static $commentTokens = array( + T_COMMENT, + T_DOC_COMMENT, + ); + + /** + * Tokens that represent strings. + * + * Note that T_STRINGS are NOT represented in this list. + * + * @var array(int) + */ + public static $stringTokens = array( + T_CONSTANT_ENCAPSED_STRING, + T_DOUBLE_QUOTED_STRING, + ); + + /** + * Tokens that include files. + * + * @var array(int) + */ + public static $includeTokens = array( + T_REQUIRE_ONCE, + T_REQUIRE, + T_INCLUDE_ONCE, + T_INCLUDE, + ); + + + /** + * A PHP_CodeSniffer_Tokens class cannot be constructed. + * + * Only static calls are allowed. + */ + private function __construct() + { + + }//end __construct() + + + /** + * Returns the highest weighted token type. + * + * Tokens are weighted by their approximate frequency of appearance in code + * - the less frequently they appear in the code, the higher the weighting. + * For example T_CLASS tokens apprear very infrequently in a file, and + * therefore have a high weighting. + * + * Returns false if there are no weightings for any of the specified tokens. + * + * @param array(int) $tokens The token types to get the highest weighted + * type for. + * + * @return int The highest weighted token. + */ + public static function getHighestWeightedToken(array $tokens) + { + $highest = -1; + $highestType = false; + + $weights = self::$weightings; + + foreach ($tokens as $token) { + if (isset($weights[$token]) === true && $weights[$token] > $highest) { + $highest = $weights[$token]; + $highestType = $token; + } + } + + return $highestType; + + }//end getHighestWeightedToken() + + +}//end class + +?> diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml new file mode 100644 index 0000000000..1eb822b30e --- /dev/null +++ b/lib/thirdpartylibs.xml @@ -0,0 +1,201 @@ + + + + adodb + AdoDB + GPL/BSD + 5.08 + + + alfresco + Alfresco + GPL + + + + bennu + Bennu + LGPL + + + + dragmath + DragMath + GPL + 0.7.7 + + + editor + TinyMCE + LGPL + 3.2.3.1 + + + evalmath + EvalMath + GPL + + + + excel + Spreadsheet WriteExcel + LGPL + + + + fpdf + FPDF + Freeware + 1.53 + + + geoip + GeoIP + LGPL + 1.6 + + + htmlpurifier + HTML Purifier + LGPL + 3.3.0 + + + jabber + XMPPHP + GPL + + + + magpie + Magpie + GPL + 0.72 + + + mp3player + + + + + + overlib + Overlib + http://www.bosrup.com/web/overlib/?License + 4.21 + + + pear + Multiple libraries + LGPL + + + + phpmailer + PHPMailer + LGPL + + + + phpxml + XML Library + http://keithdevens.com/software/license + 1.2b + + + simplepie + SimplePie + BSD + 1.1.3 + + + simpletestlib + Simpletest + LGPL + 1.0.1 + + + snoopy + Snoopy + LGPL + 1.2.4 + + + soap + SOAP wrappers + GPL/LGPL + + + + tcpdf + TCPDF + LGPL + 4.0.015 + + + typo3 + Typo3 + GPL + 4.2.1 + + + yui + YUI + BSD + 2.7.0 + + + zend + Zend Framework + new BSD + 1.7.3 + + + form + MoodleForms + GPL + + + + base32.php + Base32 Library + GPL + + + + odbc.php + ODBC server/client + Public Domain + + + + html2text.php + HTML2Text + GPL + 1.0.0 + + + kses.php + KSES + GPL + 0.2.2 + + + markdown.php + Markdown original+extra + + 1.1.6 + + + recaptchalib.php + ReCAPTCHA + + + + + xmlize.php + XMLize + GPL + 1.0 + + diff --git a/phpcs b/phpcs new file mode 100755 index 0000000000..a1addb1b8a --- /dev/null +++ b/phpcs @@ -0,0 +1,37 @@ +#!/usr/bin/php + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id$ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +error_reporting(E_ALL | E_STRICT); + +if (is_file(dirname(__FILE__).'/../CodeSniffer/CLI.php') === true) { + include_once dirname(__FILE__).'/../CodeSniffer/CLI.php'; +} else { + include_once 'lib/pear/PHP/CodeSniffer/CLI.php'; +} + +$phpcs = new PHP_CodeSniffer_CLI(); +$phpcs->checkRequirements(); + +$numErrors = $phpcs->process(); +if ($numErrors === 0) { + exit(0); +} else { + exit(1); +} + +?>