From 6283e923394694099f5136ee122a7e5ee859ef68 Mon Sep 17 00:00:00 2001 From: Gerald Raab Date: Fri, 8 Apr 2016 16:37:00 +0200 Subject: [PATCH] Codesniffer FHC-Standard --- composer.json | 9 + .../Commenting/DocBlockAlignmentSniff.php | 59 +++ .../Commenting/FunctionCommentSniff.php | 487 ++++++++++++++++++ .../Commenting/FunctionCommentTypeSniff.php | 87 ++++ .../ControlSignatureSniff.php | 58 +++ .../ElseIfDeclarationSniff.php | 48 ++ .../WhileStructuresSniff.php | 56 ++ .../Sniffs/Formatting/OneClassPerUseSniff.php | 48 ++ .../UseInAlphabeticalOrderSniff.php | 142 +++++ .../Functions/ClosureDeclarationSniff.php | 35 ++ ...unctionDeclarationArgumentSpacingSniff.php | 26 + .../CamelCapsMethodNameSniff.php | 98 ++++ .../UpperCaseConstantNameSniff.php | 223 ++++++++ .../ValidClassBracketsSniff.php | 47 ++ .../ValidFunctionNameSniff.php | 138 +++++ .../NamingConventions/ValidTraitNameSniff.php | 48 ++ .../ValidVariableNameSniff.php | 171 ++++++ .../Sniffs/PHP/DisallowShortOpenTagSniff.php | 53 ++ .../Sniffs/PHP/TypeCastingSniff.php | 74 +++ .../Strings/ConcatenationSpacingSniff.php | 48 ++ .../Sniffs/WhiteSpace/CommaSpacingSniff.php | 54 ++ .../WhiteSpace/FunctionCallSpacingSniff.php | 53 ++ .../FunctionClosingBraceSpaceSniff.php | 69 +++ .../FunctionOpeningBraceSpaceSniff.php | 62 +++ .../WhiteSpace/FunctionSpacingSniff.php | 129 +++++ .../WhiteSpace/ObjectOperatorSpacingSniff.php | 42 ++ .../WhiteSpace/OperatorSpacingSniff.php | 185 +++++++ .../Sniffs/WhiteSpace/ScopeIndentSniff.php | 280 ++++++++++ .../Sniffs/WhiteSpace/TabAndSpaceSniff.php | 63 +++ tests/codesniffer/FHComplete/ruleset.xml | 92 ++++ .../tests/FHCompletePHPStandardTest.php | 67 +++ .../FHComplete/tests/TestHelper.php | 54 ++ .../FHComplete/tests/bootstrap.php | 2 + .../tests/files/FHComplete/throws_pass.php | 36 ++ .../FHComplete/tests/files/bad_trait_name.php | 4 + .../tests/files/class_brackets_fail1.php | 4 + .../tests/files/class_brackets_fail2.php | 4 + .../tests/files/class_brackets_fail3.php | 5 + .../files/class_underscore_prefix_pass.php | 18 + .../files/control_structure_brackets_pass.php | 19 + .../tests/files/control_structure_dowhile.php | 6 + .../tests/files/control_structure_elseif.php | 6 + .../files/control_structure_indentation.php | 4 + .../files/control_structure_nobrackets.php | 4 + .../tests/files/control_structure_nospace.php | 4 + .../tests/files/control_structure_while.php | 4 + .../files/control_structures_no_newline.php | 5 + .../tests/files/docblock_align_fail.php | 32 ++ .../tests/files/docblock_align_flat_pass.php | 24 + .../tests/files/docblock_align_pass.php | 29 ++ .../FHComplete/tests/files/double_space.php | 2 + .../tests/files/for_function_comment_pass.php | 15 + .../function_comment_opening_line_fail.php | 13 + .../function_comment_opening_line_pass.php | 14 + .../files/function_comment_type_fail.php | 14 + .../files/function_comment_type_pass.php | 49 ++ .../tests/files/function_spacing.php | 5 + .../FHComplete/tests/files/mixing_indent.php | 6 + .../FHComplete/tests/files/multiple_use.php | 6 + .../tests/files/multiple_use_pass.php | 9 + .../tests/files/operator_spacing_pass.php | 25 + .../tests/files/short_open_tags_fail.php | 9 + .../tests/files/short_open_tags_pass.php | 6 + .../FHComplete/tests/files/space_tab.php | 2 + .../FHComplete/tests/files/tab_space.php | 2 + .../tests/files/traits_no_order.php | 11 + .../FHComplete/tests/files/traits_pass.php | 15 + .../tests/files/type_casting_fail.php | 4 + .../tests/files/type_casting_pass.php | 4 + .../files/use_class_and_closure_pass.php | 22 + .../tests/files/use_closure_pass.php | 8 + .../FHComplete/tests/files/use_no_order.php | 8 + .../FHComplete/tests/files/use_ok_pass.php | 10 + .../tests/files/use_with_commas.php | 7 + .../tests/files/variable_name_pass.php | 45 ++ .../tests/files/whitespace_comma.php | 2 + .../tests/files/whitespace_comma_before.php | 2 + .../tests/files/whitespace_comma_pass.php | 2 + .../codesniffer/FHComplete/tests/ruleset.xml | 52 ++ 79 files changed, 3684 insertions(+) mode change 100644 => 100755 composer.json create mode 100755 tests/codesniffer/FHComplete/Sniffs/Commenting/DocBlockAlignmentSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/Commenting/FunctionCommentSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/Commenting/FunctionCommentTypeSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/ControlStructures/ControlSignatureSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/ControlStructures/ElseIfDeclarationSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/ControlStructures/WhileStructuresSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/Formatting/OneClassPerUseSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/Formatting/UseInAlphabeticalOrderSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/Functions/ClosureDeclarationSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/NamingConventions/CamelCapsMethodNameSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidClassBracketsSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidFunctionNameSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidTraitNameSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidVariableNameSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/PHP/DisallowShortOpenTagSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/PHP/TypeCastingSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/Strings/ConcatenationSpacingSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/CommaSpacingSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionCallSpacingSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionOpeningBraceSpaceSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionSpacingSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/OperatorSpacingSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/ScopeIndentSniff.php create mode 100755 tests/codesniffer/FHComplete/Sniffs/WhiteSpace/TabAndSpaceSniff.php create mode 100755 tests/codesniffer/FHComplete/ruleset.xml create mode 100755 tests/codesniffer/FHComplete/tests/FHCompletePHPStandardTest.php create mode 100755 tests/codesniffer/FHComplete/tests/TestHelper.php create mode 100755 tests/codesniffer/FHComplete/tests/bootstrap.php create mode 100755 tests/codesniffer/FHComplete/tests/files/FHComplete/throws_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/bad_trait_name.php create mode 100755 tests/codesniffer/FHComplete/tests/files/class_brackets_fail1.php create mode 100755 tests/codesniffer/FHComplete/tests/files/class_brackets_fail2.php create mode 100755 tests/codesniffer/FHComplete/tests/files/class_brackets_fail3.php create mode 100755 tests/codesniffer/FHComplete/tests/files/class_underscore_prefix_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/control_structure_brackets_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/control_structure_dowhile.php create mode 100755 tests/codesniffer/FHComplete/tests/files/control_structure_elseif.php create mode 100755 tests/codesniffer/FHComplete/tests/files/control_structure_indentation.php create mode 100755 tests/codesniffer/FHComplete/tests/files/control_structure_nobrackets.php create mode 100755 tests/codesniffer/FHComplete/tests/files/control_structure_nospace.php create mode 100755 tests/codesniffer/FHComplete/tests/files/control_structure_while.php create mode 100755 tests/codesniffer/FHComplete/tests/files/control_structures_no_newline.php create mode 100755 tests/codesniffer/FHComplete/tests/files/docblock_align_fail.php create mode 100755 tests/codesniffer/FHComplete/tests/files/docblock_align_flat_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/docblock_align_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/double_space.php create mode 100755 tests/codesniffer/FHComplete/tests/files/for_function_comment_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/function_comment_opening_line_fail.php create mode 100755 tests/codesniffer/FHComplete/tests/files/function_comment_opening_line_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/function_comment_type_fail.php create mode 100755 tests/codesniffer/FHComplete/tests/files/function_comment_type_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/function_spacing.php create mode 100755 tests/codesniffer/FHComplete/tests/files/mixing_indent.php create mode 100755 tests/codesniffer/FHComplete/tests/files/multiple_use.php create mode 100755 tests/codesniffer/FHComplete/tests/files/multiple_use_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/operator_spacing_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/short_open_tags_fail.php create mode 100755 tests/codesniffer/FHComplete/tests/files/short_open_tags_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/space_tab.php create mode 100755 tests/codesniffer/FHComplete/tests/files/tab_space.php create mode 100755 tests/codesniffer/FHComplete/tests/files/traits_no_order.php create mode 100755 tests/codesniffer/FHComplete/tests/files/traits_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/type_casting_fail.php create mode 100755 tests/codesniffer/FHComplete/tests/files/type_casting_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/use_class_and_closure_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/use_closure_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/use_no_order.php create mode 100755 tests/codesniffer/FHComplete/tests/files/use_ok_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/use_with_commas.php create mode 100755 tests/codesniffer/FHComplete/tests/files/variable_name_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/files/whitespace_comma.php create mode 100755 tests/codesniffer/FHComplete/tests/files/whitespace_comma_before.php create mode 100755 tests/codesniffer/FHComplete/tests/files/whitespace_comma_pass.php create mode 100755 tests/codesniffer/FHComplete/tests/ruleset.xml diff --git a/composer.json b/composer.json old mode 100644 new mode 100755 index 32278e8dc..a64b8607b --- a/composer.json +++ b/composer.json @@ -5,5 +5,14 @@ "components/bootstrap": "3.3.5", "michelf/php-markdown": "1.5.0" + }, + "require-dev": + { + "squizlabs/php_codesniffer": "2.*", + "fzaninotto/faker": "1.*" + }, + "config": + { + "bin-dir": "bin" } } diff --git a/tests/codesniffer/FHComplete/Sniffs/Commenting/DocBlockAlignmentSniff.php b/tests/codesniffer/FHComplete/Sniffs/Commenting/DocBlockAlignmentSniff.php new file mode 100755 index 000000000..94b5a3e66 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/Commenting/DocBlockAlignmentSniff.php @@ -0,0 +1,59 @@ +getTokens(); + $leftWall = array( + T_CLASS, + T_NAMESPACE, + T_INTERFACE, + T_TRAIT, + T_USE + ); + $oneIndentation = array( + T_FUNCTION, + T_VARIABLE, + T_CONST + ); + $allTokens = array_merge($leftWall, $oneIndentation); + $notFlatFile = $phpcsFile->findNext(T_NAMESPACE, 0); + $next = $phpcsFile->findNext($allTokens, $stackPtr + 1); + + if ($next && $notFlatFile) { + $notWalled = (in_array($tokens[$next]['code'], $leftWall) && $tokens[$stackPtr]['column'] !== 1); + $notIndented = (in_array($tokens[$next]['code'], $oneIndentation) && $tokens[$stackPtr]['column'] !== 5); + if ($notWalled || $notIndented) { + $phpcsFile->addError('Expected docblock to be aligned with code.', $stackPtr, 'NotAllowed'); + } + } + + return; + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/Commenting/FunctionCommentSniff.php b/tests/codesniffer/FHComplete/Sniffs/Commenting/FunctionCommentSniff.php new file mode 100755 index 000000000..34ea7e796 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/Commenting/FunctionCommentSniff.php @@ -0,0 +1,487 @@ + + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('PEAR_Sniffs_Commenting_FunctionCommentSniff', true) === false) { + throw new PHP_CodeSniffer_Exception('Class PEAR_Sniffs_Commenting_FunctionCommentSniff not found'); +} + +/** + * Parses and verifies the doc comments for functions. + * + * Verifies that : + * + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class FHComplete_Sniffs_Commenting_FunctionCommentSniff extends PEAR_Sniffs_Commenting_FunctionCommentSniff +{ + /** + * Is the comment an inheritdoc? + * + * @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 boolean True if the comment is an inheritdoc + */ + protected function isInheritDoc(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $start = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1); + $end = $phpcsFile->findNext(T_DOC_COMMENT_CLOSE_TAG, $start); + $content = $phpcsFile->getTokensAsString($start, ($end - $start)); + return preg_match('#{@inheritDoc}#', $content) === 1; + } // end isInheritDoc() + + /** + * Process the return comment of this function comment. + * + * @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 $commentStart The position in the stack where the comment started. + * + * @return void + */ + protected function processReturn(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) + { + if ($this->isInheritDoc($phpcsFile, $stackPtr)) { + return; + } + + $tokens = $phpcsFile->getTokens(); + + // Skip constructor and destructor. + $className = ''; + foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condition) { + if ($condition === T_CLASS || $condition === T_INTERFACE) { + $className = $phpcsFile->getDeclarationName($condPtr); + $className = strtolower(ltrim($className, '_')); + } + } + + $methodName = $phpcsFile->getDeclarationName($stackPtr); + $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct'); + if ($methodName !== '_') { + $methodName = strtolower(ltrim($methodName, '_')); + } + + $return = null; + foreach ($tokens[$commentStart]['comment_tags'] as $tag) { + if ($tokens[$tag]['content'] === '@return') { + if ($return !== null) { + $error = 'Only 1 @return tag is allowed in a function comment'; + $phpcsFile->addError($error, $tag, 'DuplicateReturn'); + return; + } + + $return = $tag; + } + } + + if ($isSpecialMethod === true) { + return; + } + + if ($return !== null) { + $content = $tokens[($return + 2)]['content']; + if (empty($content) === true || $tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) { + $error = 'Return type missing for @return tag in function comment'; + $phpcsFile->addError($error, $return, 'MissingReturnType'); + } else { + // Check return type (can be multiple, separated by '|'). + $typeNames = explode('|', $content); + $suggestedNames = array(); + foreach ($typeNames as $i => $typeName) { + if ($typeName === 'integer') { + $suggestedName = 'int'; + } elseif ($typeName === 'boolean') { + $suggestedName = 'bool'; + } elseif (in_array($typeName, array('int', 'bool'))) { + $suggestedName = $typeName; + } else { + $suggestedName = PHP_CodeSniffer::suggestType($typeName); + } + if (in_array($suggestedName, $suggestedNames) === false) { + $suggestedNames[] = $suggestedName; + } + } + + $suggestedType = implode('|', $suggestedNames); + if ($content !== $suggestedType) { + $error = 'Function return type "%s" is invalid'; + $error = 'Expected "%s" but found "%s" for function return type'; + $data = array( + $suggestedType, + $content, + ); + $phpcsFile->addError($error, $return, 'InvalidReturn', $data); + } + + // If the return type is void, make sure there is + // no return statement in the function. + if ($content === 'void') { + if (isset($tokens[$stackPtr]['scope_closer']) === true) { + $endToken = $tokens[$stackPtr]['scope_closer']; + for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) { + if ($tokens[$returnToken]['code'] === T_CLOSURE) { + $returnToken = $tokens[$returnToken]['scope_closer']; + continue; + } + + if ($tokens[$returnToken]['code'] === T_RETURN) { + break; + } + } + + if ($returnToken !== $endToken) { + // If the function is not returning anything, just + // exiting, then there is no problem. + $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true); + if ($tokens[$semicolon]['code'] !== T_SEMICOLON) { + $error = 'Function return type is void, but function contains return statement'; + $phpcsFile->addWarning($error, $return, 'InvalidReturnVoid'); + } + } + }//end if + } elseif (!preg_match('/^mixed/', $content)) { + // If return type is not void, there needs to be a return statement + // somewhere in the function that returns something. + if (isset($tokens[$stackPtr]['scope_closer']) === true) { + $endToken = $tokens[$stackPtr]['scope_closer']; + $returnToken = $phpcsFile->findNext(T_RETURN, $stackPtr, $endToken); + if ($returnToken === false) { + $error = 'Function return type is not void, but function has no return statement'; + $phpcsFile->addWarning($error, $return, 'InvalidNoReturn'); + } elseif (!preg_match('/void/', $content)) { + $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true); + if ($tokens[$semicolon]['code'] === T_SEMICOLON) { + $error = 'Function return type is not void, but function is returning void here'; + $phpcsFile->addWarning($error, $returnToken, 'InvalidReturnNotVoid'); + } + } + } + }//end if + }//end if + } else { + $error = 'Missing @return tag in function comment'; + $phpcsFile->addWarning($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn'); + }//end if + + }//end processReturn() + + + /** + * Process any throw tags that this function comment has. + * + * @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 $commentStart The position in the stack where the comment started. + * + * @return void + */ + protected function processThrows(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) + { + $tokens = $phpcsFile->getTokens(); + + $throws = array(); + foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { + if ($tokens[$tag]['content'] !== '@throws') { + continue; + } + + $exception = null; + $comment = null; + if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) { + $matches = array(); + preg_match('/([^\s]+)(?:\s+(.*))?/', $tokens[($tag + 2)]['content'], $matches); + $exception = $matches[1]; + if (isset($matches[2]) === true) { + $comment = $matches[2]; + } + } + + if ($exception === null) { + $error = 'Exception type and comment missing for @throws tag in function comment'; + $phpcsFile->addWarning($error, $tag, 'InvalidThrows'); + } elseif ($comment === null) { + $error = 'Comment missing for @throws tag in function comment'; + $phpcsFile->addWarning($error, $tag, 'EmptyThrows'); + } else { + // Any strings until the next tag belong to this comment. + if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) { + $end = $tokens[$commentStart]['comment_tags'][($pos + 1)]; + } else { + $end = $tokens[$commentStart]['comment_closer']; + } + + for ($i = ($tag + 3); $i < $end; $i++) { + if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { + $comment .= ' '.$tokens[$i]['content']; + } + } + + // Starts with a capital letter and ends with a fullstop. + $firstChar = $comment{0}; + if (strtoupper($firstChar) !== $firstChar) { + $error = '@throws tag comment must start with a capital letter'; + $phpcsFile->addWarning($error, ($tag + 2), 'ThrowsNotCapital'); + } + + $lastChar = substr($comment, -1); + if ($lastChar !== '.') { + $error = '@throws tag comment must end with a full stop'; + $phpcsFile->addWarning($error, ($tag + 2), 'ThrowsNoFullStop'); + } + }//end if + }//end foreach + + }//end processThrows() + + + /** + * Process the function parameter comments. + * + * @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 $commentStart The position in the stack where the comment started. + * + * @return void + */ + protected function processParams(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) + { + if ($this->isInheritDoc($phpcsFile, $stackPtr)) { + return; + } + + $tokens = $phpcsFile->getTokens(); + + $params = array(); + $maxType = 0; + $maxVar = 0; + foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { + if ($tokens[$tag]['content'] !== '@param') { + continue; + } + + $type = ''; + $typeSpace = 0; + $var = ''; + $varSpace = 0; + $comment = ''; + $commentLines = array(); + if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) { + $matches = array(); + preg_match('/([^$&]+)(?:((?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/', $tokens[($tag + 2)]['content'], $matches); + + $typeLen = strlen($matches[1]); + $type = trim($matches[1]); + $typeSpace = ($typeLen - strlen($type)); + $typeLen = strlen($type); + if ($typeLen > $maxType) { + $maxType = $typeLen; + } + + if (isset($matches[2]) === true) { + $var = $matches[2]; + $varLen = strlen($var); + if ($varLen > $maxVar) { + $maxVar = $varLen; + } + + if (isset($matches[4]) === true) { + $varSpace = strlen($matches[3]); + $comment = $matches[4]; + $commentLines[] = array( + 'comment' => $comment, + 'token' => ($tag + 2), + 'indent' => $varSpace, + ); + + // Any strings until the next tag belong to this comment. + if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) { + $end = $tokens[$commentStart]['comment_tags'][($pos + 1)]; + } else { + $end = $tokens[$commentStart]['comment_closer']; + } + + for ($i = ($tag + 3); $i < $end; $i++) { + if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { + $indent = 0; + if ($tokens[($i - 1)]['code'] === T_DOC_COMMENT_WHITESPACE) { + $indent = strlen($tokens[($i - 1)]['content']); + } + + $comment .= ' '.$tokens[$i]['content']; + $commentLines[] = array( + 'comment' => $tokens[$i]['content'], + 'token' => $i, + 'indent' => $indent, + ); + } + } + } else { + $error = 'Missing parameter comment'; + $phpcsFile->addError($error, $tag, 'MissingParamComment'); + $commentLines[] = array('comment' => ''); + }//end if + } else { + $error = 'Missing parameter name'; + $phpcsFile->addError($error, $tag, 'MissingParamName'); + }//end if + } else { + $error = 'Missing parameter type'; + $phpcsFile->addError($error, $tag, 'MissingParamType'); + }//end if + + $params[] = array( + 'tag' => $tag, + 'type' => $type, + 'var' => $var, + 'comment' => $comment, + 'commentLines' => $commentLines, + 'type_space' => $typeSpace, + 'var_space' => $varSpace, + ); + }//end foreach + + $realParams = $phpcsFile->getMethodParameters($stackPtr); + $foundParams = array(); + + foreach ($params as $pos => $param) { + // If the type is empty, the whole line is empty. + if ($param['type'] === '') { + continue; + } + + // Check the param type value. + $typeNames = explode('|', $param['type']); + foreach ($typeNames as $typeName) { + if ($typeName === 'integer') { + $suggestedName = 'int'; + } elseif ($typeName === 'boolean') { + $suggestedName = 'bool'; + } elseif (in_array($typeName, array('int', 'bool'))) { + $suggestedName = $typeName; + } else { + $suggestedName = PHP_CodeSniffer::suggestType($typeName); + } + + if ($typeName !== $suggestedName) { + $error = 'Expected "%s" but found "%s" for parameter type'; + $data = array( + $suggestedName, + $typeName, + ); + + $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data); + if ($fix === true) { + $content = $suggestedName; + $content .= str_repeat(' ', $param['type_space']); + $content .= $param['var']; + $content .= str_repeat(' ', $param['var_space']); + $content .= $param['commentLines'][0]['comment']; + $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content); + } + } + }//end foreach + + if ($param['var'] === '') { + continue; + } + + $foundParams[] = $param['var']; + + // Make sure the param name is correct. + if (isset($realParams[$pos]) === true) { + $realName = $realParams[$pos]['name']; + if ($realName !== $param['var']) { + $code = 'ParamNameNoMatch'; + $data = array( + $param['var'], + $realName, + ); + + $error = 'Doc comment for parameter %s does not match '; + if (strtolower($param['var']) === strtolower($realName)) { + $error .= 'case of '; + $code = 'ParamNameNoCaseMatch'; + } + + $error .= 'actual variable name %s'; + + $phpcsFile->addWarning($error, $param['tag'], $code, $data); + } + } elseif (substr($param['var'], -4) !== ',...') { + // We must have an extra parameter comment. + $error = 'Superfluous parameter comment'; + $phpcsFile->addError($error, $param['tag'], 'ExtraParamComment'); + }//end if + + if ($param['comment'] === '') { + continue; + } + + // Param comments must start with a capital letter and end with the full stop. + $firstChar = $param['comment']{0}; + if (preg_match('|\p{Lu}|u', $firstChar) === 0) { + $error = 'Parameter comment must start with a capital letter'; + $phpcsFile->addWarning($error, $param['tag'], 'ParamCommentNotCapital'); + } + + $lastChar = substr($param['comment'], -1); + if ($lastChar !== '.') { + $error = 'Parameter comment must end with a full stop'; + $phpcsFile->addWarning($error, $param['tag'], 'ParamCommentFullStop'); + } + }//end foreach + + $realNames = array(); + foreach ($realParams as $realParam) { + $realNames[] = $realParam['name']; + } + + // Report missing comments. + $diff = array_diff($realNames, $foundParams); + foreach ($diff as $neededParam) { + $error = 'Doc comment for parameter "%s" missing'; + $data = array($neededParam); + $phpcsFile->addWarning($error, $commentStart, 'MissingParamTag', $data); + } + + }//end processParams() +}//end class diff --git a/tests/codesniffer/FHComplete/Sniffs/Commenting/FunctionCommentTypeSniff.php b/tests/codesniffer/FHComplete/Sniffs/Commenting/FunctionCommentTypeSniff.php new file mode 100755 index 000000000..722661165 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/Commenting/FunctionCommentTypeSniff.php @@ -0,0 +1,87 @@ +getTokens(); + + // We are only interested in function/class/interface doc block comments. + $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true); + $ignore = array( + T_CLASS, + T_INTERFACE, + T_FUNCTION, + T_PUBLIC, + T_PRIVATE, + T_PROTECTED, + T_STATIC, + T_ABSTRACT, + ); + + if (in_array($tokens[$nextToken]['code'], $ignore) === false) { + // Could be a file comment. + $prevToken = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($tokens[$prevToken]['code'] !== T_OPEN_TAG) { + return; + } + } + + $types = array( + 'boolean' => 'bool', + 'integer' => 'int', + ); + foreach ($types as $from => $to) { + $this->_check($phpcsFile, $stackPtr, $from, $to); + } + } + + /** + * MyFHComplete_Sniffs_Commenting_DocBlockTypeSniff::_check() + * + * @param int $stackPtr + * @param string $from + * @param string $to + * @return void + */ + protected function _check(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $from, $to) + { + $tokens = $phpcsFile->getTokens(); + $content = $tokens[$stackPtr]['content']; + + $matches = array(); + if (preg_match('/\@(\w+)\s+([\w\\|\\\\]*?)' . $from . '\b/i', $content, $matches) === 0) { + return; + } + + $error = 'Please use "' . $to . '" instead of "' . $from . '" for types in doc blocks.'; + $phpcsFile->addWarning($error, $stackPtr, 'WrongType'); + } + +} diff --git a/tests/codesniffer/FHComplete/Sniffs/ControlStructures/ControlSignatureSniff.php b/tests/codesniffer/FHComplete/Sniffs/ControlStructures/ControlSignatureSniff.php new file mode 100755 index 000000000..b78202cec --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/ControlStructures/ControlSignatureSniff.php @@ -0,0 +1,58 @@ + + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class FHComplete_Sniffs_ControlStructures_ControlSignatureSniff extends PHP_CodeSniffer_Standards_AbstractPatternSniff +{ + + /** + * If true, comments will be ignored if they are found in the code. + * + * @var boolean + */ + public $ignoreComments = true; + + + /** + * Returns the patterns that this test wishes to verify. + * + * @return string[] + */ + protected function getPatterns() + { + return array( + 'try {EOL...}\s+catch (...)EOL...{EOL...EOL...}', + 'do+EOL...{EOL...EOL...} while (...);EOL', + 'while (...)EOL...{EOL', + 'for (...) {EOL', + 'if (...)EOL...{EOL', + 'foreach (...)EOL...{EOL', + '}EOL...\s+else if (...)EOL...{EOL', + '}EOL...\s+elseif (...)EOL...{EOL', + '}EOL...\s+else+EOL...{EOL', + 'do+EOL...{EOL', + ); + + }//end getPatterns() + + +}//end class diff --git a/tests/codesniffer/FHComplete/Sniffs/ControlStructures/ElseIfDeclarationSniff.php b/tests/codesniffer/FHComplete/Sniffs/ControlStructures/ElseIfDeclarationSniff.php new file mode 100755 index 000000000..1ef2f2aaf --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/ControlStructures/ElseIfDeclarationSniff.php @@ -0,0 +1,48 @@ +getTokens(); + + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($tokens[$nextToken]['code'] !== T_IF) { + return; + } + + $error = 'Usage of ELSE IF not allowed; use ELSEIF instead'; + $phpcsFile->addError($error, $stackPtr, 'NotAllowed'); + } + +} diff --git a/tests/codesniffer/FHComplete/Sniffs/ControlStructures/WhileStructuresSniff.php b/tests/codesniffer/FHComplete/Sniffs/ControlStructures/WhileStructuresSniff.php new file mode 100755 index 000000000..421f86ba6 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/ControlStructures/WhileStructuresSniff.php @@ -0,0 +1,56 @@ +getTokens(); + + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS) { + $closer = $tokens[$nextToken]['parenthesis_closer']; + $diff = $closer - $stackPtr; + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + $diff + 1), null, true); + } + + if ($tokens[$stackPtr]['code'] === T_WHILE && $tokens[$nextToken]['code'] === T_SEMICOLON) { + /* This while is probably part of a do-while construction, skip it .. */ + return; + } + if ($tokens[$nextToken]['code'] !== T_OPEN_CURLY_BRACKET && $tokens[$nextToken]['code'] !== T_COLON) { + $error = 'Curly brackets required in a do-while or while loop'; + $phpcsFile->addError($error, $stackPtr, 'NotAllowed'); + } + } + +} diff --git a/tests/codesniffer/FHComplete/Sniffs/Formatting/OneClassPerUseSniff.php b/tests/codesniffer/FHComplete/Sniffs/Formatting/OneClassPerUseSniff.php new file mode 100755 index 000000000..2b2ee37e6 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/Formatting/OneClassPerUseSniff.php @@ -0,0 +1,48 @@ +getTokens(); + $i = 2; // Ignore use word and whitespace + $filename = $phpcsFile->getFilename(); + + while (in_array($tokens[$stackPtr + $i]['code'], array(T_STRING, T_NS_SEPARATOR, T_WHITESPACE, T_AS))) { + $i++; + } + + if ($tokens[$stackPtr + $i]['code'] === T_COMMA) { + $error = 'Only one class is allowed per use'; + $phpcsFile->addError($error, $stackPtr, 'OneClassPerUse', array()); + } + } + +} diff --git a/tests/codesniffer/FHComplete/Sniffs/Formatting/UseInAlphabeticalOrderSniff.php b/tests/codesniffer/FHComplete/Sniffs/Formatting/UseInAlphabeticalOrderSniff.php new file mode 100755 index 000000000..43da15876 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/Formatting/UseInAlphabeticalOrderSniff.php @@ -0,0 +1,142 @@ +_processed[$phpcsFile->getFilename()])) { + return; + } + $filename = $phpcsFile->getFilename(); + + $this->_uses = array(); + $next = $stackPtr; + + while ($next !== false) { + $this->_checkUseToken($phpcsFile, $next); + $next = $phpcsFile->findNext(T_USE, $next + 1); + } + + // Prevent multiple uses in the same file from entering + $this->_processed[$phpcsFile->getFilename()] = true; + + foreach ($this->_uses as $scope => $used) { + $defined = $sorted = array_keys($used); + + natcasesort($sorted); + $sorted = array_values($sorted); + if ($sorted === $defined) { + continue; + } + + foreach ($defined as $i => $name) { + if ($name !== $sorted[$i]) { + $error = 'Use classes must be in alphabetical order. Was expecting ' . $sorted[$i]; + $phpcsFile->addError($error, $used[$name], 'UseInAlphabeticalOrder', array()); + } + } + } + } + +/** + * Check all the use tokens in a file. + * + * @param PHP_CodeSniffer_File $phpcsFile The file to check. + * @param integer $stackPtr The index of the first use token. + * @return void + */ + protected function _checkUseToken($phpcsFile, $stackPtr) { + // If the use token is for a closure we want to ignore it. + $isClosure = $this->_isClosure($phpcsFile, $stackPtr); + if ($isClosure) { + return; + } + + $tokens = $phpcsFile->getTokens(); + + // Only one USE declaration allowed per statement. + $next = $phpcsFile->findNext(array(T_COMMA, T_SEMICOLON), ($stackPtr + 1)); + if ($tokens[$next]['code'] === T_COMMA) { + $error = 'There must be one USE keyword per declaration'; + $phpcsFile->addError($error, $stackPtr, 'MultipleDeclarations'); + } + + $content = ''; + $end = $phpcsFile->findNext(array(T_SEMICOLON, T_OPEN_CURLY_BRACKET), $stackPtr); + $useTokens = array_slice($tokens, $stackPtr, $end - $stackPtr, true); + + foreach ($useTokens as $index => $token) { + if ($token['code'] === T_STRING || $token['code'] === T_NS_SEPARATOR) { + $content .= $token['content']; + } + } + + // Check for class scoping on use. Traits should be + // ordered independently. + $scope = 0; + if (!empty($token['conditions'])) { + $scope = key($token['conditions']); + } + $this->_uses[$scope][$content] = $stackPtr; + } + +/** + * Check if the current stackPtr is a use token that is for a closure. + * + * @param PHP_CodeSniffer_File $phpcsFile + * @param integer $stackPtr + * @return boolean + */ + protected function _isClosure($phpcsFile, $stackPtr) { + return $phpcsFile->findPrevious( + array(T_CLOSURE), + ($stackPtr - 1), + null, + false, + null, + true + ); + } + +} diff --git a/tests/codesniffer/FHComplete/Sniffs/Functions/ClosureDeclarationSniff.php b/tests/codesniffer/FHComplete/Sniffs/Functions/ClosureDeclarationSniff.php new file mode 100755 index 000000000..6582d3011 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/Functions/ClosureDeclarationSniff.php @@ -0,0 +1,35 @@ +getTokens(); + $spaces = 0; + + if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) { + $spaces = strlen($tokens[($stackPtr + 1)]['content']); + } + + if ($spaces !== 1) { + $error = 'Expected 1 space after closure\'s function keyword; %s found'; + $data = array($spaces); + $phpcsFile->addError($error, $stackPtr, 'SpaceAfterFunction', $data); + } + } + +} diff --git a/tests/codesniffer/FHComplete/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php b/tests/codesniffer/FHComplete/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php new file mode 100755 index 000000000..260ad1c10 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php @@ -0,0 +1,26 @@ + + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +if (class_exists('Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff', true) === false) { + throw new PHP_CodeSniffer_Exception('Class Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff not found'); +} + +/** + * PSR1_Sniffs_Methods_CamelCapsMethodNameSniff. + * + * Ensures method names are defined using camel case. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class FHComplete_Sniffs_NamingConventions_CamelCapsMethodNameSniff extends Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff +{ + + + /** + * 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) + { + $methodName = $phpcsFile->getDeclarationName($stackPtr); + if ($methodName === null + || substr($methodName,-4) === '_get' + || substr($methodName,-5) === '_post' + || substr($methodName,-4) === '_put' + || substr($methodName,-6) === '_patch' + || substr($methodName,-7) === '_delete' + ) + // Ignore closures. + return; + + // Ignore magic methods. + if (preg_match('|^__|', $methodName) !== 0) { + $magicPart = strtolower(substr($methodName, 2)); + if (isset($this->magicMethods[$magicPart]) === true + || isset($this->methodsDoubleUnderscore[$magicPart]) === true + ) { + return; + } + } + + $testName = ltrim($methodName, '_'); + if ($testName !== '' && PHP_CodeSniffer::isCamelCaps($testName, false, true, false) === false) { + $error = 'Method name "%s" is not in camel caps format'; + $className = $phpcsFile->getDeclarationName($currScope); + $errorData = array($className.'::'.$methodName); + $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $errorData); + $phpcsFile->recordMetric($stackPtr, 'CamelCase method name', 'no'); + } else { + $phpcsFile->recordMetric($stackPtr, 'CamelCase method name', 'yes'); + } + + }//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) + { + + }//end processTokenOutsideScope() + + +}//end class diff --git a/tests/codesniffer/FHComplete/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php new file mode 100755 index 000000000..3137ea8cf --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php @@ -0,0 +1,223 @@ + + * @author Marc McIntyre + * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @version Release: 1.5.0RC3 + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class FHComplete_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); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param integer $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; + } + + // Special case for PHPUnit. + if ($constName === 'PHPUnit_MAIN_METHOD') { + 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(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, + T_NS_SEPARATOR, + ), + ($stackPtr - 1), + null, + true + ); + + $declarations = array( + T_FUNCTION, + T_CLASS, + T_INTERFACE, + T_TRAIT, + T_IMPLEMENTS, + T_EXTENDS, + T_INSTANCEOF, + T_NEW, + T_NAMESPACE, + T_USE, + T_AS, + T_GOTO, + T_INSTEADOF, + T_PROTECTED, + T_PRIVATE, + T_PUBLIC + ); + + 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 %s but found %s'; + $data = array( + strtoupper($constName), + $constName, + ); + $phpcsFile->addError($error, $stackPtr, 'ClassConstantNotUpperCase', $data); + } + + return; + } + + // Is this a class name? + $nextPtr = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($tokens[$nextPtr]['code'] === T_DOUBLE_COLON) { + return; + } + + // Is this a namespace name? + if ($tokens[$nextPtr]['code'] === T_NS_SEPARATOR) { + return; + } + + // Is this an insteadof name? + if ($tokens[$nextPtr]['code'] === T_INSTEADOF) { + return; + } + + // Is this an as name? + if ($tokens[$nextPtr]['code'] === T_AS) { + return; + } + + // Is this a type hint? + if ($tokens[$nextPtr]['code'] === T_VARIABLE + || $phpcsFile->isReference($nextPtr) === true + ) { + return; + } + + // Is this a member var name? + $prevPtr = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$prevPtr]['code'] === T_OBJECT_OPERATOR) { + return; + } + + // Is this a variable name, in the form ${varname} ? + if ($tokens[$prevPtr]['code'] === T_OPEN_CURLY_BRACKET) { + $nextPtr = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($tokens[$nextPtr]['code'] === T_CLOSE_CURLY_BRACKET) { + return; + } + } + + // Is this a namespace name? + if ($tokens[$prevPtr]['code'] === T_NS_SEPARATOR) { + return; + } + + // Is this an instance of declare() + $prevPtrDeclare = $phpcsFile->findPrevious( + array(T_WHITESPACE, T_OPEN_PARENTHESIS), + ($stackPtr - 1), + null, + true + ); + if ($tokens[$prevPtrDeclare]['code'] === T_DECLARE) { + return; + } + + // Is this a goto label target? + if ($tokens[$nextPtr]['code'] === T_COLON) { + if (in_array($tokens[$prevPtr]['code'], array(T_SEMICOLON, T_OPEN_CURLY_BRACKET, T_COLON), true)) { + return; + } + } + + // This is a real constant. + if (strtoupper($constName) !== $constName) { + $error = 'Constants must be uppercase; expected %s but found %s'; + $data = array( + strtoupper($constName), + $constName, + ); + $phpcsFile->addError($error, $stackPtr, 'ConstantNotUpperCase', $data); + } + + } elseif (strtolower($constName) === 'define' || strtolower($constName) === 'constant') { + /* + This may be a "define" or "constant" function call. + */ + // Make sure this is not a method call. + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR + || $tokens[$prev]['code'] === T_DOUBLE_COLON + ) { + return; + } + + // The next non-whitespace token must be the constant name. + $constPtr = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true); + if ($tokens[$constPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING) { + return; + } + + $constName = $tokens[$constPtr]['content']; + + // Check for constants like self::CONSTANT. + $prefix = ''; + $splitPos = strpos($constName, '::'); + if ($splitPos !== false) { + $prefix = substr($constName, 0, ($splitPos + 2)); + $constName = substr($constName, ($splitPos + 2)); + } + + if (strtoupper($constName) !== $constName) { + $error = 'Constants must be uppercase; expected %s but found %s'; + $data = array( + $prefix . strtoupper($constName), + $prefix . $constName, + ); + $phpcsFile->addError($error, $stackPtr, 'ConstantNotUpperCase', $data); + } + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidClassBracketsSniff.php b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidClassBracketsSniff.php new file mode 100755 index 000000000..a43ac59d0 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidClassBracketsSniff.php @@ -0,0 +1,47 @@ +getTokens(); + + $found = $phpcsFile->findNext(T_OPEN_CURLY_BRACKET, $stackPtr); + if ($tokens[$found - 1]['code'] != T_WHITESPACE) { + $error = 'Expected 1 space after class declaration, found 0'; + $phpcsFile->addError($error, $found - 1, 'InvalidSpacing', array()); + return; + } + + if (strlen($tokens[$found - 1]['content']) > 1 || $tokens[$found - 2]['code'] == T_WHITESPACE) { + $error = 'Expected 1 space after class declaration, found ' . strlen($tokens[$found - 1]['content']); + $phpcsFile->addError($error, $found - 1, 'InvalidSpacing', array()); + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidFunctionNameSniff.php new file mode 100755 index 000000000..3281d83f9 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -0,0 +1,138 @@ +getDeclarationName($stackPtr); + if ($methodName === null) { + // Ignore closures. + return; + } + + $className = $phpcsFile->getDeclarationName($currScope); + $errorData = array($className . '::' . $methodName); + + // PHP4 constructors are allowed to break our rules. + if ($methodName === $className) { + return; + } + + // PHP4 destructors are allowed to break our rules. + if ($methodName === '_' . $className) { + return; + } + + // Ignore magic methods + if (preg_match('/^__(' . implode('|', $this->_magicMethods) . ')$/', $methodName)) { + return; + } + + $methodProps = $phpcsFile->getMethodProperties($stackPtr); + if ($methodProps['scope_specified'] === false) { + // Let another sniffer take care of that + return; + } + + $isPublic = $methodProps['scope'] === 'public'; + $isProtected = $methodProps['scope'] === 'protected'; + $isPrivate = $methodProps['scope'] === 'private'; + $scope = $methodProps['scope']; + + if ($isPublic === true) { + if ($methodName[0] === '_') { + $error = 'Public method name "%s" must not be prefixed with underscore'; + $phpcsFile->addError($error, $stackPtr, 'PublicWithUnderscore', $errorData); + return; + } + // Underscored public methods in controller are allowed to break our rules. + if (substr($className, -10) === 'Controller') { + return; + } + // Underscored public methods in shells are allowed to break our rules. + if (substr($className, -5) === 'Shell') { + return; + } + // Underscored public methods in tasks are allowed to break our rules. + if (substr($className, -4) === 'Task') { + return; + } + } elseif ($isPrivate === true) { + if (substr($methodName, 0, 2) !== '__') { + $error = 'Private method name "%s" must be prefixed with 2 underscores'; + $phpcsFile->addError($error, $stackPtr, 'PrivateNoUnderscore', $errorData); + return; + } else { + $filename = $phpcsFile->getFilename(); + if (strpos($filename, '/lib/Cake/') === true) { + $warning = 'Private method name "%s" in FHComplete core is discouraged'; + $phpcsFile->addWarning($warning, $stackPtr, 'PrivateMethodInCore', $errorData); + } + } + } else { + if ($methodName[0] !== '_' || substr($methodName, 0, 2) === '__') { + $error = 'Protected method name "%s" must be prefixed with one underscore'; + $phpcsFile->addError($error, $stackPtr, 'ProtectedNoUnderscore', $errorData); + return; + } + } + } + +/** + * Processes the tokens outside the scope. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being processed. + * @param integer $stackPtr The position where this token was found. + * @return void + */ + protected function processTokenOutsideScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { + } + +} diff --git a/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidTraitNameSniff.php b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidTraitNameSniff.php new file mode 100755 index 000000000..5364eab07 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidTraitNameSniff.php @@ -0,0 +1,48 @@ +getTokens(); + $traitName = $tokens[$stackPtr + 2]['content']; + + if (substr($traitName, -5) !== 'Trait') { + $error = 'Traits must have a "Trait" suffix.'; + $phpcsFile->addError($error, $stackPtr, 'InvalidTraitName', array()); + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidVariableNameSniff.php b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidVariableNameSniff.php new file mode 100755 index 000000000..bce627331 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -0,0 +1,171 @@ +getTokens(); + $varName = ltrim($tokens[$stackPtr]['content'], '$'); + + $phpReservedVars = array( + '_SERVER', + '_GET', + '_POST', + '_REQUEST', + '_SESSION', + '_ENV', + '_COOKIE', + '_FILES', + 'GLOBALS', + ); + + // If it's a php reserved var, then its ok. + if (in_array($varName, $phpReservedVars) === true) { + return; + } + + // There is no way for us to know if the var is public or private, + // so we have to ignore a leading underscore if there is one and just + // check the main part of the variable name. + $originalVarName = $varName; + if (substr($varName, 0, 1) === '_') { + $objOperator = $phpcsFile->findPrevious(array(T_WHITESPACE), ($stackPtr - 1), null, true); + if ($tokens[$objOperator]['code'] === T_DOUBLE_COLON) { + // The variable lives within a class, and is referenced like + // this: MyClass::$_variable, so we don't know its scope. + $inClass = true; + } else { + $inClass = $phpcsFile->hasCondition($stackPtr, array(T_TRAIT, T_CLASS, T_INTERFACE)); + } + + if ($inClass === true) { + $varName = ltrim($varName, '_'); + } + } + + // $title_for_layout is allowed on controllers + $fileName = basename($phpcsFile->getFilename(), '.php'); + if ((substr($fileName, -10) === 'Controller') && ($varName == 'title_for_layout')) { + return; + } + + if ($this->_isValidVar($varName) === false) { + $error = 'Variable "%s" is not in valid camel caps format'; + $data = array($originalVarName); + $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $data); + } + } + +/** + * Processes class member variables. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param integer $stackPtr The position of the current token in the + * stack passed in $tokens. + * @return void + */ + protected function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { + } + +/** + * Processes the variable found within a double quoted string. + * + * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. + * @param integer $stackPtr The position of the double quoted string. + * @return void + */ + protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { + $tokens = $phpcsFile->getTokens(); + + $phpReservedVars = array( + '_SERVER', + '_GET', + '_POST', + '_REQUEST', + '_SESSION', + '_ENV', + '_COOKIE', + '_FILES', + 'GLOBALS', + ); + + if (preg_match_all('|[^\\\]\$([a-zA-Z0-9_]+)|', $tokens[$stackPtr]['content'], $matches) !== 0) { + foreach ($matches[1] as $varName) { + // If it's a php reserved var, then its ok. + if (in_array($varName, $phpReservedVars) === true) { + continue; + } + + // There is no way for us to know if the var is public or private, + // so we have to ignore a leading underscore if there is one and just + // check the main part of the variable name. + $originalVarName = $varName; + if (substr($varName, 0, 1) === '_') { + if ($phpcsFile->hasCondition($stackPtr, array(T_CLASS, T_INTERFACE)) === true) { + $varName = substr($varName, 1); + } + } + + if ($this->_isValidVar($varName) === false) { + $error = 'Variable "%s" is not in valid camel caps format'; + $data = array($originalVarName); + $phpcsFile->addError($error, $stackPtr, 'StringVarNotCamelCaps', $data); + } + } + } + } + +/** + * Check that a variable is a valid shape. + * + * Variables in FHComplete can either be $fooBar, $FooBar, $_fooBar, or $_FooBar. + * + * @param string $string The variable to check. + * @param boolea $public Whether or not the variable is public. + * @return boolean + */ + protected function _isValidVar($string, $public = true) { + $firstChar = '[a-zA-Z]'; + if (!$public) { + $firstChar = '[_]{1,2}' . $firstChar; + } + if (preg_match("|^$firstChar|", $string) === 0) { + return false; + } + $firstStringCount = 1; + if (preg_match("|^__|", $string)) { + $firstStringCount = 2; + } + // Check that the name only contains legal characters. + $legalChars = 'a-zA-Z0-9'; + if (preg_match("|[^$legalChars]|", substr($string, $firstStringCount)) > 0) { + return false; + } + return true; + } + +} diff --git a/tests/codesniffer/FHComplete/Sniffs/PHP/DisallowShortOpenTagSniff.php b/tests/codesniffer/FHComplete/Sniffs/PHP/DisallowShortOpenTagSniff.php new file mode 100755 index 000000000..cd3b8598e --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/PHP/DisallowShortOpenTagSniff.php @@ -0,0 +1,53 @@ +getTokens(); + $openTag = $tokens[$stackPtr]; + + if (trim($openTag['content']) === 'addError($error, $stackPtr, 'Found', $data); + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/PHP/TypeCastingSniff.php b/tests/codesniffer/FHComplete/Sniffs/PHP/TypeCastingSniff.php new file mode 100755 index 000000000..2595c4f56 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/PHP/TypeCastingSniff.php @@ -0,0 +1,74 @@ +getTokens(); + + // Process !! casts + if ($tokens[$stackPtr]['code'] == T_BOOLEAN_NOT) { + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if (!$nextToken) { + return; + } + if ($tokens[$nextToken]['code'] != T_BOOLEAN_NOT) { + return; + } + $error = 'Usage of !! cast is not allowed. Please use (bool) to cast.'; + $phpcsFile->addError($error, $stackPtr, 'NotAllowed'); + + return; + } + + // Only allow short forms if both short and long forms are possible + $matching = array( + '(boolean)' => '(bool)', + '(integer)' => '(int)', + ); + $content = $tokens[$stackPtr]['content']; + $key = strtolower($content); + if (isset($matching[$key])) { + $error = 'Please use ' . $matching[$key] . ' instead of ' . $content . '.'; + $phpcsFile->addError($error, $stackPtr, 'NotAllowed'); + return; + } + if ($content !== $key) { + $error = 'Please use ' . $key . ' instead of ' . $content . '.'; + $phpcsFile->addError($error, $stackPtr, 'NotAllowed'); + return; + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/Strings/ConcatenationSpacingSniff.php b/tests/codesniffer/FHComplete/Sniffs/Strings/ConcatenationSpacingSniff.php new file mode 100755 index 000000000..59eaaf974 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/Strings/ConcatenationSpacingSniff.php @@ -0,0 +1,48 @@ +getTokens(); + if ($tokens[($stackPtr - 1)]['code'] == T_WHITESPACE) { + $message = 'Expected 0 spaces before ., but 1 found'; + $phpcsFile->addError($message, $stackPtr, 'FoundBefore'); + } + + if ($tokens[($stackPtr + 1)]['code'] == ' ') + { + $message = 'Expected 0 spaces after ., but 1 found'; + $phpcsFile->addError($message, $stackPtr, 'FoundAfter'); + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/CommaSpacingSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/CommaSpacingSniff.php new file mode 100755 index 000000000..7506abe9f --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/CommaSpacingSniff.php @@ -0,0 +1,54 @@ +getTokens(); + + $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + + if ($tokens[$next]['code'] !== T_WHITESPACE && ($next !== $stackPtr + 2)) { + // Last character in a line is ok. + if ($tokens[$next]['line'] === $tokens[$stackPtr]['line']) { + $error = 'Missing space after comma'; + $phpcsFile->addError($error, $next); + } + } + + $previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + + if ($tokens[$previous]['code'] !== T_WHITESPACE && ($previous !== $stackPtr - 1)) { + $error = 'Space before comma, expected none, though'; + $phpcsFile->addError($error, $next); + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionCallSpacingSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionCallSpacingSniff.php new file mode 100755 index 000000000..8a3cbc8e0 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionCallSpacingSniff.php @@ -0,0 +1,53 @@ +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; + } + + // Look for funcName ( + if (($stackPtr + 1) !== $openBracket) { + $error = 'Space before opening parenthesis of function call not allowed'; + $phpcsFile->addError($error, $stackPtr, 'SpaceBeforeOpenBracket'); + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php new file mode 100755 index 000000000..83e7ac73e --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php @@ -0,0 +1,69 @@ +getTokens(); + + if (isset($tokens[$stackPtr]['scope_closer']) === false) { + // Probably an interface method. + return; + } + + $closeBrace = $tokens[$stackPtr]['scope_closer']; + $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBrace - 1), null, true); + + $braceLine = $tokens[$closeBrace]['line']; + $prevLine = $tokens[$prevContent]['line']; + + $found = ($braceLine - $prevLine - 1); + if ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true + || isset($tokens[$stackPtr]['nested_parenthesis']) === true + ) { + // Nested function. + if ($found < 0) { + $error = 'Closing brace of nested function must be on a new line'; + $phpcsFile->addError($error, $closeBrace, 'ContentBeforeClose'); + } elseif ($found > 0) { + $error = 'Expected 0 blank lines before closing brace of nested function; %s found'; + $data = array($found); + $phpcsFile->addError($error, $closeBrace, 'SpacingBeforeNestedClose', $data); + } + } else { + if ($found !== 0) { + $error = 'Expected 0 blank lines before closing function brace; %s found'; + $data = array($found); + $phpcsFile->addError($error, $closeBrace, 'SpacingBeforeClose', $data); + } + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionOpeningBraceSpaceSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionOpeningBraceSpaceSniff.php new file mode 100755 index 000000000..d202e760a --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionOpeningBraceSpaceSniff.php @@ -0,0 +1,62 @@ +getTokens(); + + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + // Probably an interface method. + return; + } + + $openBrace = $tokens[$stackPtr]['scope_opener']; + $nextContent = $phpcsFile->findNext(T_WHITESPACE, ($openBrace + 1), null, true); + + if ($nextContent === $tokens[$stackPtr]['scope_closer']) { + // The next bit of content is the closing brace, so this + // is an empty function and should have a blank line + // between the opening and closing braces. + return; + } + + $braceLine = $tokens[$openBrace]['line']; + $nextLine = $tokens[$nextContent]['line']; + + $found = ($nextLine - $braceLine - 1); + if ($found > 0) { + $error = 'Expected 0 blank lines after opening function brace; %s found'; + $data = array($found); + $phpcsFile->addError($error, $openBrace, 'SpacingAfter', $data); + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionSpacingSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionSpacingSniff.php new file mode 100755 index 000000000..09f6a55b0 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/FunctionSpacingSniff.php @@ -0,0 +1,129 @@ +getTokens(); + + /* + Check the number of blank lines + after the function. + */ + + if (isset($tokens[$stackPtr]['scope_closer']) === false) { + // Must be an interface method, so the closer is the semi-colon. + $closer = $phpcsFile->findNext(T_SEMICOLON, $stackPtr); + } else { + $closer = $tokens[$stackPtr]['scope_closer']; + } + + // There needs to be 1 blank lines after the closer. + $nextLineToken = null; + for ($i = $closer; $i < $phpcsFile->numTokens; $i++) { + if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) === false) { + continue; + } else { + $nextLineToken = ($i + 1); + break; + } + } + + if ($nextLineToken === null) { + // Never found the next line, which means + // there are 0 blank lines after the function. + $foundLines = 0; + } else { + $nextContent = $phpcsFile->findNext(array(T_WHITESPACE), ($nextLineToken + 1), null, true); + if ($nextContent === false) { + // We are at the end of the file. That is acceptable as well. + $foundLines = 1; + } else { + $foundLines = ($tokens[$nextContent]['line'] - $tokens[$nextLineToken]['line']); + } + } + + /* + Check the number of blank lines + before the function. + */ + + $prevLineToken = null; + for ($i = $stackPtr; $i > 0; $i--) { + if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) === false) { + continue; + } else { + $prevLineToken = $i; + break; + } + } + + if ($prevLineToken === null) { + // Never found the previous line, which means + // there are 0 blank lines before the function. + $foundLines = 0; + } else { + $prevContent = $phpcsFile->findPrevious(array(T_WHITESPACE, T_DOC_COMMENT), $prevLineToken, null, true); + + // Before we throw an error, check that we are not throwing an error + // for another function. We don't want to error for no blank lines after + // the previous function and no blank lines before this one as well. + $currentLine = $tokens[$stackPtr]['line']; + $prevLine = ($tokens[$prevContent]['line'] - 1); + $i = ($stackPtr - 1); + $foundLines = 0; + while ($currentLine !== $prevLine && $currentLine > 1 && $i > 0) { + if (isset($tokens[$i]['scope_condition']) === true) { + $scopeCondition = $tokens[$i]['scope_condition']; + if ($tokens[$scopeCondition]['code'] === T_FUNCTION) { + // Found a previous function. + return; + } + } elseif ($tokens[$i]['code'] === T_FUNCTION) { + // Found another interface function. + return; + } + + $currentLine = $tokens[$i]['line']; + if ($currentLine === $prevLine) { + break; + } + + if ($tokens[($i - 1)]['line'] < $currentLine && $tokens[($i + 1)]['line'] > $currentLine) { + // This token is on a line by itself. If it is whitespace, the line is empty. + if ($tokens[$i]['code'] === T_WHITESPACE) { + $foundLines++; + } + } + $i--; + } + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php new file mode 100755 index 000000000..a52680736 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php @@ -0,0 +1,42 @@ +getTokens(); + + $nextType = $tokens[($stackPtr + 1)]['code']; + if (in_array($nextType, PHP_CodeSniffer_Tokens::$emptyTokens) === true) { + $error = 'Space found after object operator'; + $phpcsFile->addError($error, $stackPtr, 'After'); + } + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/OperatorSpacingSniff.php new file mode 100755 index 000000000..efeef023a --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -0,0 +1,185 @@ +getTokens(); + + // Skip default values in function declarations. + if ($tokens[$stackPtr]['code'] === T_EQUAL + || $tokens[$stackPtr]['code'] === T_MINUS + ) { + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { + $parenthesis = array_keys($tokens[$stackPtr]['nested_parenthesis']); + $bracket = array_pop($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_EQUAL) { + // Skip for '=&' case. + if (isset($tokens[($stackPtr + 1)]) === true && $tokens[($stackPtr + 1)]['code'] === T_BITWISE_AND) { + 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) && !$this->isVariable($stackPtr, $tokens, $phpcsFile)) { + // Check there is one space before the & operator. + if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE) { + $error = 'Expected 1 space before "&" operator; 0 found'; + $phpcsFile->addError($error, $stackPtr, 'NoSpaceBeforeAmp'); + } + + // Check there is one space after the & operator. + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + $error = 'Expected 1 space after "&" operator; 0 found'; + $phpcsFile->addError($error, $stackPtr, 'NoSpaceAfterAmp'); + } + } + } else { + if ($tokens[$stackPtr]['code'] === T_MINUS) { + // Skip declaration of negative value in new array format; eg. $arr = [-1]. + if ($tokens[($stackPtr - 1)]['code'] === T_OPEN_SHORT_ARRAY) { + return; + } + + // 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, + T_DOUBLE_ARROW, + T_COLON, + T_INLINE_THEN, + T_INLINE_ELSE, + T_CASE, + ); + + if (in_array($tokens[$prev]['code'], $invalidTokens) === true) { + // Just trying to use a negative value; eg. myFunction($var, -2). + return; + } + if (in_array($tokens[$prev]['code'], PHP_CodeSniffer_Tokens::$assignmentTokens) === true) { + // Just trying to assign a negative value; eg. ($var = -1). + return; + } + } + + $operator = $tokens[$stackPtr]['content']; + + if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE) { + $error = "Expected 1 space before \"$operator\"; 0 found"; + $phpcsFile->addError($error, $stackPtr, 'NoSpaceBefore'); + } + + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + $error = "Expected 1 space after \"$operator\"; 0 found"; + $phpcsFile->addError($error, $stackPtr, 'NoSpaceAfter'); + } + } + } + + /** + * Check if the current token is inside an array. + * + * @param int $stackPtr The current token offset. + * @param array $phpcsFile The current token list. + * @return bool + */ + protected function isVariable($stackPtr, $tokens, $phpcsFile) + { + $tokenAfter = $phpcsFile->findNext( + PHP_CodeSniffer_Tokens::$emptyTokens, + ($stackPtr + 1), + null, + true + ); + $tokenBefore = $phpcsFile->findNext( + PHP_CodeSniffer_Tokens::$emptyTokens, + ($stackPtr - 1), + null, + true + ); + + if ($tokens[$tokenAfter]['code'] === T_VARIABLE && + ( + $tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS || + $tokens[$tokenBefore]['code'] === T_COMMA || + $tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY + ) + ) { + return true; + } + return false; + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/ScopeIndentSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/ScopeIndentSniff.php new file mode 100755 index 000000000..b3e998ed4 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -0,0 +1,280 @@ +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); + + $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']; + + // If the scope closer is followed by a semi-colon, the semi-colon is part + // of the closer and should also be ignored. This most commonly happens with + // CASE statements that end with "break;", where we don't want to stop + // ignoring at the break, but rather at the semi-colon. + $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($i + 1), null, true); + if ($tokens[$nextToken]['code'] === T_SEMICOLON) { + $i = $nextToken; + } + } 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; + } elseif ($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); + } + + // If we're starting a new PHP block that has the scope closer + // as the next token we'll skip the remaining checks as the scope is closed. + if ($tokens[$firstToken]['code'] === T_OPEN_TAG && + $scopeCloser == $firstToken + 1 + ) { + continue; + } + + // 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. + $comments = array( + T_COMMENT, + T_DOC_COMMENT + ); + + $isDocComment = false; + if (in_array($tokens[$firstToken]['code'], $comments) === 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; + $isDocComment = (substr($content, 0, 3) === '/**'); + } + } elseif ($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; + } + } + } + } + } + } + + /** + * 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) { + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true + && empty($tokens[$stackPtr]['nested_parenthesis']) === false + ) { + // Wrapped in parenthesis means it is probably in a + // function call (like a closure) so we have to assume indent + // is correct here and someone else will check it more + // carefully in another sniff. + return $tokens[$stackPtr]['column']; + } else { + 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); + } +} diff --git a/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/TabAndSpaceSniff.php b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/TabAndSpaceSniff.php new file mode 100755 index 000000000..0c2593aa1 --- /dev/null +++ b/tests/codesniffer/FHComplete/Sniffs/WhiteSpace/TabAndSpaceSniff.php @@ -0,0 +1,63 @@ +getTokens(); + + $line = $tokens[$stackPtr]['line']; + if ($stackPtr > 0 && $tokens[($stackPtr - 1)]['line'] !== $line) { + return; + } + + if (strpos($tokens[$stackPtr]['content'], " \t") !== false) { + $error = 'Space and tab found'; + $phpcsFile->addError($error, $stackPtr); + } + if (strpos($tokens[$stackPtr]['content'], "\t ") !== false) { + $error = 'Tab and space found'; + $phpcsFile->addError($error, $stackPtr); + } + } +} diff --git a/tests/codesniffer/FHComplete/ruleset.xml b/tests/codesniffer/FHComplete/ruleset.xml new file mode 100755 index 000000000..6673d18fe --- /dev/null +++ b/tests/codesniffer/FHComplete/ruleset.xml @@ -0,0 +1,92 @@ + + + FHComplete's coding standard + + \.git + */Config/*.ini.php + /*/tmp/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */tests/* + + + */tests/* + + + + + diff --git a/tests/codesniffer/FHComplete/tests/FHCompletePHPStandardTest.php b/tests/codesniffer/FHComplete/tests/FHCompletePHPStandardTest.php new file mode 100755 index 000000000..8c933aa95 --- /dev/null +++ b/tests/codesniffer/FHComplete/tests/FHCompletePHPStandardTest.php @@ -0,0 +1,67 @@ +helper)) { + $this->helper = new TestHelper(); + } + } + +/** + * testFiles + * + * Run simple syntax checks, if the filename ends with pass.php - expect it to pass + */ + public static function testProvider() { + $tests = array(); + + $standard = dirname(dirname(__FILE__)); + + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__FILE__) . '/files')); + foreach ($iterator as $dir) { + if ($dir->isDir()) { + continue; + } + + $file = $dir->getPathname(); + $expectPass = (substr($file, -8) === 'pass.php'); + $tests[] = array( + $file, + $standard, + $expectPass + ); + } + return $tests; + } + +/** + * _testFile + * + * @dataProvider testProvider + * + * @param string $file + * @param string $standard + * @param boolean $expectPass + */ + public function testFile($file, $standard, $expectPass) { + $outputStr = $this->helper->runPhpCs($file); + if ($expectPass) { + $this->assertNotRegExp( + "/FOUND \d+ ERROR/", + $outputStr, + basename($file) . ' - expected to pass with no errors, some were reported. ' + ); + } else { + $this->assertRegExp( + "/FOUND \d+ ERROR/", + $outputStr, + basename($file) . ' - expected failures, none reported. ' + ); + } + } + +} diff --git a/tests/codesniffer/FHComplete/tests/TestHelper.php b/tests/codesniffer/FHComplete/tests/TestHelper.php new file mode 100755 index 000000000..4d86b5fe4 --- /dev/null +++ b/tests/codesniffer/FHComplete/tests/TestHelper.php @@ -0,0 +1,54 @@ +_phpcs = new PHP_CodeSniffer_CLI(); + } + +/** + * Run PHPCS on a file. + * + * @param string $file to run. + * @return string The output from phpcs. + */ + public function runPhpCs($file) { + $defaults = $this->_phpcs->getDefaults(); + $standard = dirname(__FILE__) . '/ruleset.xml'; + if ( + defined('PHP_CodeSniffer::VERSION') && + version_compare(PHP_CodeSniffer::VERSION, '1.5.0') != -1 + ) { + $standard = array($standard); + } + $options = array( + 'encoding' => 'utf-8', + 'files' => array($file), + 'standard' => $standard, + 'showSources' => true, + ) + $defaults; + + // New PHPCS has a strange issue where the method arguments + // are not stored on the instance causing weird errors. + $reflection = new ReflectionProperty($this->_phpcs, 'values'); + $reflection->setAccessible(true); + $reflection->setValue($this->_phpcs, $options); + + ob_start(); + $this->_phpcs->process($options); + $result = ob_get_contents(); + ob_end_clean(); + return $result; + } + +} diff --git a/tests/codesniffer/FHComplete/tests/bootstrap.php b/tests/codesniffer/FHComplete/tests/bootstrap.php new file mode 100755 index 000000000..b6ef51c7c --- /dev/null +++ b/tests/codesniffer/FHComplete/tests/bootstrap.php @@ -0,0 +1,2 @@ + 100) { + echo 'i > 100'; +} + +while (false) { + echo 'false'; +} + +do { + echo 'dowhile test'; +} while (false); diff --git a/tests/codesniffer/FHComplete/tests/files/control_structure_dowhile.php b/tests/codesniffer/FHComplete/tests/files/control_structure_dowhile.php new file mode 100755 index 000000000..54b2190d4 --- /dev/null +++ b/tests/codesniffer/FHComplete/tests/files/control_structure_dowhile.php @@ -0,0 +1,6 @@ +something ('testing'); +fail_whale (); diff --git a/tests/codesniffer/FHComplete/tests/files/mixing_indent.php b/tests/codesniffer/FHComplete/tests/files/mixing_indent.php new file mode 100755 index 000000000..0400dd514 --- /dev/null +++ b/tests/codesniffer/FHComplete/tests/files/mixing_indent.php @@ -0,0 +1,6 @@ + 'after 2 tabs' + ); +} diff --git a/tests/codesniffer/FHComplete/tests/files/multiple_use.php b/tests/codesniffer/FHComplete/tests/files/multiple_use.php new file mode 100755 index 000000000..b239a6419 --- /dev/null +++ b/tests/codesniffer/FHComplete/tests/files/multiple_use.php @@ -0,0 +1,6 @@ + + + + +passingPublic = 'changed'; + $this->underscored = 'has value now'; + $this->doubleUnderscore = 'not recommended'; + } + + /** + * [setStatics description] + * + * @return void + */ + public static function setStatics() + { + self::$publicStatic = true; + self::$protectedStatic = true; + self::$privateStatic = true; + } +} diff --git a/tests/codesniffer/FHComplete/tests/files/whitespace_comma.php b/tests/codesniffer/FHComplete/tests/files/whitespace_comma.php new file mode 100755 index 000000000..78e43d5f0 --- /dev/null +++ b/tests/codesniffer/FHComplete/tests/files/whitespace_comma.php @@ -0,0 +1,2 @@ + + + FH-Complete coding standard + + + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +