Skip to content

Instantly share code, notes, and snippets.

@voku
Last active February 2, 2022 23:17
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save voku/62ae01fdbe6275db8ff3c46a83cf684e to your computer and use it in GitHub Desktop.
<?php
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
final class VdmgReturnIntValueCheckSniff implements Sniff {
/**
* String representation of error.
*
* @var string
*/
private $errorMessage = 'Identical operator === / !== / >= / <= / > / < is not used for testing the return value of %s function';
/**
* String representation of error.
*
* @var string
*/
private $errorMessageBoolNotUsage = '"!" is used for testing the return value of %s function';
/**
* Error violation code.
*
* @var string
*/
private $errorCode = 'ImproperValueTesting';
/**
* Searched functions.
*
* @var string[]
*/
private $functions = [
'count',
];
/**
* All tokens from current file.
*
* @var array
*/
private $tokens = [];
/**
* PHP_CodeSniffer file.
*
* @var File
*/
private $file;
/**
* Left limit for search of identical operators.
*
* @var int
*/
private $leftLimit;
/**
* Right limit for search of identical operators.
*
* @var int
*/
private $rightLimit;
/**
* List of tokens which declares left bound of current scope.
*
* @var array
*/
private $leftRangeTokens = [
\T_IS_IDENTICAL,
\T_IS_NOT_IDENTICAL,
T_LESS_THAN,
T_GREATER_THAN,
\T_IS_SMALLER_OR_EQUAL,
\T_IS_GREATER_OR_EQUAL,
T_OPEN_PARENTHESIS,
\T_BOOLEAN_AND,
\T_BOOLEAN_OR,
];
/**
* List of tokens which declares right bound of current scope.
*
* @var array
*/
private $rightRangeTokens = [
\T_IS_IDENTICAL,
\T_IS_NOT_IDENTICAL,
T_LESS_THAN,
T_GREATER_THAN,
\T_IS_SMALLER_OR_EQUAL,
\T_IS_GREATER_OR_EQUAL,
T_CLOSE_PARENTHESIS,
\T_BOOLEAN_AND,
\T_BOOLEAN_OR,
];
/**
* List of tokens which declares identical operators.
*
* @var array
*/
private $identical = [
\T_IS_IDENTICAL,
\T_IS_NOT_IDENTICAL,
T_LESS_THAN,
T_GREATER_THAN,
\T_IS_SMALLER_OR_EQUAL,
\T_IS_GREATER_OR_EQUAL,
];
/**
* Finds the position of close parenthesis of detected function.
*
* @param int $currentPosition
*/
private function findFunctionParenthesisCloser($currentPosition) {
$nextOpenParenthesis = $this->file->findNext(T_OPEN_PARENTHESIS, $currentPosition, $this->rightLimit);
return $nextOpenParenthesis ? $this->tokens[(int)$nextOpenParenthesis]['parenthesis_closer'] : false;
}
/**
* Recursively finds identical operators in current scope.
*
* @param int $leftCurrentPosition
* @param int $rightCurrentPosition
*
* @return bool
*/
private function findIdentical($leftCurrentPosition, $rightCurrentPosition) {
$leftBound = $this->file->findPrevious($this->leftRangeTokens, $leftCurrentPosition, $this->leftLimit - 1);
$rightBound = $this->file->findNext($this->rightRangeTokens, $rightCurrentPosition, $this->rightLimit + 1);
if ($leftBound === false || $rightBound === false) {
return false;
}
$leftToken = $this->tokens[(int)$leftBound];
$rightToken = $this->tokens[(int)$rightBound];
if (
$leftToken['code'] === T_OPEN_PARENTHESIS
&&
$rightToken['code'] === T_CLOSE_PARENTHESIS
) {
return $this->findIdentical($leftBound - 1, $rightBound + 1);
}
return (
in_array($leftToken['code'], $this->identical)
||
in_array($rightToken['code'], $this->identical)
) ?: false;
}
/**
* {@inheritdoc}
*/
public function process(File $phpcsFile, $stackPtr): void {
$this->tokens = $phpcsFile->getTokens();
$this->file = $phpcsFile;
$open = $this->tokens[$stackPtr]['parenthesis_opener'];
$this->leftLimit = $open;
$close = $this->tokens[$stackPtr]['parenthesis_closer'];
$this->rightLimit = $close;
for ($i = ($open + 1); $i < $close; $i++) {
if (
(
$this->tokens[$i]['code'] === \T_STRING
&&
$this->tokens[$i + 1]['code'] === T_OPEN_PARENTHESIS
&&
in_array($this->tokens[$i]['content'], $this->functions, true)
)
&&
(
$this->tokens[$i - 1]['code'] === T_BOOLEAN_NOT
||
!$this->findIdentical($i - 1, $this->findFunctionParenthesisCloser($i) + 1)
)
) {
$foundFunctionName = $this->tokens[$i]['content'];
if ($this->tokens[$i - 1]['code'] === T_BOOLEAN_NOT) {
$phpcsFile->addError($this->errorMessageBoolNotUsage, $i, $this->errorCode, [$foundFunctionName]);
} else {
$phpcsFile->addError($this->errorMessage, $i, $this->errorCode, [$foundFunctionName]);
}
}
}
}
/**
* {@inheritdoc}
*/
public function register() {
return [\T_IF, \T_ELSEIF];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment