Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functions\ReturnType - better recognise returned value #23

Merged
merged 1 commit into from
May 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 90 additions & 22 deletions src/WebimpressCodingStandard/Sniffs/Functions/ReturnTypeSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@
use const T_ARRAY;
use const T_ARRAY_CAST;
use const T_BOOL_CAST;
use const T_BOOLEAN_AND;
use const T_BOOLEAN_NOT;
use const T_BOOLEAN_OR;
use const T_CLOSE_PARENTHESIS;
use const T_CLOSURE;
use const T_COALESCE;
use const T_COLON;
use const T_CONSTANT_ENCAPSED_STRING;
use const T_DIR;
Expand All @@ -48,9 +51,21 @@
use const T_FALSE;
use const T_FILE;
use const T_FUNCTION;
use const T_GREATER_THAN;
use const T_INLINE_ELSE;
use const T_INLINE_THEN;
use const T_INT_CAST;
use const T_IS_EQUAL;
use const T_IS_GREATER_OR_EQUAL;
use const T_IS_IDENTICAL;
use const T_IS_NOT_EQUAL;
use const T_IS_NOT_IDENTICAL;
use const T_IS_SMALLER_OR_EQUAL;
use const T_LESS_THAN;
use const T_LNUMBER;
use const T_LOGICAL_AND;
use const T_LOGICAL_OR;
use const T_LOGICAL_XOR;
use const T_NEW;
use const T_NS_SEPARATOR;
use const T_NULL;
Expand All @@ -62,6 +77,7 @@
use const T_RETURN;
use const T_SELF;
use const T_SEMICOLON;
use const T_SPACESHIP;
use const T_STATIC;
use const T_STRING;
use const T_STRING_CAST;
Expand Down Expand Up @@ -745,10 +761,84 @@ static function (string $a, string $b) {
}
}

private function endOfExpression(File $phpcsFile, int $ptr) : ?int
{
$tokens = $phpcsFile->getTokens();

do {
if ($tokens[$ptr]['code'] === T_OPEN_PARENTHESIS
|| $tokens[$ptr]['code'] === T_ARRAY
) {
$ptr = $tokens[$ptr]['parenthesis_closer'];
} elseif ($tokens[$ptr]['code'] === T_OPEN_SQUARE_BRACKET
|| $tokens[$ptr]['code'] === T_OPEN_CURLY_BRACKET
|| $tokens[$ptr]['code'] === T_OPEN_SHORT_ARRAY
) {
$ptr = $tokens[$ptr]['bracket_closer'];
} elseif (in_array(
$tokens[$ptr]['code'],
Tokens::$booleanOperators + Tokens::$comparisonTokens
+ [
T_SEMICOLON,
T_CLOSE_PARENTHESIS,
T_INLINE_ELSE,
T_INLINE_THEN,
],
true
)) {
return $ptr;
}
} while (++$ptr);

return null;
}

private function getReturnValue(File $phpcsFile, int $ptr) : string
{
$tokens = $phpcsFile->getTokens();

$boolExpressionTokens = [
// comparision tokens
T_IS_EQUAL,
T_IS_IDENTICAL,
T_IS_NOT_EQUAL,
T_IS_NOT_IDENTICAL,
T_LESS_THAN,
T_GREATER_THAN,
T_IS_SMALLER_OR_EQUAL,
T_IS_GREATER_OR_EQUAL,
// boolean operators
T_BOOLEAN_AND,
T_BOOLEAN_OR,
T_LOGICAL_AND,
T_LOGICAL_OR,
T_LOGICAL_XOR,
];

$endOfExpression = $this->endOfExpression($phpcsFile, $ptr);
$isBool = in_array($tokens[$endOfExpression]['code'], $boolExpressionTokens, true);

while ($endOfExpression && in_array($tokens[$endOfExpression]['code'], $boolExpressionTokens, true)) {
$endOfExpression = $this->endOfExpression($phpcsFile, $endOfExpression + 1);
}

if ($endOfExpression
&& ($tokens[$endOfExpression]['code'] === T_INLINE_THEN
|| $tokens[$endOfExpression]['code'] === T_COALESCE
|| $tokens[$endOfExpression]['code'] === T_SPACESHIP)
) {
return 'unknown';
}

if ($isBool) {
if (! $this->hasCorrectType(['bool', '?bool'], ['bool', 'boolean'])) {
$error = 'Function return type is not bool, but function returns boolean value here';
$phpcsFile->addError($error, $ptr, 'ReturnBool');
}

return 'bool';
}

switch ($tokens[$ptr]['code']) {
case T_ARRAY:
case T_ARRAY_CAST:
Expand All @@ -769,28 +859,6 @@ private function getReturnValue(File $phpcsFile, int $ptr) : string

case T_BOOL_CAST:
case T_BOOLEAN_NOT:
$end = $ptr;
while (++$end) {
if ($tokens[$end]['code'] === T_OPEN_PARENTHESIS) {
$end = $tokens[$end]['parenthesis_closer'];
continue;
}
if ($tokens[$end]['code'] === T_OPEN_SQUARE_BRACKET
|| $tokens[$end]['code'] === T_OPEN_CURLY_BRACKET
|| $tokens[$end]['code'] === T_OPEN_SHORT_ARRAY
) {
$end = $tokens[$end]['bracket_closer'];
continue;
}

if (in_array($tokens[$end]['code'], [T_SEMICOLON, T_INLINE_THEN], true)) {
break;
}
}
if ($tokens[$end]['code'] !== T_SEMICOLON) {
return 'unknown';
}

if (! $this->hasCorrectType(['bool', '?bool'], ['bool', 'boolean'])) {
$error = 'Function return type is not bool, but function returns boolean value here';
$phpcsFile->addError($error, $ptr, 'ReturnBool');
Expand Down
37 changes: 37 additions & 0 deletions test/Sniffs/Functions/ReturnTypeUnitTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -291,4 +291,41 @@ abstract class FunctionCommentReturn
{
return new $a();
}

public function yodaNull(?int $a) : bool
{
return null !== $a;
}

public function complexReturn($a, $b) : bool
{
return 0 === (static function() {
return ['y'];
})() + ['x'] + array('z') + $a[0] + $b{0};
}

public function ternaryUnrecognisedType1($a) : int
{
return null === $a ?: 'b';
}

public function ternaryUnrecognisedType2($b) : int
{
return null === $b && 1 < 2 ? 'a' : 'b';
}

public function ternaryUnrecognisedType3($a, $b) : int
{
return $a || $b ? 'a' : 'b';
}

public function coalesceUnrecognisedType() : int
{
return self::$a ?? 'b';
}

public function spaceshipUnrecognisedType(array $a, $b, $c) : string
{
return $a[0] * $b <=> $c ? ['a'] : 0.1;
}
}