Skip to content

Commit

Permalink
Add ReturnType Sniff
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
GaryJones committed Aug 4, 2017
1 parent 0b2401e commit b9e86b1
Show file tree
Hide file tree
Showing 4 changed files with 382 additions and 0 deletions.
140 changes: 140 additions & 0 deletions WordPress/Sniffs/Functions/ReturnTypeSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php
/**
* WordPress Coding Standard.
*
* @package WPCS\WordPressCodingStandards
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
* @license https://opensource.org/licenses/MIT MIT
*/

namespace WordPress\Sniffs\WhiteSpace;

use PHP_CodeSniffer_Tokens as Tokens;
use WordPress\Sniff;

/**
* Enforces formatting of return types.
*
* @package WPCS\WordPressCodingStandards
*
* @since 0.14.0
*
* @link https://github.com/zendframework/zend-coding-standard/blob/52b435c714879609262aa36b004c4075cd967acc/src/ZendCodingStandard/Sniffs/Formatting/ReturnTypeSniff.php
*/
class ReturnTypeSniff extends Sniff {
/**
* Simple return types.
*
* @since 0.14.0
*
* @var string[]
*/
private $simple_return_types = array(
'void',
'int',
'float',
'double',
'string',
'array',
'iterable',
'callable',
'parent',
'self',
'bool',
);

/**
* 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.
*/
public function process_token( $stackPtr ) {
$colon = (int) $this->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 ) );
}
}
}
}
84 changes: 84 additions & 0 deletions WordPress/Tests/Functions/ReturnTypeUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

function fooa(): array {} // Good.
function foob() : array {} // Bad.
function fooc() : array {} // Bad.
function food() // Bad.
: array {}
function fooe():array {} // Bad.
function foof(): array {} // Bad.
function foog(): // Bad.
array {}

// Don't touch case statements here.
$v = 1;
switch ($v) {
case 1:
$x = $f1(-1 * $v);
break;
case (2) :
$x = $f2(-1 * $v, $v);
break;
default:
$x = $f1($v) + $f2(-1 * $v, $v);
break;
}

class ReturnType
{
public function method() :array // Bad.
{
return [];
}

private function priv(
$a,
$b
)
: int { // Bad.
return $a * $b;
}
}

$c = new class() {
public function method() : // Bad.
float {
return mt_rand();
}
};

abstract class AbsCla
{
abstract public function x() :bool; // Bad.
}

interface MyInterface
{ // All below are bad.
public function a():vOid;
public function b() : Int;
public function c() :?int;
public function d() :Float;
public function e() :? float;
public function f():Double;
public function g():?double;
public function h():Array;
public function i() : ?array;
public function j():String;
public function k():?string;
public function l():Parent;
public function m():?parent;
public function n():Callable;
public function o():?callable;
public function p():Bool;
public function q():?bool;
public function r():Self;
public function s():?self;
public function t():Iterable;
public function u():?iterable;

public function v($a) : \DateTime;
public function w():?\DateTime;

public function y($a, $b, $c) : \My\TestClass;
public function z():? \ReturnType \MyType;
}
80 changes: 80 additions & 0 deletions WordPress/Tests/Functions/ReturnTypeUnitTest.inc.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

function fooa(): array {} // Good.
function foob(): array {} // Bad.
function fooc(): array {} // Bad.
function food(): array {} // Bad.
function fooe(): array {} // Bad.
function foof(): array {} // Bad.
function foog(): array {} // Bad.

// Don't touch case statements here.
$v = 1;
switch ($v) {
case 1:
$x = $f1(-1 * $v);
break;
case (2) :
$x = $f2(-1 * $v, $v);
break;
default:
$x = $f1($v) + $f2(-1 * $v, $v);
break;
}

class ReturnType
{
public function method(): array // Bad.
{
return [];
}

private function priv(
$a,
$b
): int { // Bad.
return $a * $b;
}
}

$c = new class() {
public function method(): float { // Bad.
return mt_rand();
}
};

abstract class AbsCla
{
abstract public function x(): bool; // Bad.
}

interface MyInterface
{ // All below are bad.
public function a(): void;
public function b(): int;
public function c(): ?int;
public function d(): float;
public function e(): ?float;
public function f(): double;
public function g(): ?double;
public function h(): array;
public function i(): ?array;
public function j(): string;
public function k(): ?string;
public function l(): parent;
public function m(): ?parent;
public function n(): callable;
public function o(): ?callable;
public function p(): bool;
public function q(): ?bool;
public function r(): self;
public function s(): ?self;
public function t(): iterable;
public function u(): ?iterable;

public function v($a): \DateTime;
public function w(): ?\DateTime;

public function y($a, $b, $c): \My\TestClass;
public function z(): ?\ReturnType\MyType;
}
78 changes: 78 additions & 0 deletions WordPress/Tests/Functions/ReturnTypeUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
/**
* Unit test class for WordPress Coding Standard.
*
* @package WPCS\WordPressCodingStandards
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
* @license https://opensource.org/licenses/MIT MIT
*/

namespace WordPress\Tests\Functions;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Unit test class for the ReturnType sniff.
*
* @package WPCS\WordPressCodingStandards
*
* @since 0.14.0
*/
class ReturnTypeUnitTest extends AbstractSniffUnitTest {

/**
* Returns the lines where errors should occur.
*
* @return array <int line number> => <int number of errors>
*/
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 <int line number> => <int number of warnings>
*/
public function getWarningList() {
return array();

}

}

0 comments on commit b9e86b1

Please sign in to comment.