From bb1b4fe8cc0c4eaa759e0397b8b9334dd41216e2 Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Fri, 4 Aug 2017 12:11:13 +0100 Subject: [PATCH] Add ReturnType Sniff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only slightly adapted from the [work](https://github.com/zendframework/zend-coding-standard/blob/52b435c714879609262aa36b004c4075cd967acc/src/ZendCodingStandard/Sniffs/Formatting/ReturnTypeSniff.php) Michał Bundyra (@webimpress) did for the Zend Coding Standard. Required format for WordPress is the same as most others in the PHP community: - no space before the colon - exactly one space after colon before return type - no space after nullable operator (hasn't been discussed yet, but I see no reason to vary) - simple types should be given as lowercase End result: ```php function foo(): bool { // Simple return type. }; function foo(): ?string { // Nullable simple return type. }; function foo( $a ): \MyClass { // Return type of a class. }; ``` ❗️ Note: Not yet added to `WordPress-Core/ruleset.xml`. See #547. --- .../Sniffs/Functions/ReturnTypeSniff.php | 140 ++++++++++++++++++ .../Tests/Functions/ReturnTypeUnitTest.inc | 84 +++++++++++ .../Functions/ReturnTypeUnitTest.inc.fixed | 80 ++++++++++ .../Tests/Functions/ReturnTypeUnitTest.php | 78 ++++++++++ 4 files changed, 382 insertions(+) create mode 100644 WordPress/Sniffs/Functions/ReturnTypeSniff.php create mode 100644 WordPress/Tests/Functions/ReturnTypeUnitTest.inc create mode 100644 WordPress/Tests/Functions/ReturnTypeUnitTest.inc.fixed create mode 100644 WordPress/Tests/Functions/ReturnTypeUnitTest.php diff --git a/WordPress/Sniffs/Functions/ReturnTypeSniff.php b/WordPress/Sniffs/Functions/ReturnTypeSniff.php new file mode 100644 index 0000000000..19bf187d99 --- /dev/null +++ b/WordPress/Sniffs/Functions/ReturnTypeSniff.php @@ -0,0 +1,140 @@ +phpcsFile->findPrevious( T_COLON, $stackPtr - 1 ); + + // Space before colon disallowed. + if ( T_CLOSE_PARENTHESIS !== $this->tokens[ $colon - 1 ]['code'] ) { + $error = 'There must be no space before colon before a return type.'; + $fix = $this->phpcsFile->addFixableError( $error, $colon - 1, 'SpaceBeforeColon' ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + $token = $colon - 1; + do { + $this->phpcsFile->fixer->replaceToken( $token, '' ); + -- $token; + } while ( 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 a space after colon and before return type declaration.'; + $fix = $this->phpcsFile->addFixableError( $error, $colon, 'NoSpaceAfterColon' ); + if ( true === $fix ) { + $this->phpcsFile->fixer->addContent( $colon, ' ' ); + } + } elseif ( ' ' !== $this->tokens[ $colon + 1 ]['content'] ) { + $error = 'There must be exactly one space after colon and before return type declaration.'; + $fix = $this->phpcsFile->addFixableError( $error, $colon + 1, 'TooManySpacesAfterColon' ); + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( $colon + 1, ' ' ); + } + } + + $nullable = $this->phpcsFile->findNext( T_NULLABLE, $colon + 1, $stackPtr ); + if ( $nullable ) { + // Check if there is space after nullable operator. + if ( T_WHITESPACE === $this->tokens[ $nullable + 1 ]['code'] ) { + $error = 'Space is not not allowed after nullable operator.'; + $fix = $this->phpcsFile->addFixableError( $error, $nullable + 1, 'SpaceAfterNullable' ); + if ( $fix ) { + $this->phpcsFile->fixer->replaceToken( $nullable + 1, '' ); + } + } + } + + $first = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $nullable ?: $colon ) + 1, null, true ); + $end = $this->phpcsFile->findNext( [ T_SEMICOLON, T_OPEN_CURLY_BRACKET ], $stackPtr + 1 ); + $last = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $end - 1, null, true ); + $invalid = $this->phpcsFile->findNext( [ T_STRING, T_NS_SEPARATOR, T_RETURN_TYPE ], $first, $last + 1, true ); + if ( $invalid ) { + $error = 'Return type declaration contains invalid token %s'; + $data = array( $this->tokens[ $invalid ]['type'] ); + $fix = $this->phpcsFile->addFixableError( $error, $invalid, 'InvalidToken', $data ); + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( $invalid, '' ); + } + + return; + } + + $returnType = trim( $this->phpcsFile->getTokensAsString( $first, $last - $first + 1 ) ); + + if ( $first === $last + && in_array( strtolower( $returnType ), $this->simple_return_types, true ) + && ! in_array( $returnType, $this->simple_return_types, true ) + ) { + $error = 'Simple return type must be lowercase. Found "%s", expected "%s"'; + $data = array( + $returnType, + strtolower( $returnType ), + ); + $fix = $this->phpcsFile->addFixableError( $error, $first, 'LowerCaseSimpleType', $data ); + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( $stackPtr, strtolower( $returnType ) ); + } + } + } +} diff --git a/WordPress/Tests/Functions/ReturnTypeUnitTest.inc b/WordPress/Tests/Functions/ReturnTypeUnitTest.inc new file mode 100644 index 0000000000..facce511a6 --- /dev/null +++ b/WordPress/Tests/Functions/ReturnTypeUnitTest.inc @@ -0,0 +1,84 @@ + => + */ + public function getErrorList() { + return array( + 4 => 1, + 5 => 1, + 6 => 1, + 8 => 1, + 9 => 1, + 10 => 1, + 29 => 2, + 38 => 1, + 44 => 2, + 52 => 2, + 57 => 2, + 58 => 2, + 59 => 2, + 60 => 3, + 61 => 3, + 62 => 2, + 63 => 1, + 64 => 2, + 65 => 1, + 66 => 2, + 67 => 1, + 68 => 2, + 69 => 1, + 70 => 2, + 71 => 1, + 72 => 2, + 73 => 1, + 74 => 2, + 75 => 1, + 76 => 2, + 77 => 1, + 79 => 2, + 80 => 1, + 82 => 1, + 83 => 3, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() { + return array(); + + } + +}