From 04fd547c691ca2baae3fa8e195a46b0c9dd671c5 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Mon, 2 May 2011 14:10:14 +0100 Subject: [PATCH] First commit based on John's earlier work --- README.md | 51 ++ Sniffs/Arrays/ArrayDeclarationSniff.php | 450 ++++++++++++++++++ Sniffs/Classes/ValidClassNameSniff.php | 93 ++++ Sniffs/Files/FileNameSniff.php | 72 +++ .../MultipleStatementAlignmentSniff.php | 292 ++++++++++++ .../Functions/FunctionCallSignatureSniff.php | 237 +++++++++ ...unctionDeclarationArgumentSpacingSniff.php | 186 ++++++++ .../ValidFunctionNameSniff.php | 124 +++++ Sniffs/Objects/ObjectInstantiationSniff.php | 82 ++++ ...DiscouragedFunctionsSniff.php.disabled.php | 72 +++ Sniffs/Strings/DoubleQuoteUsageSniff.php | 120 +++++ .../ControlStructureSpacingSniff.php | 189 ++++++++ Sniffs/WhiteSpace/OperatorSpacingSniff.php | 164 +++++++ WordpressCodingStandard.php | 77 +++ ruleset.xml | 6 + 15 files changed, 2215 insertions(+) create mode 100644 README.md create mode 100755 Sniffs/Arrays/ArrayDeclarationSniff.php create mode 100755 Sniffs/Classes/ValidClassNameSniff.php create mode 100755 Sniffs/Files/FileNameSniff.php create mode 100755 Sniffs/Formatting/MultipleStatementAlignmentSniff.php create mode 100755 Sniffs/Functions/FunctionCallSignatureSniff.php create mode 100755 Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php create mode 100755 Sniffs/NamingConventions/ValidFunctionNameSniff.php create mode 100755 Sniffs/Objects/ObjectInstantiationSniff.php create mode 100755 Sniffs/PHP/DiscouragedFunctionsSniff.php.disabled.php create mode 100755 Sniffs/Strings/DoubleQuoteUsageSniff.php create mode 100755 Sniffs/WhiteSpace/ControlStructureSpacingSniff.php create mode 100755 Sniffs/WhiteSpace/OperatorSpacingSniff.php create mode 100755 WordpressCodingStandard.php create mode 100755 ruleset.xml diff --git a/README.md b/README.md new file mode 100644 index 0000000000..31e4ac06f6 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +### Wordpress Coding Standards for Codesniffer 1.3.0 + +This is an version of the Coding Standards available at [Urban Giraffe][], which were missing a `ruleset.xml` file, that stopped them being detected when I downloaded them and tried passing some Wordpress core code through them. + +I know very little about Codesniffer beyond what I picked up in the last hour or two of reading the docs but I'm aiming to find a happy medium between letting developers stay productive, but stopping really shocking code being committed on projects, and me stumbling through this CodeSniffer tutorial here on [pear.php.net][] + +### How to use this + +Once you've installed PEAR, install Codesniffer: + + pear install --alldeps PHP_CodeSniffer + +Then install Wordpress standards + + pear install pear install http://github.com/mrchrisadams/PATH_TO_TARFILE.tar.gz + +Then run the PHP code sniffer commandline tool on a given file, for +example `wp-cron.php` + + phpcs --standard=Wordpress -s wp-cron.php + +You can use this to sniff individual files, or use different flags to recursively scan all the directories in a project. This command will show you each file it's scanning, and how many errors it's finding: + + phpcs -p -s -v --standard=Wordpress . + +Output will like this: + + Registering sniffs in Wordpress standard... DONE (11 sniffs registered) + Creating file list... DONE (705 files in queue) + Processing index.php [47 tokens in 31 lines]... DONE in < 1 second (2 errors, 0 warnings) + Processing wp-activate.php [750 tokens in 102 lines]... DONE in < 1 second (47 errors, 2 warnings) + Processing admin-ajax.php [14523 tokens in 1475 lines]... DONE in 2 seconds (449 errors, 44 warnings) + Processing admin-footer.php [183 tokens in 43 lines]... DONE in < 1 second (19 errors, 0 warnings) + Processing admin-functions.php [43 tokens in 16 lines]... DONE in < 1 second (2 errors, 0 warnings) + Processing admin-header.php [1619 tokens in 196 lines]... DONE in < 1 second (110 errors, 1 warnings) + Processing admin-post.php [144 tokens in 33 lines]... DONE in < 1 second (8 errors, 0 warnings) + Processing admin.php [1906 tokens in 238 lines]... DONE in 1 second (128 errors, 1 warnings) + Processing async-upload.php [623 tokens in 70 lines]... DONE in < 1 second (41 errors, 0 warnings) + Processing comment.php [2241 tokens in 289 lines]... DONE in < 1 second (110 errors, 3 warnings) + Processing colors-classic-rtl.css [517 tokens in 1 lines]... DONE in < 1 second (0 errors, 0 warnings) + Processing colors-classic-rtl.dev.css [661 tokens in 79 lines]... DONE in < 1 second (0 errors, 0 warnings) + Processing colors-classic.css ^C + + ... and so on... + +### Caveats + +Right now, this standard is so pedantic it's almost useless. Over the coming weeks, I'm hoping to work out how to make it more reasonable, so can serve a useful purpose on future coding projects. + +[pear.php.net]: http://pear.php.net/manual/en/package.php.php-codesniffer.coding-standard-tutorial.php +[Urban Giraffe]: http://urbangiraffe.com/articles/wordpress-codesniffer-standard/ diff --git a/Sniffs/Arrays/ArrayDeclarationSniff.php b/Sniffs/Arrays/ArrayDeclarationSniff.php new file mode 100755 index 0000000000..abd474443e --- /dev/null +++ b/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -0,0 +1,450 @@ + + * @author Greg Sherwood + * @author Marc McIntyre + */ + +/** + * Enforces WordPress array format + * + * @category PHP + * @package PHP_CodeSniffer + * @author John Godley + * @author Greg Sherwood + * @author Marc McIntyre + */ +class WordPress_Sniffs_Arrays_ArrayDeclarationSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return array(T_ARRAY); + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The current file being checked. + * @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(); + + // Array keyword should be lower case. + if (strtolower($tokens[$stackPtr]['content']) !== $tokens[$stackPtr]['content']) { + $error = 'Array keyword should be lower case; expected "array" but found "'.$tokens[$stackPtr]['content'].'"'; + $phpcsFile->addError($error, $stackPtr); + } + + $arrayStart = $tokens[$stackPtr]['parenthesis_opener']; + $arrayEnd = $tokens[$arrayStart]['parenthesis_closer']; + $keywordStart = $tokens[$stackPtr]['column']; + + if ($arrayStart != ($stackPtr + 1)) { + $error = 'There must be no space between the Array keyword and the opening parenthesis'; + $phpcsFile->addError($error, $stackPtr); + } + + // Check for empty arrays. + $content = $phpcsFile->findNext(array(T_WHITESPACE), ($arrayStart + 1), ($arrayEnd + 1), true); + if ($content === $arrayEnd) { + // Empty array, but if the brackets aren't together, there's a problem. + if (($arrayEnd - $arrayStart) !== 1) { + $error = 'Empty array declaration must have no space between the parentheses'; + $phpcsFile->addError($error, $stackPtr); + + // We can return here because there is nothing else to check. All code + // below can assume that the array is not empty. + return; + } + } + + if ($tokens[$arrayStart]['line'] === $tokens[$arrayEnd]['line']) { + $openBracket = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($tokens[($openBracket + 1)]['code'] !== T_WHITESPACE && $tokens[($openBracket + 1)]['code'] !== T_CLOSE_PARENTHESIS) { + // Checking this: $value = my_function([*]...). + $error = 'No space after opening parenthesis of array prohibited'; + $phpcsFile->addError($error, $stackPtr); + } + $closer = $tokens[$openBracket]['parenthesis_closer']; + + if ($tokens[($closer - 1)]['code'] !== T_WHITESPACE) { + // Checking this: $value = my_function(...[*]). + $between = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true); + + // Only throw an error if there is some content between the parenthesis. + // i.e., 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 = 'No space before closing parenthesis of array prohibited'; + $phpcsFile->addError($error, $closer); + } + } + + // Single line array. + // Check if there are multiple values. If so, then it has to be multiple lines + // unless it is contained inside a function call or condition. + $nextComma = $arrayStart; + $valueCount = 0; + $commas = array(); + while (($nextComma = $phpcsFile->findNext(array(T_COMMA), ($nextComma + 1), $arrayEnd)) !== false) { + $valueCount++; + $commas[] = $nextComma; + } + + // Now check each of the double arrows (if any). + $nextArrow = $arrayStart; + while (($nextArrow = $phpcsFile->findNext(T_DOUBLE_ARROW, ($nextArrow + 1), $arrayEnd)) !== false) { + if ($tokens[($nextArrow - 1)]['code'] !== T_WHITESPACE) { + $content = $tokens[($nextArrow - 1)]['content']; + $error = "Expected 1 space between \"$content\" and double arrow; 0 found"; + $phpcsFile->addError($error, $nextArrow); + } else { + $spaceLength = strlen($tokens[($nextArrow - 1)]['content']); + if ($spaceLength !== 1) { + $content = $tokens[($nextArrow - 2)]['content']; + $error = "Expected 1 space between \"$content\" and double arrow; $spaceLength found"; + $phpcsFile->addError($error, $nextArrow); + } + } + + if ($tokens[($nextArrow + 1)]['code'] !== T_WHITESPACE) { + $content = $tokens[($nextArrow + 1)]['content']; + $error = "Expected 1 space between double arrow and \"$content\"; 0 found"; + $phpcsFile->addError($error, $nextArrow); + } else { + $spaceLength = strlen($tokens[($nextArrow + 1)]['content']); + if ($spaceLength !== 1) { + $content = $tokens[($nextArrow + 2)]['content']; + $error = "Expected 1 space between double arrow and \"$content\"; $spaceLength found"; + $phpcsFile->addError($error, $nextArrow); + } + } + }//end while + + if ($valueCount > 0) { + $conditionCheck = $phpcsFile->findPrevious(array(T_OPEN_PARENTHESIS, T_SEMICOLON), ($stackPtr - 1), null, false); + + // We have a multiple value array that is inside a condition or + // function. Check its spacing is correct. + foreach ($commas as $comma) { + if ($tokens[($comma + 1)]['code'] !== T_WHITESPACE) { + $content = $tokens[($comma + 1)]['content']; + $error = "Expected 1 space between comma and \"$content\"; 0 found"; + $phpcsFile->addError($error, $comma); + } else { + $spaceLength = strlen($tokens[($comma + 1)]['content']); + if ($spaceLength !== 1) { + $content = $tokens[($comma + 2)]['content']; + $error = "Expected 1 space between comma and \"$content\"; $spaceLength found"; + $phpcsFile->addError($error, $comma); + } + } + + if ($tokens[($comma - 1)]['code'] === T_WHITESPACE) { + $content = $tokens[($comma - 2)]['content']; + $spaceLength = strlen($tokens[($comma - 1)]['content']); + $error = "Expected 0 spaces between \"$content\" and comma; $spaceLength found"; + $phpcsFile->addError($error, $comma); + } + }//end foreach + }//end if + + return; + }//end if + + $nextToken = $stackPtr; + $lastComma = $stackPtr; + $keyUsed = false; + $singleUsed = false; + $lastToken = ''; + $indices = array(); + $maxLength = 0; + + // Find all the double arrows that reside in this scope. + while (($nextToken = $phpcsFile->findNext(array(T_DOUBLE_ARROW, T_COMMA, T_ARRAY), ($nextToken + 1), $arrayEnd)) !== false) { + $currentEntry = array(); + + if ($tokens[$nextToken]['code'] === T_ARRAY) { + // Let subsequent calls of this test handle nested arrays. + $indices[] = array( + 'value' => $nextToken, + ); + $nextToken = $tokens[$tokens[$nextToken]['parenthesis_opener']]['parenthesis_closer']; + continue; + } + + if ($tokens[$nextToken]['code'] === T_COMMA) { + $stackPtrCount = 0; + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { + $stackPtrCount = count($tokens[$stackPtr]['nested_parenthesis']); + } + + if (count($tokens[$nextToken]['nested_parenthesis']) > ($stackPtrCount + 1)) { + // This comma is inside more parenthesis than the ARRAY keyword, + // then there it is actually a comma used to seperate arguments + // in a function call. + continue; + } + + if ($keyUsed === true && $lastToken === T_COMMA) { + $error = 'No key specified for array entry; first entry specifies key'; + $phpcsFile->addError($error, $nextToken); + return; + } + + if ($keyUsed === false) { + if ($tokens[($nextToken - 1)]['code'] === T_WHITESPACE) { + $content = $tokens[($nextToken - 2)]['content']; + $spaceLength = strlen($tokens[($nextToken - 1)]['content']); + $error = "Expected 0 spaces between \"$content\" and comma; $spaceLength found"; + $phpcsFile->addError($error, $nextToken); + } + + // Find the value, which will be the first token on the line, + // excluding the leading whitespace. + $valueContent = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($nextToken - 1), null, true); + while ($tokens[$valueContent]['line'] === $tokens[$nextToken]['line']) { + if ($valueContent === $arrayStart) { + // Value must have been on the same line as the array + // parenthesis, so we have reached the start of the value. + break; + } + + $valueContent--; + } + + $valueContent = $phpcsFile->findNext(T_WHITESPACE, ($valueContent + 1), $nextToken, true); + $indices[] = array('value' => $valueContent); + $singleUsed = true; + }//end if + + $lastToken = T_COMMA; + continue; + }//end if + + if ($tokens[$nextToken]['code'] === T_DOUBLE_ARROW) { + if ($singleUsed === true) { + $error = 'Key specified for array entry; first entry has no key'; + $phpcsFile->addError($error, $nextToken); + return; + } + + $currentEntry['arrow'] = $nextToken; + $keyUsed = true; + + // Find the start of index that uses this double arrow. + $indexEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($nextToken - 1), $arrayStart, true); + $indexStart = $phpcsFile->findPrevious(T_WHITESPACE, $indexEnd, $arrayStart); + + if ($indexStart === false) { + $index = $indexEnd; + } else { + $index = ($indexStart + 1); + } + + $currentEntry['index'] = $index; + $currentEntry['index_content'] = $phpcsFile->getTokensAsString($index, ($indexEnd - $index + 1)); + + $indexLength = strlen($currentEntry['index_content']); + if ($maxLength < $indexLength) { + $maxLength = $indexLength; + } + + // Find the value of this index. + $nextContent = $phpcsFile->findNext(array(T_WHITESPACE), ($nextToken + 1), $arrayEnd, true); + $currentEntry['value'] = $nextContent; + $indices[] = $currentEntry; + $lastToken = T_DOUBLE_ARROW; + }//end if + }//end while + + // Check for mutli-line arrays that should be single-line. + $singleValue = false; + + if (empty($indices) === true) { + $singleValue = true; + } else if (count($indices) === 1) { + if ($lastToken === T_COMMA) { + // There may be another array value without a comma. + $exclude = PHP_CodeSniffer_Tokens::$emptyTokens; + $exclude[] = T_COMMA; + $nextContent = $phpcsFile->findNext($exclude, ($indices[0]['value'] + 1), $arrayEnd, true); + if ($nextContent === false) { + $singleValue = true; + } + } + + if ($singleValue === false && isset($indices[0]['arrow']) === false) { + // A single nested array as a value is fine. + if ($tokens[$indices[0]['value']]['code'] !== T_ARRAY) { + $singleValue === true; + } + } + } + + if ($singleValue === true) { + // Array cannot be empty, so this is a multi-line array with + // a single value. It should be defined on single line. + $error = 'Multi-line array contains a single value; use single-line array instead'; + $phpcsFile->addError($error, $stackPtr); + return; + } + + /* + This section checks for arrays that don't specify keys. + + Arrays such as: + array( + 'aaa', + 'bbb', + 'd', + ); + */ + + if ($keyUsed === false && empty($indices) === false) { + $count = count($indices); + $lastIndex = $indices[($count - 1)]['value']; + + $trailingContent = $phpcsFile->findPrevious(T_WHITESPACE, ($arrayEnd - 1), $lastIndex, true); + if ($tokens[$trailingContent]['code'] !== T_COMMA) { + $error = 'Comma required after last value in array declaration'; + $phpcsFile->addError($error, $trailingContent); + } + + foreach ($indices as $value) { + if (empty($value['value']) === true) { + // Array was malformed and we couldn't figure out + // the array value correctly, so we have to ignore it. + // Other parts of this sniff will correct the error. + continue; + } +/* + if ($tokens[($value['value'] - 1)]['code'] === T_WHITESPACE) { + // A whitespace token before this value means that the value + // was indented and not flush with the opening parenthesis. + if ($tokens[$value['value']]['column'] !== ($keywordStart + 1)) { + $error = 'Array value not aligned correctly; expected '.($keywordStart + 1).' spaces but found '.$tokens[$value['value']]['column']; + $phpcsFile->addError($error, $value['value']); + } + }*/ + } + }//end if + + /* + Below the actual indentation of the array is checked. + Errors will be thrown when a key is not aligned, when + a double arrow is not aligned, and when a value is not + aligned correctly. + If an error is found in one of the above areas, then errors + are not reported for the rest of the line to avoid reporting + spaces and columns incorrectly. Often fixing the first + problem will fix the other 2 anyway. + + For example: + + $a = array( + 'index' => '2', + ); + + In this array, the double arrow is indented too far, but this + will also cause an error in the value's alignment. If the arrow were + to be moved back one space however, then both errors would be fixed. + */ + + $numValues = count($indices); + + $indicesStart = ($keywordStart + 1); + $arrowStart = ($indicesStart + $maxLength + 1); + $valueStart = ($arrowStart + 3); + foreach ($indices as $index) { + if (isset($index['index']) === false) { + // Array value only. + if (($tokens[$index['value']]['line'] === $tokens[$stackPtr]['line']) && ($numValues > 1)) { + $phpcsFile->addError('The first value in a multi-value array must be on a new line', $stackPtr); + } + + continue; + } + + if (isset($index['index_content']) === true) { + $indexContent = trim($index['index_content'], "'"); + if (preg_match('|^[a-zA-Z0-9_]+$|', $indexContent) === 1) { + if (strtolower($indexContent) !== $indexContent) { + $error = 'Array index "'.$indexContent.'" should not contain uppercase characters'; + $phpcsFile->addError($error, $index['index']); + } + } + } + + if (($tokens[$index['index']]['line'] === $tokens[$stackPtr]['line'])) { + $phpcsFile->addError('The first index in a multi-value array must be on a new line', $stackPtr); + continue; + } +/* + if ($tokens[$index['index']]['column'] !== $indicesStart) { + $phpcsFile->addError('Array key not aligned correctly; expected '.$indicesStart.' spaces but found '.$tokens[$index['index']]['column'], $index['index']); + continue; + } + + if ($tokens[$index['arrow']]['column'] !== $arrowStart) { + $expected = ($arrowStart - (strlen($index['index_content']) + $tokens[$index['index']]['column'])); + $expected .= ($expected === 1) ? ' space' : ' spaces'; + $found = ($tokens[$index['arrow']]['column'] - (strlen($index['index_content']) + $tokens[$index['index']]['column'])); + $phpcsFile->addError("Array double arrow not aligned correctly; expected $expected but found $found", $index['arrow']); + continue; + } + + if ($tokens[$index['value']]['column'] !== $valueStart) { + $expected = ($valueStart - (strlen($tokens[$index['arrow']]['content']) + $tokens[$index['arrow']]['column'])); + $expected .= ($expected === 1) ? ' space' : ' spaces'; + $found = ($tokens[$index['value']]['column'] - (strlen($tokens[$index['arrow']]['content']) + $tokens[$index['arrow']]['column'])); + $phpcsFile->addError("Array value not aligned correctly; expected $expected but found $found", $index['arrow']); + } +*/ + // Check each line ends in a comma. + if ($tokens[$index['value']]['code'] !== T_ARRAY) { + $nextComma = $phpcsFile->findNext(array(T_COMMA), ($index['value'] + 1)); + if (($nextComma === false) || ($tokens[$nextComma]['line'] !== $tokens[$index['value']]['line'])) { + $error = 'Each line in an array declaration must end in a comma'; + $phpcsFile->addError($error, $index['value']); + } + + // Check that there is no space before the comma. + if ($nextComma !== false && $tokens[($nextComma - 1)]['code'] === T_WHITESPACE) { + $content = $tokens[($nextComma - 2)]['content']; + $spaceLength = strlen($tokens[($nextComma - 1)]['content']); + $error = "Expected 0 spaces between \"$content\" and comma; $spaceLength found"; + $phpcsFile->addError($error, $nextComma); + } + } + }//end foreach + + }//end process() + + +}//end class + +?> diff --git a/Sniffs/Classes/ValidClassNameSniff.php b/Sniffs/Classes/ValidClassNameSniff.php new file mode 100755 index 0000000000..f7c304828c --- /dev/null +++ b/Sniffs/Classes/ValidClassNameSniff.php @@ -0,0 +1,93 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id: ValidClassNameSniff.php,v 1.6 2008/05/19 05:59:25 squiz Exp $ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Squiz_Sniffs_Classes_ValidClassNameSniff. + * + * Ensures classes are in camel caps, and the first letter is capitalised + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.2.0RC1 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class WordPress_Sniffs_Classes_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(); + + 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; + } + + // Determine the name of the class or interface. Note that we cannot + // simply look for the first T_STRING because a class name + // starting with the number will be multiple tokens. + $opener = $tokens[$stackPtr]['scope_opener']; + $nameStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), $opener, true); + $nameEnd = $phpcsFile->findNext(T_WHITESPACE, $nameStart, $opener); + $name = trim($phpcsFile->getTokensAsString($nameStart, ($nameEnd - $nameStart))); + + // Check for camel caps format. + $valid = PHP_CodeSniffer::isCamelCaps(str_replace('_', '', $name), true, true, false); + if ($valid === false) { + $type = ucfirst($tokens[$stackPtr]['content']); + $error = "$type name \"$name\" is not in camel caps format"; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + + +?> diff --git a/Sniffs/Files/FileNameSniff.php b/Sniffs/Files/FileNameSniff.php new file mode 100755 index 0000000000..985dd663ec --- /dev/null +++ b/Sniffs/Files/FileNameSniff.php @@ -0,0 +1,72 @@ +getTokens(); + + // Make sure this is the first PHP open tag so we don't process + // the same file twice. + $prevOpenTag = $phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)); + if ($prevOpenTag !== false) { + return; + } + + $fileName = basename($phpcsFile->getFileName()); + if (strpos($fileName, '_') !== false) { + $expected = str_replace('_', '-', $fileName); + $error = ucfirst('Filename "'.$fileName.'" with underscores found; use '.$expected.' instead'); + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + + +?> diff --git a/Sniffs/Formatting/MultipleStatementAlignmentSniff.php b/Sniffs/Formatting/MultipleStatementAlignmentSniff.php new file mode 100755 index 0000000000..7496834b0d --- /dev/null +++ b/Sniffs/Formatting/MultipleStatementAlignmentSniff.php @@ -0,0 +1,292 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id: MultipleStatementAlignmentSniff.php,v 1.23 2008/12/02 02:38:34 squiz Exp $ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Generic_Sniffs_Formatting_MultipleStatementAlignmentSniff. + * + * Checks alignment of assignments. If there are multiple adjacent assignments, + * it will check that the equals signs of each assignment are aligned. It will + * display a warning to advise that the signs should be aligned. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.2.0RC1 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class WordPress_Sniffs_Formatting_MultipleStatementAlignmentSniff 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 = false; + + /** + * The maximum amount of padding before the alignment is ignored. + * + * If the amount of padding required to align this assignment with the + * surrounding assignments exceeds this number, the assignment will be + * ignored and no errors or warnings will be thrown. + * + * @var int + */ + protected $maxPadding = 1000; + + /** + * If true, multi-line assignments are not checked. + * + * @var int + */ + protected $ignoreMultiLine = false; + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return PHP_CodeSniffer_Tokens::$assignmentTokens; + + }//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(); + + // Ignore assignments used in a condition, like an IF or FOR. + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { + foreach ($tokens[$stackPtr]['nested_parenthesis'] as $start => $end) { + if (isset($tokens[$start]['parenthesis_owner']) === true) { + return; + } + } + } + + /* + By this stage, it is known that there is an assignment on this line. + We only want to process the block once we reach the last assignment, + so we need to determine if there are more to follow. + */ + + // The assignment may span over multiple lines, so look for the + // end of the assignment so we can check assignment blocks correctly. + $lineEnd = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1)); + + $nextAssign = $phpcsFile->findNext( + PHP_CodeSniffer_Tokens::$assignmentTokens, + ($lineEnd + 1) + ); + + if ($nextAssign !== false) { + $isAssign = true; + if ($tokens[$nextAssign]['line'] === ($tokens[$lineEnd]['line'] + 1)) { + // Assignment may be in the same block as this one. Just make sure + // it is not used in a condition, like an IF or FOR. + if (isset($tokens[$nextAssign]['nested_parenthesis']) === true) { + foreach ($tokens[$nextAssign]['nested_parenthesis'] as $start => $end) { + if (isset($tokens[$start]['parenthesis_owner']) === true) { + // Not an assignment. + $isAssign = false; + break; + } + } + } + + if ($isAssign === true) { + return; + } + } + } + + // Getting here means that this is the last in a block of statements. + $assignments = array(); + $assignments[] = $stackPtr; + $prevAssignment = $stackPtr; + $lastLine = $tokens[$stackPtr]['line']; + + while (($prevAssignment = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$assignmentTokens, ($prevAssignment - 1))) !== false) { + + // The assignment's end token must be on the line directly + // above the current one to be in the same assignment block. + $lineEnd = $phpcsFile->findNext(T_SEMICOLON, ($prevAssignment + 1)); + + // And the end token must actually belong to this assignment. + $nextOpener = $phpcsFile->findNext( + PHP_CodeSniffer_Tokens::$scopeOpeners, + ($prevAssignment + 1) + ); + + if ($nextOpener !== false && $nextOpener < $lineEnd) { + break; + } + + if ($tokens[$lineEnd]['line'] !== ($lastLine - 1)) { + break; + } + + // Make sure it is not assigned inside a condition (eg. IF, FOR). + if (isset($tokens[$prevAssignment]['nested_parenthesis']) === true) { + foreach ($tokens[$prevAssignment]['nested_parenthesis'] as $start => $end) { + if (isset($tokens[$start]['parenthesis_owner']) === true) { + break(2); + } + } + } + + $assignments[] = $prevAssignment; + $lastLine = $tokens[$prevAssignment]['line']; + }//end while + + $assignmentData = array(); + $maxAssignmentLength = 0; + $maxVariableLength = 0; + + foreach ($assignments as $assignment) { + $prev = $phpcsFile->findPrevious( + PHP_CodeSniffer_Tokens::$emptyTokens, + ($assignment - 1), + null, + true + ); + + $endColumn = $tokens[($prev + 1)]['column']; + + if ($maxVariableLength < $endColumn) { + $maxVariableLength = $endColumn; + } + + if ($maxAssignmentLength < strlen($tokens[$assignment]['content'])) { + $maxAssignmentLength = strlen($tokens[$assignment]['content']); + } + + $assignmentData[$assignment] + = array( + 'variable_length' => $endColumn, + 'assignment_length' => strlen($tokens[$assignment]['content']), + ); + }//end foreach + + foreach ($assignmentData as $assignment => $data) { + if ($data['assignment_length'] === $maxAssignmentLength) { + if ($data['variable_length'] === $maxVariableLength) { + // The assignment is the longest possible, so the column that + // everything has to align to is based on it. + $column = ($maxVariableLength + 1); + break; + } else { + // The assignment token is the longest out of all of the + // assignments, but the variable name is not, so the column + // the start at can go back more to cover the space + // between the variable name and the assigment operator. + $column = ($maxVariableLength - ($maxAssignmentLength - 1) + 1); + } + } + } + + // Determine the actual position that each equals sign should be in. + foreach ($assignments as $assignment) { + // Actual column takes into account the length of the assignment operator. + $actualColumn = ($column + $maxAssignmentLength - strlen($tokens[$assignment]['content'])); + if ($tokens[$assignment]['column'] !== $actualColumn) { + $prev = $phpcsFile->findPrevious( + PHP_CodeSniffer_Tokens::$emptyTokens, + ($assignment - 1), + null, + true + ); + + $expected = ($actualColumn - $tokens[($prev + 1)]['column']); + + if ($tokens[$assignment]['line'] !== $tokens[$prev]['line']) { + // Instead of working out how many spaces there are + // across new lines, the error message becomes more + // generic below. + $found = null; + } else { + $found = ($tokens[$assignment]['column'] - $tokens[($prev + 1)]['column']); + } + + // If the expected number of spaces for alignment exceeds the + // maxPadding rule, we can ignore this assignment. + if ($expected > $this->maxPadding) { + continue; + } + + // Skip multi-line assignments if required. + if ($found === null && $this->ignoreMultiLine === true) { + continue; + } + + $expected .= ($expected === 1) ? ' space' : ' spaces'; + if ($found === null) { + $found = 'a new line'; + } else { + $found .= ($found === 1) ? ' space' : ' spaces'; + } + + if (count($assignments) === 1) { + $error = "Equals sign not aligned correctly; expected $expected but found $found"; + } else if ($expected < 6) { + $error = "Equals sign not aligned with surrounding assignments; expected $expected but found $found"; + } + else + $error = false; + + if ($error !== false) + { + if ($this->error === true) { + $phpcsFile->addError($error, $assignment); + } else { + $phpcsFile->addWarning($error, $assignment); + } + } + }//end if + }//end foreach + + }//end process() + + +}//end class + +?> \ No newline at end of file diff --git a/Sniffs/Functions/FunctionCallSignatureSniff.php b/Sniffs/Functions/FunctionCallSignatureSniff.php new file mode 100755 index 0000000000..4246876116 --- /dev/null +++ b/Sniffs/Functions/FunctionCallSignatureSniff.php @@ -0,0 +1,237 @@ + + * @author Greg Sherwood + * @author Marc McIntyre + */ + +/** + * Enforces WordPress array format + * + * @category PHP + * @package PHP_CodeSniffer + * @author John Godley + * @author Greg Sherwood + * @author Marc McIntyre + */ +class WordPress_Sniffs_Functions_FunctionCallSignatureSniff implements PHP_CodeSniffer_Sniff +{ + private $type; + + /** + * 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. + $openBracket = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true); + + if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) { + // Not a function call. + return; + } + + if (isset($tokens[$openBracket]['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) + $this->type = 'definition'; + +/* if ($tokens[$previous]['code'] === T_NEW) { + // We are creating an object, not calling a function. + return; + } + */ + $closeBracket = $tokens[$openBracket]['parenthesis_closer']; + + if (($stackPtr + 1) !== $openBracket) { + // Checking this: $value = my_function[*](...). + $error = 'Space before opening parenthesis of function '.$this->type.' prohibited'; + $phpcsFile->addError($error, $stackPtr); + } + + $next = $phpcsFile->findNext(T_WHITESPACE, ($closeBracket + 1), null, true); + if ($tokens[$next]['code'] === T_SEMICOLON) { + if (in_array($tokens[($closeBracket + 1)]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === true) { + $error = 'Space after closing parenthesis of function '.$this->type.' prohibited'; + $phpcsFile->addError($error, $closeBracket); + } + } + + // Check if this is a single line or multi-line function call. + if ($tokens[$openBracket]['line'] === $tokens[$closeBracket]['line']) { + $this->processSingleLineCall($phpcsFile, $stackPtr, $openBracket, $tokens); + } else { + $this->processMultiLineCall($phpcsFile, $stackPtr, $openBracket, $tokens); + } + + }//end process() + + + /** + * Processes single-line calls. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * @param int $openBracket The position of the openning bracket + * in the stack passed in $tokens. + * @param array $tokens The stack of tokens that make up + * the file. + * + * @return void + */ + public function processSingleLineCall(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens) + { + if ($tokens[($openBracket + 1)]['code'] !== T_WHITESPACE && $tokens[($openBracket + 1)]['code'] !== T_CLOSE_PARENTHESIS) { + // Checking this: $value = my_function([*]...). + $error = 'No space after opening parenthesis of function '.$this->type.' prohibited'; + $phpcsFile->addError($error, $stackPtr); + } + + $closer = $tokens[$openBracket]['parenthesis_closer']; + + if ($tokens[($closer - 1)]['code'] !== T_WHITESPACE) { + // Checking this: $value = my_function(...[*]). + $between = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true); + + // Only throw an error if there is some content between the parenthesis. + // i.e., 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 = 'No space before closing parenthesis of function '.$this->type.' prohibited'; + $phpcsFile->addError($error, $closer); + } + } + + }//end processSingleLineCall() + + + /** + * Processes multi-line calls. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * @param int $openBracket The position of the openning bracket + * in the stack passed in $tokens. + * @param array $tokens The stack of tokens that make up + * the file. + * + * @return void + */ + public function processMultiLineCall(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens) + { + // We need to work out how far indented the function + // call itself is, so we can work out how far to + // indent the arguments. + $functionIndent = 0; + for ($i = ($stackPtr - 1); $i >= 0; $i--) { + if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) { + $i++; + break; + } + } + + if ($tokens[$i]['code'] === T_WHITESPACE) { + $functionIndent = strlen($tokens[$i]['content']); + } + + // Each line between the parenthesis should be indented 4 spaces. + $closeBracket = $tokens[$openBracket]['parenthesis_closer']; + $lastLine = $tokens[$openBracket]['line']; + for ($i = ($openBracket + 1); $i < $closeBracket; $i++) { + // Skip nested function calls. + if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) { + $i = $tokens[$i]['parenthesis_closer']; + $lastLine = $tokens[$i]['line']; + continue; + } + + if ($tokens[$i]['line'] !== $lastLine) { + $lastLine = $tokens[$i]['line']; + + // We changed lines, so this should be a whitespace indent token. + if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$heredocTokens) === true) { + // Ignore heredoc indentation. + continue; + } + + if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$stringTokens) === true) { + if ($tokens[$i]['code'] === $tokens[($i - 1)]['code']) { + // Ignore multi-line string indentation. + continue; + } + } + + if ($tokens[$i]['line'] === $tokens[$closeBracket]['line']) { + // Closing brace needs to be indented to the same level + // as the function call. + $expectedIndent = $functionIndent; + } else { + $expectedIndent = ($functionIndent + 4); + } + + if ($tokens[$i]['code'] !== T_WHITESPACE) { + $foundIndent = 0; + } else { + $foundIndent = strlen($tokens[$i]['content']); + } + + if ($expectedIndent !== $foundIndent) { + $error = "Multi-line function ".$this->type." not indented correctly; expected $expectedIndent spaces but found $foundIndent"; + $phpcsFile->addError($error, $i); + } + }//end if + }//end for + + if ($tokens[($openBracket + 1)]['content'] !== $phpcsFile->eolChar) { + $error = 'Opening parenthesis of a multi-line function call must be the last content on the line'; + $phpcsFile->addError($error, $stackPtr); + } + + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBracket - 1), null, true); + if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) { + $error = 'Closing parenthesis of a multi-line function '.$this->type.' must be on a line by itself'; + $phpcsFile->addError($error, $closeBracket); + } + + }//end processMultiLineCall() + + +}//end class +?> \ No newline at end of file diff --git a/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php b/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php new file mode 100755 index 0000000000..238ca9b7e6 --- /dev/null +++ b/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php @@ -0,0 +1,186 @@ + + * @author Greg Sherwood + * @author Marc McIntyre + */ + +/** + * Enforces WordPress array format + * + * @category PHP + * @package PHP_CodeSniffer + * @author John Godley + * @author Greg Sherwood + * @author Marc McIntyre + */ +class WordPress_Sniffs_Functions_FunctionDeclarationArgumentSpacingSniff 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(); + + $functionName = $phpcsFile->findNext(array(T_STRING), $stackPtr); + $openBracket = $tokens[$stackPtr]['parenthesis_opener']; + $closeBracket = $tokens[$stackPtr]['parenthesis_closer']; + + $multiLine = ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']); + + $nextParam = $openBracket; + $params = array(); + while (($nextParam = $phpcsFile->findNext(T_VARIABLE, ($nextParam + 1), $closeBracket)) !== false) { + + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($nextParam + 1), ($closeBracket + 1), true); + if ($nextToken === false) { + break; + } + + $nextCode = $tokens[$nextToken]['code']; + + if ($nextCode === T_EQUAL) { + // Check parameter default spacing. + if (($nextToken - $nextParam) != 2) { + $gap = strlen($tokens[($nextParam + 1)]['content']); + $arg = $tokens[$nextParam]['content']; + $error = "Expected 1 space between argument \"$arg\" and equals sign; ".($gap - 1)." found"; + $phpcsFile->addError($error, $nextToken); + } + + if ($tokens[($nextToken + 1)]['code'] !== T_WHITESPACE) { + $gap = strlen($tokens[($nextToken + 1)]['content']); + $arg = $tokens[$nextParam]['content']; + $error = "Expected 1 space between default value and equals sign for argument \"$arg\";"; + $phpcsFile->addError($error, $nextToken); + } + } + + // Find and check the comma (if there is one). + $nextComma = $phpcsFile->findNext(T_COMMA, ($nextParam + 1), $closeBracket); + if ($nextComma !== false) { + // Comma found. + if ($tokens[($nextComma - 1)]['code'] === T_WHITESPACE) { + $space = strlen($tokens[($nextComma - 1)]['content']); + $arg = $tokens[$nextParam]['content']; + $error = "Expected 0 spaces between argument \"$arg\" and comma; $space found"; + $phpcsFile->addError($error, $nextToken); + } + } + + // Take references into account when expecting the + // location of whitespace. + if ($phpcsFile->isReference(($nextParam - 1)) === true) { + $whitespace = $tokens[($nextParam - 2)]; + } else { + $whitespace = $tokens[($nextParam - 1)]; + } + + if (empty($params) === false) { + // This is not the first argument in the function declaration. + $arg = $tokens[$nextParam]['content']; + + if ($whitespace['code'] === T_WHITESPACE) { + $gap = strlen($whitespace['content']); + + // Before we throw an error, make sure there is no type hint. + $comma = $phpcsFile->findPrevious(T_COMMA, ($nextParam - 1)); + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($comma + 1), null, true); + if ($phpcsFile->isReference($nextToken) === true) { + $nextToken++; + } + + if ($nextToken !== $nextParam) { + // There was a type hint, so check the spacing between + // the hint and the variable as well. + $hint = $tokens[$nextToken]['content']; + + if ($gap !== 1) { + $error = "Expected 1 space between type hint and argument \"$arg\"; $gap found"; + $phpcsFile->addError($error, $nextToken); + } + + if ($multiLine === false) { + if ($tokens[($comma + 1)]['code'] !== T_WHITESPACE) { + $error = "Expected 1 space between comma and type hint \"$hint\"; 0 found"; + $phpcsFile->addError($error, $nextToken); + } else { + $gap = strlen($tokens[($comma + 1)]['content']); + if ($gap !== 1) { + $error = "Expected 1 space between comma and type hint \"$hint\"; $gap found"; + $phpcsFile->addError($error, $nextToken); + } + } + } + } else if ($multiLine === false && $gap !== 1) { + $error = "Expected 1 space between comma and argument \"$arg\"; $gap found"; + $phpcsFile->addError($error, $nextToken); + }//end if + } else { + $error = "Expected 1 space between comma and argument \"$arg\"; 0 found"; + $phpcsFile->addError($error, $nextToken); + }//end if + } else { + // First argument in function declaration. + if ($whitespace['code'] === T_WHITESPACE) { + $gap = strlen($whitespace['content']); + $arg = $tokens[$nextParam]['content']; + + // Before we throw an error, make sure there is no type hint. + $bracket = $phpcsFile->findPrevious(T_OPEN_PARENTHESIS, ($nextParam - 1)); + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($bracket + 1), null, true); + if ($phpcsFile->isReference($nextToken) === true) { + $nextToken++; + } + + if ($nextToken !== $nextParam) { + // There was a type hint, so check the spacing between + // the hint and the variable as well. + $hint = $tokens[$nextToken]['content']; + + if ($gap !== 1) { + $error = "Expected 1 space between type hint and argument \"$arg\"; $gap found"; + $phpcsFile->addError($error, $nextToken); + } + } + }//end if + }//end if + + $params[] = $nextParam; + + }//end while + + + }//end process() + + +}//end class + +?> diff --git a/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/Sniffs/NamingConventions/ValidFunctionNameSniff.php new file mode 100755 index 0000000000..15ba47cf6f --- /dev/null +++ b/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -0,0 +1,124 @@ + + */ + +/** + * Enforces WordPress array format + * + * @category PHP + * @package PHP_CodeSniffer + * @author John Godley + */ +class Wordpress_Sniffs_NamingConventions_ValidFunctionNameSniff extends PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff +{ + private $_magicMethods = array( + 'construct', + 'destruct', + 'call', + 'callStatic', + 'get', + 'set', + 'isset', + 'unset', + 'sleep', + 'wakeup', + 'toString', + 'set_state', + 'clone', + ); + + /** + * 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); + + if (strtolower($functionName) != $functionName) { + $suggested = preg_replace('/([A-Z])/', '_$1', $functionName); + $suggested = strtolower ($suggested); + $suggested = str_replace ('__', '_', $suggested); + + $error = "Function name \"$functionName\" is in camel caps format, try '".$suggested."'"; + $phpcsFile->addError($error, $stackPtr); + } + + }//end processTokenOutsideScope() + + /** + * 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']; + + // If the scope was specified on the method, then the method must be + // camel caps and an underscore should be checked for. If it wasn't + // specified, treat it like a public method and remove the underscore + // prefix if there is one because we cant determine if it is private or + // public. + $testMethodName = $methodName; + if ($scopeSpecified === false && $methodName{0} === '_') { + $testMethodName = substr($methodName, 1); + } + + if (strtolower($testMethodName) != $testMethodName) { + $suggested = preg_replace('/([A-Z])/', '_$1', $functionName); + $suggested = strtolower ($suggested); + $suggested = str_replace ('__', '_', $suggested); + + $error = "Function name \"$functionName\" is in camel caps format, try '".$suggested."'"; + $phpcsFile->addError($error, $stackPtr); + } + }//end processTokenWithinScope() + +}//end class + +?> diff --git a/Sniffs/Objects/ObjectInstantiationSniff.php b/Sniffs/Objects/ObjectInstantiationSniff.php new file mode 100755 index 0000000000..af6f751bb9 --- /dev/null +++ b/Sniffs/Objects/ObjectInstantiationSniff.php @@ -0,0 +1,82 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id: ObjectInstantiationSniff.php,v 1.3 2007/10/23 06:05:14 squiz Exp $ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Squiz_Sniffs_Objects_ObjectInstantiationSniff. + * + * Ensures objects are assigned to a variable when instantiated. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.2.0RC1 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class WordPress_Sniffs_Objects_ObjectInstantiationSniff implements PHP_CodeSniffer_Sniff +{ + + + /** + * Registers the token types that this sniff wishes to listen to. + * + * @return array + */ + public function register() + { + return array(T_NEW); + + }//end register() + + + /** + * Process the tokens that this sniff is listening for. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. + * @param int $stackPtr The position in the stack where + * the token was found. + * + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $allowedTokens = PHP_CodeSniffer_Tokens::$emptyTokens; + $allowedTokens[] = T_BITWISE_AND; + + $prev = $phpcsFile->findPrevious($allowedTokens, ($stackPtr - 1), null, true); + + $allowedTokens = array( + T_EQUAL, + T_DOUBLE_ARROW, + T_THROW, + T_RETURN + ); + + if (in_array($tokens[$prev]['code'], $allowedTokens) === false) { + $error = 'New objects must be assigned to a variable'; + $phpcsFile->addError($error, $stackPtr); + } + + }//end process() + + +}//end class + +?> diff --git a/Sniffs/PHP/DiscouragedFunctionsSniff.php.disabled.php b/Sniffs/PHP/DiscouragedFunctionsSniff.php.disabled.php new file mode 100755 index 0000000000..31bb20c185 --- /dev/null +++ b/Sniffs/PHP/DiscouragedFunctionsSniff.php.disabled.php @@ -0,0 +1,72 @@ + string|null) + */ + protected $forbiddenFunctions = array( + 'error_log' => null, + 'print_r' => null, + 'ereg_replace' => 'preg_replace', + 'ereg' => null, + 'eregi_replace' => 'preg_replace', + 'split' => null, + 'spliti' => null, + + // WordPress + 'find_base_dir' => 'WP_Filesystem::abspath', + 'get_base_dir' => 'WP_Filesystem::abspath', + 'dropdown_categories' => 'wp_link_category_checklist', + 'dropdown_link_categories' => 'wp_link_category_checklist', + 'get_link' => 'get_bookmark', + 'get_catname' => 'get_cat_name', + 'register_globals' => null, + 'wp_setcookie' => 'wp_set_auth_cookie', + 'wp_get_cookie_login' => null, + 'wp_login' => 'wp_signon', + 'get_the_attachment_link' => 'wp_get_attachment_link', + 'get_attachment_icon_src' => 'wp_get_attachment_image_src', + 'get_attachment_icon' => 'wp_get_attachment_image', + 'get_attachment_innerHTML' => 'wp_get_attachment_image', + + ); + + /** + * If true, an error will be thrown; otherwise a warning. + * + * @var bool + */ + protected $error = false; + +}//end class + +?> diff --git a/Sniffs/Strings/DoubleQuoteUsageSniff.php b/Sniffs/Strings/DoubleQuoteUsageSniff.php new file mode 100755 index 0000000000..8fa5868786 --- /dev/null +++ b/Sniffs/Strings/DoubleQuoteUsageSniff.php @@ -0,0 +1,120 @@ + + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version CVS: $Id: DoubleQuoteUsageSniff.php,v 1.7 2008/10/01 23:46:47 squiz Exp $ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/** + * Squiz_Sniffs_Strings_DoubleQuoteUsageSniff. + * + * Makes sure that any use of Double Quotes ("") are warranted. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) + * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence + * @version Release: 1.2.0RC1 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class WordPress_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, + T_DOUBLE_QUOTED_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(); + + // The use of variables in double quoted strings is not allowed. + if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING) { + $stringTokens = token_get_all('numTokens; $i++) { + if ($tokens[$i]['type'] !== 'T_CONSTANT_ENCAPSED_STRING') { + break; + } + + $workingString .= $tokens[$i]['content']; + } + + $allowedChars = array( + '\n', + '\r', + '\f', + '\t', + '\v', + '\x', + '\'', + ); + + foreach ($allowedChars as $testChar) { + if (strpos($workingString, $testChar) !== false) { + return; + } + } + + $error = "String $workingString does not require double quotes; use single quotes instead"; + $phpcsFile->addError($error, $stackPtr); + + }//end process() + + +}//end class + +?> diff --git a/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php b/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php new file mode 100755 index 0000000000..1d130af719 --- /dev/null +++ b/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php @@ -0,0 +1,189 @@ + + * @author Greg Sherwood + * @author Marc McIntyre + */ + +/** + * Squiz_Sniffs_WhiteSpace_ControlStructureSpacingSniff. + * + * Checks that any array declarations are lower case. + * + * @category PHP + * @package PHP_CodeSniffer + * @author John Godley + * @author Greg Sherwood + * @author Marc McIntyre + */ +class WordPress_Sniffs_WhiteSpace_ControlStructureSpacingSniff 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_IF, + T_WHILE, + T_FOREACH, + T_FOR, + T_SWITCH, + T_DO, + T_ELSE, + 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(); + + if (isset($tokens[$stackPtr]['scope_closer']) === false) { + return; + } + + $scopeCloser = $tokens[$stackPtr]['scope_closer']; + $scopeOpener = $tokens[$stackPtr]['scope_opener']; + + $openBracket = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true); + + if (($stackPtr + 1) === $openBracket) { + // Checking this: $value = my_function[*](...). + $error = 'No space before opening parenthesis is prohibited'; + $phpcsFile->addError($error, $stackPtr); + } + + if ($tokens[($openBracket + 1)]['code'] !== T_WHITESPACE && $tokens[($openBracket + 1)]['code'] !== T_CLOSE_PARENTHESIS) { + // Checking this: $value = my_function([*]...). + $error = 'No space after opening parenthesis is prohibited'; + $phpcsFile->addError($error, $stackPtr); + } + + if (isset ($tokens[$openBracket]['parenthesis_closer'])) + { + $closer = $tokens[$openBracket]['parenthesis_closer']; + + if ($tokens[($closer - 1)]['code'] !== T_WHITESPACE) { + $error = 'No space before closing parenthesis is prohibited'; + $phpcsFile->addError($error, $closer); + } + + $arrayLine = $tokens[$scopeOpener]['line']; + + if (isset ($tokens[$arrayLine]['scope_opener']) && $tokens[$arrayLine]['line'] != $tokens[$tokens[$arrayLine]['scope_opener']]['line']) { + $error = 'Opening brace should be on the same line as the declaration'; + $phpcsFile->addError($error, $openBracket); + return; + } + } + + + $firstContent = $phpcsFile->findNext(T_WHITESPACE, ($scopeOpener + 1), null, true); + if ($tokens[$firstContent]['line'] !== ($tokens[$scopeOpener]['line'] + 1) && $tokens[$firstContent]['code'] != T_CLOSE_TAG) { + $error = 'Blank line found at start of control structure'; + $phpcsFile->addError($error, $scopeOpener); + } + + $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($scopeCloser - 1), null, true); + if ($tokens[$lastContent]['line'] !== ($tokens[$scopeCloser]['line'] - 1)) { + $errorToken = $scopeCloser; + for ($i = ($scopeCloser - 1); $i > $lastContent; $i--) { + if ($tokens[$i]['line'] < $tokens[$scopeCloser]['line'] && $tokens[$firstContent]['code'] != T_OPEN_TAG) { + + $error = 'Blank line found at end of control structure'; + $phpcsFile->addError($error, $i); + break; + } + } + + } + + $trailingContent = $phpcsFile->findNext(T_WHITESPACE, ($scopeCloser + 1), null, true); + if ($tokens[$trailingContent]['code'] === T_ELSE) { + if ($tokens[$stackPtr]['code'] === T_IF) { + // IF with ELSE. + return; + } + } + + if ($tokens[$trailingContent]['code'] === T_COMMENT) { + if ($tokens[$trailingContent]['line'] === $tokens[$scopeCloser]['line']) { + if (substr($tokens[$trailingContent]['content'], 0, 5) === '//end') { + // There is an end comment, so we have to get the next piece + // of content. + $trailingContent = $phpcsFile->findNext(T_WHITESPACE, ($trailingContent + 1), null, true); + } + } + } + + if ($tokens[$trailingContent]['code'] === T_BREAK) { + // If this BREAK is closing a CASE, we don't need the + // blank line after this control structure. + if (isset($tokens[$trailingContent]['scope_condition']) === true) { + $condition = $tokens[$trailingContent]['scope_condition']; + if ($tokens[$condition]['code'] === T_CASE || $tokens[$condition]['code'] === T_DEFAULT) { + return; + } + } + } + + if ($tokens[$trailingContent]['code'] === T_CLOSE_TAG) { + // At the end of the script or embedded code. + return; + } + + if ($tokens[$trailingContent]['code'] === T_CLOSE_CURLY_BRACKET) { + // Another control structure's closing brace. + if (isset($tokens[$trailingContent]['scope_condition']) === true) { + $owner = $tokens[$trailingContent]['scope_condition']; + if ($tokens[$owner]['code'] === T_FUNCTION) { + // The next content is the closing brace of a function + // so normal function rules apply and we can ignore it. + return; + } + } + + if ($tokens[$trailingContent]['line'] !== ($tokens[$scopeCloser]['line'] + 1)) { + $error = 'Blank line found after control structure'; + $phpcsFile->addError($error, $scopeCloser); + } + } + + }//end process() + + +}//end class + +?> \ No newline at end of file diff --git a/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/Sniffs/WhiteSpace/OperatorSpacingSniff.php new file mode 100755 index 0000000000..751f2e9b89 --- /dev/null +++ b/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -0,0 +1,164 @@ + + * @author Marc McIntyre + */ + +/** + * Enforces WordPress array format + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + */ +class WordPress_Sniffs_WhiteSpace_OperatorSpacingSniff 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() + { + $comparison = PHP_CodeSniffer_Tokens::$comparisonTokens; + $operators = PHP_CodeSniffer_Tokens::$operators; + $assignment = PHP_CodeSniffer_Tokens::$assignmentTokens; + + return array_unique(array_merge($comparison, $operators, $assignment)); + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The current file being checked. + * @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_EQUAL) { + // Skip for '=&' case. + if (isset($tokens[($stackPtr + 1)]) === true && $tokens[($stackPtr + 1)]['code'] === T_BITWISE_AND) { + return; + } + + // Skip default values in function declarations. + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { + $bracket = end($tokens[$stackPtr]['nested_parenthesis']); + if (isset($tokens[$bracket]['parenthesis_owner']) === true) { + $function = $tokens[$bracket]['parenthesis_owner']; + if ($tokens[$function]['code'] === T_FUNCTION) { + return; + } + } + } + } + + if ($tokens[$stackPtr]['code'] === T_BITWISE_AND) { + // If its not a reference, then we expect one space either side of the + // bitwise operator. + if ($phpcsFile->isReference($stackPtr) === false) { + + }//end if + } else { + if ($tokens[$stackPtr]['code'] === T_MINUS) { + // Check minus spacing, but make sure we aren't just assigning + // a minus value or returning one. + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$prev]['code'] === T_RETURN) { + // Just returning a negative value; eg. return -1. + return; + } + + if (in_array($tokens[$prev]['code'], PHP_CodeSniffer_Tokens::$operators) === true) { + // Just trying to operate on a negative value; eg. ($var * -1). + return; + } + + if (in_array($tokens[$prev]['code'], PHP_CodeSniffer_Tokens::$comparisonTokens) === true) { + // Just trying to compare a negative value; eg. ($var === -1). + return; + } + + // A list of tokens that indicate that the token is not + // part of an arithmetic operation. + $invalidTokens = array( + T_COMMA, + T_OPEN_PARENTHESIS, + T_OPEN_SQUARE_BRACKET, + ); + + if (in_array($tokens[$prev]['code'], $invalidTokens) === true) { + // Just trying to use a negative value; eg. myFunction($var, -2). + return; + } + + $number = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($tokens[$number]['code'] === T_LNUMBER) { + $semi = $phpcsFile->findNext(T_WHITESPACE, ($number + 1), null, true); + if ($tokens[$semi]['code'] === T_SEMICOLON) { + if ($prev !== false && (in_array($tokens[$prev]['code'], PHP_CodeSniffer_Tokens::$assignmentTokens) === true)) { + // This is a negative assignment. + return; + } + } + } + }//end if + + $operator = $tokens[$stackPtr]['content']; + + if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE) { + $error = "Expected 1 space before \"$operator\"; 0 found"; + $phpcsFile->addError($error, $stackPtr); + } else if (strlen($tokens[($stackPtr - 1)]['content']) !== 1) { + // Don't throw an error for assignments, because other standards allow + // multiple spaces there to align multiple assignments. + if (in_array($tokens[$stackPtr]['code'], PHP_CodeSniffer_Tokens::$assignmentTokens) === false) { + $found = strlen($tokens[($stackPtr - 1)]['content']); + $error = "Expected 1 space before \"$operator\"; $found found"; + $phpcsFile->addError($error, $stackPtr); + } + } + + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + $error = "Expected 1 space after \"$operator\"; 0 found"; + $phpcsFile->addError($error, $stackPtr); + } else if (strlen($tokens[($stackPtr + 1)]['content']) !== 1) { + $found = strlen($tokens[($stackPtr + 1)]['content']); + $error = "Expected 1 space after \"$operator\"; $found found"; + $phpcsFile->addError($error, $stackPtr); + } + + }//end if + + }//end process() + + +}//end class + +?> \ No newline at end of file diff --git a/WordpressCodingStandard.php b/WordpressCodingStandard.php new file mode 100755 index 0000000000..6b55c1ec27 --- /dev/null +++ b/WordpressCodingStandard.php @@ -0,0 +1,77 @@ + \ No newline at end of file diff --git a/ruleset.xml b/ruleset.xml new file mode 100755 index 0000000000..bd98bc2d38 --- /dev/null +++ b/ruleset.xml @@ -0,0 +1,6 @@ + + + A custom coding standard. + + +