diff --git a/WordPress/Sniffs/Functions/ReturnTypeSniff.php b/WordPress/Sniffs/Functions/ReturnTypeSniff.php new file mode 100644 index 0000000000..9eeb6db6e8 --- /dev/null +++ b/WordPress/Sniffs/Functions/ReturnTypeSniff.php @@ -0,0 +1,165 @@ + true, + 'bool' => true, + 'callable' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'object' => true, + 'parent' => true, + 'self' => true, + 'string' => true, + 'void' => true, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return array( + T_RETURN_TYPE, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * @return void Return before end if return type declaration contains an invalid token. + */ + public function process_token( $stackPtr ) { + $colon = $this->phpcsFile->findPrevious( T_COLON, $stackPtr - 1, null, false, null, true ); + + if ( false === $colon ) { + // Shouldn't happen, but just in case. + return; + } + + // Space before colon disallowed. + if ( isset( $this->tokens[ $colon - 1 ] ) && T_CLOSE_PARENTHESIS !== $this->tokens[ $colon - 1 ]['code'] ) { + $error = 'There must be nothing between the closing parenthesis and the colon when declaring a return type for a function.'; + $error_code = 'SpaceBeforeColon'; + + $previousNonWhitespace = $this->phpcsFile->findPrevious( T_WHITESPACE, $colon - 1, null, true, null, true ); + if ( false !== $previousNonWhitespace && T_CLOSE_PARENTHESIS !== $this->tokens[ $previousNonWhitespace ]['code'] ) { + // Don't auto-fix: Something other than whitespace found between closing parenthesis and colon. + $this->phpcsFile->addError( $error, $colon - 1, $error_code ); + } else { + $fix = $this->phpcsFile->addFixableError( $error, $colon - 1, $error_code ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + $token = $colon - 1; + do { + $this->phpcsFile->fixer->replaceToken( $token, '' ); + -- $token; + } while ( isset( $this->tokens[ $token ] ) && T_CLOSE_PARENTHESIS !== $this->tokens[ $token ]['code'] ); + $this->phpcsFile->fixer->endChangeset(); + } + } + } + + // Only one space after colon. + if ( T_WHITESPACE !== $this->tokens[ $colon + 1 ]['code'] ) { + $error = 'There must be one space between the colon and the return type. None found.'; + $fix = $this->phpcsFile->addFixableError( $error, $colon, 'NoSpaceAfterColon' ); + if ( true === $fix ) { + $this->phpcsFile->fixer->addContent( $colon, ' ' ); + } + } else { + $error = 'There must be exactly one space between the colon and the return type. Found: %s'; + $error_code = 'TooManySpacesAfterColon'; + + $nextNonWhiteSpace = $this->phpcsFile->findNext( array( T_WHITESPACE, T_NS_SEPARATOR ), $colon + 1, null, true, null, true ); + + if ( $stackPtr !== $nextNonWhiteSpace ) { + // Don't auto-fix: Something other than whitespace or namespace separator found between closing parenthesis and colon. + $this->phpcsFile->addError( $error, $colon + 1, $error_code ); + } else { + $spacesAfterColon = strlen( $this->tokens[ $colon + 1 ]['content'] ); + if ( false !== strpos( $this->tokens[ $colon + 1 ]['content'], $this->phpcsFile->eolChar ) ) { + $spacesAfterColon = 'newline'; + } + if ( ' ' !== $this->tokens[ $colon + 1 ]['content'] ) { + $data = array( $spacesAfterColon ); + $fix = $this->phpcsFile->addFixableError( $error, $colon + 1, $error_code, $data ); + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + $token = $colon + 1; + do { + $this->phpcsFile->fixer->replaceToken( $token, '' ); + $token ++; + } while ( isset( $this->tokens[ $token ] ) && $token < $nextNonWhiteSpace - 1 ); + $this->phpcsFile->fixer->replaceToken( $colon + 1, ' ' ); + $this->phpcsFile->fixer->endChangeset(); + } + } + } + } + + $nullable = $this->phpcsFile->findNext( T_NULLABLE, $colon + 1, $stackPtr ); + // Check if there is space after nullable operator. + if ( false !== $nullable && T_WHITESPACE === $this->tokens[ $nullable + 1 ]['code'] ) { + $error = 'Space is not allowed after nullable operator.'; + $fix = $this->phpcsFile->addFixableError( $error, $nullable + 1, 'SpaceAfterNullable' ); + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( $nullable + 1, '' ); + } + } + + $contentLC = strtolower( $this->tokens[ $stackPtr ]['content'] ); + // Check if simple return type is in lowercase. + if ( isset( $this->simple_return_types[ $contentLC ] ) && $contentLC !== $this->tokens[ $stackPtr ]['content'] ) { + $error = 'Simple return type must be lowercase. Found "%s", expected "%s"'; + $data = array( + $this->tokens[ $stackPtr ]['content'], + $contentLC, + ); + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'LowerCaseSimpleType', $data ); + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( $stackPtr, $contentLC ); + } + } + } +} diff --git a/WordPress/Tests/Functions/ReturnTypeUnitTest.inc b/WordPress/Tests/Functions/ReturnTypeUnitTest.inc new file mode 100644 index 0000000000..184347399c --- /dev/null +++ b/WordPress/Tests/Functions/ReturnTypeUnitTest.inc @@ -0,0 +1,88 @@ + => + */ + public function getErrorList() { + return array( + 4 => 1, + 5 => 1, + 6 => 1, + 7 => 1, + 9 => 1, + 11 => 1, + 13 => 1, + 16 => 1, + 36 => 2, + 45 => 1, + 51 => 2, + 59 => 2, + 64 => 2, + 65 => 2, + 66 => 2, + 67 => 3, + 68 => 3, + 69 => 2, + 70 => 2, + 71 => 2, + 72 => 1, + 73 => 2, + 74 => 1, + 75 => 2, + 76 => 1, + 77 => 2, + 78 => 1, + 79 => 2, + 80 => 1, + 81 => 2, + 82 => 1, + 84 => 2, + 85 => 1, + 87 => 2, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() { + return array(); + + } + +}