Skip to content

Commit

Permalink
Added PSR12.Functions.ReturnTypeDeclaration sniff (ref squizlabs#750)
Browse files Browse the repository at this point in the history
  • Loading branch information
gsherwood committed Aug 23, 2019
1 parent 581e964 commit 779d547
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 2 deletions.
6 changes: 6 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
-- Enforce the use of a strict types declaration in PHP files
- Added PSR12.Files.DeclareStatement sniff
-- Enforces the formatting of declare statements within a file
- Added PSR12.Functions.ReturnTypeDeclaration sniff
-- Enforces the formatting of return type declarations in functions and closures
- Added PSR12.Properties.ConstantVisibility sniff
-- Enforces that constants must have their visibility defined
-- Uses a warning instead of an error due to this conditionally requiring the project to support PHP 7.1+
Expand Down Expand Up @@ -1090,6 +1092,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
</dir>
<dir name="Functions">
<file baseinstalldir="PHP/CodeSniffer" name="NullableTypeDeclarationSniff.php" role="php" />
<file baseinstalldir="PHP/CodeSniffer" name="ReturnTypeDeclarationSniff.php" role="php" />
</dir>
<dir name="Keywords">
<file baseinstalldir="PHP/CodeSniffer" name="ShortFormTypeKeywordsSniff.php" role="php" />
Expand Down Expand Up @@ -1122,6 +1125,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="PHP/CodeSniffer" name="NullableTypeDeclarationUnitTest.inc" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="NullableTypeDeclarationUnitTest.inc.fixed" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="NullableTypeDeclarationUnitTest.php" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="ReturnTypeDeclarationUnitTest.inc" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="ReturnTypeDeclarationUnitTest.inc.fixed" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="ReturnTypeDeclarationUnitTest.php" role="test" />
</dir>
<dir name="Keywords">
<file baseinstalldir="PHP/CodeSniffer" name="ShortFormTypeKeywordsUnitTest.inc" role="test" />
Expand Down
106 changes: 106 additions & 0 deletions src/Standards/PSR12/Sniffs/Functions/ReturnTypeDeclarationSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php
/**
* Ensure return types are defined correctly for fucnctions and closures.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Standards\PSR12\Sniffs\Functions;

use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;

class ReturnTypeDeclarationSniff implements Sniff
{


/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return [
T_FUNCTION,
T_CLOSURE,
];

}//end register()


/**
* Processes this test when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

if (isset($tokens[$stackPtr]['parenthesis_opener']) === false
|| isset($tokens[$stackPtr]['parenthesis_closer']) === false
|| $tokens[$stackPtr]['parenthesis_opener'] === null
|| $tokens[$stackPtr]['parenthesis_closer'] === null
) {
return;
}

$methodProperties = $phpcsFile->getMethodProperties($stackPtr);
if ($methodProperties['return_type'] === '') {
return;
}

$returnType = $methodProperties['return_type_token'];
if ($tokens[($returnType - 1)]['code'] !== T_WHITESPACE
|| $tokens[($returnType - 1)]['content'] !== ' '
|| $tokens[($returnType - 2)]['code'] !== T_COLON
) {
$error = 'There must be a single space between the colon and type in a return type declaration';
if ($tokens[($returnType - 1)]['code'] === T_WHITESPACE
&& $tokens[($returnType - 2)]['code'] === T_COLON
) {
$fix = $phpcsFile->addFixableError($error, $returnType, 'SpaceBeforeReturnType');
if ($fix === true) {
$phpcsFile->fixer->replaceToken(($returnType - 1), ' ');
}
} else if ($tokens[($returnType - 1)]['code'] === T_COLON) {
$fix = $phpcsFile->addFixableError($error, $returnType, 'SpaceBeforeReturnType');
if ($fix === true) {
$phpcsFile->fixer->addContentBefore($returnType, ' ');
}
} else {
$phpcsFile->addError($error, $returnType, 'SpaceBeforeReturnType');
}
}

$colon = $phpcsFile->findPrevious(T_COLON, $returnType);
if ($tokens[($colon - 1)]['code'] !== T_CLOSE_PARENTHESIS) {
$error = 'There must not be a space before the colon in a return type declaration';
$prev = $phpcsFile->findPrevious(T_WHITESPACE, ($colon - 1), null, true);
if ($tokens[$prev]['code'] === T_CLOSE_PARENTHESIS) {
$fix = $phpcsFile->addFixableError($error, $colon, 'SpaceBeforeColon');
if ($fix === true) {
$phpcsFile->fixer->beginChangeset();
for ($x = ($prev + 1); $x < $colon; $x++) {
$phpcsFile->fixer->replaceToken($x, '');
}

$phpcsFile->fixer->endChangeset();
}
} else {
$phpcsFile->addError($error, $colon, 'SpaceBeforeColon');
}
}

}//end process()


}//end class
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

public function functionName(int $arg1, $arg2): string
{
return 'foo';
}

public function anotherFunction(
int $baz
): string {
return 'foo';
}

$longArgs_noVars = function (
$longArgument,
): string {
// body
};

$shortArgs_longVars = function ($arg) use (
$longVar1,
): string {
// body
};

public function functionName(int $arg1, $arg2)
:
string
{
return 'foo';
}

public function anotherFunction(
int $baz
) : string {
return 'foo';
}

$longArgs_noVars = function (
$longArgument,
) :string {
// body
};

$shortArgs_longVars = function ($arg) use (
$longVar1,
)
:string {
// body
};

public function functionName(int $arg1, $arg2) /* can't fix */ : string {}

public function functionName(int $arg1, $arg2) /* can't fix */
: // phpcs:ignore Standard.Category.Sniff
string {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

public function functionName(int $arg1, $arg2): string
{
return 'foo';
}

public function anotherFunction(
int $baz
): string {
return 'foo';
}

$longArgs_noVars = function (
$longArgument,
): string {
// body
};

$shortArgs_longVars = function ($arg) use (
$longVar1,
): string {
// body
};

public function functionName(int $arg1, $arg2): string
{
return 'foo';
}

public function anotherFunction(
int $baz
): string {
return 'foo';
}

$longArgs_noVars = function (
$longArgument,
): string {
// body
};

$shortArgs_longVars = function ($arg) use (
$longVar1,
): string {
// body
};

public function functionName(int $arg1, $arg2) /* can't fix */ : string {}

public function functionName(int $arg1, $arg2) /* can't fix */
: // phpcs:ignore Standard.Category.Sniff
string {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
/**
* Unit test class for the ReturnTypeDeclaration sniff.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2018 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Standards\PSR12\Tests\Functions;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

class ReturnTypeDeclarationUnitTest extends AbstractSniffUnitTest
{


/**
* Returns the lines where errors should occur.
*
* The key of the array should represent the line number and the value
* should represent the number of errors that should occur on that line.
*
* @return array<int, int>
*/
protected function getErrorList()
{
return [
27 => 1,
28 => 1,
35 => 2,
41 => 2,
48 => 2,
52 => 1,
55 => 1,
56 => 1,
];

}//end getErrorList()


/**
* Returns the lines where warnings should occur.
*
* The key of the array should represent the line number and the value
* should represent the number of warnings that should occur on that line.
*
* @return array<int, int>
*/
protected function getWarningList()
{
return [];

}//end getWarningList()


}//end class
6 changes: 4 additions & 2 deletions src/Standards/PSR12/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,8 @@

<!-- When wishing to declare strict types in files containing markup outside PHP opening and closing tags, the declaration MUST be on the first line of the file and include an opening PHP tag, the strict types declaration and closing tag. -->
<!-- Declare statements MUST contain no spaces and MUST be exactly declare(strict_types=1) (with an optional semi-colon terminator). -->
<!-- checked by PSR12.Files.DeclareStatement -->

<!-- Block declare statements are allowed and MUST be formatted as below. -->
<!-- checked by PSR12.Files.DeclareStatement -->

<!-- 4. Classes, Properties, and Methods -->

Expand Down Expand Up @@ -186,6 +185,7 @@
<rule ref="Squiz.Functions.MultiLineFunctionDeclaration"/>

<!-- When you have a return type declaration present, there MUST be one space after the colon followed by the type declaration. The colon and declaration MUST be on the same line as the argument list closing parenthesis with no spaces between the two characters. -->
<!-- checked by PSR12.Functions.ReturnTypeDeclaration -->

<!-- In nullable type declarations, there MUST NOT be a space between the question mark and the type. -->
<!-- checked by PSR12.Functions.NullableTypeDeclaration -->
Expand Down Expand Up @@ -250,6 +250,7 @@
<!-- 5.1 if, elseif, else -->

<!-- else and elseif are on the same line as the closing brace from the earlier body. -->
<!-- checked by Squiz.ControlStructures.ControlSignature included above -->

<!-- The keyword elseif SHOULD be used instead of else if so that all control keywords look like single words. -->
<rule ref="PSR2.ControlStructures.ElseIfDeclaration"/>
Expand Down Expand Up @@ -315,6 +316,7 @@
<!-- checked by Squiz.Functions.MultiLineFunctionDeclaration -->

<!-- If a return type is present, it MUST follow the same rules as with normal functions and methods; if the use keyword is present, the colon MUST follow the use list closing parentheses with no spaces between the two characters. -->
<!-- checked by PSR12.Functions.ReturnTypeDeclaration -->

<!-- 8. Anonymous Classes -->

Expand Down

0 comments on commit 779d547

Please sign in to comment.