--- /dev/null
+<?php
+/**
+ * PHP_CodeSniffer tokenises PHP code and detects violations of a
+ * defined set of coding standards.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
+ echo '<phpcs version="1.1.0">'.PHP_EOL;
+
+ $errorsShown = 0;
+
+ $report = $this->prepareErrorReport($showWarnings);
+ foreach ($report['files'] as $filename => $file) {
+ if (empty($file['messages']) === true) {
+ continue;
+ }
+
+ echo ' <file name="'.$filename.'" errors="'.$file['errors'].'" warnings="'.$file['warnings'].'">'.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']).'</'.$error['type'].'>'.PHP_EOL;
+ $errorsShown++;
+ }
+ }
+ }//end foreach
+
+ echo ' </file>'.PHP_EOL;
+
+ }//end foreach
+
+ echo '</phpcs>'.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 '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
+ echo '<checkstyle version="1.1.0">'.PHP_EOL;
+
+ $errorsShown = 0;
+
+ $report = $this->prepareErrorReport($showWarnings);
+ foreach ($report['files'] as $filename => $file) {
+ echo ' <file name="'.$filename.'">'.PHP_EOL;
+
+ foreach ($file['messages'] as $line => $lineErrors) {
+ foreach ($lineErrors as $column => $colErrors) {
+ foreach ($colErrors as $error) {
+ $error['type'] = strtolower($error['type']);
+ echo ' <error';
+ echo ' line="'.$line.'" column="'.$column.'"';
+ echo ' severity="'.$error['type'].'"';
+ $message = utf8_encode(htmlspecialchars($error['message']));
+ echo ' message="'.$message.'"';
+ echo '/>'.PHP_EOL;
+ $errorsShown++;
+ }
+ }
+ }//end foreach
+
+ echo ' </file>'.PHP_EOL;
+
+ }//end foreach
+
+ echo '</checkstyle>'.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 <code>token_get_all()</code> 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
+
+?>
--- /dev/null
+<?php
+/**
+ * A class to process command line phpcs scripts.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @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('/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/', substr($arg, 7));
+ } else if (substr($arg, 0, 10) === 'generator=') {
+ $values['generator'] = substr($arg, 10);
+ } else if (substr($arg, 0, 10) === 'tab-width=') {
+ $values['tabWidth'] = (int) substr($arg, 10);
+ } else {
+ $values = $this->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=<report>]'.PHP_EOL;
+ echo ' [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL;
+ echo ' [--generator=<generator>] [--extensions=<extensions>]'.PHP_EOL;
+ echo ' [--ignore=<patterns>] [--tab-width=<width>] <file> ...'.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 ' <file> One or more files and/or directories to check'.PHP_EOL;
+ echo ' <extensions> A comma separated list of file extensions to check'.PHP_EOL;
+ echo ' (only valid if checking a directory)'.PHP_EOL;
+ echo ' <patterns> A comma separated list of patterns that are used'.PHP_EOL;
+ echo ' to ignore directories and files'.PHP_EOL;
+ echo ' <width> The number of spaces each tab represents'.PHP_EOL;
+ echo ' <generator> The name of a doc generator to use'.PHP_EOL;
+ echo ' (forces doc generation instead of checking)'.PHP_EOL;
+ echo ' <report> 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
+
+?>
--- /dev/null
+<?php
+/**
+ * A class to handle most of the parsing operations of a doc comment element.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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:
+ *
+ * <code>
+ * protected function getSubElements()
+ * {
+ * return array(
+ * 'type',
+ * 'variable',
+ * 'comment',
+ * );
+ * }
+ * </code>
+ *
+ * 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:
+ *
+ * <code>
+ * protected function processSubElement($name, $content, $whitespaceBefore)
+ * {
+ * if ($name === 'type') {
+ * echo 'The name of the variable was '.$content;
+ * }
+ * // Process other tags.
+ * }
+ * </code>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Parses doc comments.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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:
+ *
+ * <ul>
+ * <li>The short description and the long description</li>
+ * <li>@see</li>
+ * <li>@link</li>
+ * <li>@deprecated</li>
+ * <li>@since</li>
+ * </ul>
+ *
+ * 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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Parses Class doc comments.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * A class to represent Comments of a doc comment.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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.
+ * <code>
+ * /** <--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
+ * {@/}
+ * </code>
+ *
+ * Note that the sentence before two newlines is assumed
+ * the short comment description.
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * A DocElement represents a logical element within a Doc Comment.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Parses function doc comments.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
--- /dev/null
+<?php
+/**
+ * Parses class member comments.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * A class to represent elements that have a value => comment format.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * A class to represent param tags within a function comment.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * An exception to be thrown when a DocCommentParser finds an anomilty in a
+ * doc comment.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * A class to represent single element doc tags.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * The base class for all PHP_CodeSniffer documentation generators.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * A doc generator that outputs documentation in one big HTML file.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 '<html>'.PHP_EOL;
+ echo ' <head>'.PHP_EOL;
+ echo " <title>$standard Coding Standards</title>".PHP_EOL;
+ echo ' <style>
+ body {
+ background-color: #FFFFFF;
+ font-size: 14px;
+ font-family: Arial, Helvetica, sans-serif;
+ color: #000000;
+ }
+
+ h1 {
+ color: #666666;
+ font-size: 20px;
+ font-weight: bold;
+ margin-top: 0px;
+ background-color: #E6E7E8;
+ padding: 20px;
+ border: 1px solid #BBBBBB;
+ }
+
+ h2 {
+ color: #00A5E3;
+ font-size: 16px;
+ font-weight: normal;
+ margin-top: 50px;
+ }
+
+ .code-comparison {
+ width: 100%;
+ }
+
+ .code-comparison td {
+ border: 1px solid #CCCCCC;
+ }
+
+ .code-comparison-title, .code-comparison-code {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #000000;
+ vertical-align: top;
+ padding: 4px;
+ width: 50%;
+ background-color: #F1F1F1;
+ line-height: 15px;
+ }
+
+ .code-comparison-code {
+ font-family: Courier;
+ background-color: #F9F9F9;
+ }
+
+ .code-comparison-highlight {
+ background-color: #DDF1F7;
+ border: 1px solid #00A5E3;
+ line-height: 15px;
+ }
+
+ .tag-line {
+ text-align: center;
+ width: 100%;
+ margin-top: 30px;
+ font-size: 12px;
+ }
+
+ .tag-line a {
+ color: #000000;
+ }
+ </style>'.PHP_EOL;
+ echo ' </head>'.PHP_EOL;
+ echo ' <body>'.PHP_EOL;
+ echo " <h1>$standard Coding Standards</h1>".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 ' <h2>Table of Contents</h2>'.PHP_EOL;
+ echo ' <ul class="toc">'.PHP_EOL;
+
+ foreach ($standardFiles as $standard) {
+ $doc = new DOMDocument();
+ $doc->load($standard);
+ $documentation = $doc->getElementsByTagName('documentation')->item(0);
+ $title = $this->getTitle($documentation);
+ echo ' <li><a href="#'.str_replace(' ', '-', $title)."\">$title</a></li>".PHP_EOL;
+ }
+
+ echo ' </ul>'.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 ' <div class="tag-line">';
+ echo 'Documentation generated on '.date('r');
+ echo ' by <a href="http://pear.php.net/package/PHP_CodeSniffer">PHP_CodeSniffer 1.1.0</a>';
+ echo '</div>'.PHP_EOL;
+ error_reporting(E_ALL | E_STRICT);
+
+ echo ' </body>'.PHP_EOL;
+ echo '</html>'.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 ' <a name="'.str_replace(' ', '-', $title).'" />'.PHP_EOL;
+ echo " <h2>$title</h2>".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>', '<em>', $content);
+ $content = str_replace('</em>', '</em>', $content);
+
+ echo " <p class=\"text\">$content</p>".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", '</br>', $first);
+ $first = str_replace(' ', ' ', $first);
+ $first = str_replace('<em>', '<span class="code-comparison-highlight">', $first);
+ $first = str_replace('</em>', '</span>', $first);
+
+ $secondTitle = $codeBlocks->item(1)->getAttribute('title');
+ $second = trim($codeBlocks->item(1)->nodeValue);
+ $second = str_replace("\n", '</br>', $second);
+ $second = str_replace(' ', ' ', $second);
+ $second = str_replace('<em>', '<span class="code-comparison-highlight">', $second);
+ $second = str_replace('</em>', '</span>', $second);
+
+ echo ' <table class="code-comparison">'.PHP_EOL;
+ echo ' <tr>'.PHP_EOL;
+ echo " <td class=\"code-comparison-title\">$firstTitle</td>".PHP_EOL;
+ echo " <td class=\"code-comparison-title\">$secondTitle</td>".PHP_EOL;
+ echo ' </tr>'.PHP_EOL;
+ echo ' <tr>'.PHP_EOL;
+ echo " <td class=\"code-comparison-code\">$first</td>".PHP_EOL;
+ echo " <td class=\"code-comparison-code\">$second</td>".PHP_EOL;
+ echo ' </tr>'.PHP_EOL;
+ echo ' </table>'.PHP_EOL;
+
+ }//end printCodeComparisonBlock()
+
+
+}//end class
+
+?>
--- /dev/null
+<?php
+/**
+ * A doc generator that outputs text-based documentation.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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('<em>', '*', $text);
+ $text = str_replace('</em>', '*', $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('<em>', '', $first);
+ $first = str_replace('</em>', '', $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('<em>', '', $second);
+ $second = str_replace('</em>', '', $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
+
+?>
--- /dev/null
+<?php
+/**
+ * An exception thrown by PHP_CodeSniffer when it encounters an unrecoverable error.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
--- /dev/null
+<?php
+/**
+ * A PHP_CodeSniffer_File object represents a PHP source file and the tokens
+ * associated with it.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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.
+ *
+ * <b>Token Information</b>
+ *
+ * Each token within the stack contains information about itself:
+ *
+ * <code>
+ * 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)
+ * ),
+ * );
+ * </code>
+ *
+ * <b>Conditional Tokens</b>
+ *
+ * In addition to the standard token fields, conditions contain information to
+ * determine where their scope begins and ends:
+ *
+ * <code>
+ * 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
+ * );
+ * </code>
+ *
+ * The condition, the scope opener and the scope closer each contain this
+ * information.
+ *
+ * <b>Parenthesis Tokens</b>
+ *
+ * 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.
+ *
+ * <code>
+ * array(
+ * 'parenthesis_opener' => 34,
+ * 'parenthesis_closer' => 40,
+ * );
+ * </code>
+ *
+ * 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.
+ *
+ * <code>
+ * array(
+ * 'parenthesis_opener' => 34,
+ * 'parenthesis_closer' => 40,
+ * 'parenthesis_owner' => 33,
+ * );
+ * </code>
+ *
+ * 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.
+ *
+ * <code>
+ * 'nested_parentheisis' => array(
+ * 12 => 15
+ * 11 => 14
+ * );
+ * </code>
+ *
+ * <b>Extended Tokens</b>
+ *
+ * PHP_CodeSniffer extends and augments some of the tokens created by
+ * <i>token_get_all()</i>. A full list of these tokens can be seen in the
+ * <i>Tokens.php</i> file.
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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:
+ *
+ * <code>
+ * 0 => array(
+ * 'name' => '$var', // The variable name.
+ * 'pass_by_reference' => false, // Passed by reference.
+ * 'type_hint' => string, // Type hint for array or custom type
+ * )
+ * </code>
+ *
+ * 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:
+ * <code>
+ * 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.
+ * );
+ * </code>
+ *
+ * @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:
+ *
+ * <code>
+ * array(
+ * 'scope' => 'public', // public private or protected
+ * 'is_static' => false, // true if the static keyword was found.
+ * );
+ * </code>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Represents a PHP_CodeSniffer sniff for sniffing coding standards.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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:
+ *
+ * <code>
+ * return array(
+ * T_WHITESPACE,
+ * T_DOC_COMMENT,
+ * T_COMMENT,
+ * );
+ * </code>
+ *
+ * @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:
+ *
+ * <code>
+ * $tokens = $phpcsFile->getTokens();
+ * echo 'Encountered a '.$tokens[$stackPtr]['type'].' token';
+ * echo 'token information: ';
+ * print_r($tokens[$stackPtr]);
+ * </code>
+ *
+ * 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:
+ *
+ * <code>
+ * $phpcsFile->addError('Encountered an error', $stackPtr);
+ * </code>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Processes pattern strings and checks that the code conforms to the pattern.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <i>AbstractPatternTest</i> should implement the
+ * <i>getPatterns()</i> 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:
+ * <code>
+ * array(
+ * T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token
+ * // should occur in the pattern.
+ * );
+ * </code>
+ *
+ * @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('<?php '.$str.'?>');
+
+ // Remove the <?php tag from the front and the end php tag from the back.
+ $tokens = array_slice($tokens, 1, (count($tokens) - 2));
+
+ foreach ($tokens as &$token) {
+ $token = PHP_CodeSniffer::standardiseToken($token);
+ }
+
+ $patterns = array();
+ foreach ($tokens as $patternInfo) {
+ $patterns[] = array(
+ 'type' => 'token',
+ 'token' => $patternInfo['code'],
+ 'value' => $patternInfo['content'],
+ );
+ }
+
+ return $patterns;
+
+ }//end _createTokenPattern()
+
+
+}//end class
+
+?>
--- /dev/null
+<?php
+/**
+ * An AbstractScopeTest allows for tests that extend from this class to
+ * listen for tokens within a particluar scope.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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:
+ * <code>
+ * 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;
+ * }
+ * }
+ * </code>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * A class to find T_VARIABLE tokens.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Bass Coding Standard class.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * An exception thrown if the pattern being processed is not supposed to be
+ * validating the code in question.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle Coding Standard.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @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 <nicolasconnault@gmail.com>
+ * @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
+?>
--- /dev/null
+<?php
+/**
+ * Class Declaration Test.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * This file is part of the CodeAnalysis addon for PHP_CodeSniffer.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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.
+ *
+ * <code>
+ * stmt {
+ * // foo
+ * }
+ * stmt (conditions) {
+ * // foo
+ * }
+ * </code>
+ *
+ * Statements covered by this sniff are <b>catch</b>, <b>do</b>, <b>else</b>,
+ * <b>elsif</b>, <b>for</b>, <b>foreach<b>, <b>if</b>, <b>switch</b>, <b>try</b>
+ * and <b>while</b>.
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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<boolean>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * This file is part of the CodeAnalysis addon for PHP_CodeSniffer.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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.
+ *
+ * <code>
+ * class Foo
+ * {
+ * public function bar($x)
+ * {
+ * for (;true;) true; // No Init or Update part, may as well be: while (true)
+ * }
+ * }
+ * </code>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * This file is part of the CodeAnalysis addon for PHP_CodeSniffer.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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.
+ *
+ * <code>
+ * class Foo
+ * {
+ * public function bar($x)
+ * {
+ * $a = array(1, 2, 3, 4);
+ * for ($i = 0; $i < count($a); $i++) {
+ * $a[$i] *= $i;
+ * }
+ * }
+ * }
+ * </code>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * This file is part of the CodeAnalysis addon for PHP_CodeSniffer.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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.
+ *
+ * <code>
+ * class Foo
+ * {
+ * public function bar($x)
+ * {
+ * for ($i = 0; $i < 10; $i++)
+ * {
+ * for ($k = 0; $k < 20; $i++)
+ * {
+ * echo 'Hello';
+ * }
+ * }
+ * }
+ * }
+ * </code>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * This file is part of the CodeAnalysis addon for PHP_CodeSniffer.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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 <b>true</b> or <b>false</b>
+ *
+ * <code>
+ * class Foo
+ * {
+ * public function close()
+ * {
+ * if (true)
+ * {
+ * // ...
+ * }
+ * }
+ * }
+ * </code>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * This file is part of the CodeAnalysis addon for PHP_CodeSniffer.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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.
+ *
+ * <code>
+ * final class Foo
+ * {
+ * public final function bar()
+ * {
+ * }
+ * }
+ * </code>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * This file is part of the CodeAnalysis addon for PHP_CodeSniffer.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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 <mapi@manuel-pichler.de>
+ * @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 <token>.
+ 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('<?php %s;?>', $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
+
+?>
--- /dev/null
+<?php
+/**
+ * This file is part of the CodeAnalysis addon for PHP_CodeSniffer.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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.
+ *
+ * <code>
+ * final class Foo
+ * {
+ * public final function bar()
+ * {
+ * }
+ * }
+ * </code>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Manuel Pichler <mapi@manuel-pichler.de>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Parses and verifies the doc comments for classes.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 :
+ * <ul>
+ * <li>A doc comment exists.</li>
+ * <li>There is a blank newline after the short description.</li>
+ * <li>There is a blank newline between the long and short description.</li>
+ * <li>There is a blank newline between the long description and tags.</li>
+ * <li>Check the order of the tags.</li>
+ * <li>Check the indentation of each tag.</li>
+ * <li>Check required and optional tags and the format of their content.</li>
+ * </ul>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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: <package_version>\" instead";
+ $this->currentFile->addWarning($error, $errorPos);
+ }
+ }
+
+ }//end processVersion()
+
+
+}//end class
+
+?>
--- /dev/null
+<?php
+/**
+ * Parses and verifies the doc comments for files.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @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 :
+ * <ul>
+ * <li>A doc comment exists.</li>
+ * <li>There is a blank newline after the short description.</li>
+ * <li>There is a blank newline between the long and short description.</li>
+ * <li>There is a blank newline between the long description and tags.</li>
+ * <li>A PHP version is specified.</li>
+ * <li>Check the order of the tags.</li>
+ * <li>Check the indentation of each tag.</li>
+ * <li>Check required and optional tags and the format of their content.</li>
+ * </ul>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @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 <http://www.gnu.org/licenses/>.';
+
+ $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 <username@example.com>"';
+ $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: <cvs_id>\" or \"SVN: <svn_id>\" instead";
+ $this->currentFile->addWarning($error, $errorPos);
+ }
+ }
+
+ }//end processVersion()
+
+
+}//end class
+
+?>
--- /dev/null
+<?php
+/**
+ * Parses and verifies the doc comments for functions.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 :
+ * <ul>
+ * <li>A comment exists</li>
+ * <li>There is a blank newline after the short description.</li>
+ * <li>There is a blank newline between the long and short description.</li>
+ * <li>There is a blank newline between the long description and tags.</li>
+ * <li>Parameter names represent those in the method.</li>
+ * <li>Parameter comments are in the correct order</li>
+ * <li>Parameter comments are complete</li>
+ * <li>A space is present before the first and after the last parameter</li>
+ * <li>A return type exists</li>
+ * <li>There must be one blank line between body and headline comments.</li>
+ * <li>Any throw tag must have an exception class.</li>
+ * </ul>
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * PHP_CodeSniffer_Sniffs_Moodle_Commenting_InlineCommentSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Verifies that control statements conform to their coding standards.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_ControlStructures_InlineControlStructureSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Files_IncludingFileSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @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 <nicolasconnault@gmail.com>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Files_LineEndingsSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Files_LineLengthSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Formatting_SpaceAfterCastSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Functions_FunctionCallArgumentSpacingSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Functions_FunctionCallSignatureSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Functions_FunctionDeclarationSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @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 <nicolasconnault@gmail.com>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Functions_LowercaseFunctionKeywordsSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @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 <nicolasconnault@gmail.com>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Functions_ValidDefaultValueSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_NamingConventions_UpperCaseConstantNameSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_NamingConventions_ValidClassNameSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_NamingConventions_ValidFunctionNameSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_NamingConventions_ValidVariableNameSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ * @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 <nicolasconnault@gmail.com>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_PHP_DisallowShortOpenTagSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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'] === '<?') {
+ $error = 'Short PHP opening tag used. Found "'.$openTag['content'].'" Expected "<?php".';
+ $phpcsFile->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 "<?php echo '.$nextVar['content'].' ...".';
+ $phpcsFile->addError($error, $stackPtr);
+ }
+
+ }//end process()
+
+
+}//end class
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_PHP_LowerCaseConstantSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_PHP_LowercasePHPFunctionsSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Strings_DoubleQuoteUsageSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Strings_EchoedStringsSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_WhiteSpace_DisallowTabIndentSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Verifies that class members are spaced correctly.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Whitespace_ScopeClosingBraceSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Nicolas Connault <nicolasconnault@gmail.com>
+ *
+ * @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 <nicolasconnault@gmail.com>
+ *
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Moodle_Sniffs_Whitespace_ScopeIndentSniff.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Tokenizes JS code.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * Tokenizes PHP code.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?php
+/**
+ * The Tokens class contains weightings for tokens based on their
+ * probability of occurance in a file.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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 <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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
+
+?>
--- /dev/null
+<?xml version="1.0"?>
+<libraries>
+ <library>
+ <location>adodb</location>
+ <name>AdoDB</name>
+ <license>GPL/BSD</license>
+ <version>5.08</version>
+ </library>
+ <library>
+ <location>alfresco</location>
+ <name>Alfresco</name>
+ <license>GPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>bennu</location>
+ <name>Bennu</name>
+ <license>LGPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>dragmath</location>
+ <name>DragMath</name>
+ <license>GPL</license>
+ <version>0.7.7</version>
+ </library>
+ <library>
+ <location>editor</location>
+ <name>TinyMCE</name>
+ <license>LGPL</license>
+ <version>3.2.3.1</version>
+ </library>
+ <library>
+ <location>evalmath</location>
+ <name>EvalMath</name>
+ <license>GPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>excel</location>
+ <name>Spreadsheet WriteExcel</name>
+ <license>LGPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>fpdf</location>
+ <name>FPDF</name>
+ <license>Freeware</license>
+ <version>1.53</version>
+ </library>
+ <library>
+ <location>geoip</location>
+ <name>GeoIP</name>
+ <license>LGPL</license>
+ <version>1.6</version>
+ </library>
+ <library>
+ <location>htmlpurifier</location>
+ <name>HTML Purifier</name>
+ <license>LGPL</license>
+ <version>3.3.0</version>
+ </library>
+ <library>
+ <location>jabber</location>
+ <name>XMPPHP</name>
+ <license>GPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>magpie</location>
+ <name>Magpie</name>
+ <license>GPL</license>
+ <version>0.72</version>
+ </library>
+ <library>
+ <location>mp3player</location>
+ <name></name>
+ <license></license>
+ <version></version>
+ </library>
+ <library>
+ <location>overlib</location>
+ <name>Overlib</name>
+ <license>http://www.bosrup.com/web/overlib/?License</license>
+ <version>4.21</version>
+ </library>
+ <library>
+ <location>pear</location>
+ <name>Multiple libraries</name>
+ <license>LGPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>phpmailer</location>
+ <name>PHPMailer</name>
+ <license>LGPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>phpxml</location>
+ <name>XML Library</name>
+ <license>http://keithdevens.com/software/license</license>
+ <version>1.2b</version>
+ </library>
+ <library>
+ <location>simplepie</location>
+ <name>SimplePie</name>
+ <license>BSD</license>
+ <version>1.1.3</version>
+ </library>
+ <library>
+ <location>simpletestlib</location>
+ <name>Simpletest</name>
+ <license>LGPL</license>
+ <version>1.0.1</version>
+ </library>
+ <library>
+ <location>snoopy</location>
+ <name>Snoopy</name>
+ <license>LGPL</license>
+ <version>1.2.4</version>
+ </library>
+ <library>
+ <location>soap</location>
+ <name>SOAP wrappers</name>
+ <license>GPL/LGPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>tcpdf</location>
+ <name>TCPDF</name>
+ <license>LGPL</license>
+ <version>4.0.015</version>
+ </library>
+ <library>
+ <location>typo3</location>
+ <name>Typo3</name>
+ <license>GPL</license>
+ <version>4.2.1</version>
+ </library>
+ <library>
+ <location>yui</location>
+ <name>YUI</name>
+ <license>BSD</license>
+ <version>2.7.0</version>
+ </library>
+ <library>
+ <location>zend</location>
+ <name>Zend Framework</name>
+ <license>new BSD</license>
+ <version>1.7.3</version>
+ </library>
+ <library>
+ <location>form</location>
+ <name>MoodleForms</name>
+ <license>GPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>base32.php</location>
+ <name>Base32 Library</name>
+ <license>GPL</license>
+ <version></version>
+ </library>
+ <library>
+ <location>odbc.php</location>
+ <name>ODBC server/client</name>
+ <license>Public Domain</license>
+ <version></version>
+ </library>
+ <library>
+ <location>html2text.php</location>
+ <name>HTML2Text</name>
+ <license>GPL</license>
+ <version>1.0.0</version>
+ </library>
+ <library>
+ <location>kses.php</location>
+ <name>KSES</name>
+ <license>GPL</license>
+ <version>0.2.2</version>
+ </library>
+ <library>
+ <location>markdown.php</location>
+ <name>Markdown original+extra</name>
+ <license></license>
+ <version>1.1.6</version>
+ </library>
+ <library>
+ <location>recaptchalib.php</location>
+ <name>ReCAPTCHA</name>
+ <license></license>
+ <version></version>
+ </library>
+ <library>
+ <location>xmlize.php</location>
+ <name>XMLize</name>
+ <license>GPL</license>
+ <version>1.0</version>
+ </library>
+</libraries>
--- /dev/null
+#!/usr/bin/php
+<?php
+/**
+ * PHP_CodeSniffer tokenises PHP code and detects violations of a
+ * defined set of coding standards.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package PHP_CodeSniffer
+ * @author Greg Sherwood <gsherwood@squiz.net>
+ * @author Marc McIntyre <mmcintyre@squiz.net>
+ * @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);
+}
+
+?>