基础代码

This commit is contained in:
2021-02-26 22:23:13 +08:00
parent 7884df52f0
commit a719feebba
2585 changed files with 328263 additions and 0 deletions
@@ -0,0 +1,147 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility;
use PHP_CodeSniffer_File as File;
/**
* Abstract base class for sniffs based on complex arrays with PHP version information.
*
* @since 7.1.0
*/
abstract class AbstractComplexVersionSniff extends Sniff implements ComplexVersionInterface
{
/**
* Handle the retrieval of relevant information and - if necessary - throwing of an
* error/warning for an item.
*
* @since 7.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
*
* @return void
*/
public function handleFeature(File $phpcsFile, $stackPtr, array $itemInfo)
{
$itemArray = $this->getItemArray($itemInfo);
$errorInfo = $this->getErrorInfo($itemArray, $itemInfo);
if ($this->shouldThrowError($errorInfo) === true) {
$this->addError($phpcsFile, $stackPtr, $itemInfo, $errorInfo);
}
}
/**
* Determine whether an error/warning should be thrown for an item based on collected information.
*
* @since 7.1.0
*
* @param array $errorInfo Detail information about an item.
*
* @return bool
*/
abstract protected function shouldThrowError(array $errorInfo);
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array();
}
/**
* Retrieve a subset of an item array containing only the array keys which
* contain PHP version information.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about an item.
*
* @return array Array with only the version information.
*/
protected function getVersionArray(array $itemArray)
{
return array_diff_key($itemArray, array_flip($this->getNonVersionArrayKeys()));
}
/**
* Get the item name to be used for the creation of the error code and in the error message.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
* @param array $errorInfo Detail information about an item.
*
* @return string
*/
protected function getItemName(array $itemInfo, array $errorInfo)
{
return $itemInfo['name'];
}
/**
* Get the error message template for a specific sniff.
*
* @since 7.1.0
*
* @return string
*/
abstract protected function getErrorMsgTemplate();
/**
* Allow for concrete child classes to filter the error message before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param string $error The error message which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return string
*/
protected function filterErrorMsg($error, array $itemInfo, array $errorInfo)
{
return $error;
}
/**
* Allow for concrete child classes to filter the error data before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param array $data The error data array which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return array
*/
protected function filterErrorData(array $data, array $itemInfo, array $errorInfo)
{
return $data;
}
}
@@ -0,0 +1,193 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Abstract class to use as a base for examining the parameter values passed to function calls.
*
* @since 8.2.0
*/
abstract class AbstractFunctionCallParameterSniff extends Sniff
{
/**
* Is the sniff looking for a function call or a method call ?
*
* Note: the child class may need to do additional checks to make sure that
* the method called is of the right class/object.
* Checking that is outside of the scope of this abstract sniff.
*
* @since 8.2.0
*
* @var bool False (default) if the sniff is looking for function calls.
* True if the sniff is looking for method calls.
*/
protected $isMethod = false;
/**
* Functions the sniff is looking for. Should be defined in the child class.
*
* @since 8.2.0
*
* @var array The only requirement for this array is that the top level
* array keys are the names of the functions you're looking for.
* Other than that, the array can have arbitrary content
* depending on your needs.
*/
protected $targetFunctions = array();
/**
* List of tokens which when they preceed the $stackPtr indicate that this
* is not a function call.
*
* @since 8.2.0
*
* @var array
*/
private $ignoreTokens = array(
\T_DOUBLE_COLON => true,
\T_OBJECT_OPERATOR => true,
\T_FUNCTION => true,
\T_NEW => true,
\T_CONST => true,
\T_USE => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.2.0
*
* @return array
*/
public function register()
{
// Handle case-insensitivity of function names.
$this->targetFunctions = $this->arrayKeysToLowercase($this->targetFunctions);
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->bowOutEarly() === true) {
return;
}
$tokens = $phpcsFile->getTokens();
$function = $tokens[$stackPtr]['content'];
$functionLc = strtolower($function);
if (isset($this->targetFunctions[$functionLc]) === false) {
return;
}
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($this->isMethod === true) {
if ($tokens[$prevNonEmpty]['code'] !== \T_DOUBLE_COLON
&& $tokens[$prevNonEmpty]['code'] !== \T_OBJECT_OPERATOR
) {
// Not a call to a PHP method.
return;
}
} else {
if (isset($this->ignoreTokens[$tokens[$prevNonEmpty]['code']]) === true) {
// Not a call to a PHP function.
return;
}
if ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR
&& $tokens[$prevNonEmpty - 1]['code'] === \T_STRING
) {
// Namespaced function.
return;
}
}
$parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
if (empty($parameters)) {
return $this->processNoParameters($phpcsFile, $stackPtr, $function);
} else {
return $this->processParameters($phpcsFile, $stackPtr, $function, $parameters);
}
}
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 8.2.0
*
* If the check done in a child class is not specific to one PHP version,
* this function should return `false`.
*
* @return bool
*/
abstract protected function bowOutEarly();
/**
* Process the parameters of a matched function.
*
* This method has to be made concrete in child classes.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
abstract public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters);
/**
* Process the function if no parameters were found.
*
* Defaults to doing nothing. Can be overloaded in child classes to handle functions
* were parameters are expected, but none found.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processNoParameters(File $phpcsFile, $stackPtr, $functionName)
{
return;
}
}
@@ -0,0 +1,115 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility;
use PHP_CodeSniffer_File as File;
/**
* Base class for new feature sniffs.
*
* @since 7.1.0
*/
abstract class AbstractNewFeatureSniff extends AbstractComplexVersionSniff
{
/**
* Determine whether an error/warning should be thrown for an item based on collected information.
*
* @since 7.1.0
*
* @param array $errorInfo Detail information about an item.
*
* @return bool
*/
protected function shouldThrowError(array $errorInfo)
{
return ($errorInfo['not_in_version'] !== '');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = array(
'not_in_version' => '',
'error' => true,
);
$versionArray = $this->getVersionArray($itemArray);
if (empty($versionArray) === false) {
foreach ($versionArray as $version => $present) {
if ($errorInfo['not_in_version'] === '' && $present === false
&& $this->supportsBelow($version) === true
) {
$errorInfo['not_in_version'] = $version;
}
}
}
return $errorInfo;
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return '%s is not present in PHP version %s or earlier';
}
/**
* Generates the error or warning for this item.
*
* @since 7.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
* @param array $errorInfo Array with detail (version) information
* relevant to the item.
*
* @return void
*/
public function addError(File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo)
{
$itemName = $this->getItemName($itemInfo, $errorInfo);
$error = $this->getErrorMsgTemplate();
$errorCode = $this->stringToErrorCode($itemName) . 'Found';
$data = array(
$itemName,
$errorInfo['not_in_version'],
);
$error = $this->filterErrorMsg($error, $itemInfo, $errorInfo);
$data = $this->filterErrorData($data, $itemInfo, $errorInfo);
$this->addMessage($phpcsFile, $error, $stackPtr, $errorInfo['error'], $errorCode, $data);
}
}
@@ -0,0 +1,156 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility;
use PHP_CodeSniffer_File as File;
/**
* Base class for removed feature sniffs.
*
* @since 7.1.0
*/
abstract class AbstractRemovedFeatureSniff extends AbstractComplexVersionSniff
{
/**
* Determine whether an error/warning should be thrown for an item based on collected information.
*
* @since 7.1.0
*
* @param array $errorInfo Detail information about an item.
*
* @return bool
*/
protected function shouldThrowError(array $errorInfo)
{
return ($errorInfo['deprecated'] !== '' || $errorInfo['removed'] !== '');
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* By default, removed feature version arrays, contain an additional 'alternative' array key.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array('alternative');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = array(
'deprecated' => '',
'removed' => '',
'alternative' => '',
'error' => false,
);
$versionArray = $this->getVersionArray($itemArray);
if (empty($versionArray) === false) {
foreach ($versionArray as $version => $removed) {
if ($this->supportsAbove($version) === true) {
if ($removed === true && $errorInfo['removed'] === '') {
$errorInfo['removed'] = $version;
$errorInfo['error'] = true;
} elseif ($errorInfo['deprecated'] === '') {
$errorInfo['deprecated'] = $version;
}
}
}
}
if (isset($itemArray['alternative']) === true) {
$errorInfo['alternative'] = $itemArray['alternative'];
}
return $errorInfo;
}
/**
* Get the error message template for suggesting an alternative for a specific sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getAlternativeOptionTemplate()
{
return '; Use %s instead';
}
/**
* Generates the error or warning for this item.
*
* @since 7.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
* @param array $errorInfo Array with detail (version) information
* relevant to the item.
*
* @return void
*/
public function addError(File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo)
{
$itemName = $this->getItemName($itemInfo, $errorInfo);
$error = $this->getErrorMsgTemplate();
$errorCode = $this->stringToErrorCode($itemName);
$data = array($itemName);
if ($errorInfo['deprecated'] !== '') {
$error .= 'deprecated since PHP %s and ';
$errorCode .= 'Deprecated';
$data[] = $errorInfo['deprecated'];
}
if ($errorInfo['removed'] !== '') {
$error .= 'removed since PHP %s and ';
$errorCode .= 'Removed';
$data[] = $errorInfo['removed'];
}
// Remove the last 'and' from the message.
$error = substr($error, 0, (\strlen($error) - 5));
if ($errorInfo['alternative'] !== '') {
$error .= $this->getAlternativeOptionTemplate();
$data[] = $errorInfo['alternative'];
}
$error = $this->filterErrorMsg($error, $itemInfo, $errorInfo);
$data = $this->filterErrorData($data, $itemInfo, $errorInfo);
$this->addMessage($phpcsFile, $error, $stackPtr, $errorInfo['error'], $errorCode, $data);
}
}
@@ -0,0 +1,84 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility;
use PHP_CodeSniffer_File as File;
/**
* Complex Version Interface.
*
* Interface to be implemented by sniffs using a multi-dimensional array of
* PHP features (functions, classes etc) being sniffed for with version
* information in sub-arrays.
*
* @since 7.1.0
*/
interface ComplexVersionInterface
{
/**
* Handle the retrieval of relevant information and - if necessary - throwing of an
* error/warning for an item.
*
* @since 7.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
*
* @return void
*/
public function handleFeature(File $phpcsFile, $stackPtr, array $itemInfo);
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo);
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo);
/**
* Generates the error or warning for this item.
*
* @since 7.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
* @param array $errorInfo Array with detail (version) information
* relevant to the item.
*
* @return void
*/
public function addError(File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo);
}
@@ -0,0 +1,678 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility;
use PHP_CodeSniffer_Exception as PHPCS_Exception;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* PHPCS cross-version compatibility helper class.
*
* A number of PHPCS classes were split up into several classes in PHPCS 3.x
* Those classes cannot be aliased as they don't represent the same object.
* This class provides helper methods for functions which were contained in
* one of these classes and which are used within the PHPCompatibility library.
*
* Additionally, this class contains some duplicates of PHPCS native methods.
* These methods have received bug fixes or improved functionality between the
* lowest supported PHPCS version and the latest PHPCS stable version and
* to provide the same results cross-version, PHPCompatibility needs to use
* the up-to-date versions of these methods.
*
* @since 8.0.0
* @since 8.2.0 The duplicate PHPCS methods have been moved from the `Sniff`
* base class to this class.
*/
class PHPCSHelper
{
/**
* Get the PHPCS version number.
*
* @since 8.0.0
*
* @return string
*/
public static function getVersion()
{
if (\defined('\PHP_CodeSniffer\Config::VERSION')) {
// PHPCS 3.x.
return \PHP_CodeSniffer\Config::VERSION;
} else {
// PHPCS 2.x.
return \PHP_CodeSniffer::VERSION;
}
}
/**
* Pass config data to PHPCS.
*
* PHPCS cross-version compatibility helper.
*
* @since 8.0.0
*
* @param string $key The name of the config value.
* @param string|null $value The value to set. If null, the config entry
* is deleted, reverting it to the default value.
* @param boolean $temp Set this config data temporarily for this script run.
* This will not write the config data to the config file.
*
* @return void
*/
public static function setConfigData($key, $value, $temp = false)
{
if (method_exists('\PHP_CodeSniffer\Config', 'setConfigData')) {
// PHPCS 3.x.
\PHP_CodeSniffer\Config::setConfigData($key, $value, $temp);
} else {
// PHPCS 2.x.
\PHP_CodeSniffer::setConfigData($key, $value, $temp);
}
}
/**
* Get the value of a single PHPCS config key.
*
* @since 8.0.0
*
* @param string $key The name of the config value.
*
* @return string|null
*/
public static function getConfigData($key)
{
if (method_exists('\PHP_CodeSniffer\Config', 'getConfigData')) {
// PHPCS 3.x.
return \PHP_CodeSniffer\Config::getConfigData($key);
} else {
// PHPCS 2.x.
return \PHP_CodeSniffer::getConfigData($key);
}
}
/**
* Get the value of a single PHPCS config key.
*
* This config key can be set in the `CodeSniffer.conf` file, on the
* command-line or in a ruleset.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param string $key The name of the config value.
*
* @return string|null
*/
public static function getCommandLineData(File $phpcsFile, $key)
{
if (class_exists('\PHP_CodeSniffer\Config')) {
// PHPCS 3.x.
$config = $phpcsFile->config;
if (isset($config->{$key})) {
return $config->{$key};
}
} else {
// PHPCS 2.x.
$config = $phpcsFile->phpcs->cli->getCommandLineValues();
if (isset($config[$key])) {
return $config[$key];
}
}
return null;
}
/**
* Returns the position of the first non-whitespace token in a statement.
*
* {@internal Duplicate of same method as contained in the `\PHP_CodeSniffer_File`
* class and introduced in PHPCS 2.1.0 and improved in PHPCS 2.7.1.
*
* Once the minimum supported PHPCS version for this standard goes beyond
* that, this method can be removed and calls to it replaced with
* `$phpcsFile->findStartOfStatement($start, $ignore)` calls.
*
* Last synced with PHPCS version: PHPCS 3.3.2 at commit 6ad28354c04b364c3c71a34e4a18b629cc3b231e}
*
* @since 9.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
* @param int $start The position to start searching from in the token stack.
* @param int|array $ignore Token types that should not be considered stop points.
*
* @return int
*/
public static function findStartOfStatement(File $phpcsFile, $start, $ignore = null)
{
if (version_compare(self::getVersion(), '2.7.1', '>=') === true) {
return $phpcsFile->findStartOfStatement($start, $ignore);
}
$tokens = $phpcsFile->getTokens();
$endTokens = Tokens::$blockOpeners;
$endTokens[\T_COLON] = true;
$endTokens[\T_COMMA] = true;
$endTokens[\T_DOUBLE_ARROW] = true;
$endTokens[\T_SEMICOLON] = true;
$endTokens[\T_OPEN_TAG] = true;
$endTokens[\T_CLOSE_TAG] = true;
$endTokens[\T_OPEN_SHORT_ARRAY] = true;
if ($ignore !== null) {
$ignore = (array) $ignore;
foreach ($ignore as $code) {
if (isset($endTokens[$code]) === true) {
unset($endTokens[$code]);
}
}
}
$lastNotEmpty = $start;
for ($i = $start; $i >= 0; $i--) {
if (isset($endTokens[$tokens[$i]['code']]) === true) {
// Found the end of the previous statement.
return $lastNotEmpty;
}
if (isset($tokens[$i]['scope_opener']) === true
&& $i === $tokens[$i]['scope_closer']
) {
// Found the end of the previous scope block.
return $lastNotEmpty;
}
// Skip nested statements.
if (isset($tokens[$i]['bracket_opener']) === true
&& $i === $tokens[$i]['bracket_closer']
) {
$i = $tokens[$i]['bracket_opener'];
} elseif (isset($tokens[$i]['parenthesis_opener']) === true
&& $i === $tokens[$i]['parenthesis_closer']
) {
$i = $tokens[$i]['parenthesis_opener'];
}
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) {
$lastNotEmpty = $i;
}
}//end for
return 0;
}
/**
* Returns the position of the last non-whitespace token in a statement.
*
* {@internal Duplicate of same method as contained in the `\PHP_CodeSniffer_File`
* class and introduced in PHPCS 2.1.0 and improved in PHPCS 2.7.1 and 3.3.0.
*
* Once the minimum supported PHPCS version for this standard goes beyond
* that, this method can be removed and calls to it replaced with
* `$phpcsFile->findEndOfStatement($start, $ignore)` calls.
*
* Last synced with PHPCS version: PHPCS 3.3.0-alpha at commit f5d899dcb5c534a1c3cca34668624517856ba823}
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
* @param int $start The position to start searching from in the token stack.
* @param int|array $ignore Token types that should not be considered stop points.
*
* @return int
*/
public static function findEndOfStatement(File $phpcsFile, $start, $ignore = null)
{
if (version_compare(self::getVersion(), '3.3.0', '>=') === true) {
return $phpcsFile->findEndOfStatement($start, $ignore);
}
$tokens = $phpcsFile->getTokens();
$endTokens = array(
\T_COLON => true,
\T_COMMA => true,
\T_DOUBLE_ARROW => true,
\T_SEMICOLON => true,
\T_CLOSE_PARENTHESIS => true,
\T_CLOSE_SQUARE_BRACKET => true,
\T_CLOSE_CURLY_BRACKET => true,
\T_CLOSE_SHORT_ARRAY => true,
\T_OPEN_TAG => true,
\T_CLOSE_TAG => true,
);
if ($ignore !== null) {
$ignore = (array) $ignore;
foreach ($ignore as $code) {
if (isset($endTokens[$code]) === true) {
unset($endTokens[$code]);
}
}
}
$lastNotEmpty = $start;
for ($i = $start; $i < $phpcsFile->numTokens; $i++) {
if ($i !== $start && isset($endTokens[$tokens[$i]['code']]) === true) {
// Found the end of the statement.
if ($tokens[$i]['code'] === \T_CLOSE_PARENTHESIS
|| $tokens[$i]['code'] === \T_CLOSE_SQUARE_BRACKET
|| $tokens[$i]['code'] === \T_CLOSE_CURLY_BRACKET
|| $tokens[$i]['code'] === \T_CLOSE_SHORT_ARRAY
|| $tokens[$i]['code'] === \T_OPEN_TAG
|| $tokens[$i]['code'] === \T_CLOSE_TAG
) {
return $lastNotEmpty;
}
return $i;
}
// Skip nested statements.
if (isset($tokens[$i]['scope_closer']) === true
&& ($i === $tokens[$i]['scope_opener']
|| $i === $tokens[$i]['scope_condition'])
) {
if ($i === $start && isset(Tokens::$scopeOpeners[$tokens[$i]['code']]) === true) {
return $tokens[$i]['scope_closer'];
}
$i = $tokens[$i]['scope_closer'];
} elseif (isset($tokens[$i]['bracket_closer']) === true
&& $i === $tokens[$i]['bracket_opener']
) {
$i = $tokens[$i]['bracket_closer'];
} elseif (isset($tokens[$i]['parenthesis_closer']) === true
&& $i === $tokens[$i]['parenthesis_opener']
) {
$i = $tokens[$i]['parenthesis_closer'];
}
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) {
$lastNotEmpty = $i;
}
}//end for
return ($phpcsFile->numTokens - 1);
}
/**
* Returns the name of the class that the specified class extends
* (works for classes, anonymous classes and interfaces).
*
* Returns FALSE on error or if there is no extended class name.
*
* {@internal Duplicate of same method as contained in the `\PHP_CodeSniffer_File`
* class, but with some improvements which have been introduced in
* PHPCS 2.8.0.
* {@link https://github.com/squizlabs/PHP_CodeSniffer/commit/0011d448119d4c568e3ac1f825ae78815bf2cc34}.
*
* Once the minimum supported PHPCS version for this standard goes beyond
* that, this method can be removed and calls to it replaced with
* `$phpcsFile->findExtendedClassName($stackPtr)` calls.
*
* Last synced with PHPCS version: PHPCS 3.1.0-alpha at commit a9efcc9b0703f3f9f4a900623d4e97128a6aafc6}
*
* @since 7.1.4
* @since 8.2.0 Moved from the `Sniff` class to this class.
*
* @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
* @param int $stackPtr The position of the class token in the stack.
*
* @return string|false
*/
public static function findExtendedClassName(File $phpcsFile, $stackPtr)
{
if (version_compare(self::getVersion(), '3.1.0', '>=') === true) {
return $phpcsFile->findExtendedClassName($stackPtr);
}
$tokens = $phpcsFile->getTokens();
// Check for the existence of the token.
if (isset($tokens[$stackPtr]) === false) {
return false;
}
if ($tokens[$stackPtr]['code'] !== \T_CLASS
&& $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
&& $tokens[$stackPtr]['type'] !== 'T_INTERFACE'
) {
return false;
}
if (isset($tokens[$stackPtr]['scope_closer']) === false) {
return false;
}
$classCloserIndex = $tokens[$stackPtr]['scope_closer'];
$extendsIndex = $phpcsFile->findNext(\T_EXTENDS, $stackPtr, $classCloserIndex);
if ($extendsIndex === false) {
return false;
}
$find = array(
\T_NS_SEPARATOR,
\T_STRING,
\T_WHITESPACE,
);
$end = $phpcsFile->findNext($find, ($extendsIndex + 1), $classCloserIndex, true);
$name = $phpcsFile->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
$name = trim($name);
if ($name === '') {
return false;
}
return $name;
}
/**
* Returns the name(s) of the interface(s) that the specified class implements.
*
* Returns FALSE on error or if there are no implemented interface names.
*
* {@internal Duplicate of same method as introduced in PHPCS 2.7.
* This method also includes an improvement we use which was only introduced
* in PHPCS 2.8.0, so only defer to upstream for higher versions.
* Once the minimum supported PHPCS version for this sniff library goes beyond
* that, this method can be removed and calls to it replaced with
* `$phpcsFile->findImplementedInterfaceNames($stackPtr)` calls.}
*
* @since 7.0.3
* @since 8.2.0 Moved from the `Sniff` class to this class.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the class token.
*
* @return array|false
*/
public static function findImplementedInterfaceNames(File $phpcsFile, $stackPtr)
{
if (version_compare(self::getVersion(), '2.7.1', '>') === true) {
return $phpcsFile->findImplementedInterfaceNames($stackPtr);
}
$tokens = $phpcsFile->getTokens();
// Check for the existence of the token.
if (isset($tokens[$stackPtr]) === false) {
return false;
}
if ($tokens[$stackPtr]['code'] !== \T_CLASS
&& $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
) {
return false;
}
if (isset($tokens[$stackPtr]['scope_closer']) === false) {
return false;
}
$classOpenerIndex = $tokens[$stackPtr]['scope_opener'];
$implementsIndex = $phpcsFile->findNext(\T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
if ($implementsIndex === false) {
return false;
}
$find = array(
\T_NS_SEPARATOR,
\T_STRING,
\T_WHITESPACE,
\T_COMMA,
);
$end = $phpcsFile->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
$name = $phpcsFile->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
$name = trim($name);
if ($name === '') {
return false;
} else {
$names = explode(',', $name);
$names = array_map('trim', $names);
return $names;
}
}
/**
* Returns the method parameters for the specified function token.
*
* Each parameter is in the following format:
*
* <code>
* 0 => array(
* 'name' => '$var', // The variable name.
* 'token' => integer, // The stack pointer to the variable name.
* 'content' => string, // The full content of the variable definition.
* 'pass_by_reference' => boolean, // Is the variable passed by reference?
* 'variable_length' => boolean, // Is the param of variable length through use of `...` ?
* 'type_hint' => string, // The type hint for the variable.
* 'type_hint_token' => integer, // The stack pointer to the type hint
* // or false if there is no type hint.
* 'nullable_type' => boolean, // Is the variable using a nullable type?
* )
* </code>
*
* Parameters with default values have an additional array index of
* 'default' with the value of the default as a string.
*
* {@internal Duplicate of same method as contained in the `\PHP_CodeSniffer_File`
* class.
*
* Last synced with PHPCS version: PHPCS 3.3.0-alpha at commit 53a28408d345044c0360c2c1b4a2aaebf4a3b8c9}
*
* @since 7.0.3
* @since 8.2.0 Moved from the `Sniff` class to this class.
*
* @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
* @param int $stackPtr The position in the stack of the
* function token to acquire the
* parameters for.
*
* @return array|false
* @throws \PHP_CodeSniffer_Exception If the specified $stackPtr is not of
* type T_FUNCTION or T_CLOSURE.
*/
public static function getMethodParameters(File $phpcsFile, $stackPtr)
{
if (version_compare(self::getVersion(), '3.3.0', '>=') === true) {
return $phpcsFile->getMethodParameters($stackPtr);
}
$tokens = $phpcsFile->getTokens();
// Check for the existence of the token.
if (isset($tokens[$stackPtr]) === false) {
return false;
}
if ($tokens[$stackPtr]['code'] !== \T_FUNCTION
&& $tokens[$stackPtr]['code'] !== \T_CLOSURE
) {
throw new PHPCS_Exception('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
}
$opener = $tokens[$stackPtr]['parenthesis_opener'];
$closer = $tokens[$stackPtr]['parenthesis_closer'];
$vars = array();
$currVar = null;
$paramStart = ($opener + 1);
$defaultStart = null;
$paramCount = 0;
$passByReference = false;
$variableLength = false;
$typeHint = '';
$typeHintToken = false;
$nullableType = false;
for ($i = $paramStart; $i <= $closer; $i++) {
// Check to see if this token has a parenthesis or bracket opener. If it does
// it's likely to be an array which might have arguments in it. This
// could cause problems in our parsing below, so lets just skip to the
// end of it.
if (isset($tokens[$i]['parenthesis_opener']) === true) {
// Don't do this if it's the close parenthesis for the method.
if ($i !== $tokens[$i]['parenthesis_closer']) {
$i = ($tokens[$i]['parenthesis_closer'] + 1);
}
}
if (isset($tokens[$i]['bracket_opener']) === true) {
// Don't do this if it's the close parenthesis for the method.
if ($i !== $tokens[$i]['bracket_closer']) {
$i = ($tokens[$i]['bracket_closer'] + 1);
}
}
switch ($tokens[$i]['type']) {
case 'T_BITWISE_AND':
if ($defaultStart === null) {
$passByReference = true;
}
break;
case 'T_VARIABLE':
$currVar = $i;
break;
case 'T_ELLIPSIS':
$variableLength = true;
break;
case 'T_ARRAY_HINT': // Pre-PHPCS 3.3.0.
case 'T_CALLABLE':
if ($typeHintToken === false) {
$typeHintToken = $i;
}
$typeHint .= $tokens[$i]['content'];
break;
case 'T_SELF':
case 'T_PARENT':
case 'T_STATIC':
// Self and parent are valid, static invalid, but was probably intended as type hint.
if (isset($defaultStart) === false) {
if ($typeHintToken === false) {
$typeHintToken = $i;
}
$typeHint .= $tokens[$i]['content'];
}
break;
case 'T_STRING':
// This is a string, so it may be a type hint, but it could
// also be a constant used as a default value.
$prevComma = false;
for ($t = $i; $t >= $opener; $t--) {
if ($tokens[$t]['code'] === \T_COMMA) {
$prevComma = $t;
break;
}
}
if ($prevComma !== false) {
$nextEquals = false;
for ($t = $prevComma; $t < $i; $t++) {
if ($tokens[$t]['code'] === \T_EQUAL) {
$nextEquals = $t;
break;
}
}
if ($nextEquals !== false) {
break;
}
}
if ($defaultStart === null) {
if ($typeHintToken === false) {
$typeHintToken = $i;
}
$typeHint .= $tokens[$i]['content'];
}
break;
case 'T_NS_SEPARATOR':
// Part of a type hint or default value.
if ($defaultStart === null) {
if ($typeHintToken === false) {
$typeHintToken = $i;
}
$typeHint .= $tokens[$i]['content'];
}
break;
case 'T_NULLABLE':
case 'T_INLINE_THEN': // Pre-PHPCS 2.8.0.
if ($defaultStart === null) {
$nullableType = true;
$typeHint .= $tokens[$i]['content'];
}
break;
case 'T_CLOSE_PARENTHESIS':
case 'T_COMMA':
// If it's null, then there must be no parameters for this
// method.
if ($currVar === null) {
break;
}
$vars[$paramCount] = array();
$vars[$paramCount]['token'] = $currVar;
$vars[$paramCount]['name'] = $tokens[$currVar]['content'];
$vars[$paramCount]['content'] = trim($phpcsFile->getTokensAsString($paramStart, ($i - $paramStart)));
if ($defaultStart !== null) {
$vars[$paramCount]['default'] = trim(
$phpcsFile->getTokensAsString(
$defaultStart,
($i - $defaultStart)
)
);
}
$vars[$paramCount]['pass_by_reference'] = $passByReference;
$vars[$paramCount]['variable_length'] = $variableLength;
$vars[$paramCount]['type_hint'] = $typeHint;
$vars[$paramCount]['type_hint_token'] = $typeHintToken;
$vars[$paramCount]['nullable_type'] = $nullableType;
// Reset the vars, as we are about to process the next parameter.
$defaultStart = null;
$paramStart = ($i + 1);
$passByReference = false;
$variableLength = false;
$typeHint = '';
$typeHintToken = false;
$nullableType = false;
$paramCount++;
break;
case 'T_EQUAL':
$defaultStart = ($i + 1);
break;
}//end switch
}//end for
return $vars;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,90 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Classes;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Abstract private methods are not allowed since PHP 5.1.
*
* Abstract private methods were supported between PHP 5.0.0 and PHP 5.0.4, but
* were then disallowed on the grounds that the behaviours of `private` and `abstract`
* are mutually exclusive.
*
* PHP version 5.1
*
* @link https://www.php.net/manual/en/migration51.oop.php#migration51.oop-methods
*
* @since 9.2.0
*/
class ForbiddenAbstractPrivateMethodsSniff extends Sniff
{
/**
* Valid scopes to check for abstract private methods.
*
* @since 9.2.0
*
* @var array
*/
public $ooScopeTokens = array(
'T_CLASS' => true,
'T_TRAIT' => true,
'T_ANON_CLASS' => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.2.0
*
* @return array
*/
public function register()
{
return array(\T_FUNCTION);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.2.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('5.1') === false) {
return;
}
if ($this->validDirectScope($phpcsFile, $stackPtr, $this->ooScopeTokens) === false) {
// Function, not method.
return;
}
$properties = $phpcsFile->getMethodProperties($stackPtr);
if ($properties['scope'] !== 'private' || $properties['is_abstract'] !== true) {
return;
}
$phpcsFile->addError(
'Abstract methods cannot be declared as private since PHP 5.1',
$stackPtr,
'Found'
);
}
}
@@ -0,0 +1,91 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Classes;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Anonymous classes are supported since PHP 7.0.
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/language.oop5.anonymous.php
* @link https://wiki.php.net/rfc/anonymous_classes
*
* @since 7.0.0
*/
class NewAnonymousClassesSniff extends Sniff
{
/**
* Tokens which in various PHP versions indicate the `class` keyword.
*
* The dedicated anonymous class token is added from the `register()`
* method if the token is available.
*
* @since 7.1.2
*
* @var array
*/
private $indicators = array(
\T_CLASS => \T_CLASS,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
*
* @return array
*/
public function register()
{
if (\defined('T_ANON_CLASS')) {
$this->indicators[\T_ANON_CLASS] = \T_ANON_CLASS;
}
return array(\T_NEW);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.6') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
if ($nextNonEmpty === false || isset($this->indicators[$tokens[$nextNonEmpty]['code']]) === false) {
return;
}
// Still here ? In that case, it is an anonymous class.
$phpcsFile->addError(
'Anonymous classes are not supported in PHP 5.6 or earlier',
$stackPtr,
'Found'
);
}
}
@@ -0,0 +1,913 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Classes;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect use of new PHP native classes.
*
* The sniff analyses the following constructs to find usage of new classes:
* - Class instantiation using the `new` keyword.
* - (Anonymous) Class declarations to detect new classes being extended by userland classes.
* - Static use of class properties, constants or functions using the double colon.
* - Function/closure declarations to detect new classes used as parameter type declarations.
* - Function/closure declarations to detect new classes used as return type declarations.
* - Try/catch statements to detect new exception classes being caught.
*
* PHP version All
*
* @since 5.5
* @since 5.6 Now extends the base `Sniff` class.
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` class.
*/
class NewClassesSniff extends AbstractNewFeatureSniff
{
/**
* A list of new classes, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the class appears.
*
* @since 5.5
*
* @var array(string => array(string => bool))
*/
protected $newClasses = array(
'ArrayObject' => array(
'4.4' => false,
'5.0' => true,
),
'ArrayIterator' => array(
'4.4' => false,
'5.0' => true,
),
'CachingIterator' => array(
'4.4' => false,
'5.0' => true,
),
'DirectoryIterator' => array(
'4.4' => false,
'5.0' => true,
),
'RecursiveDirectoryIterator' => array(
'4.4' => false,
'5.0' => true,
),
'RecursiveIteratorIterator' => array(
'4.4' => false,
'5.0' => true,
),
'php_user_filter' => array(
'4.4' => false,
'5.0' => true,
),
'tidy' => array(
'4.4' => false,
'5.0' => true,
),
'SimpleXMLElement' => array(
'5.0.0' => false,
'5.0.1' => true,
),
'tidyNode' => array(
'5.0.0' => false,
'5.0.1' => true,
),
'libXMLError' => array(
'5.0' => false,
'5.1' => true,
),
'PDO' => array(
'5.0' => false,
'5.1' => true,
),
'PDOStatement' => array(
'5.0' => false,
'5.1' => true,
),
'AppendIterator' => array(
'5.0' => false,
'5.1' => true,
),
'EmptyIterator' => array(
'5.0' => false,
'5.1' => true,
),
'FilterIterator' => array(
'5.0' => false,
'5.1' => true,
),
'InfiniteIterator' => array(
'5.0' => false,
'5.1' => true,
),
'IteratorIterator' => array(
'5.0' => false,
'5.1' => true,
),
'LimitIterator' => array(
'5.0' => false,
'5.1' => true,
),
'NoRewindIterator' => array(
'5.0' => false,
'5.1' => true,
),
'ParentIterator' => array(
'5.0' => false,
'5.1' => true,
),
'RecursiveArrayIterator' => array(
'5.0' => false,
'5.1' => true,
),
'RecursiveCachingIterator' => array(
'5.0' => false,
'5.1' => true,
),
'RecursiveFilterIterator' => array(
'5.0' => false,
'5.1' => true,
),
'SimpleXMLIterator' => array(
'5.0' => false,
'5.1' => true,
),
'SplFileObject' => array(
'5.0' => false,
'5.1' => true,
),
'XMLReader' => array(
'5.0' => false,
'5.1' => true,
),
'SplFileInfo' => array(
'5.1.1' => false,
'5.1.2' => true,
),
'SplTempFileObject' => array(
'5.1.1' => false,
'5.1.2' => true,
),
'XMLWriter' => array(
'5.1.1' => false,
'5.1.2' => true,
),
'DateTime' => array(
'5.1' => false,
'5.2' => true,
),
'DateTimeZone' => array(
'5.1' => false,
'5.2' => true,
),
'RegexIterator' => array(
'5.1' => false,
'5.2' => true,
),
'RecursiveRegexIterator' => array(
'5.1' => false,
'5.2' => true,
),
'ReflectionFunctionAbstract' => array(
'5.1' => false,
'5.2' => true,
),
'ZipArchive' => array(
'5.1' => false,
'5.2' => true,
),
'Closure' => array(
'5.2' => false,
'5.3' => true,
),
'DateInterval' => array(
'5.2' => false,
'5.3' => true,
),
'DatePeriod' => array(
'5.2' => false,
'5.3' => true,
),
'finfo' => array(
'5.2' => false,
'5.3' => true,
),
'Collator' => array(
'5.2' => false,
'5.3' => true,
),
'NumberFormatter' => array(
'5.2' => false,
'5.3' => true,
),
'Locale' => array(
'5.2' => false,
'5.3' => true,
),
'Normalizer' => array(
'5.2' => false,
'5.3' => true,
),
'MessageFormatter' => array(
'5.2' => false,
'5.3' => true,
),
'IntlDateFormatter' => array(
'5.2' => false,
'5.3' => true,
),
'Phar' => array(
'5.2' => false,
'5.3' => true,
),
'PharData' => array(
'5.2' => false,
'5.3' => true,
),
'PharFileInfo' => array(
'5.2' => false,
'5.3' => true,
),
'FilesystemIterator' => array(
'5.2' => false,
'5.3' => true,
),
'GlobIterator' => array(
'5.2' => false,
'5.3' => true,
),
'MultipleIterator' => array(
'5.2' => false,
'5.3' => true,
),
'RecursiveTreeIterator' => array(
'5.2' => false,
'5.3' => true,
),
'SplDoublyLinkedList' => array(
'5.2' => false,
'5.3' => true,
),
'SplFixedArray' => array(
'5.2' => false,
'5.3' => true,
),
'SplHeap' => array(
'5.2' => false,
'5.3' => true,
),
'SplMaxHeap' => array(
'5.2' => false,
'5.3' => true,
),
'SplMinHeap' => array(
'5.2' => false,
'5.3' => true,
),
'SplObjectStorage' => array(
'5.2' => false,
'5.3' => true,
),
'SplPriorityQueue' => array(
'5.2' => false,
'5.3' => true,
),
'SplQueue' => array(
'5.2' => false,
'5.3' => true,
),
'SplStack' => array(
'5.2' => false,
'5.3' => true,
),
'ResourceBundle' => array(
'5.3.1' => false,
'5.3.2' => true,
),
'CallbackFilterIterator' => array(
'5.3' => false,
'5.4' => true,
),
'RecursiveCallbackFilterIterator' => array(
'5.3' => false,
'5.4' => true,
),
'ReflectionZendExtension' => array(
'5.3' => false,
'5.4' => true,
),
'SessionHandler' => array(
'5.3' => false,
'5.4' => true,
),
'SNMP' => array(
'5.3' => false,
'5.4' => true,
),
'Transliterator' => array(
'5.3' => false,
'5.4' => true,
),
'Spoofchecker' => array(
'5.3' => false,
'5.4' => true,
),
'Generator' => array(
'5.4' => false,
'5.5' => true,
),
'CURLFile' => array(
'5.4' => false,
'5.5' => true,
),
'DateTimeImmutable' => array(
'5.4' => false,
'5.5' => true,
),
'IntlCalendar' => array(
'5.4' => false,
'5.5' => true,
),
'IntlGregorianCalendar' => array(
'5.4' => false,
'5.5' => true,
),
'IntlTimeZone' => array(
'5.4' => false,
'5.5' => true,
),
'IntlBreakIterator' => array(
'5.4' => false,
'5.5' => true,
),
'IntlRuleBasedBreakIterator' => array(
'5.4' => false,
'5.5' => true,
),
'IntlCodePointBreakIterator' => array(
'5.4' => false,
'5.5' => true,
),
'UConverter' => array(
'5.4' => false,
'5.5' => true,
),
'GMP' => array(
'5.5' => false,
'5.6' => true,
),
'IntlChar' => array(
'5.6' => false,
'7.0' => true,
),
'ReflectionType' => array(
'5.6' => false,
'7.0' => true,
),
'ReflectionGenerator' => array(
'5.6' => false,
'7.0' => true,
),
'ReflectionClassConstant' => array(
'7.0' => false,
'7.1' => true,
),
'FFI' => array(
'7.3' => false,
'7.4' => true,
),
'FFI\CData' => array(
'7.3' => false,
'7.4' => true,
),
'FFI\CType' => array(
'7.3' => false,
'7.4' => true,
),
'ReflectionReference' => array(
'7.3' => false,
'7.4' => true,
),
'WeakReference' => array(
'7.3' => false,
'7.4' => true,
),
);
/**
* A list of new Exception classes, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the class appears.
*
* {@internal Classes listed here do not need to be added to the $newClasses
* property as well.
* This list is automatically added to the $newClasses property
* in the `register()` method.}
*
* {@internal Helper to update this list: https://3v4l.org/MhlUp}
*
* @since 7.1.4
*
* @var array(string => array(string => bool))
*/
protected $newExceptions = array(
'com_exception' => array(
'4.4' => false,
'5.0' => true,
),
'DOMException' => array(
'4.4' => false,
'5.0' => true,
),
'Exception' => array(
// According to the docs introduced in PHP 5.1, but this appears to be.
// an error. Class was introduced with try/catch keywords in PHP 5.0.
'4.4' => false,
'5.0' => true,
),
'ReflectionException' => array(
'4.4' => false,
'5.0' => true,
),
'SoapFault' => array(
'4.4' => false,
'5.0' => true,
),
'SQLiteException' => array(
'4.4' => false,
'5.0' => true,
),
'ErrorException' => array(
'5.0' => false,
'5.1' => true,
),
'BadFunctionCallException' => array(
'5.0' => false,
'5.1' => true,
),
'BadMethodCallException' => array(
'5.0' => false,
'5.1' => true,
),
'DomainException' => array(
'5.0' => false,
'5.1' => true,
),
'InvalidArgumentException' => array(
'5.0' => false,
'5.1' => true,
),
'LengthException' => array(
'5.0' => false,
'5.1' => true,
),
'LogicException' => array(
'5.0' => false,
'5.1' => true,
),
'mysqli_sql_exception' => array(
'5.0' => false,
'5.1' => true,
),
'OutOfBoundsException' => array(
'5.0' => false,
'5.1' => true,
),
'OutOfRangeException' => array(
'5.0' => false,
'5.1' => true,
),
'OverflowException' => array(
'5.0' => false,
'5.1' => true,
),
'PDOException' => array(
'5.0' => false,
'5.1' => true,
),
'RangeException' => array(
'5.0' => false,
'5.1' => true,
),
'RuntimeException' => array(
'5.0' => false,
'5.1' => true,
),
'UnderflowException' => array(
'5.0' => false,
'5.1' => true,
),
'UnexpectedValueException' => array(
'5.0' => false,
'5.1' => true,
),
'PharException' => array(
'5.2' => false,
'5.3' => true,
),
'SNMPException' => array(
'5.3' => false,
'5.4' => true,
),
'IntlException' => array(
'5.4' => false,
'5.5' => true,
),
'Error' => array(
'5.6' => false,
'7.0' => true,
),
'ArithmeticError' => array(
'5.6' => false,
'7.0' => true,
),
'AssertionError' => array(
'5.6' => false,
'7.0' => true,
),
'DivisionByZeroError' => array(
'5.6' => false,
'7.0' => true,
),
'ParseError' => array(
'5.6' => false,
'7.0' => true,
),
'TypeError' => array(
'5.6' => false,
'7.0' => true,
),
'ClosedGeneratorException' => array(
'5.6' => false,
'7.0' => true,
),
'UI\Exception\InvalidArgumentException' => array(
'5.6' => false,
'7.0' => true,
),
'UI\Exception\RuntimeException' => array(
'5.6' => false,
'7.0' => true,
),
'ArgumentCountError' => array(
'7.0' => false,
'7.1' => true,
),
'SodiumException' => array(
'7.1' => false,
'7.2' => true,
),
'CompileError' => array(
'7.2' => false,
'7.3' => true,
),
'JsonException' => array(
'7.2' => false,
'7.3' => true,
),
'FFI\Exception' => array(
'7.3' => false,
'7.4' => true,
),
'FFI\ParserException' => array(
'7.3' => false,
'7.4' => true,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
* @since 7.0.3 - Now also targets the `class` keyword to detect extended classes.
* - Now also targets double colons to detect static class use.
* @since 7.1.4 - Now also targets anonymous classes to detect extended classes.
* - Now also targets functions/closures to detect new classes used
* as parameter type declarations.
* - Now also targets the `catch` control structure to detect new
* exception classes being caught.
* @since 8.2.0 Now also targets the `T_RETURN_TYPE` token to detect new classes used
* as return type declarations.
*
* @return array
*/
public function register()
{
// Handle case-insensitivity of class names.
$this->newClasses = $this->arrayKeysToLowercase($this->newClasses);
$this->newExceptions = $this->arrayKeysToLowercase($this->newExceptions);
// Add the Exception classes to the Classes list.
$this->newClasses = array_merge($this->newClasses, $this->newExceptions);
$targets = array(
\T_NEW,
\T_CLASS,
\T_DOUBLE_COLON,
\T_FUNCTION,
\T_CLOSURE,
\T_CATCH,
);
if (\defined('T_ANON_CLASS')) {
$targets[] = \T_ANON_CLASS;
}
if (\defined('T_RETURN_TYPE')) {
$targets[] = \T_RETURN_TYPE;
}
return $targets;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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();
switch ($tokens[$stackPtr]['type']) {
case 'T_FUNCTION':
case 'T_CLOSURE':
$this->processFunctionToken($phpcsFile, $stackPtr);
// Deal with older PHPCS version which don't recognize return type hints
// as well as newer PHPCS versions (3.3.0+) where the tokenization has changed.
$returnTypeHint = $this->getReturnTypeHintToken($phpcsFile, $stackPtr);
if ($returnTypeHint !== false) {
$this->processReturnTypeToken($phpcsFile, $returnTypeHint);
}
break;
case 'T_CATCH':
$this->processCatchToken($phpcsFile, $stackPtr);
break;
case 'T_RETURN_TYPE':
$this->processReturnTypeToken($phpcsFile, $stackPtr);
break;
default:
$this->processSingularToken($phpcsFile, $stackPtr);
break;
}
}
/**
* Processes this test for when a token resulting in a singular class name is encountered.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processSingularToken(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$FQClassName = '';
if ($tokens[$stackPtr]['type'] === 'T_NEW') {
$FQClassName = $this->getFQClassNameFromNewToken($phpcsFile, $stackPtr);
} elseif ($tokens[$stackPtr]['type'] === 'T_CLASS' || $tokens[$stackPtr]['type'] === 'T_ANON_CLASS') {
$FQClassName = $this->getFQExtendedClassName($phpcsFile, $stackPtr);
} elseif ($tokens[$stackPtr]['type'] === 'T_DOUBLE_COLON') {
$FQClassName = $this->getFQClassNameFromDoubleColonToken($phpcsFile, $stackPtr);
}
if ($FQClassName === '') {
return;
}
$className = substr($FQClassName, 1); // Remove global namespace indicator.
$classNameLc = strtolower($className);
if (isset($this->newClasses[$classNameLc]) === false) {
return;
}
$itemInfo = array(
'name' => $className,
'nameLc' => $classNameLc,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Processes this test for when a function token is encountered.
*
* - Detect new classes when used as a parameter type declaration.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processFunctionToken(File $phpcsFile, $stackPtr)
{
// Retrieve typehints stripped of global NS indicator and/or nullable indicator.
$typeHints = $this->getTypeHintsFromFunctionDeclaration($phpcsFile, $stackPtr);
if (empty($typeHints) || \is_array($typeHints) === false) {
return;
}
foreach ($typeHints as $hint) {
$typeHintLc = strtolower($hint);
if (isset($this->newClasses[$typeHintLc]) === true) {
$itemInfo = array(
'name' => $hint,
'nameLc' => $typeHintLc,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
}
}
/**
* Processes this test for when a catch token is encountered.
*
* - Detect exceptions when used in a catch statement.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processCatchToken(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Bow out during live coding.
if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
return;
}
$opener = $tokens[$stackPtr]['parenthesis_opener'];
$closer = ($tokens[$stackPtr]['parenthesis_closer'] + 1);
$name = '';
$listen = array(
// Parts of a (namespaced) class name.
\T_STRING => true,
\T_NS_SEPARATOR => true,
// End/split tokens.
\T_VARIABLE => false,
\T_BITWISE_OR => false,
\T_CLOSE_CURLY_BRACKET => false, // Shouldn't be needed as we expect a var before this.
);
for ($i = ($opener + 1); $i < $closer; $i++) {
if (isset($listen[$tokens[$i]['code']]) === false) {
continue;
}
if ($listen[$tokens[$i]['code']] === true) {
$name .= $tokens[$i]['content'];
continue;
} else {
if (empty($name) === true) {
// Weird, we should have a name by the time we encounter a variable or |.
// So this may be the closer.
continue;
}
$name = ltrim($name, '\\');
$nameLC = strtolower($name);
if (isset($this->newExceptions[$nameLC]) === true) {
$itemInfo = array(
'name' => $name,
'nameLc' => $nameLC,
);
$this->handleFeature($phpcsFile, $i, $itemInfo);
}
// Reset for a potential multi-catch.
$name = '';
}
}
}
/**
* Processes this test for when a return type token is encountered.
*
* - Detect new classes when used as a return type declaration.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processReturnTypeToken(File $phpcsFile, $stackPtr)
{
$returnTypeHint = $this->getReturnTypeHintName($phpcsFile, $stackPtr);
if (empty($returnTypeHint)) {
return;
}
$returnTypeHint = ltrim($returnTypeHint, '\\');
$returnTypeHintLc = strtolower($returnTypeHint);
if (isset($this->newClasses[$returnTypeHintLc]) === false) {
return;
}
// Still here ? Then this is a return type declaration using a new class.
$itemInfo = array(
'name' => $returnTypeHint,
'nameLc' => $returnTypeHintLc,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newClasses[$itemInfo['nameLc']];
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'The built-in class ' . parent::getErrorMsgTemplate();
}
}
@@ -0,0 +1,80 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Classes;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Visibility for class constants is available since PHP 7.1.
*
* PHP version 7.1
*
* @link https://wiki.php.net/rfc/class_const_visibility
* @link https://www.php.net/manual/en/language.oop5.constants.php#language.oop5.basic.class.this
*
* @since 7.0.7
*/
class NewConstVisibilitySniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.7
*
* @return array
*/
public function register()
{
return array(\T_CONST);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.7
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
// Is the previous token a visibility indicator ?
if ($prevToken === false || isset(Tokens::$scopeModifiers[$tokens[$prevToken]['code']]) === false) {
return;
}
// Is this a class constant ?
if ($this->isClassConstant($phpcsFile, $stackPtr) === false) {
// This may be a constant declaration in the global namespace with visibility,
// but that would throw a parse error, i.e. not our concern.
return;
}
$phpcsFile->addError(
'Visibility indicators for class constants are not supported in PHP 7.0 or earlier. Found "%s const"',
$stackPtr,
'Found',
array($tokens[$prevToken]['content'])
);
}
}
@@ -0,0 +1,88 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Classes;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect use of late static binding as introduced in PHP 5.3.
*
* Checks for:
* - Late static binding as introduced in PHP 5.3.
* - Late static binding being used outside of class scope (unsupported).
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/language.oop5.late-static-bindings.php
* @link https://wiki.php.net/rfc/lsb_parentself_forwarding
*
* @since 7.0.3
* @since 9.0.0 Renamed from `LateStaticBindingSniff` to `NewLateStaticBindingSniff`.
*/
class NewLateStaticBindingSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.3
*
* @return array
*/
public function register()
{
return array(\T_STATIC);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.3
*
* @param \PHP_CodeSniffer_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)
{
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
if ($nextNonEmpty === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if ($tokens[$nextNonEmpty]['code'] !== \T_DOUBLE_COLON) {
return;
}
$inClass = $this->inClassScope($phpcsFile, $stackPtr, false);
if ($inClass === true && $this->supportsBelow('5.2') === true) {
$phpcsFile->addError(
'Late static binding is not supported in PHP 5.2 or earlier.',
$stackPtr,
'Found'
);
}
if ($inClass === false) {
$phpcsFile->addError(
'Late static binding is not supported outside of class scope.',
$stackPtr,
'OutsideClassScope'
);
}
}
}
@@ -0,0 +1,132 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Classes;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Typed class property declarations are available since PHP 7.4.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.typed-properties
* @link https://wiki.php.net/rfc/typed_properties_v2
*
* @since 9.2.0
*/
class NewTypedPropertiesSniff extends Sniff
{
/**
* Valid property modifier keywords.
*
* @since 9.2.0
*
* @var array
*/
private $modifierKeywords = array(
\T_PRIVATE => \T_PRIVATE,
\T_PROTECTED => \T_PROTECTED,
\T_PUBLIC => \T_PUBLIC,
\T_STATIC => \T_STATIC,
\T_VAR => \T_VAR,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.2.0
*
* @return array
*/
public function register()
{
return array(\T_VARIABLE);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->isClassProperty($phpcsFile, $stackPtr) === false) {
return;
}
$find = $this->modifierKeywords;
$find += array(
\T_SEMICOLON => \T_SEMICOLON,
\T_OPEN_CURLY_BRACKET => \T_OPEN_CURLY_BRACKET,
);
$tokens = $phpcsFile->getTokens();
$modifier = $phpcsFile->findPrevious($find, ($stackPtr - 1));
if ($modifier === false
|| $tokens[$modifier]['code'] === \T_SEMICOLON
|| $tokens[$modifier]['code'] === \T_OPEN_CURLY_BRACKET
) {
// Parse error. Ignore.
return;
}
$type = $phpcsFile->findNext(Tokens::$emptyTokens, ($modifier + 1), null, true);
if ($tokens[$type]['code'] === \T_VARIABLE) {
return;
}
// Still here ? In that case, this will be a typed property.
if ($this->supportsBelow('7.3') === true) {
$phpcsFile->addError(
'Typed properties are not supported in PHP 7.3 or earlier',
$type,
'Found'
);
}
if ($this->supportsAbove('7.4') === true) {
// Examine the type to verify it's valid.
if ($tokens[$type]['type'] === 'T_NULLABLE'
// Needed to support PHPCS < 3.5.0 which doesn't correct to the nullable token type yet.
|| $tokens[$type]['code'] === \T_INLINE_THEN
) {
$type = $phpcsFile->findNext(Tokens::$emptyTokens, ($type + 1), null, true);
}
$content = $tokens[$type]['content'];
if ($content === 'void' || $content === 'callable') {
$phpcsFile->addError(
'%s is not supported as a type declaration for properties',
$type,
'InvalidType',
array($content)
);
}
}
$endOfStatement = $phpcsFile->findNext(\T_SEMICOLON, ($stackPtr + 1));
if ($endOfStatement !== false) {
// Don't throw the same error multiple times for multi-property declarations.
return ($endOfStatement + 1);
}
}
}
@@ -0,0 +1,115 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Classes;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Using `parent` inside a class without parent is deprecated since PHP 7.4.
*
* This will throw a compile-time error in the future. Currently an error will only
* be generated if/when the parent is accessed at run-time.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.deprecated.php#migration74.deprecated.core.parent
*
* @since 9.2.0
*/
class RemovedOrphanedParentSniff extends Sniff
{
/**
* Class scopes to check the class declaration.
*
* @since 9.2.0
*
* @var array
*/
public $classScopeTokens = array(
'T_CLASS' => true,
'T_ANON_CLASS' => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.2.0
*
* @return array
*/
public function register()
{
return array(\T_PARENT);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.2.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (empty($tokens[$stackPtr]['conditions']) === true) {
// Use within the global namespace. Not our concern.
return;
}
/*
* Find the class within which this parent keyword is used.
*/
$conditions = $tokens[$stackPtr]['conditions'];
$conditions = array_reverse($conditions, true);
$classPtr = false;
foreach ($conditions as $ptr => $type) {
if (isset($this->classScopeTokens[$tokens[$ptr]['type']])) {
$classPtr = $ptr;
break;
}
}
if ($classPtr === false) {
// Use outside of a class scope. Not our concern.
return;
}
if (isset($tokens[$classPtr]['scope_opener']) === false) {
// No scope opener known. Probably a parse error.
return;
}
$extends = $phpcsFile->findNext(\T_EXTENDS, ($classPtr + 1), $tokens[$classPtr]['scope_opener']);
if ($extends !== false) {
// Class has a parent.
return;
}
$phpcsFile->addError(
'Using "parent" inside a class without parent is deprecated since PHP 7.4',
$stackPtr,
'Deprecated'
);
}
}
@@ -0,0 +1,80 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Constants;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect usage of the magic `::class` constant introduced in PHP 5.5.
*
* The special `ClassName::class` constant is available as of PHP 5.5.0, and allows
* for fully qualified class name resolution at compile time.
*
* PHP version 5.5
*
* @link https://wiki.php.net/rfc/class_name_scalars
* @link https://www.php.net/manual/en/language.oop5.constants.php#example-186
*
* @since 7.1.4
* @since 7.1.5 Removed the incorrect checks against invalid usage of the constant.
*/
class NewMagicClassConstantSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.1.4
*
* @return array
*/
public function register()
{
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (strtolower($tokens[$stackPtr]['content']) !== 'class') {
return;
}
$prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
if ($prevToken === false || $tokens[$prevToken]['code'] !== \T_DOUBLE_COLON) {
return;
}
$phpcsFile->addError(
'The magic class constant ClassName::class was not available in PHP 5.4 or earlier',
$stackPtr,
'Found'
);
}
}
@@ -0,0 +1,574 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Constants;
use PHPCompatibility\AbstractRemovedFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect use of deprecated and/or removed PHP native global constants.
*
* PHP version All
*
* @since 8.1.0
*/
class RemovedConstantsSniff extends AbstractRemovedFeatureSniff
{
/**
* A list of removed PHP Constants.
*
* The array lists : version number with false (deprecated) or true (removed).
* If's sufficient to list the first version where the constant was deprecated/removed.
*
* Optional, the array can contain an `alternative` key listing an alternative constant
* to be used instead.
*
* Note: PHP Constants are case-sensitive!
*
* @since 8.1.0
*
* @var array(string => array(string => bool|string))
*/
protected $removedConstants = array(
// Disabled since PHP 5.3.0 due to thread safety issues.
'FILEINFO_COMPRESS' => array(
'5.3' => true,
),
'CURLOPT_CLOSEPOLICY' => array(
'5.6' => true,
),
'CURLCLOSEPOLICY_LEAST_RECENTLY_USED' => array(
'5.6' => true,
),
'CURLCLOSEPOLICY_LEAST_TRAFFIC' => array(
'5.6' => true,
),
'CURLCLOSEPOLICY_SLOWEST' => array(
'5.6' => true,
),
'CURLCLOSEPOLICY_CALLBACK' => array(
'5.6' => true,
),
'CURLCLOSEPOLICY_OLDEST' => array(
'5.6' => true,
),
'PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT' => array(
'7.0' => true,
),
'T_CHARACTER' => array(
'7.0' => true,
),
'T_BAD_CHARACTER' => array(
'7.0' => true,
),
'INTL_IDNA_VARIANT_2003' => array(
'7.2' => false,
),
'MCRYPT_MODE_ECB' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_MODE_CBC' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_MODE_CFB' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_MODE_OFB' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_MODE_NOFB' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_MODE_STREAM' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_ENCRYPT' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_DECRYPT' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_DEV_RANDOM' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_DEV_URANDOM' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RAND' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_3DES' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_ARCFOUR_IV' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_ARCFOUR' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_BLOWFISH' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_CAST_128' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_CAST_256' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_CRYPT' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_DES' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_DES_COMPAT' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_ENIGMA' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_GOST' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_IDEA' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_LOKI97' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_MARS' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_PANAMA' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RIJNDAEL_128' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RIJNDAEL_192' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RIJNDAEL_256' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RC2' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RC4' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RC6' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RC6_128' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RC6_192' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_RC6_256' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_SAFER64' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_SAFER128' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_SAFERPLUS' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_SERPENT' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_SERPENT_128' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_SERPENT_192' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_SERPENT_256' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_SKIPJACK' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_TEAN' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_THREEWAY' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_TRIPLEDES' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_TWOFISH' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_TWOFISH128' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_TWOFISH192' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_TWOFISH256' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_WAKE' => array(
'7.1' => false,
'7.2' => true,
),
'MCRYPT_XTEA' => array(
'7.1' => false,
'7.2' => true,
),
'PHPDBG_FILE' => array(
'7.3' => true,
),
'PHPDBG_METHOD' => array(
'7.3' => true,
),
'PHPDBG_LINENO' => array(
'7.3' => true,
),
'PHPDBG_FUNC' => array(
'7.3' => true,
),
'FILTER_FLAG_SCHEME_REQUIRED' => array(
'7.3' => false,
),
'FILTER_FLAG_HOST_REQUIRED' => array(
'7.3' => false,
),
'CURLPIPE_HTTP1' => array(
'7.4' => false,
),
'FILTER_SANITIZE_MAGIC_QUOTES' => array(
'7.4' => false,
'alternative' => 'FILTER_SANITIZE_ADD_SLASHES',
),
'IBASE_BKP_CONVERT' => array(
'7.4' => true,
),
'IBASE_BKP_IGNORE_CHECKSUMS' => array(
'7.4' => true,
),
'IBASE_BKP_IGNORE_LIMBO' => array(
'7.4' => true,
),
'IBASE_BKP_METADATA_ONLY' => array(
'7.4' => true,
),
'IBASE_BKP_NO_GARBAGE_COLLECT' => array(
'7.4' => true,
),
'IBASE_BKP_NON_TRANSPORTABLE' => array(
'7.4' => true,
),
'IBASE_BKP_OLD_DESCRIPTIONS' => array(
'7.4' => true,
),
'IBASE_COMMITTED' => array(
'7.4' => true,
),
'IBASE_CONCURRENCY' => array(
'7.4' => true,
),
'IBASE_CONSISTENCY' => array(
'7.4' => true,
),
'IBASE_DEFAULT' => array(
'7.4' => true,
),
'IBASE_FETCH_ARRAYS' => array(
'7.4' => true,
),
'IBASE_FETCH_BLOBS' => array(
'7.4' => true,
),
'IBASE_NOWAIT' => array(
'7.4' => true,
),
'IBASE_PRP_ACCESS_MODE' => array(
'7.4' => true,
),
'IBASE_PRP_ACTIVATE' => array(
'7.4' => true,
),
'IBASE_PRP_AM_READONLY' => array(
'7.4' => true,
),
'IBASE_PRP_AM_READWRITE' => array(
'7.4' => true,
),
'IBASE_PRP_DENY_NEW_ATTACHMENTS' => array(
'7.4' => true,
),
'IBASE_PRP_DENY_NEW_TRANSACTIONS' => array(
'7.4' => true,
),
'IBASE_PRP_DB_ONLINE' => array(
'7.4' => true,
),
'IBASE_PRP_PAGE_BUFFERS' => array(
'7.4' => true,
),
'IBASE_PRP_RES' => array(
'7.4' => true,
),
'IBASE_PRP_RES_USE_FULL' => array(
'7.4' => true,
),
'IBASE_PRP_RESERVE_SPACE' => array(
'7.4' => true,
),
'IBASE_PRP_SET_SQL_DIALECT' => array(
'7.4' => true,
),
'IBASE_PRP_SHUTDOWN_DB' => array(
'7.4' => true,
),
'IBASE_PRP_SWEEP_INTERVAL' => array(
'7.4' => true,
),
'IBASE_PRP_WM_ASYNC' => array(
'7.4' => true,
),
'IBASE_PRP_WM_SYNC' => array(
'7.4' => true,
),
'IBASE_PRP_WRITE_MODE' => array(
'7.4' => true,
),
'IBASE_READ' => array(
'7.4' => true,
),
'IBASE_RES_CREATE' => array(
'7.4' => true,
),
'IBASE_RES_DEACTIVATE_IDX' => array(
'7.4' => true,
),
'IBASE_RES_NO_SHADOW' => array(
'7.4' => true,
),
'IBASE_RES_NO_VALIDITY' => array(
'7.4' => true,
),
'IBASE_RES_ONE_AT_A_TIME' => array(
'7.4' => true,
),
'IBASE_RES_REPLACE' => array(
'7.4' => true,
),
'IBASE_RES_USE_ALL_SPACE' => array(
'7.4' => true,
),
'IBASE_RPR_CHECK_DB' => array(
'7.4' => true,
),
'IBASE_RPR_FULL' => array(
'7.4' => true,
),
'IBASE_RPR_IGNORE_CHECKSUM' => array(
'7.4' => true,
),
'IBASE_RPR_KILL_SHADOWS' => array(
'7.4' => true,
),
'IBASE_RPR_MEND_DB' => array(
'7.4' => true,
),
'IBASE_RPR_SWEEP_DB' => array(
'7.4' => true,
),
'IBASE_RPR_VALIDATE_DB' => array(
'7.4' => true,
),
'IBASE_STS_DATA_PAGES' => array(
'7.4' => true,
),
'IBASE_STS_DB_LOG' => array(
'7.4' => true,
),
'IBASE_STS_HDR_PAGES' => array(
'7.4' => true,
),
'IBASE_STS_IDX_PAGES' => array(
'7.4' => true,
),
'IBASE_STS_SYS_RELATIONS' => array(
'7.4' => true,
),
'IBASE_SVC_GET_ENV' => array(
'7.4' => true,
),
'IBASE_SVC_GET_ENV_LOCK' => array(
'7.4' => true,
),
'IBASE_SVC_GET_ENV_MSG' => array(
'7.4' => true,
),
'IBASE_SVC_GET_USERS' => array(
'7.4' => true,
),
'IBASE_SVC_IMPLEMENTATION' => array(
'7.4' => true,
),
'IBASE_SVC_SERVER_VERSION' => array(
'7.4' => true,
),
'IBASE_SVC_SVR_DB_INFO' => array(
'7.4' => true,
),
'IBASE_SVC_USER_DBPATH' => array(
'7.4' => true,
),
'IBASE_UNIXTIME' => array(
'7.4' => true,
),
'IBASE_WAIT' => array(
'7.4' => true,
),
'IBASE_WRITE' => array(
'7.4' => true,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.1.0
*
* @return array
*/
public function register()
{
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 8.1.0
*
* @param \PHP_CodeSniffer_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();
$constantName = $tokens[$stackPtr]['content'];
if (isset($this->removedConstants[$constantName]) === false) {
return;
}
if ($this->isUseOfGlobalConstant($phpcsFile, $stackPtr) === false) {
return;
}
$itemInfo = array(
'name' => $constantName,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 8.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->removedConstants[$itemInfo['name']];
}
/**
* Get the error message template for this sniff.
*
* @since 8.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'The constant "%s" is ';
}
}
@@ -0,0 +1,238 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ControlStructures;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect use of `continue` in `switch` control structures.
*
* As of PHP 7.3, PHP will throw a warning when `continue` is used to target a `switch`
* control structure.
* The sniff takes numeric arguments used with `continue` into account.
*
* PHP version 7.3
*
* @link https://www.php.net/manual/en/migration73.incompatible.php#migration73.incompatible.core.continue-targeting-switch
* @link https://wiki.php.net/rfc/continue_on_switch_deprecation
* @link https://github.com/php/php-src/commit/04e3523b7d095341f65ed5e71a3cac82fca690e4
* (actual implementation which is different from the RFC).
* @link https://www.php.net/manual/en/control-structures.switch.php
*
* @since 8.2.0
*/
class DiscouragedSwitchContinueSniff extends Sniff
{
/**
* Token codes of control structures which can be targeted using continue.
*
* @since 8.2.0
*
* @var array
*/
protected $loopStructures = array(
\T_FOR => \T_FOR,
\T_FOREACH => \T_FOREACH,
\T_WHILE => \T_WHILE,
\T_DO => \T_DO,
\T_SWITCH => \T_SWITCH,
);
/**
* Tokens which start a new case within a switch.
*
* @since 8.2.0
*
* @var array
*/
protected $caseTokens = array(
\T_CASE => \T_CASE,
\T_DEFAULT => \T_DEFAULT,
);
/**
* Token codes which are accepted to determine the level for the continue.
*
* This array is enriched with the arithmetic operators in the register() method.
*
* @since 8.2.0
*
* @var array
*/
protected $acceptedLevelTokens = array(
\T_LNUMBER => \T_LNUMBER,
\T_OPEN_PARENTHESIS => \T_OPEN_PARENTHESIS,
\T_CLOSE_PARENTHESIS => \T_CLOSE_PARENTHESIS,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.2.0
*
* @return array
*/
public function register()
{
$this->acceptedLevelTokens += Tokens::$arithmeticTokens;
$this->acceptedLevelTokens += Tokens::$emptyTokens;
return array(\T_SWITCH);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.3') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
return;
}
$switchOpener = $tokens[$stackPtr]['scope_opener'];
$switchCloser = $tokens[$stackPtr]['scope_closer'];
// Quick check whether we need to bother with the more complex logic.
$hasContinue = $phpcsFile->findNext(\T_CONTINUE, ($switchOpener + 1), $switchCloser);
if ($hasContinue === false) {
return;
}
$caseDefault = $switchOpener;
do {
$caseDefault = $phpcsFile->findNext($this->caseTokens, ($caseDefault + 1), $switchCloser);
if ($caseDefault === false) {
break;
}
if (isset($tokens[$caseDefault]['scope_opener']) === false) {
// Unknown start of the case, skip.
continue;
}
$caseOpener = $tokens[$caseDefault]['scope_opener'];
$nextCaseDefault = $phpcsFile->findNext($this->caseTokens, ($caseDefault + 1), $switchCloser);
if ($nextCaseDefault === false) {
$caseCloser = $switchCloser;
} else {
$caseCloser = $nextCaseDefault;
}
// Check for unscoped control structures within the case.
$controlStructure = $caseOpener;
$doCount = 0;
while (($controlStructure = $phpcsFile->findNext($this->loopStructures, ($controlStructure + 1), $caseCloser)) !== false) {
if ($tokens[$controlStructure]['code'] === \T_DO) {
$doCount++;
}
if (isset($tokens[$controlStructure]['scope_opener'], $tokens[$controlStructure]['scope_closer']) === false) {
if ($tokens[$controlStructure]['code'] === \T_WHILE && $doCount > 0) {
// While in a do-while construct.
$doCount--;
continue;
}
// Control structure without braces found within the case, ignore this case.
continue 2;
}
}
// Examine the contents of the case.
$continue = $caseOpener;
do {
$continue = $phpcsFile->findNext(\T_CONTINUE, ($continue + 1), $caseCloser);
if ($continue === false) {
break;
}
$nextSemicolon = $phpcsFile->findNext(array(\T_SEMICOLON, \T_CLOSE_TAG), ($continue + 1), $caseCloser);
$codeString = '';
for ($i = ($continue + 1); $i < $nextSemicolon; $i++) {
if (isset($this->acceptedLevelTokens[$tokens[$i]['code']]) === false) {
// Function call/variable or other token which make numeric level impossible to determine.
continue 2;
}
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
continue;
}
$codeString .= $tokens[$i]['content'];
}
$level = null;
if ($codeString !== '') {
if (is_numeric($codeString)) {
$level = (int) $codeString;
} else {
// With the above logic, the string can only contain digits and operators, eval!
$level = eval("return ( $codeString );");
}
}
if (isset($level) === false || $level === 0) {
$level = 1;
}
// Examine which control structure is being targeted by the continue statement.
if (isset($tokens[$continue]['conditions']) === false) {
continue;
}
$conditions = array_reverse($tokens[$continue]['conditions'], true);
// PHPCS adds more structures to the conditions array than we want to take into
// consideration, so clean up the array.
foreach ($conditions as $tokenPtr => $tokenCode) {
if (isset($this->loopStructures[$tokenCode]) === false) {
unset($conditions[$tokenPtr]);
}
}
$targetCondition = \array_slice($conditions, ($level - 1), 1, true);
if (empty($targetCondition)) {
continue;
}
$conditionToken = key($targetCondition);
if ($conditionToken === $stackPtr) {
$phpcsFile->addWarning(
"Targeting a 'switch' control structure with a 'continue' statement is strongly discouraged and will throw a warning as of PHP 7.3.",
$continue,
'Found'
);
}
} while ($continue < $caseCloser);
} while ($caseDefault < $switchCloser);
}
}
@@ -0,0 +1,116 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ControlStructures;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect using `break` and/or `continue` statements outside of a looping structure.
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.other.break-continue
* @link https://www.php.net/manual/en/control-structures.break.php
* @link https://www.php.net/manual/en/control-structures.continue.php
*
* @since 7.0.7
*/
class ForbiddenBreakContinueOutsideLoopSniff extends Sniff
{
/**
* Token codes of control structure in which usage of break/continue is valid.
*
* @since 7.0.7
*
* @var array
*/
protected $validLoopStructures = array(
\T_FOR => true,
\T_FOREACH => true,
\T_WHILE => true,
\T_DO => true,
\T_SWITCH => true,
);
/**
* Token codes which did not correctly get a condition assigned in older PHPCS versions.
*
* @since 7.0.7
*
* @var array
*/
protected $backCompat = array(
\T_CASE => true,
\T_DEFAULT => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.7
*
* @return array
*/
public function register()
{
return array(
\T_BREAK,
\T_CONTINUE,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.7
*
* @param \PHP_CodeSniffer_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();
$token = $tokens[$stackPtr];
// Check if the break/continue is within a valid loop structure.
if (empty($token['conditions']) === false) {
foreach ($token['conditions'] as $tokenCode) {
if (isset($this->validLoopStructures[$tokenCode]) === true) {
return;
}
}
} else {
// Deal with older PHPCS versions.
if (isset($token['scope_condition']) === true && isset($this->backCompat[$tokens[$token['scope_condition']]['code']]) === true) {
return;
}
}
// If we're still here, no valid loop structure container has been found, so throw an error.
$error = "Using '%s' outside of a loop or switch structure is invalid";
$isError = false;
$errorCode = 'Found';
$data = array($token['content']);
if ($this->supportsAbove('7.0')) {
$error .= ' and will throw a fatal error since PHP 7.0';
$isError = true;
$errorCode = 'FatalError';
}
$this->addMessage($phpcsFile, $error, $stackPtr, $isError, $errorCode, $data);
}
}
@@ -0,0 +1,110 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ControlStructures;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detects using 0 and variable numeric arguments on `break` and `continue` statements.
*
* This sniff checks for:
* - Using `break` and/or `continue` with a variable as the numeric argument.
* - Using `break` and/or `continue` with a zero - 0 - as the numeric argument.
*
* PHP version 5.4
*
* @link https://www.php.net/manual/en/migration54.incompatible.php
* @link https://www.php.net/manual/en/control-structures.break.php
* @link https://www.php.net/manual/en/control-structures.continue.php
*
* @since 5.5
* @since 5.6 Now extends the base `Sniff` class.
*/
class ForbiddenBreakContinueVariableArgumentsSniff extends Sniff
{
/**
* Error types this sniff handles for forbidden break/continue arguments.
*
* Array key is the error code. Array value will be used as part of the error message.
*
* @since 7.0.5
* @since 7.1.0 Changed from class constants to property.
*
* @var array
*/
private $errorTypes = array(
'variableArgument' => 'a variable argument',
'zeroArgument' => '0 as an argument',
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
return array(\T_BREAK, \T_CONTINUE);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('5.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$nextSemicolonToken = $phpcsFile->findNext(array(\T_SEMICOLON, \T_CLOSE_TAG), ($stackPtr), null, false);
$errorType = '';
for ($curToken = $stackPtr + 1; $curToken < $nextSemicolonToken; $curToken++) {
if ($tokens[$curToken]['type'] === 'T_STRING') {
// If the next non-whitespace token after the string
// is an opening parenthesis then it's a function call.
$openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, $curToken + 1, null, true);
if ($tokens[$openBracket]['code'] === \T_OPEN_PARENTHESIS) {
$errorType = 'variableArgument';
break;
}
} elseif (\in_array($tokens[$curToken]['type'], array('T_VARIABLE', 'T_FUNCTION', 'T_CLOSURE'), true)) {
$errorType = 'variableArgument';
break;
} elseif ($tokens[$curToken]['type'] === 'T_LNUMBER' && $tokens[$curToken]['content'] === '0') {
$errorType = 'zeroArgument';
break;
}
}
if ($errorType !== '') {
$error = 'Using %s on break or continue is forbidden since PHP 5.4';
$errorCode = $errorType . 'Found';
$data = array($this->errorTypes[$errorType]);
$phpcsFile->addError($error, $stackPtr, $errorCode, $data);
}
}
}
@@ -0,0 +1,81 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ControlStructures;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Switch statements can not have multiple default blocks since PHP 7.0.
*
* PHP version 7.0
*
* @link https://wiki.php.net/rfc/switch.default.multiple
* @link https://www.php.net/manual/en/control-structures.switch.php
*
* @since 7.0.0
*/
class ForbiddenSwitchWithMultipleDefaultBlocksSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
*
* @return array
*/
public function register()
{
return array(\T_SWITCH);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['scope_closer']) === false) {
return;
}
$defaultToken = $stackPtr;
$defaultCount = 0;
$targetLevel = $tokens[$stackPtr]['level'] + 1;
while ($defaultCount < 2 && ($defaultToken = $phpcsFile->findNext(array(\T_DEFAULT), $defaultToken + 1, $tokens[$stackPtr]['scope_closer'])) !== false) {
// Same level or one below (= two default cases after each other).
if ($tokens[$defaultToken]['level'] === $targetLevel || $tokens[$defaultToken]['level'] === ($targetLevel + 1)) {
$defaultCount++;
}
}
if ($defaultCount > 1) {
$phpcsFile->addError(
'Switch statements can not have multiple default blocks since PHP 7.0',
$stackPtr,
'Found'
);
}
}
}
@@ -0,0 +1,378 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ControlStructures;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Check for valid execution directives set with `declare()`.
*
* The sniff contains three distinct checks:
* - Check if the execution directive used is valid. PHP currently only supports
* three execution directives.
* - Check if the execution directive used is available in the PHP versions
* for which support is being checked.
* In the case of the `encoding` directive on PHP 5.3, support is conditional
* on the `--enable-zend-multibyte` compilation option. This will be indicated as such.
* - Check whether the value for the directive is valid.
*
* PHP version All
*
* @link https://www.php.net/manual/en/control-structures.declare.php
* @link https://wiki.php.net/rfc/scalar_type_hints_v5#strict_types_declare_directive
*
* @since 7.0.3
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class.
*/
class NewExecutionDirectivesSniff extends AbstractNewFeatureSniff
{
/**
* A list of new execution directives
*
* The array lists : version number with false (not present) or true (present).
* If the execution order is conditional, add the condition as a string to the version nr.
* If's sufficient to list the first version where the execution directive appears.
*
* @since 7.0.3
*
* @var array(string => array(string => bool|string|array))
*/
protected $newDirectives = array(
'ticks' => array(
'3.1' => false,
'4.0' => true,
'valid_value_callback' => 'isNumeric',
),
'encoding' => array(
'5.2' => false,
'5.3' => '--enable-zend-multibyte', // Directive ignored unless.
'5.4' => true,
'valid_value_callback' => 'validEncoding',
),
'strict_types' => array(
'5.6' => false,
'7.0' => true,
'valid_values' => array(1),
),
);
/**
* Tokens to ignore when trying to find the value for the directive.
*
* @since 7.0.3
*
* @var array
*/
protected $ignoreTokens = array();
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.3
*
* @return array
*/
public function register()
{
$this->ignoreTokens = Tokens::$emptyTokens;
$this->ignoreTokens[\T_EQUAL] = \T_EQUAL;
return array(\T_DECLARE);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.3
*
* @param \PHP_CodeSniffer_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'], $tokens[$stackPtr]['parenthesis_closer']) === true) {
$openParenthesis = $tokens[$stackPtr]['parenthesis_opener'];
$closeParenthesis = $tokens[$stackPtr]['parenthesis_closer'];
} else {
if (version_compare(PHPCSHelper::getVersion(), '2.3.4', '>=')) {
return;
}
// Deal with PHPCS 2.3.0-2.3.3 which do not yet set the parenthesis properly for declare statements.
$openParenthesis = $phpcsFile->findNext(\T_OPEN_PARENTHESIS, ($stackPtr + 1), null, false, null, true);
if ($openParenthesis === false || isset($tokens[$openParenthesis]['parenthesis_closer']) === false) {
return;
}
$closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
}
$directivePtr = $phpcsFile->findNext(\T_STRING, ($openParenthesis + 1), $closeParenthesis, false);
if ($directivePtr === false) {
return;
}
$directiveContent = $tokens[$directivePtr]['content'];
if (isset($this->newDirectives[$directiveContent]) === false) {
$error = 'Declare can only be used with the directives %s. Found: %s';
$data = array(
implode(', ', array_keys($this->newDirectives)),
$directiveContent,
);
$phpcsFile->addError($error, $stackPtr, 'InvalidDirectiveFound', $data);
} else {
// Check for valid directive for version.
$itemInfo = array(
'name' => $directiveContent,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
// Check for valid directive value.
$valuePtr = $phpcsFile->findNext($this->ignoreTokens, $directivePtr + 1, $closeParenthesis, true);
if ($valuePtr === false) {
return;
}
$this->addWarningOnInvalidValue($phpcsFile, $valuePtr, $directiveContent);
}
}
/**
* Determine whether an error/warning should be thrown for an item based on collected information.
*
* @since 7.1.0
*
* @param array $errorInfo Detail information about an item.
*
* @return bool
*/
protected function shouldThrowError(array $errorInfo)
{
return ($errorInfo['not_in_version'] !== '' || $errorInfo['conditional_version'] !== '');
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newDirectives[$itemInfo['name']];
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array(
'valid_value_callback',
'valid_values',
);
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
$errorInfo['conditional_version'] = '';
$errorInfo['condition'] = '';
$versionArray = $this->getVersionArray($itemArray);
if (empty($versionArray) === false) {
foreach ($versionArray as $version => $present) {
if (\is_string($present) === true && $this->supportsBelow($version) === true) {
// We cannot test for compilation option (ok, except by scraping the output of phpinfo...).
$errorInfo['conditional_version'] = $version;
$errorInfo['condition'] = $present;
}
}
}
return $errorInfo;
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'Directive ' . parent::getErrorMsgTemplate();
}
/**
* Generates the error or warning for this item.
*
* @since 7.0.3
* @since 7.1.0 This method now overloads the method from the `AbstractNewFeatureSniff` class.
* - Renamed from `maybeAddError()` to `addError()`.
* - Changed visibility from `protected` to `public`.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
* @param array $errorInfo Array with detail (version) information
* relevant to the item.
*
* @return void
*/
public function addError(File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo)
{
if ($errorInfo['not_in_version'] !== '') {
parent::addError($phpcsFile, $stackPtr, $itemInfo, $errorInfo);
} elseif ($errorInfo['conditional_version'] !== '') {
$error = 'Directive %s is present in PHP version %s but will be disregarded unless PHP is compiled with %s';
$errorCode = $this->stringToErrorCode($itemInfo['name']) . 'WithConditionFound';
$data = array(
$itemInfo['name'],
$errorInfo['conditional_version'],
$errorInfo['condition'],
);
$phpcsFile->addWarning($error, $stackPtr, $errorCode, $data);
}
}
/**
* Generates a error or warning for this sniff.
*
* @since 7.0.3
* @since 7.0.6 Renamed from `addErrorOnInvalidValue()` to `addWarningOnInvalidValue()`.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the execution directive value
* in the token array.
* @param string $directive The directive.
*
* @return void
*/
protected function addWarningOnInvalidValue(File $phpcsFile, $stackPtr, $directive)
{
$tokens = $phpcsFile->getTokens();
$value = $tokens[$stackPtr]['content'];
if (isset(Tokens::$stringTokens[$tokens[$stackPtr]['code']]) === true) {
$value = $this->stripQuotes($value);
}
$isError = false;
if (isset($this->newDirectives[$directive]['valid_values'])) {
if (\in_array($value, $this->newDirectives[$directive]['valid_values']) === false) {
$isError = true;
}
} elseif (isset($this->newDirectives[$directive]['valid_value_callback'])) {
$valid = \call_user_func(array($this, $this->newDirectives[$directive]['valid_value_callback']), $value);
if ($valid === false) {
$isError = true;
}
}
if ($isError === true) {
$error = 'The execution directive %s does not seem to have a valid value. Please review. Found: %s';
$errorCode = $this->stringToErrorCode($directive) . 'InvalidValueFound';
$data = array(
$directive,
$value,
);
$phpcsFile->addWarning($error, $stackPtr, $errorCode, $data);
}
}
/**
* Check whether a value is numeric.
*
* Callback function to test whether the value for an execution directive is valid.
*
* @since 7.0.3
*
* @param mixed $value The value to test.
*
* @return bool
*/
protected function isNumeric($value)
{
return is_numeric($value);
}
/**
* Check whether a value is a valid encoding.
*
* Callback function to test whether the value for an execution directive is valid.
*
* @since 7.0.3
*
* @param mixed $value The value to test.
*
* @return bool
*/
protected function validEncoding($value)
{
static $encodings;
if (isset($encodings) === false && function_exists('mb_list_encodings')) {
$encodings = mb_list_encodings();
}
if (empty($encodings) || \is_array($encodings) === false) {
// If we can't test the encoding, let it pass through.
return true;
}
return \in_array($value, $encodings, true);
}
}
@@ -0,0 +1,99 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ControlStructures;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect `foreach` expression referencing.
*
* Before PHP 5.5.0, referencing `$value` in a `foreach` was only possible
* if the iterated array could be referenced (i.e. if it is a variable).
*
* PHP version 5.5
*
* @link https://www.php.net/manual/en/control-structures.foreach.php
*
* @since 9.0.0
*/
class NewForeachExpressionReferencingSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.0.0
*
* @return array
*/
public function register()
{
return array(\T_FOREACH);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
return;
}
$opener = $tokens[$stackPtr]['parenthesis_opener'];
$closer = $tokens[$stackPtr]['parenthesis_closer'];
$asToken = $phpcsFile->findNext(\T_AS, ($opener + 1), $closer);
if ($asToken === false) {
return;
}
/*
* Note: referencing $key is not allowed in any version, so this should only find referenced $values.
* If it does find a referenced key, it would be a parse error anyway.
*/
$hasReference = $phpcsFile->findNext(\T_BITWISE_AND, ($asToken + 1), $closer);
if ($hasReference === false) {
return;
}
$nestingLevel = 0;
if ($asToken !== ($opener + 1) && isset($tokens[$opener + 1]['nested_parenthesis'])) {
$nestingLevel = \count($tokens[$opener + 1]['nested_parenthesis']);
}
if ($this->isVariable($phpcsFile, ($opener + 1), $asToken, $nestingLevel) === true) {
return;
}
// Non-variable detected before the `as` keyword.
$phpcsFile->addError(
'Referencing $value is only possible if the iterated array is a variable in PHP 5.4 or earlier.',
$hasReference,
'Found'
);
}
}
@@ -0,0 +1,84 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ControlStructures;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect unpacking nested arrays with `list()` in a `foreach()` as available since PHP 5.5.
*
* PHP version 5.5
*
* @link https://www.php.net/manual/en/migration55.new-features.php#migration55.new-features.foreach-list
* @link https://wiki.php.net/rfc/foreachlist
* @link https://www.php.net/manual/en/control-structures.foreach.php#control-structures.foreach.list
*
* @since 9.0.0
*/
class NewListInForeachSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.0.0
*
* @return array
*/
public function register()
{
return array(\T_FOREACH);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
return;
}
$opener = $tokens[$stackPtr]['parenthesis_opener'];
$closer = $tokens[$stackPtr]['parenthesis_closer'];
$asToken = $phpcsFile->findNext(\T_AS, ($opener + 1), $closer);
if ($asToken === false) {
return;
}
$hasList = $phpcsFile->findNext(array(\T_LIST, \T_OPEN_SHORT_ARRAY), ($asToken + 1), $closer);
if ($hasList === false) {
return;
}
$phpcsFile->addError(
'Unpacking nested arrays with list() in a foreach is not supported in PHP 5.4 or earlier.',
$hasList,
'Found'
);
}
}
@@ -0,0 +1,78 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ControlStructures;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Catching multiple exception types in one statement is available since PHP 7.1.
*
* PHP version 7.1
*
* @link https://www.php.net/manual/en/migration71.new-features.php#migration71.new-features.mulit-catch-exception-handling
* @link https://wiki.php.net/rfc/multiple-catch
* @link https://www.php.net/manual/en/language.exceptions.php#language.exceptions.catch
*
* @since 7.0.7
*/
class NewMultiCatchSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.7
*
* @return array
*/
public function register()
{
return array(\T_CATCH);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.7
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$token = $tokens[$stackPtr];
// Bow out during live coding.
if (isset($token['parenthesis_opener'], $token['parenthesis_closer']) === false) {
return;
}
$hasBitwiseOr = $phpcsFile->findNext(\T_BITWISE_OR, $token['parenthesis_opener'], $token['parenthesis_closer']);
if ($hasBitwiseOr === false) {
return;
}
$phpcsFile->addError(
'Catching multiple exceptions within one statement is not supported in PHP 7.0 or earlier.',
$hasBitwiseOr,
'Found'
);
}
}
@@ -0,0 +1,346 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Extensions;
use PHPCompatibility\AbstractRemovedFeatureSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect the use of deprecated and/or removed PHP extensions.
*
* This sniff examines function calls made and flags function calls to functions
* prefixed with the dedicated prefix from a deprecated/removed native PHP extension.
*
* Suggests alternative extensions if available.
*
* As userland functions may be prefixed with a prefix also used by a native
* PHP extension, the sniff offers the ability to whitelist specific functions
* from being flagged by this sniff via a property in a custom ruleset
* (since PHPCompatibility 7.0.2).
*
* {@internal This sniff is a candidate for removal once all functions from all
* deprecated/removed extensions have been added to the RemovedFunctions sniff.}
*
* PHP version All
*
* @since 5.5
* @since 7.1.0 Now extends the `AbstractRemovedFeatureSniff` instead of the base `Sniff` class.
*/
class RemovedExtensionsSniff extends AbstractRemovedFeatureSniff
{
/**
* A list of functions to whitelist, if any.
*
* This is intended for projects using functions which start with the same
* prefix as one of the removed extensions.
*
* This property can be set from the ruleset, like so:
* <rule ref="PHPCompatibility.Extensions.RemovedExtensions">
* <properties>
* <property name="functionWhitelist" type="array" value="mysql_to_rfc3339,mysql_another_function" />
* </properties>
* </rule>
*
* @since 7.0.2
*
* @var array
*/
public $functionWhitelist;
/**
* A list of removed extensions with their alternative, if any.
*
* The array lists : version number with false (deprecated) and true (removed).
* If's sufficient to list the first version where the extension was deprecated/removed.
*
* @since 5.5
*
* @var array(string => array(string => bool|string|null))
*/
protected $removedExtensions = array(
'activescript' => array(
'5.1' => true,
'alternative' => 'pecl/activescript',
),
'cpdf' => array(
'5.1' => true,
'alternative' => 'pecl/pdflib',
),
'dbase' => array(
'5.3' => true,
'alternative' => null,
),
'dbx' => array(
'5.1' => true,
'alternative' => 'pecl/dbx',
),
'dio' => array(
'5.1' => true,
'alternative' => 'pecl/dio',
),
'ereg' => array(
'5.3' => false,
'7.0' => true,
'alternative' => 'pcre',
),
'fam' => array(
'5.1' => true,
'alternative' => null,
),
'fbsql' => array(
'5.3' => true,
'alternative' => null,
),
'fdf' => array(
'5.3' => true,
'alternative' => 'pecl/fdf',
),
'filepro' => array(
'5.2' => true,
'alternative' => null,
),
'hw_api' => array(
'5.2' => true,
'alternative' => null,
),
'ibase' => array(
'7.4' => true,
'alternative' => 'pecl/ibase',
),
'ingres' => array(
'5.1' => true,
'alternative' => 'pecl/ingres',
),
'ircg' => array(
'5.1' => true,
'alternative' => null,
),
'mcrypt' => array(
'7.1' => false,
'7.2' => true,
'alternative' => 'openssl (preferred) or pecl/mcrypt once available',
),
'mcve' => array(
'5.1' => true,
'alternative' => 'pecl/mcve',
),
'ming' => array(
'5.3' => true,
'alternative' => 'pecl/ming',
),
'mnogosearch' => array(
'5.1' => true,
'alternative' => null,
),
'msql' => array(
'5.3' => true,
'alternative' => null,
),
'mssql' => array(
'7.0' => true,
'alternative' => null,
),
'mysql_' => array(
'5.5' => false,
'7.0' => true,
'alternative' => 'mysqli',
),
'ncurses' => array(
'5.3' => true,
'alternative' => 'pecl/ncurses',
),
'oracle' => array(
'5.1' => true,
'alternative' => 'oci8 or pdo_oci',
),
'ovrimos' => array(
'5.1' => true,
'alternative' => null,
),
'pfpro_' => array(
'5.1' => true,
'alternative' => null,
),
'recode' => array(
'7.4' => true,
'alternative' => 'iconv or mbstring',
),
'sqlite' => array(
'5.4' => true,
'alternative' => null,
),
// Has to be before `sybase` as otherwise it will never match.
'sybase_ct' => array(
'7.0' => true,
'alternative' => null,
),
'sybase' => array(
'5.3' => true,
'alternative' => 'sybase_ct',
),
'w32api' => array(
'5.1' => true,
'alternative' => 'pecl/ffi',
),
'wddx' => array(
'7.4' => true,
'alternative' => 'pecl/wddx',
),
'yp' => array(
'5.1' => true,
'alternative' => null,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
// Handle case-insensitivity of function names.
$this->removedExtensions = $this->arrayKeysToLowercase($this->removedExtensions);
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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();
// Find the next non-empty token.
$openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($tokens[$openBracket]['code'] !== \T_OPEN_PARENTHESIS) {
// Not a function call.
return;
}
if (isset($tokens[$openBracket]['parenthesis_closer']) === false) {
// Not a function call.
return;
}
// Find the previous non-empty token.
$search = Tokens::$emptyTokens;
$search[] = \T_BITWISE_AND;
$previous = $phpcsFile->findPrevious($search, ($stackPtr - 1), null, true);
if ($tokens[$previous]['code'] === \T_FUNCTION) {
// It's a function definition, not a function call.
return;
}
if ($tokens[$previous]['code'] === \T_NEW) {
// We are creating an object, not calling a function.
return;
}
if ($tokens[$previous]['code'] === \T_OBJECT_OPERATOR) {
// We are calling a method of an object.
return;
}
$function = $tokens[$stackPtr]['content'];
$functionLc = strtolower($function);
if ($this->isWhiteListed($functionLc) === true) {
// Function is whitelisted.
return;
}
foreach ($this->removedExtensions as $extension => $versionList) {
if (strpos($functionLc, $extension) === 0) {
$itemInfo = array(
'name' => $extension,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
break;
}
}
}
/**
* Is the current function being checked whitelisted ?
*
* Parsing the list late as it may be provided as a property, but also inline.
*
* @since 7.0.2
*
* @param string $content Content of the current token.
*
* @return bool
*/
protected function isWhiteListed($content)
{
if (isset($this->functionWhitelist) === false) {
return false;
}
if (\is_string($this->functionWhitelist) === true) {
if (strpos($this->functionWhitelist, ',') !== false) {
$this->functionWhitelist = explode(',', $this->functionWhitelist);
} else {
$this->functionWhitelist = (array) $this->functionWhitelist;
}
}
if (\is_array($this->functionWhitelist) === true) {
$this->functionWhitelist = array_map('strtolower', $this->functionWhitelist);
return \in_array($content, $this->functionWhitelist, true);
}
return false;
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->removedExtensions[$itemInfo['name']];
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return "Extension '%s' is ";
}
}
@@ -0,0 +1,79 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
/**
* Detect the use of superglobals as parameters for functions, support for which was removed in PHP 5.4.
*
* {@internal List of superglobals is maintained in the parent class.}
*
* PHP version 5.4
*
* @link https://www.php.net/manual/en/migration54.incompatible.php
*
* @since 7.0.0
*/
class ForbiddenParameterShadowSuperGlobalsSniff extends Sniff
{
/**
* Register the tokens to listen for.
*
* @since 7.0.0
* @since 7.1.3 Allows for closures.
*
* @return array
*/
public function register()
{
return array(
\T_FUNCTION,
\T_CLOSURE,
);
}
/**
* Processes the test.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->supportsAbove('5.4') === false) {
return;
}
// Get all parameters from function signature.
$parameters = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
if (empty($parameters) || \is_array($parameters) === false) {
return;
}
foreach ($parameters as $param) {
if (isset($this->superglobals[$param['name']]) === true) {
$error = 'Parameter shadowing super global (%s) causes fatal error since PHP 5.4';
$errorCode = $this->stringToErrorCode(substr($param['name'], 1)) . 'Found';
$data = array($param['name']);
$phpcsFile->addError($error, $param['token'], $errorCode, $data);
}
}
}
}
@@ -0,0 +1,88 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
/**
* Functions can not have multiple parameters with the same name since PHP 7.0.
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.other.func-parameters
*
* @since 7.0.0
*/
class ForbiddenParametersWithSameNameSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
* @since 7.1.3 Allows for closures.
*
* @return array
*/
public function register()
{
return array(
\T_FUNCTION,
\T_CLOSURE,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$token = $tokens[$stackPtr];
// Skip function without body.
if (isset($token['scope_opener']) === false) {
return;
}
// Get all parameters from method signature.
$parameters = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
if (empty($parameters) || \is_array($parameters) === false) {
return;
}
$paramNames = array();
foreach ($parameters as $param) {
$paramNames[] = $param['name'];
}
if (\count($paramNames) !== \count(array_unique($paramNames))) {
$phpcsFile->addError(
'Functions can not have multiple parameters with the same name since PHP 7.0',
$stackPtr,
'Found'
);
}
}
}
@@ -0,0 +1,99 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
/**
* As of PHP 5.3, the __toString() magic method can no longer accept arguments.
*
* Sister-sniff to `PHPCompatibility.MethodUse.ForbiddenToStringParameters`.
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/migration53.incompatible.php
* @link https://www.php.net/manual/en/language.oop5.magic.php#object.tostring
*
* @since 9.2.0
*/
class ForbiddenToStringParametersSniff extends Sniff
{
/**
* Valid scopes for the __toString() method to live in.
*
* @since 9.2.0
* @since 9.3.2 Visibility changed from `public` to `protected`.
*
* @var array
*/
protected $ooScopeTokens = array(
'T_CLASS' => true,
'T_INTERFACE' => true,
'T_TRAIT' => true,
'T_ANON_CLASS' => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.2.0
*
* @return array
*/
public function register()
{
return array(\T_FUNCTION);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.2.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('5.3') === false) {
return;
}
$functionName = $phpcsFile->getDeclarationName($stackPtr);
if (strtolower($functionName) !== '__tostring') {
// Not the right function.
return;
}
if ($this->validDirectScope($phpcsFile, $stackPtr, $this->ooScopeTokens) === false) {
// Function, not method.
return;
}
$params = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
if (empty($params)) {
// Function declared without parameters.
return;
}
$phpcsFile->addError(
'The __toString() magic method can no longer accept arguments since PHP 5.3',
$stackPtr,
'Declared'
);
}
}
@@ -0,0 +1,122 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect variable names forbidden to be used in closure `use` statements.
*
* Variables bound to a closure via the `use` construct cannot use the same name
* as any superglobals, `$this`, or any parameter since PHP 7.1.
*
* PHP version 7.1
*
* @link https://www.php.net/manual/en/migration71.incompatible.php#migration71.incompatible.lexical-names
* @link https://www.php.net/manual/en/functions.anonymous.php
*
* @since 7.1.4
*/
class ForbiddenVariableNamesInClosureUseSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.1.4
*
* @return array
*/
public function register()
{
return array(\T_USE);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.1') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
// Verify this use statement is used with a closure - if so, it has to have parenthesis before it.
$previousNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
if ($previousNonEmpty === false || $tokens[$previousNonEmpty]['code'] !== \T_CLOSE_PARENTHESIS
|| isset($tokens[$previousNonEmpty]['parenthesis_opener']) === false
) {
return;
}
// ... and (a variable within) parenthesis after it.
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
return;
}
if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
// Live coding.
return;
}
$closurePtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($tokens[$previousNonEmpty]['parenthesis_opener'] - 1), null, true);
if ($closurePtr === false || $tokens[$closurePtr]['code'] !== \T_CLOSURE) {
return;
}
// Get the parameters declared by the closure.
$closureParams = PHPCSHelper::getMethodParameters($phpcsFile, $closurePtr);
$errorMsg = 'Variables bound to a closure via the use construct cannot use the same name as superglobals, $this, or a declared parameter since PHP 7.1. Found: %s';
for ($i = ($nextNonEmpty + 1); $i < $tokens[$nextNonEmpty]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== \T_VARIABLE) {
continue;
}
$variableName = $tokens[$i]['content'];
if ($variableName === '$this') {
$phpcsFile->addError($errorMsg, $i, 'FoundThis', array($variableName));
continue;
}
if (isset($this->superglobals[$variableName]) === true) {
$phpcsFile->addError($errorMsg, $i, 'FoundSuperglobal', array($variableName));
continue;
}
// Check whether it is one of the parameters declared by the closure.
if (empty($closureParams) === false) {
foreach ($closureParams as $param) {
if ($param['name'] === $variableName) {
$phpcsFile->addError($errorMsg, $i, 'FoundShadowParam', array($variableName));
continue 2;
}
}
}
}
}
}
@@ -0,0 +1,264 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect closures and verify that the features used are supported.
*
* Version based checks:
* - Closures are available since PHP 5.3.
* - Closures can be declared as `static` since PHP 5.4.
* - Closures can use the `$this` variable within a class context since PHP 5.4.
* - Closures can use `self`/`parent`/`static` since PHP 5.4.
*
* Version independent checks:
* - Static closures don't have access to the `$this` variable.
* - Closures declared outside of a class context don't have access to the `$this`
* variable unless bound to an object.
*
* PHP version 5.3
* PHP version 5.4
*
* @link https://www.php.net/manual/en/functions.anonymous.php
* @link https://wiki.php.net/rfc/closures
* @link https://wiki.php.net/rfc/closures/object-extension
*
* @since 7.0.0
*/
class NewClosureSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
*
* @return array
*/
public function register()
{
return array(\T_CLOSURE);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
* @since 7.1.4 - Added check for closure being declared as static < 5.4.
* - Added check for use of `$this` variable in class context < 5.4.
* - Added check for use of `$this` variable in static closures (unsupported).
* - Added check for use of `$this` variable outside class context (unsupported).
* @since 8.2.0 Added check for use of `self`/`static`/`parent` < 5.4.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->supportsBelow('5.2')) {
$phpcsFile->addError(
'Closures / anonymous functions are not available in PHP 5.2 or earlier',
$stackPtr,
'Found'
);
}
/*
* Closures can only be declared as static since PHP 5.4.
*/
$isStatic = $this->isClosureStatic($phpcsFile, $stackPtr);
if ($this->supportsBelow('5.3') && $isStatic === true) {
$phpcsFile->addError(
'Closures / anonymous functions could not be declared as static in PHP 5.3 or earlier',
$stackPtr,
'StaticFound'
);
}
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
// Live coding or parse error.
return;
}
$scopeStart = ($tokens[$stackPtr]['scope_opener'] + 1);
$scopeEnd = $tokens[$stackPtr]['scope_closer'];
$usesThis = $this->findThisUsageInClosure($phpcsFile, $scopeStart, $scopeEnd);
if ($this->supportsBelow('5.3')) {
/*
* Closures declared within classes only have access to $this since PHP 5.4.
*/
if ($usesThis !== false) {
$thisFound = $usesThis;
do {
$phpcsFile->addError(
'Closures / anonymous functions did not have access to $this in PHP 5.3 or earlier',
$thisFound,
'ThisFound'
);
$thisFound = $this->findThisUsageInClosure($phpcsFile, ($thisFound + 1), $scopeEnd);
} while ($thisFound !== false);
}
/*
* Closures declared within classes only have access to self/parent/static since PHP 5.4.
*/
$usesClassRef = $this->findClassRefUsageInClosure($phpcsFile, $scopeStart, $scopeEnd);
if ($usesClassRef !== false) {
do {
$phpcsFile->addError(
'Closures / anonymous functions could not use "%s::" in PHP 5.3 or earlier',
$usesClassRef,
'ClassRefFound',
array(strtolower($tokens[$usesClassRef]['content']))
);
$usesClassRef = $this->findClassRefUsageInClosure($phpcsFile, ($usesClassRef + 1), $scopeEnd);
} while ($usesClassRef !== false);
}
}
/*
* Check for correct usage.
*/
if ($this->supportsAbove('5.4') && $usesThis !== false) {
$thisFound = $usesThis;
do {
/*
* Closures only have access to $this if not declared as static.
*/
if ($isStatic === true) {
$phpcsFile->addError(
'Closures / anonymous functions declared as static do not have access to $this',
$thisFound,
'ThisFoundInStatic'
);
}
/*
* Closures only have access to $this if used within a class context.
*/
elseif ($this->inClassScope($phpcsFile, $stackPtr, false) === false) {
$phpcsFile->addWarning(
'Closures / anonymous functions only have access to $this if used within a class or when bound to an object using bindTo(). Please verify.',
$thisFound,
'ThisFoundOutsideClass'
);
}
$thisFound = $this->findThisUsageInClosure($phpcsFile, ($thisFound + 1), $scopeEnd);
} while ($thisFound !== false);
}
// Prevent double reporting for nested closures.
return $scopeEnd;
}
/**
* Check whether the closure is declared as static.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return bool
*/
protected function isClosureStatic(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
return ($prevToken !== false && $tokens[$prevToken]['code'] === \T_STATIC);
}
/**
* Check if the code within a closure uses the $this variable.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $startToken The position within the closure to continue searching from.
* @param int $endToken The closure scope closer to stop searching at.
*
* @return int|false The stackPtr to the first $this usage if found or false if
* $this is not used.
*/
protected function findThisUsageInClosure(File $phpcsFile, $startToken, $endToken)
{
// Make sure the $startToken is valid.
if ($startToken >= $endToken) {
return false;
}
return $phpcsFile->findNext(
\T_VARIABLE,
$startToken,
$endToken,
false,
'$this'
);
}
/**
* Check if the code within a closure uses "self/parent/static".
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $startToken The position within the closure to continue searching from.
* @param int $endToken The closure scope closer to stop searching at.
*
* @return int|false The stackPtr to the first classRef usage if found or false if
* they are not used.
*/
protected function findClassRefUsageInClosure(File $phpcsFile, $startToken, $endToken)
{
// Make sure the $startToken is valid.
if ($startToken >= $endToken) {
return false;
}
$tokens = $phpcsFile->getTokens();
$classRef = $phpcsFile->findNext(array(\T_SELF, \T_PARENT, \T_STATIC), $startToken, $endToken);
if ($classRef === false || $tokens[$classRef]['code'] !== \T_STATIC) {
return $classRef;
}
// T_STATIC, make sure it is used as a class reference.
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($classRef + 1), $endToken, true);
if ($next === false || $tokens[$next]['code'] !== \T_DOUBLE_COLON) {
return false;
}
return $classRef;
}
}
@@ -0,0 +1,171 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* As of PHP 7.4, throwing exceptions from a `__toString()` method is allowed.
*
* PHP version 7.4
*
* @link https://wiki.php.net/rfc/tostring_exceptions
* @link https://www.php.net/manual/en/language.oop5.magic.php#object.tostring
*
* @since 9.2.0
*/
class NewExceptionsFromToStringSniff extends Sniff
{
/**
* Valid scopes for the __toString() method to live in.
*
* @since 9.2.0
* @since 9.3.0 Visibility changed from `public` to `protected`.
*
* @var array
*/
protected $ooScopeTokens = array(
'T_CLASS' => true,
'T_TRAIT' => true,
'T_ANON_CLASS' => true,
);
/**
* Tokens which should be ignored when they preface a function declaration
* when trying to find the docblock (if any).
*
* Array will be added to in the register() method.
*
* @since 9.3.0
*
* @var array
*/
private $docblockIgnoreTokens = array(
\T_WHITESPACE => \T_WHITESPACE,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.2.0
*
* @return array
*/
public function register()
{
// Enhance the array of tokens to ignore for finding the docblock.
$this->docblockIgnoreTokens += Tokens::$methodPrefixes;
if (isset(Tokens::$phpcsCommentTokens)) {
$this->docblockIgnoreTokens += Tokens::$phpcsCommentTokens;
}
return array(\T_FUNCTION);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.2.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('7.3') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
// Abstract function, interface function, live coding or parse error.
return;
}
$functionName = $phpcsFile->getDeclarationName($stackPtr);
if (strtolower($functionName) !== '__tostring') {
// Not the right function.
return;
}
if ($this->validDirectScope($phpcsFile, $stackPtr, $this->ooScopeTokens) === false) {
// Function, not method.
return;
}
/*
* Examine the content of the function.
*/
$error = 'Throwing exceptions from __toString() was not allowed prior to PHP 7.4';
$throwPtr = $tokens[$stackPtr]['scope_opener'];
$errorThrown = false;
do {
$throwPtr = $phpcsFile->findNext(\T_THROW, ($throwPtr + 1), $tokens[$stackPtr]['scope_closer']);
if ($throwPtr === false) {
break;
}
$conditions = $tokens[$throwPtr]['conditions'];
$conditions = array_reverse($conditions, true);
$inTryCatch = false;
foreach ($conditions as $ptr => $type) {
if ($type === \T_TRY) {
$inTryCatch = true;
break;
}
if ($ptr === $stackPtr) {
// Don't check the conditions outside the function scope.
break;
}
}
if ($inTryCatch === false) {
$phpcsFile->addError($error, $throwPtr, 'Found');
$errorThrown = true;
}
} while (true);
if ($errorThrown === true) {
// We've already thrown an error for this method, no need to examine the docblock.
return;
}
/*
* Check whether the function has a docblock and if so, whether it contains a @throws tag.
*
* {@internal This can be partially replaced by the findCommentAboveFunction()
* utility function in due time.}
*/
$commentEnd = $phpcsFile->findPrevious($this->docblockIgnoreTokens, ($stackPtr - 1), null, true);
if ($commentEnd === false || $tokens[$commentEnd]['code'] !== \T_DOC_COMMENT_CLOSE_TAG) {
return;
}
$commentStart = $tokens[$commentEnd]['comment_opener'];
foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
if ($tokens[$tag]['content'] !== '@throws') {
continue;
}
// Found a throws tag.
$phpcsFile->addError($error, $stackPtr, 'ThrowsTagFoundInDocblock');
break;
}
}
}
@@ -0,0 +1,169 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Nullable parameter type declarations and return types are available since PHP 7.1.
*
* PHP version 7.1
*
* @link https://www.php.net/manual/en/migration71.new-features.php#migration71.new-features.nullable-types
* @link https://wiki.php.net/rfc/nullable_types
* @link https://www.php.net/manual/en/functions.arguments.php#example-146
*
* @since 7.0.7
*/
class NewNullableTypesSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* {@internal Not sniffing for T_NULLABLE which was introduced in PHPCS 2.7.2
* as in that case we can't distinguish between parameter type hints and
* return type hints for the error message.}
*
* @since 7.0.7
*
* @return array
*/
public function register()
{
$tokens = array(
\T_FUNCTION,
\T_CLOSURE,
);
if (\defined('T_RETURN_TYPE')) {
$tokens[] = \T_RETURN_TYPE;
}
return $tokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.7
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$tokenCode = $tokens[$stackPtr]['code'];
if ($tokenCode === \T_FUNCTION || $tokenCode === \T_CLOSURE) {
$this->processFunctionDeclaration($phpcsFile, $stackPtr);
// Deal with older PHPCS version which don't recognize return type hints
// as well as newer PHPCS versions (3.3.0+) where the tokenization has changed.
$returnTypeHint = $this->getReturnTypeHintToken($phpcsFile, $stackPtr);
if ($returnTypeHint !== false) {
$this->processReturnType($phpcsFile, $returnTypeHint);
}
} else {
$this->processReturnType($phpcsFile, $stackPtr);
}
}
/**
* Process this test for function tokens.
*
* @since 7.0.7
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
protected function processFunctionDeclaration(File $phpcsFile, $stackPtr)
{
$params = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
if (empty($params) === false && \is_array($params)) {
foreach ($params as $param) {
if ($param['nullable_type'] === true) {
$phpcsFile->addError(
'Nullable type declarations are not supported in PHP 7.0 or earlier. Found: %s',
$param['token'],
'typeDeclarationFound',
array($param['type_hint'])
);
}
}
}
}
/**
* Process this test for return type tokens.
*
* @since 7.0.7
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
protected function processReturnType(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[($stackPtr - 1)]['code']) === false) {
return;
}
$previous = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
// Deal with namespaced class names.
if ($tokens[$previous]['code'] === \T_NS_SEPARATOR) {
$validTokens = Tokens::$emptyTokens;
$validTokens[\T_STRING] = true;
$validTokens[\T_NS_SEPARATOR] = true;
$stackPtr--;
while (isset($validTokens[$tokens[($stackPtr - 1)]['code']]) === true) {
$stackPtr--;
}
$previous = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
}
// T_NULLABLE token was introduced in PHPCS 2.7.2. Before that it identified as T_INLINE_THEN.
if ((\defined('T_NULLABLE') === true && $tokens[$previous]['type'] === 'T_NULLABLE')
|| (\defined('T_NULLABLE') === false && $tokens[$previous]['code'] === \T_INLINE_THEN)
) {
$phpcsFile->addError(
'Nullable return types are not supported in PHP 7.0 or earlier.',
$stackPtr,
'returnTypeFound'
);
}
}
}
@@ -0,0 +1,237 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
/**
* Detect and verify the use of parameter type declarations in function declarations.
*
* Parameter type declarations - class/interface names only - is available since PHP 5.0.
* - Since PHP 5.1, the `array` keyword can be used.
* - Since PHP 5.2, `self` and `parent` can be used. Previously, those were interpreted as
* class names.
* - Since PHP 5.4, the `callable` keyword.
* - Since PHP 7.0, scalar type declarations are available.
* - Since PHP 7.1, the `iterable` pseudo-type is available.
* - Since PHP 7.2, the generic `object` type is available.
*
* Additionally, this sniff does a cursory check for typical invalid type declarations,
* such as:
* - `boolean` (should be `bool`), `integer` (should be `int`) and `static`.
* - `self`/`parent` as type declaration used outside class context throws a fatal error since PHP 7.0.
*
* PHP version 5.0+
*
* @link https://www.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration
* @link https://wiki.php.net/rfc/callable
* @link https://wiki.php.net/rfc/scalar_type_hints_v5
* @link https://wiki.php.net/rfc/iterable
* @link https://wiki.php.net/rfc/object-typehint
*
* @since 7.0.0
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class.
* @since 9.0.0 Renamed from `NewScalarTypeDeclarationsSniff` to `NewParamTypeDeclarationsSniff`.
*/
class NewParamTypeDeclarationsSniff extends AbstractNewFeatureSniff
{
/**
* A list of new types.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the keyword appears.
*
* @since 7.0.0
* @since 7.0.3 Now lists all param type declarations, not just the PHP 7+ scalar ones.
*
* @var array(string => array(string => bool))
*/
protected $newTypes = array(
'array' => array(
'5.0' => false,
'5.1' => true,
),
'self' => array(
'5.1' => false,
'5.2' => true,
),
'parent' => array(
'5.1' => false,
'5.2' => true,
),
'callable' => array(
'5.3' => false,
'5.4' => true,
),
'int' => array(
'5.6' => false,
'7.0' => true,
),
'float' => array(
'5.6' => false,
'7.0' => true,
),
'bool' => array(
'5.6' => false,
'7.0' => true,
),
'string' => array(
'5.6' => false,
'7.0' => true,
),
'iterable' => array(
'7.0' => false,
'7.1' => true,
),
'object' => array(
'7.1' => false,
'7.2' => true,
),
);
/**
* Invalid types
*
* The array lists : the invalid type hint => what was probably intended/alternative.
*
* @since 7.0.3
*
* @var array(string => string)
*/
protected $invalidTypes = array(
'static' => 'self',
'boolean' => 'bool',
'integer' => 'int',
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
* @since 7.1.3 Now also checks closures.
*
* @return array
*/
public function register()
{
return array(
\T_FUNCTION,
\T_CLOSURE,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
* @since 7.0.3 - Added check for non-scalar type declarations.
* - Added check for invalid type declarations.
* - Added check for usage of `self` type declaration outside
* class scope.
* @since 8.2.0 Added check for `parent` type declaration outside class scope.
*
* @param \PHP_CodeSniffer_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)
{
// Get all parameters from method signature.
$paramNames = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
if (empty($paramNames)) {
return;
}
$supportsPHP4 = $this->supportsBelow('4.4');
foreach ($paramNames as $param) {
if ($param['type_hint'] === '') {
continue;
}
// Strip off potential nullable indication.
$typeHint = ltrim($param['type_hint'], '?');
if ($supportsPHP4 === true) {
$phpcsFile->addError(
'Type declarations were not present in PHP 4.4 or earlier.',
$param['token'],
'TypeHintFound'
);
} elseif (isset($this->newTypes[$typeHint])) {
$itemInfo = array(
'name' => $typeHint,
);
$this->handleFeature($phpcsFile, $param['token'], $itemInfo);
// As of PHP 7.0, using `self` or `parent` outside class scope throws a fatal error.
// Only throw this error for PHP 5.2+ as before that the "type hint not supported" error
// will be thrown.
if (($typeHint === 'self' || $typeHint === 'parent')
&& $this->inClassScope($phpcsFile, $stackPtr, false) === false
&& $this->supportsAbove('5.2') !== false
) {
$phpcsFile->addError(
"'%s' type cannot be used outside of class scope",
$param['token'],
ucfirst($typeHint) . 'OutsideClassScopeFound',
array($typeHint)
);
}
} elseif (isset($this->invalidTypes[$typeHint])) {
$error = "'%s' is not a valid type declaration. Did you mean %s ?";
$data = array(
$typeHint,
$this->invalidTypes[$typeHint],
);
$phpcsFile->addError($error, $param['token'], 'InvalidTypeHintFound', $data);
}
}
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newTypes[$itemInfo['name']];
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return "'%s' type declaration is not present in PHP version %s or earlier";
}
}
@@ -0,0 +1,194 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect and verify the use of return type declarations in function declarations.
*
* Return type declarations are available since PHP 7.0.
* - Since PHP 7.1, the `iterable` and `void` pseudo-types are available.
* - Since PHP 7.2, the generic `object` type is available.
*
* PHP version 7.0+
*
* @link https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.return-type-declarations
* @link https://www.php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration
* @link https://wiki.php.net/rfc/return_types
* @link https://wiki.php.net/rfc/iterable
* @link https://wiki.php.net/rfc/void_return_type
* @link https://wiki.php.net/rfc/object-typehint
*
* @since 7.0.0
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class.
* @since 7.1.2 Renamed from `NewScalarReturnTypeDeclarationsSniff` to `NewReturnTypeDeclarationsSniff`.
*/
class NewReturnTypeDeclarationsSniff extends AbstractNewFeatureSniff
{
/**
* A list of new types
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the keyword appears.
*
* @since 7.0.0
*
* @var array(string => array(string => bool))
*/
protected $newTypes = array(
'int' => array(
'5.6' => false,
'7.0' => true,
),
'float' => array(
'5.6' => false,
'7.0' => true,
),
'bool' => array(
'5.6' => false,
'7.0' => true,
),
'string' => array(
'5.6' => false,
'7.0' => true,
),
'array' => array(
'5.6' => false,
'7.0' => true,
),
'callable' => array(
'5.6' => false,
'7.0' => true,
),
'parent' => array(
'5.6' => false,
'7.0' => true,
),
'self' => array(
'5.6' => false,
'7.0' => true,
),
'Class name' => array(
'5.6' => false,
'7.0' => true,
),
'iterable' => array(
'7.0' => false,
'7.1' => true,
),
'void' => array(
'7.0' => false,
'7.1' => true,
),
'object' => array(
'7.1' => false,
'7.2' => true,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
* @since 7.1.2 Now also checks based on the function and closure keywords.
*
* @return array
*/
public function register()
{
$tokens = array(
\T_FUNCTION,
\T_CLOSURE,
);
if (\defined('T_RETURN_TYPE')) {
$tokens[] = \T_RETURN_TYPE;
}
return $tokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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();
// Deal with older PHPCS version which don't recognize return type hints
// as well as newer PHPCS versions (3.3.0+) where the tokenization has changed.
if ($tokens[$stackPtr]['code'] === \T_FUNCTION || $tokens[$stackPtr]['code'] === \T_CLOSURE) {
$returnTypeHint = $this->getReturnTypeHintToken($phpcsFile, $stackPtr);
if ($returnTypeHint !== false) {
$stackPtr = $returnTypeHint;
}
}
if (isset($this->newTypes[$tokens[$stackPtr]['content']]) === true) {
$itemInfo = array(
'name' => $tokens[$stackPtr]['content'],
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
// Handle class name based return types.
elseif ($tokens[$stackPtr]['code'] === \T_STRING
|| (\defined('T_RETURN_TYPE') && $tokens[$stackPtr]['code'] === \T_RETURN_TYPE)
) {
$itemInfo = array(
'name' => 'Class name',
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newTypes[$itemInfo['name']];
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return '%s return type is not present in PHP version %s or earlier';
}
}
@@ -0,0 +1,216 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionDeclarations;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Verifies the use of the correct visibility and static properties of magic methods.
*
* The requirements have always existed, but as of PHP 5.3, a warning will be thrown
* when magic methods have the wrong modifiers.
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/language.oop5.magic.php
*
* @since 5.5
* @since 5.6 Now extends the base `Sniff` class.
*/
class NonStaticMagicMethodsSniff extends Sniff
{
/**
* A list of PHP magic methods and their visibility and static requirements.
*
* Method names in the array should be all *lowercase*.
* Visibility can be either 'public', 'protected' or 'private'.
* Static can be either 'true' - *must* be static, or 'false' - *must* be non-static.
* When a method does not have a specific requirement for either visibility or static,
* do *not* add the key.
*
* @since 5.5
* @since 5.6 The array format has changed to allow the sniff to also verify the
* use of the correct visibility for a magic method.
*
* @var array(string)
*/
protected $magicMethods = array(
'__construct' => array(
'static' => false,
),
'__destruct' => array(
'visibility' => 'public',
'static' => false,
),
'__clone' => array(
'static' => false,
),
'__get' => array(
'visibility' => 'public',
'static' => false,
),
'__set' => array(
'visibility' => 'public',
'static' => false,
),
'__isset' => array(
'visibility' => 'public',
'static' => false,
),
'__unset' => array(
'visibility' => 'public',
'static' => false,
),
'__call' => array(
'visibility' => 'public',
'static' => false,
),
'__callstatic' => array(
'visibility' => 'public',
'static' => true,
),
'__sleep' => array(
'visibility' => 'public',
),
'__tostring' => array(
'visibility' => 'public',
),
'__set_state' => array(
'visibility' => 'public',
'static' => true,
),
'__debuginfo' => array(
'visibility' => 'public',
'static' => false,
),
'__invoke' => array(
'visibility' => 'public',
'static' => false,
),
'__serialize' => array(
'visibility' => 'public',
'static' => false,
),
'__unserialize' => array(
'visibility' => 'public',
'static' => false,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
* @since 5.6 Now also checks traits.
* @since 7.1.4 Now also checks anonymous classes.
*
* @return array
*/
public function register()
{
$targets = array(
\T_CLASS,
\T_INTERFACE,
\T_TRAIT,
);
if (\defined('T_ANON_CLASS')) {
$targets[] = \T_ANON_CLASS;
}
return $targets;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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)
{
// Should be removed, the requirement was previously also there, 5.3 just started throwing a warning about it.
if ($this->supportsAbove('5.3') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['scope_closer']) === false) {
return;
}
$classScopeCloser = $tokens[$stackPtr]['scope_closer'];
$functionPtr = $stackPtr;
// Find all the functions in this class or interface.
while (($functionToken = $phpcsFile->findNext(\T_FUNCTION, $functionPtr, $classScopeCloser)) !== false) {
/*
* Get the scope closer for this function in order to know how
* to advance to the next function.
* If no body of function (e.g. for interfaces), there is
* no closing curly brace; advance the pointer differently.
*/
if (isset($tokens[$functionToken]['scope_closer'])) {
$scopeCloser = $tokens[$functionToken]['scope_closer'];
} else {
$scopeCloser = ($functionToken + 1);
}
$methodName = $phpcsFile->getDeclarationName($functionToken);
$methodNameLc = strtolower($methodName);
if (isset($this->magicMethods[$methodNameLc]) === false) {
$functionPtr = $scopeCloser;
continue;
}
$methodProperties = $phpcsFile->getMethodProperties($functionToken);
$errorCodeBase = $this->stringToErrorCode($methodNameLc);
if (isset($this->magicMethods[$methodNameLc]['visibility']) && $this->magicMethods[$methodNameLc]['visibility'] !== $methodProperties['scope']) {
$error = 'Visibility for magic method %s must be %s. Found: %s';
$errorCode = $errorCodeBase . 'MethodVisibility';
$data = array(
$methodName,
$this->magicMethods[$methodNameLc]['visibility'],
$methodProperties['scope'],
);
$phpcsFile->addError($error, $functionToken, $errorCode, $data);
}
if (isset($this->magicMethods[$methodNameLc]['static']) && $this->magicMethods[$methodNameLc]['static'] !== $methodProperties['is_static']) {
$error = 'Magic method %s cannot be defined as static.';
$errorCode = $errorCodeBase . 'MethodStatic';
$data = array($methodName);
if ($this->magicMethods[$methodNameLc]['static'] === true) {
$error = 'Magic method %s must be defined as static.';
$errorCode = $errorCodeBase . 'MethodNonStatic';
}
$phpcsFile->addError($error, $functionToken, $errorCode, $data);
}
// Advance to next function.
$functionPtr = $scopeCloser;
}
}
}
@@ -0,0 +1,231 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionNameRestrictions;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Warns for non-magic behaviour of magic methods prior to becoming magic.
*
* PHP version 5.0+
*
* @link https://www.php.net/manual/en/language.oop5.magic.php
* @link https://wiki.php.net/rfc/closures#additional_goodyinvoke
* @link https://wiki.php.net/rfc/debug-info
*
* @since 7.0.4
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class.
*/
class NewMagicMethodsSniff extends AbstractNewFeatureSniff
{
/**
* A list of new magic methods, not considered magic in older versions.
*
* Method names in the array should be all *lowercase*.
* The array lists : version number with false (not magic) or true (magic).
* If's sufficient to list the first version where the method became magic.
*
* @since 7.0.4
*
* @var array(string => array(string => bool|string))
*/
protected $newMagicMethods = array(
'__construct' => array(
'4.4' => false,
'5.0' => true,
),
'__destruct' => array(
'4.4' => false,
'5.0' => true,
),
'__get' => array(
'4.4' => false,
'5.0' => true,
),
'__isset' => array(
'5.0' => false,
'5.1' => true,
),
'__unset' => array(
'5.0' => false,
'5.1' => true,
),
'__set_state' => array(
'5.0' => false,
'5.1' => true,
),
'__callstatic' => array(
'5.2' => false,
'5.3' => true,
),
'__invoke' => array(
'5.2' => false,
'5.3' => true,
),
'__debuginfo' => array(
'5.5' => false,
'5.6' => true,
),
// Special case - only became properly magical in 5.2.0,
// before that it was only called for echo and print.
'__tostring' => array(
'5.1' => false,
'5.2' => true,
'message' => 'The method %s() was not truly magical in PHP version %s and earlier. The associated magic functionality will only be called when directly combined with echo or print.',
),
'__serialize' => array(
'7.3' => false,
'7.4' => true,
),
'__unserialize' => array(
'7.3' => false,
'7.4' => true,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.4
*
* @return array
*/
public function register()
{
return array(\T_FUNCTION);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.4
*
* @param \PHP_CodeSniffer_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)
{
$functionName = $phpcsFile->getDeclarationName($stackPtr);
$functionNameLc = strtolower($functionName);
if (isset($this->newMagicMethods[$functionNameLc]) === false) {
return;
}
if ($this->inClassScope($phpcsFile, $stackPtr, false) === false) {
return;
}
$itemInfo = array(
'name' => $functionName,
'nameLc' => $functionNameLc,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newMagicMethods[$itemInfo['nameLc']];
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array('message');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
$errorInfo['error'] = false; // Warning, not error.
$errorInfo['message'] = '';
if (empty($itemArray['message']) === false) {
$errorInfo['message'] = $itemArray['message'];
}
return $errorInfo;
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'The method %s() was not magical in PHP version %s and earlier. The associated magic functionality will not be invoked.';
}
/**
* Allow for concrete child classes to filter the error message before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param string $error The error message which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return string
*/
protected function filterErrorMsg($error, array $itemInfo, array $errorInfo)
{
if ($errorInfo['message'] !== '') {
$error = $errorInfo['message'];
}
return $error;
}
}
@@ -0,0 +1,92 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionNameRestrictions;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect declaration of the magic `__autoload()` method.
*
* This method has been deprecated in PHP 7.2 in favour of `spl_autoload_register()`.
*
* PHP version 7.2
*
* @link https://www.php.net/manual/en/migration72.deprecated.php#migration72.deprecated.__autoload-method
* @link https://wiki.php.net/rfc/deprecations_php_7_2#autoload
* @link https://www.php.net/manual/en/function.autoload.php
*
* @since 8.1.0
* @since 9.0.0 Renamed from `DeprecatedMagicAutoloadSniff` to `RemovedMagicAutoloadSniff`.
*/
class RemovedMagicAutoloadSniff extends Sniff
{
/**
* Scopes to look for when testing using validDirectScope.
*
* @since 8.1.0
*
* @var array
*/
private $checkForScopes = array(
'T_CLASS' => true,
'T_ANON_CLASS' => true,
'T_INTERFACE' => true,
'T_TRAIT' => true,
'T_NAMESPACE' => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.1.0
*
* @return array
*/
public function register()
{
return array(\T_FUNCTION);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 8.1.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.2') === false) {
return;
}
$funcName = $phpcsFile->getDeclarationName($stackPtr);
if (strtolower($funcName) !== '__autoload') {
return;
}
if ($this->validDirectScope($phpcsFile, $stackPtr, $this->checkForScopes) !== false) {
return;
}
if ($this->determineNamespace($phpcsFile, $stackPtr) !== '') {
return;
}
$phpcsFile->addWarning('Use of __autoload() function is deprecated since PHP 7.2', $stackPtr, 'Found');
}
}
@@ -0,0 +1,101 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionNameRestrictions;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect declaration of a namespaced function called `assert()`.
*
* As of PHP 7.3, a compile-time deprecation warning will be thrown when a function
* called `assert()` is declared. In PHP 8 this will become a compile-error.
*
* Methods are unaffected.
* Global, non-namespaced, `assert()` function declarations were always a fatal
* "function already declared" error, so not the concern of this sniff.
*
* PHP version 7.3
*
* @link https://www.php.net/manual/en/migration73.deprecated.php#migration73.deprecated.core.assert
* @link https://wiki.php.net/rfc/deprecations_php_7_3#defining_a_free-standing_assert_function
* @link https://www.php.net/manual/en/function.assert.php
*
* @since 9.0.0
*/
class RemovedNamespacedAssertSniff extends Sniff
{
/**
* Scopes in which an `assert` function can be declared without issue.
*
* @since 9.0.0
*
* @var array
*/
private $scopes = array(
\T_CLASS,
\T_INTERFACE,
\T_TRAIT,
\T_CLOSURE,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.0.0
*
* @return array
*/
public function register()
{
// Enrich the scopes list.
if (\defined('T_ANON_CLASS')) {
$this->scopes[] = \T_ANON_CLASS;
}
return array(\T_FUNCTION);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.3') === false) {
return;
}
$funcName = $phpcsFile->getDeclarationName($stackPtr);
if (strtolower($funcName) !== 'assert') {
return;
}
if ($phpcsFile->hasCondition($stackPtr, $this->scopes) === true) {
return;
}
if ($this->determineNamespace($phpcsFile, $stackPtr) === '') {
// Not a namespaced function declaration. This may be a parse error, but not our concern.
return;
}
$phpcsFile->addWarning('Declaring a free-standing function called assert() is deprecated since PHP 7.3.', $stackPtr, 'Found');
}
}
@@ -0,0 +1,158 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionNameRestrictions;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect declarations of PHP 4 style constructors which are deprecated as of PHP 7.0.0.
*
* PHP 4 style constructors - methods that have the same name as the class they are defined in -
* are deprecated as of PHP 7.0.0, and will be removed in the future.
* PHP 7 will emit `E_DEPRECATED` if a PHP 4 constructor is the only constructor defined
* within a class. Classes that implement a `__construct()` method are unaffected.
*
* Note: Methods with the same name as the class they are defined in _within a namespace_
* are not recognized as constructors anyway and therefore outside the scope of this sniff.
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/migration70.deprecated.php#migration70.deprecated.php4-constructors
* @link https://wiki.php.net/rfc/remove_php4_constructors
* @link https://www.php.net/manual/en/language.oop5.decon.php
*
* @since 7.0.0
* @since 7.0.8 This sniff now throws a warning instead of an error as the functionality is
* only deprecated (for now).
* @since 9.0.0 Renamed from `DeprecatedPHP4StyleConstructorsSniff` to `RemovedPHP4StyleConstructorsSniff`.
*/
class RemovedPHP4StyleConstructorsSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
*
* @return array
*/
public function register()
{
return array(
\T_CLASS,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
* @since 7.0.8 The message is downgraded from error to warning as - for now - support
* for PHP4-style constructors is just deprecated, not yet removed.
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
if ($this->determineNamespace($phpcsFile, $stackPtr) !== '') {
/*
* Namespaced methods with the same name as the class are treated as
* regular methods, so we can bow out if we're in a namespace.
*
* Note: the exception to this is PHP 5.3.0-5.3.2. This is currently
* not dealt with.
*/
return;
}
$tokens = $phpcsFile->getTokens();
$class = $tokens[$stackPtr];
if (isset($class['scope_closer']) === false) {
return;
}
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_STRING) {
// Anonymous class in combination with PHPCS 2.3.x.
return;
}
$scopeCloser = $class['scope_closer'];
$className = $tokens[$nextNonEmpty]['content'];
if (empty($className) || \is_string($className) === false) {
return;
}
$nextFunc = $stackPtr;
$classNameLc = strtolower($className);
$newConstructorFound = false;
$oldConstructorFound = false;
$oldConstructorPos = -1;
while (($nextFunc = $phpcsFile->findNext(array(\T_FUNCTION, \T_DOC_COMMENT_OPEN_TAG), ($nextFunc + 1), $scopeCloser)) !== false) {
// Skip over docblocks.
if ($tokens[$nextFunc]['code'] === \T_DOC_COMMENT_OPEN_TAG) {
$nextFunc = $tokens[$nextFunc]['comment_closer'];
continue;
}
$functionScopeCloser = $nextFunc;
if (isset($tokens[$nextFunc]['scope_closer'])) {
// Normal (non-interface, non-abstract) method.
$functionScopeCloser = $tokens[$nextFunc]['scope_closer'];
}
$funcName = $phpcsFile->getDeclarationName($nextFunc);
if (empty($funcName) || \is_string($funcName) === false) {
$nextFunc = $functionScopeCloser;
continue;
}
$funcNameLc = strtolower($funcName);
if ($funcNameLc === '__construct') {
$newConstructorFound = true;
}
if ($funcNameLc === $classNameLc) {
$oldConstructorFound = true;
$oldConstructorPos = $nextFunc;
}
// If both have been found, no need to continue looping through the functions.
if ($newConstructorFound === true && $oldConstructorFound === true) {
break;
}
$nextFunc = $functionScopeCloser;
}
if ($newConstructorFound === false && $oldConstructorFound === true) {
$phpcsFile->addWarning(
'Use of deprecated PHP4 style class constructor is not supported since PHP 7.',
$oldConstructorPos,
'Found'
);
}
}
}
@@ -0,0 +1,205 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionNameRestrictions;
use Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff as PHPCS_CamelCapsFunctionNameSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Standards_AbstractScopeSniff as PHPCS_AbstractScopeSniff;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* All function and method names starting with double underscore are reserved by PHP.
*
* PHP version All
*
* {@internal Extends an upstream sniff to benefit from the properties contained therein.
* The properties are lists of valid PHP magic function and method names, which
* should be ignored for the purposes of this sniff.
* As this sniff is not PHP version specific, we don't need access to the utility
* methods in the PHPCompatibility\Sniff, so extending the upstream sniff is fine.
* As the upstream sniff checks the same (and more, but we don't need the rest),
* the logic in this sniff is largely the same as used upstream.
* Extending the upstream sniff instead of including it via the ruleset, however,
* prevents hard to debug issues of errors not being reported from the upstream sniff
* if this library is used in combination with other rulesets.}
*
* @link https://www.php.net/manual/en/language.oop5.magic.php
*
* @since 8.2.0 This was previously, since 7.0.3, checked by the upstream sniff.
* @since 9.3.2 The sniff will now ignore functions marked as `@deprecated` by design.
*/
class ReservedFunctionNamesSniff extends PHPCS_CamelCapsFunctionNameSniff
{
/**
* Overload the constructor to work round various PHPCS cross-version compatibility issues.
*
* @since 8.2.0
*/
public function __construct()
{
$scopeTokens = array(\T_CLASS, \T_INTERFACE, \T_TRAIT);
if (\defined('T_ANON_CLASS')) {
$scopeTokens[] = \T_ANON_CLASS;
}
// Call the grand-parent constructor directly.
PHPCS_AbstractScopeSniff::__construct($scopeTokens, array(\T_FUNCTION), true);
// Make sure debuginfo is included in the array. Upstream only includes it since 2.5.1.
$this->magicMethods['debuginfo'] = true;
}
/**
* Processes the tokens within the scope.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being processed.
* @param int $stackPtr The position where this token was
* found.
* @param int $currScope The position of the current scope.
*
* @return void
*/
protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope)
{
$tokens = $phpcsFile->getTokens();
/*
* Determine if this is a function which needs to be examined.
* The `processTokenWithinScope()` is called for each valid scope a method is in,
* so for nested classes, we need to make sure we only examine the token for
* the lowest level valid scope.
*/
$conditions = $tokens[$stackPtr]['conditions'];
end($conditions);
$deepestScope = key($conditions);
if ($deepestScope !== $currScope) {
return;
}
if ($this->isFunctionDeprecated($phpcsFile, $stackPtr) === true) {
/*
* Deprecated functions don't have to comply with the naming conventions,
* otherwise functions deprecated in favour of a function with a compliant
* name would still trigger an error.
*/
return;
}
$methodName = $phpcsFile->getDeclarationName($stackPtr);
if ($methodName === null) {
// Ignore closures.
return;
}
// Is this a magic method. i.e., is prefixed with "__" ?
if (preg_match('|^__[^_]|', $methodName) > 0) {
$magicPart = strtolower(substr($methodName, 2));
if (isset($this->magicMethods[$magicPart]) === false
&& isset($this->methodsDoubleUnderscore[$magicPart]) === false
) {
$className = '[anonymous class]';
$scopeNextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($currScope + 1), null, true);
if ($scopeNextNonEmpty !== false && $tokens[$scopeNextNonEmpty]['code'] === \T_STRING) {
$className = $tokens[$scopeNextNonEmpty]['content'];
}
$phpcsFile->addWarning(
'Method name "%s" is discouraged; PHP has reserved all method names with a double underscore prefix for future use.',
$stackPtr,
'MethodDoubleUnderscore',
array($className . '::' . $methodName)
);
}
}
}
/**
* Processes the tokens outside the scope.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being processed.
* @param int $stackPtr The position where this token was
* found.
*
* @return void
*/
protected function processTokenOutsideScope(File $phpcsFile, $stackPtr)
{
if ($this->isFunctionDeprecated($phpcsFile, $stackPtr) === true) {
/*
* Deprecated functions don't have to comply with the naming conventions,
* otherwise functions deprecated in favour of a function with a compliant
* name would still trigger an error.
*/
return;
}
$functionName = $phpcsFile->getDeclarationName($stackPtr);
if ($functionName === null) {
// Ignore closures.
return;
}
// Is this a magic function. i.e., it is prefixed with "__".
if (preg_match('|^__[^_]|', $functionName) > 0) {
$magicPart = strtolower(substr($functionName, 2));
if (isset($this->magicFunctions[$magicPart]) === false) {
$phpcsFile->addWarning(
'Function name "%s" is discouraged; PHP has reserved all method names with a double underscore prefix for future use.',
$stackPtr,
'FunctionDoubleUnderscore',
array($functionName)
);
}
}
}
/**
* Check whether a function has been marked as deprecated via a @deprecated tag
* in the function docblock.
*
* @since 9.3.2
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of a T_FUNCTION
* token in the stack.
*
* @return bool
*/
private function isFunctionDeprecated(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$find = Tokens::$methodPrefixes;
$find[] = \T_WHITESPACE;
$commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);
if ($tokens[$commentEnd]['code'] !== \T_DOC_COMMENT_CLOSE_TAG) {
// Function doesn't have a doc comment or is using the wrong type of comment.
return false;
}
$commentStart = $tokens[$commentEnd]['comment_opener'];
foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
if ($tokens[$tag]['content'] === '@deprecated') {
return true;
}
}
return false;
}
}
@@ -0,0 +1,455 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionUse;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Functions inspecting function arguments report the current parameter value
* instead of the original since PHP 7.0.
*
* `func_get_arg()`, `func_get_args()`, `debug_backtrace()` and exception backtraces
* will no longer report the original parameter value as was passed to the function,
* but will instead provide the current value (which might have been modified).
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.other.func-parameter-modified
*
* @since 9.1.0
*/
class ArgumentFunctionsReportCurrentValueSniff extends Sniff
{
/**
* A list of functions that, when called, can behave differently in PHP 7
* when dealing with parameters of the function they're called in.
*
* @since 9.1.0
*
* @var array
*/
protected $changedFunctions = array(
'func_get_arg' => true,
'func_get_args' => true,
'debug_backtrace' => true,
'debug_print_backtrace' => true,
);
/**
* Tokens to look out for to allow us to skip past nested scoped structures.
*
* @since 9.1.0
*
* @var array
*/
private $skipPastNested = array(
'T_CLASS' => true,
'T_ANON_CLASS' => true,
'T_INTERFACE' => true,
'T_TRAIT' => true,
'T_FUNCTION' => true,
'T_CLOSURE' => true,
);
/**
* List of tokens which when they preceed a T_STRING *within a function* indicate
* this is not a call to a PHP native function.
*
* This list already takes into account that nested scoped structures are being
* skipped over, so doesn't check for those again.
* Similarly, as constants won't have parentheses, those don't need to be checked
* for either.
*
* @since 9.1.0
*
* @var array
*/
private $noneFunctionCallIndicators = array(
\T_DOUBLE_COLON => true,
\T_OBJECT_OPERATOR => true,
);
/**
* The tokens for variable incrementing/decrementing.
*
* @since 9.1.0
*
* @var array
*/
private $plusPlusMinusMinus = array(
\T_DEC => true,
\T_INC => true,
);
/**
* Tokens to ignore when determining the start of a statement.
*
* @since 9.1.0
*
* @var array
*/
private $ignoreForStartOfStatement = array(
\T_COMMA,
\T_DOUBLE_ARROW,
\T_OPEN_SQUARE_BRACKET,
\T_OPEN_PARENTHESIS,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.1.0
*
* @return array
*/
public function register()
{
return array(
\T_FUNCTION,
\T_CLOSURE,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.1.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
// Abstract function, interface function, live coding or parse error.
return;
}
$scopeOpener = $tokens[$stackPtr]['scope_opener'];
$scopeCloser = $tokens[$stackPtr]['scope_closer'];
// Does the function declaration have parameters ?
$params = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
if (empty($params)) {
// No named arguments found, so no risk of them being changed.
return;
}
$paramNames = array();
foreach ($params as $param) {
$paramNames[] = $param['name'];
}
for ($i = ($scopeOpener + 1); $i < $scopeCloser; $i++) {
if (isset($this->skipPastNested[$tokens[$i]['type']]) && isset($tokens[$i]['scope_closer'])) {
// Skip past nested structures.
$i = $tokens[$i]['scope_closer'];
continue;
}
if ($tokens[$i]['code'] !== \T_STRING) {
continue;
}
$foundFunctionName = strtolower($tokens[$i]['content']);
if (isset($this->changedFunctions[$foundFunctionName]) === false) {
// Not one of the target functions.
continue;
}
/*
* Ok, so is this really a function call to one of the PHP native functions ?
*/
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
if ($next === false || $tokens[$next]['code'] !== \T_OPEN_PARENTHESIS) {
// Live coding, parse error or not a function call.
continue;
}
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), null, true);
if ($prev !== false) {
if (isset($this->noneFunctionCallIndicators[$tokens[$prev]['code']])) {
continue;
}
// Check for namespaced functions, ie: \foo\bar() not \bar().
if ($tokens[ $prev ]['code'] === \T_NS_SEPARATOR) {
$pprev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true);
if ($pprev !== false && $tokens[ $pprev ]['code'] === \T_STRING) {
continue;
}
}
}
/*
* Address some special cases.
*/
if ($foundFunctionName !== 'func_get_args') {
$paramOne = $this->getFunctionCallParameter($phpcsFile, $i, 1);
if ($paramOne !== false) {
switch ($foundFunctionName) {
/*
* Check if `debug_(print_)backtrace()` is called with the
* `DEBUG_BACKTRACE_IGNORE_ARGS` option.
*/
case 'debug_backtrace':
case 'debug_print_backtrace':
$hasIgnoreArgs = $phpcsFile->findNext(
\T_STRING,
$paramOne['start'],
($paramOne['end'] + 1),
false,
'DEBUG_BACKTRACE_IGNORE_ARGS'
);
if ($hasIgnoreArgs !== false) {
// Debug_backtrace() called with ignore args option.
continue 2;
}
break;
/*
* Collect the necessary information to only throw a notice if the argument
* touched/changed is in line with the passed $arg_num.
*
* Also, we can ignore `func_get_arg()` if the argument offset passed is
* higher than the number of named parameters.
*
* {@internal Note: This does not take calculations into account!
* Should be exceptionally rare and can - if needs be - be addressed at a later stage.}
*/
case 'func_get_arg':
$number = $phpcsFile->findNext(\T_LNUMBER, $paramOne['start'], ($paramOne['end'] + 1));
if ($number !== false) {
$argNumber = $tokens[$number]['content'];
if (isset($paramNames[$argNumber]) === false) {
// Requesting a non-named additional parameter. Ignore.
continue 2;
}
}
break;
}
}
} else {
/*
* Check if the call to func_get_args() happens to be in an array_slice() or
* array_splice() with an $offset higher than the number of named parameters.
* In that case, we can ignore it.
*
* {@internal Note: This does not take offset calculations into account!
* Should be exceptionally rare and can - if needs be - be addressed at a later stage.}
*/
if ($prev !== false && $tokens[$prev]['code'] === \T_OPEN_PARENTHESIS) {
$maybeFunctionCall = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true);
if ($maybeFunctionCall !== false
&& $tokens[$maybeFunctionCall]['code'] === \T_STRING
&& ($tokens[$maybeFunctionCall]['content'] === 'array_slice'
|| $tokens[$maybeFunctionCall]['content'] === 'array_splice')
) {
$parentFuncParamTwo = $this->getFunctionCallParameter($phpcsFile, $maybeFunctionCall, 2);
$number = $phpcsFile->findNext(
\T_LNUMBER,
$parentFuncParamTwo['start'],
($parentFuncParamTwo['end'] + 1)
);
if ($number !== false && isset($paramNames[$tokens[$number]['content']]) === false) {
// Requesting non-named additional parameters. Ignore.
continue ;
}
// Slice starts at a named argument, but we know which params are being accessed.
$paramNamesSubset = \array_slice($paramNames, $tokens[$number]['content']);
}
}
}
/*
* For debug_backtrace(), check if the result is being dereferenced and if so,
* whether the `args` index is used.
* I.e. whether `$index` in `debug_backtrace()[$stackFrame][$index]` is a string
* with the content `args`.
*
* Note: We already know that $next is the open parenthesis of the function call.
*/
if ($foundFunctionName === 'debug_backtrace' && isset($tokens[$next]['parenthesis_closer'])) {
$afterParenthesis = $phpcsFile->findNext(
Tokens::$emptyTokens,
($tokens[$next]['parenthesis_closer'] + 1),
null,
true
);
if ($tokens[$afterParenthesis]['code'] === \T_OPEN_SQUARE_BRACKET
&& isset($tokens[$afterParenthesis]['bracket_closer'])
) {
$afterStackFrame = $phpcsFile->findNext(
Tokens::$emptyTokens,
($tokens[$afterParenthesis]['bracket_closer'] + 1),
null,
true
);
if ($tokens[$afterStackFrame]['code'] === \T_OPEN_SQUARE_BRACKET
&& isset($tokens[$afterStackFrame]['bracket_closer'])
) {
$arrayIndex = $phpcsFile->findNext(
\T_CONSTANT_ENCAPSED_STRING,
($afterStackFrame + 1),
$tokens[$afterStackFrame]['bracket_closer']
);
if ($arrayIndex !== false && $this->stripQuotes($tokens[$arrayIndex]['content']) !== 'args') {
continue;
}
}
}
}
/*
* Only check for variables before the start of the statement to
* prevent false positives on the return value of the function call
* being assigned to one of the parameters, i.e.:
* `$param = func_get_args();`.
*/
$startOfStatement = PHPCSHelper::findStartOfStatement($phpcsFile, $i, $this->ignoreForStartOfStatement);
/*
* Ok, so we've found one of the target functions in the right scope.
* Now, let's check if any of the passed parameters were touched.
*/
$scanResult = 'clean';
for ($j = ($scopeOpener + 1); $j < $startOfStatement; $j++) {
if (isset($this->skipPastNested[$tokens[$j]['type']])
&& isset($tokens[$j]['scope_closer'])
) {
// Skip past nested structures.
$j = $tokens[$j]['scope_closer'];
continue;
}
if ($tokens[$j]['code'] !== \T_VARIABLE) {
continue;
}
if ($foundFunctionName === 'func_get_arg' && isset($argNumber)) {
if (isset($paramNames[$argNumber])
&& $tokens[$j]['content'] !== $paramNames[$argNumber]
) {
// Different param than the one requested by func_get_arg().
continue;
}
} elseif ($foundFunctionName === 'func_get_args' && isset($paramNamesSubset)) {
if (\in_array($tokens[$j]['content'], $paramNamesSubset, true) === false) {
// Different param than the ones requested by func_get_args().
continue;
}
} elseif (\in_array($tokens[$j]['content'], $paramNames, true) === false) {
// Variable is not one of the function parameters.
continue;
}
/*
* Ok, so we've found a variable which was passed as one of the parameters.
* Now, is this variable being changed, i.e. incremented, decremented or
* assigned something ?
*/
$scanResult = 'warning';
if (isset($variableToken) === false) {
$variableToken = $j;
}
$beforeVar = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($j - 1), null, true);
if ($beforeVar !== false && isset($this->plusPlusMinusMinus[$tokens[$beforeVar]['code']])) {
// Variable is being (pre-)incremented/decremented.
$scanResult = 'error';
$variableToken = $j;
break;
}
$afterVar = $phpcsFile->findNext(Tokens::$emptyTokens, ($j + 1), null, true);
if ($afterVar === false) {
// Shouldn't be possible, but just in case.
continue;
}
if (isset($this->plusPlusMinusMinus[$tokens[$afterVar]['code']])) {
// Variable is being (post-)incremented/decremented.
$scanResult = 'error';
$variableToken = $j;
break;
}
if ($tokens[$afterVar]['code'] === \T_OPEN_SQUARE_BRACKET
&& isset($tokens[$afterVar]['bracket_closer'])
) {
// Skip past array access on the variable.
while (($afterVar = $phpcsFile->findNext(Tokens::$emptyTokens, ($tokens[$afterVar]['bracket_closer'] + 1), null, true)) !== false) {
if ($tokens[$afterVar]['code'] !== \T_OPEN_SQUARE_BRACKET
|| isset($tokens[$afterVar]['bracket_closer']) === false
) {
break;
}
}
}
if ($afterVar !== false
&& isset(Tokens::$assignmentTokens[$tokens[$afterVar]['code']])
) {
// Variable is being assigned something.
$scanResult = 'error';
$variableToken = $j;
break;
}
}
unset($argNumber, $paramNamesSubset);
if ($scanResult === 'clean') {
continue;
}
$error = 'Since PHP 7.0, functions inspecting arguments, like %1$s(), no longer report the original value as passed to a parameter, but will instead provide the current value. The parameter "%2$s" was %4$s on line %3$s.';
$data = array(
$foundFunctionName,
$tokens[$variableToken]['content'],
$tokens[$variableToken]['line'],
);
if ($scanResult === 'error') {
$data[] = 'changed';
$phpcsFile->addError($error, $i, 'Changed', $data);
} elseif ($scanResult === 'warning') {
$data[] = 'used, and possibly changed (by reference),';
$phpcsFile->addWarning($error, $i, 'NeedsInspection', $data);
}
unset($variableToken);
}
}
}
@@ -0,0 +1,169 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionUse;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect usage of `func_get_args()`, `func_get_arg()` and `func_num_args()` in invalid context.
*
* Checks for:
* - Prior to PHP 5.3, these functions could not be used as a function call parameter.
* - Calling these functions from the outermost scope of a file which has been included by
* calling `include` or `require` from within a function in the calling file, worked
* prior to PHP 5.3. As of PHP 5.3, this will generate a warning and will always return false/-1.
* If the file was called directly or included in the global scope, calls to these
* functions would already generate a warning prior to PHP 5.3.
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/migration53.incompatible.php
*
* @since 8.2.0
*/
class ArgumentFunctionsUsageSniff extends Sniff
{
/**
* The target functions for this sniff.
*
* @since 8.2.0
*
* @var array
*/
protected $targetFunctions = array(
'func_get_args' => true,
'func_get_arg' => true,
'func_num_args' => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.2.0
*
* @return array
*/
public function register()
{
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_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();
$functionLc = strtolower($tokens[$stackPtr]['content']);
if (isset($this->targetFunctions[$functionLc]) === false) {
return;
}
// Next non-empty token should be the open parenthesis.
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
return;
}
$ignore = array(
\T_DOUBLE_COLON => true,
\T_OBJECT_OPERATOR => true,
\T_FUNCTION => true,
\T_NEW => true,
);
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if (isset($ignore[$tokens[$prevNonEmpty]['code']]) === true) {
// Not a call to a PHP function.
return;
} elseif ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR && $tokens[$prevNonEmpty - 1]['code'] === \T_STRING) {
// Namespaced function.
return;
}
$data = $tokens[$stackPtr]['content'];
/*
* Check for use of the functions in the global scope.
*
* As PHPCS can not determine whether a file is included from within a function in
* another file, so always throw a warning/error.
*/
if ($phpcsFile->hasCondition($stackPtr, array(\T_FUNCTION, \T_CLOSURE)) === false) {
$isError = false;
$message = 'Use of %s() outside of a user-defined function is only supported if the file is included from within a user-defined function in another file prior to PHP 5.3.';
if ($this->supportsAbove('5.3') === true) {
$isError = true;
$message .= ' As of PHP 5.3, it is no longer supported at all.';
}
$this->addMessage($phpcsFile, $message, $stackPtr, $isError, 'OutsideFunctionScope', $data);
}
/*
* Check for use of the functions as a parameter in a function call.
*/
if ($this->supportsBelow('5.2') === false) {
return;
}
if (isset($tokens[$stackPtr]['nested_parenthesis']) === false) {
return;
}
$throwError = false;
$closer = end($tokens[$stackPtr]['nested_parenthesis']);
if (isset($tokens[$closer]['parenthesis_owner'])
&& $tokens[$tokens[$closer]['parenthesis_owner']]['type'] === 'T_CLOSURE'
) {
$throwError = true;
} else {
$opener = key($tokens[$stackPtr]['nested_parenthesis']);
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true);
if ($tokens[$prevNonEmpty]['code'] !== \T_STRING) {
return;
}
$prevPrevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevNonEmpty - 1), null, true);
if ($tokens[$prevPrevNonEmpty]['code'] === \T_FUNCTION) {
return;
}
$throwError = true;
}
if ($throwError === false) {
return;
}
$phpcsFile->addError(
'%s() could not be used in parameter lists prior to PHP 5.3.',
$stackPtr,
'InParameterList',
$data
);
}
}
@@ -0,0 +1,173 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionUse;
use PHPCompatibility\Sniffs\FunctionUse\RequiredToOptionalFunctionParametersSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect missing required function parameters in calls to native PHP functions.
*
* Specifically when those function parameters used to be optional in older PHP versions.
*
* PHP version All
*
* @link https://www.php.net/manual/en/doc.changelog.php
*
* @since 8.1.0
* @since 9.0.0 Renamed from `OptionalRequiredFunctionParametersSniff` to `OptionalToRequiredFunctionParametersSniff`.
*/
class OptionalToRequiredFunctionParametersSniff extends RequiredToOptionalFunctionParametersSniff
{
/**
* A list of function parameters, which were optional in older versions and became required later on.
*
* The array lists : version number with true (required) and false (optional use deprecated).
*
* The index is the location of the parameter in the parameter list, starting at 0 !
* If's sufficient to list the last version in which the parameter was not yet required.
*
* @since 8.1.0
*
* @var array
*/
protected $functionParameters = array(
// Special case, the optional nature is not deprecated, but usage is recommended
// and leaving the parameter out will throw an E_NOTICE.
'crypt' => array(
1 => array(
'name' => 'salt',
'5.6' => 'recommended',
),
),
'parse_str' => array(
1 => array(
'name' => 'result',
'7.2' => false,
),
),
);
/**
* Determine whether an error/warning should be thrown for an item based on collected information.
*
* @since 8.1.0
*
* @param array $errorInfo Detail information about an item.
*
* @return bool
*/
protected function shouldThrowError(array $errorInfo)
{
return ($errorInfo['optionalDeprecated'] !== ''
|| $errorInfo['optionalRemoved'] !== ''
|| $errorInfo['optionalRecommended'] !== '');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 8.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = array(
'paramName' => '',
'optionalRecommended' => '',
'optionalDeprecated' => '',
'optionalRemoved' => '',
'error' => false,
);
$versionArray = $this->getVersionArray($itemArray);
if (empty($versionArray) === false) {
foreach ($versionArray as $version => $required) {
if ($this->supportsAbove($version) === true) {
if ($required === true && $errorInfo['optionalRemoved'] === '') {
$errorInfo['optionalRemoved'] = $version;
$errorInfo['error'] = true;
} elseif ($required === 'recommended' && $errorInfo['optionalRecommended'] === '') {
$errorInfo['optionalRecommended'] = $version;
} elseif ($errorInfo['optionalDeprecated'] === '') {
$errorInfo['optionalDeprecated'] = $version;
}
}
}
}
$errorInfo['paramName'] = $itemArray['name'];
return $errorInfo;
}
/**
* Generates the error or warning for this item.
*
* @since 8.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
* @param array $errorInfo Array with detail (version) information
* relevant to the item.
*
* @return void
*/
public function addError(File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo)
{
$error = 'The "%s" parameter for function %s() is missing. Passing this parameter is ';
if ($errorInfo['optionalRecommended'] === '') {
$error .= 'no longer optional. The optional nature of the parameter is ';
} else {
$error .= 'strongly recommended ';
}
$errorCode = $this->stringToErrorCode($itemInfo['name'] . '_' . $errorInfo['paramName']);
$data = array(
$errorInfo['paramName'],
$itemInfo['name'],
);
if ($errorInfo['optionalRecommended'] !== '') {
$error .= 'since PHP %s ';
$errorCode .= 'SoftRecommended';
$data[] = $errorInfo['optionalRecommended'];
} else {
if ($errorInfo['optionalDeprecated'] !== '') {
$error .= 'deprecated since PHP %s and ';
$errorCode .= 'SoftRequired';
$data[] = $errorInfo['optionalDeprecated'];
}
if ($errorInfo['optionalRemoved'] !== '') {
$error .= 'removed since PHP %s and ';
$errorCode .= 'HardRequired';
$data[] = $errorInfo['optionalRemoved'];
}
// Remove the last 'and' from the message.
$error = substr($error, 0, (\strlen($error) - 5));
}
$this->addMessage($phpcsFile, $error, $stackPtr, $errorInfo['error'], $errorCode, $data);
}
}
@@ -0,0 +1,292 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionUse;
use PHPCompatibility\AbstractRemovedFeatureSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect use of deprecated/removed function parameters in calls to native PHP functions.
*
* PHP version All
*
* @link https://www.php.net/manual/en/doc.changelog.php
*
* @since 7.0.0
* @since 7.1.0 Now extends the `AbstractRemovedFeatureSniff` instead of the base `Sniff` class.
*/
class RemovedFunctionParametersSniff extends AbstractRemovedFeatureSniff
{
/**
* A list of removed function parameters, which were present in older versions.
*
* The array lists : version number with false (deprecated) and true (removed).
* The index is the location of the parameter in the parameter list, starting at 0 !
* If's sufficient to list the first version where the function parameter was deprecated/removed.
*
* The optional `callback` key can be used to pass a method name which should be called for an
* additional check. The method will be passed the parameter info and should return true
* if the notice should be thrown or false otherwise.
*
* @since 7.0.0
* @since 7.0.2 Visibility changed from `public` to `protected`.
* @since 9.3.0 Optional `callback` key.
*
* @var array
*/
protected $removedFunctionParameters = array(
'curl_version' => array(
0 => array(
'name' => 'age',
'7.4' => false,
'callback' => 'curlVersionInvalidValue',
),
),
'define' => array(
2 => array(
'name' => 'case_insensitive',
'7.3' => false, // Slated for removal in PHP 8.0.0.
),
),
'gmmktime' => array(
6 => array(
'name' => 'is_dst',
'5.1' => false,
'7.0' => true,
),
),
'ldap_first_attribute' => array(
2 => array(
'name' => 'ber_identifier',
'5.2.4' => true,
),
),
'ldap_next_attribute' => array(
2 => array(
'name' => 'ber_identifier',
'5.2.4' => true,
),
),
'mktime' => array(
6 => array(
'name' => 'is_dst',
'5.1' => false,
'7.0' => true,
),
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
*
* @return array
*/
public function register()
{
// Handle case-insensitivity of function names.
$this->removedFunctionParameters = $this->arrayKeysToLowercase($this->removedFunctionParameters);
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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();
$ignore = array(
\T_DOUBLE_COLON => true,
\T_OBJECT_OPERATOR => true,
\T_FUNCTION => true,
\T_CONST => true,
);
$prevToken = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
// Not a call to a PHP function.
return;
}
$function = $tokens[$stackPtr]['content'];
$functionLc = strtolower($function);
if (isset($this->removedFunctionParameters[$functionLc]) === false) {
return;
}
$parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
$parameterCount = \count($parameters);
if ($parameterCount === 0) {
return;
}
// If the parameter count returned > 0, we know there will be valid open parenthesis.
$openParenthesis = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
$parameterOffsetFound = $parameterCount - 1;
foreach ($this->removedFunctionParameters[$functionLc] as $offset => $parameterDetails) {
if ($offset <= $parameterOffsetFound) {
if (isset($parameterDetails['callback']) && method_exists($this, $parameterDetails['callback'])) {
if ($this->{$parameterDetails['callback']}($phpcsFile, $parameters[($offset + 1)]) === false) {
continue;
}
}
$itemInfo = array(
'name' => $function,
'nameLc' => $functionLc,
'offset' => $offset,
);
$this->handleFeature($phpcsFile, $openParenthesis, $itemInfo);
}
}
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->removedFunctionParameters[$itemInfo['nameLc']][$itemInfo['offset']];
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array('name', 'callback');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
$errorInfo['paramName'] = $itemArray['name'];
return $errorInfo;
}
/**
* Get the item name to be used for the creation of the error code.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
* @param array $errorInfo Detail information about an item.
*
* @return string
*/
protected function getItemName(array $itemInfo, array $errorInfo)
{
return $itemInfo['name'] . '_' . $errorInfo['paramName'];
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'The "%s" parameter for function %s() is ';
}
/**
* Filter the error data before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param array $data The error data array which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return array
*/
protected function filterErrorData(array $data, array $itemInfo, array $errorInfo)
{
array_shift($data);
array_unshift($data, $errorInfo['paramName'], $itemInfo['name']);
return $data;
}
/**
* Check whether curl_version() was passed the default CURLVERSION_NOW.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param array $parameter Parameter info array.
*
* @return bool True if the value was not CURLVERSION_NOW, false otherwise.
*/
protected function curlVersionInvalidValue(File $phpcsFile, array $parameter)
{
$tokens = $phpcsFile->getTokens();
$raw = '';
for ($i = $parameter['start']; $i <= $parameter['end']; $i++) {
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) {
continue;
}
$raw .= $tokens[$i]['content'];
}
if ($raw !== 'CURLVERSION_NOW'
&& $raw !== (string) \CURLVERSION_NOW
) {
return true;
}
return false;
}
}
@@ -0,0 +1,350 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\FunctionUse;
use PHPCompatibility\AbstractComplexVersionSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect missing required function parameters in calls to native PHP functions.
*
* Specifically when those function parameters are no longer required in more recent PHP versions.
*
* PHP version All
*
* @link https://www.php.net/manual/en/doc.changelog.php
*
* @since 7.0.3
* @since 7.1.0 Now extends the `AbstractComplexVersionSniff` instead of the base `Sniff` class.
* @since 9.0.0 Renamed from `RequiredOptionalFunctionParametersSniff` to `RequiredToOptionalFunctionParametersSniff`.
*/
class RequiredToOptionalFunctionParametersSniff extends AbstractComplexVersionSniff
{
/**
* A list of function parameters, which were required in older versions and became optional later on.
*
* The array lists : version number with true (required) and false (optional).
*
* The index is the location of the parameter in the parameter list, starting at 0 !
* If's sufficient to list the last version in which the parameter was still required.
*
* @since 7.0.3
*
* @var array
*/
protected $functionParameters = array(
'array_merge' => array(
0 => array(
'name' => 'array(s) to merge',
'7.3' => true,
'7.4' => false,
),
),
'array_merge_recursive' => array(
0 => array(
'name' => 'array(s) to merge',
'7.3' => true,
'7.4' => false,
),
),
'array_push' => array(
1 => array(
'name' => 'element to push',
'7.2' => true,
'7.3' => false,
),
),
'array_unshift' => array(
1 => array(
'name' => 'element to prepend',
'7.2' => true,
'7.3' => false,
),
),
'bcscale' => array(
0 => array(
'name' => 'scale',
'7.2' => true,
'7.3' => false,
),
),
'ftp_fget' => array(
3 => array(
'name' => 'mode',
'7.2' => true,
'7.3' => false,
),
),
'ftp_fput' => array(
3 => array(
'name' => 'mode',
'7.2' => true,
'7.3' => false,
),
),
'ftp_get' => array(
3 => array(
'name' => 'mode',
'7.2' => true,
'7.3' => false,
),
),
'ftp_nb_fget' => array(
3 => array(
'name' => 'mode',
'7.2' => true,
'7.3' => false,
),
),
'ftp_nb_fput' => array(
3 => array(
'name' => 'mode',
'7.2' => true,
'7.3' => false,
),
),
'ftp_nb_get' => array(
3 => array(
'name' => 'mode',
'7.2' => true,
'7.3' => false,
),
),
'ftp_nb_put' => array(
3 => array(
'name' => 'mode',
'7.2' => true,
'7.3' => false,
),
),
'ftp_put' => array(
3 => array(
'name' => 'mode',
'7.2' => true,
'7.3' => false,
),
),
'getenv' => array(
0 => array(
'name' => 'varname',
'7.0' => true,
'7.1' => false,
),
),
'preg_match_all' => array(
2 => array(
'name' => 'matches',
'5.3' => true,
'5.4' => false,
),
),
'stream_socket_enable_crypto' => array(
2 => array(
'name' => 'crypto_type',
'5.5' => true,
'5.6' => false,
),
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.3
*
* @return array
*/
public function register()
{
// Handle case-insensitivity of function names.
$this->functionParameters = $this->arrayKeysToLowercase($this->functionParameters);
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.3
*
* @param \PHP_CodeSniffer_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();
$ignore = array(
\T_DOUBLE_COLON => true,
\T_OBJECT_OPERATOR => true,
\T_FUNCTION => true,
\T_CONST => true,
\T_NEW => true,
);
$prevToken = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
// Not a call to a PHP function.
return;
}
$function = $tokens[$stackPtr]['content'];
$functionLc = strtolower($function);
if (isset($this->functionParameters[$functionLc]) === false) {
return;
}
$parameterCount = $this->getFunctionCallParameterCount($phpcsFile, $stackPtr);
$openParenthesis = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
// If the parameter count returned > 0, we know there will be valid open parenthesis.
if ($parameterCount === 0 && $tokens[$openParenthesis]['code'] !== \T_OPEN_PARENTHESIS) {
return;
}
$parameterOffsetFound = $parameterCount - 1;
foreach ($this->functionParameters[$functionLc] as $offset => $parameterDetails) {
if ($offset > $parameterOffsetFound) {
$itemInfo = array(
'name' => $function,
'nameLc' => $functionLc,
'offset' => $offset,
);
$this->handleFeature($phpcsFile, $openParenthesis, $itemInfo);
}
}
}
/**
* Determine whether an error/warning should be thrown for an item based on collected information.
*
* @since 7.1.0
*
* @param array $errorInfo Detail information about an item.
*
* @return bool
*/
protected function shouldThrowError(array $errorInfo)
{
return ($errorInfo['requiredVersion'] !== '');
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->functionParameters[$itemInfo['nameLc']][$itemInfo['offset']];
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array('name');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = array(
'paramName' => '',
'requiredVersion' => '',
);
$versionArray = $this->getVersionArray($itemArray);
if (empty($versionArray) === false) {
foreach ($versionArray as $version => $required) {
if ($required === true && $this->supportsBelow($version) === true) {
$errorInfo['requiredVersion'] = $version;
}
}
}
$errorInfo['paramName'] = $itemArray['name'];
return $errorInfo;
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'The "%s" parameter for function %s() is missing, but was required for PHP version %s and lower';
}
/**
* Generates the error or warning for this item.
*
* @since 7.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
* @param array $errorInfo Array with detail (version) information
* relevant to the item.
*
* @return void
*/
public function addError(File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo)
{
$error = $this->getErrorMsgTemplate();
$errorCode = $this->stringToErrorCode($itemInfo['name'] . '_' . $errorInfo['paramName']) . 'Missing';
$data = array(
$errorInfo['paramName'],
$itemInfo['name'],
$errorInfo['requiredVersion'],
);
$phpcsFile->addError($error, $stackPtr, $errorCode, $data);
}
}
@@ -0,0 +1,158 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Generators;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
/**
* As of PHP 7.0, a `return` statement can be used within a generator for a final expression to be returned.
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.generator-return-expressions
* @link https://wiki.php.net/rfc/generator-return-expressions
* @link https://www.php.net/manual/en/language.generators.syntax.php
*
* @since 8.2.0
*/
class NewGeneratorReturnSniff extends Sniff
{
/**
* Scope conditions within which a yield can exist.
*
* @since 9.0.0
*
* @var array
*/
private $validConditions = array(
\T_FUNCTION => \T_FUNCTION,
\T_CLOSURE => \T_CLOSURE,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.2.0
*
* @return array
*/
public function register()
{
$targets = array(
\T_YIELD,
);
/*
* The `yield` keyword was introduced in PHP 5.5 with the token T_YIELD.
* The `yield from` keyword was introduced in PHP 7.0 and tokenizes as
* "T_YIELD T_WHITESPACE T_STRING".
*
* Pre-PHPCS 3.1.0, the T_YIELD token was not correctly back-filled for PHP < 5.5.
* Also, as of PHPCS 3.1.0, the PHPCS tokenizer adds a new T_YIELD_FROM
* token.
*
* So for PHP 5.3-5.4 icw PHPCS < 3.1.0, we need to look for T_STRING with content "yield".
* For PHP 5.5+ we need to look for T_YIELD.
* For PHPCS 3.1.0+, we also need to look for T_YIELD_FROM.
*/
if (version_compare(\PHP_VERSION_ID, '50500', '<') === true
&& version_compare(PHPCSHelper::getVersion(), '3.1.0', '<') === true
) {
$targets[] = \T_STRING;
}
if (\defined('T_YIELD_FROM')) {
$targets[] = \T_YIELD_FROM;
}
return $targets;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return void|int Void or a stack pointer to skip forward.
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->supportsBelow('5.6') !== true) {
return;
}
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['code'] === \T_STRING
&& $tokens[$stackPtr]['content'] !== 'yield'
) {
return;
}
if (empty($tokens[$stackPtr]['conditions']) === true) {
return;
}
// Walk the condition from inner to outer to see if we can find a valid function/closure scope.
$conditions = array_reverse($tokens[$stackPtr]['conditions'], true);
foreach ($conditions as $ptr => $type) {
if (isset($this->validConditions[$type]) === true) {
$function = $ptr;
break;
}
}
if (isset($function) === false) {
// Yield outside function scope, fatal error, but not our concern.
return;
}
if (isset($tokens[$function]['scope_opener'], $tokens[$function]['scope_closer']) === false) {
// Can't reliably determine start/end of function scope.
return;
}
$targets = array(\T_RETURN, \T_CLOSURE, \T_FUNCTION, \T_CLASS);
if (\defined('T_ANON_CLASS')) {
$targets[] = \T_ANON_CLASS;
}
$current = $tokens[$function]['scope_opener'];
while (($current = $phpcsFile->findNext($targets, ($current + 1), $tokens[$function]['scope_closer'])) !== false) {
if ($tokens[$current]['code'] === \T_RETURN) {
$phpcsFile->addError(
'Returning a final expression from a generator was not supported in PHP 5.6 or earlier',
$current,
'ReturnFound'
);
return $tokens[$function]['scope_closer'];
}
// Found a nested scope in which return can exist without problems.
if (isset($tokens[$current]['scope_closer'])) {
// Skip past the nested scope.
$current = $tokens[$current]['scope_closer'];
}
}
// Don't examine this function again.
return $tokens[$function]['scope_closer'];
}
}
@@ -0,0 +1,855 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\IniDirectives;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect the use of new INI directives through `ini_set()` or `ini_get()`.
*
* PHP version All
*
* @link https://www.php.net/manual/en/ini.list.php
* @link https://www.php.net/manual/en/ini.core.php
*
* @since 5.5
* @since 7.0.7 When a new directive is used with `ini_set()`, the sniff will now throw an error
* instead of a warning.
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class..
*/
class NewIniDirectivesSniff extends AbstractNewFeatureSniff
{
/**
* A list of new INI directives
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the ini directive appears.
*
* @since 5.5
* @since 7.0.3 Support for 'alternative' has been added.
*
* @var array(string)
*/
protected $newIniDirectives = array(
'auto_globals_jit' => array(
'4.4' => false,
'5.0' => true,
),
'com.code_page' => array(
'4.4' => false,
'5.0' => true,
),
'date.default_latitude' => array(
'4.4' => false,
'5.0' => true,
),
'date.default_longitude' => array(
'4.4' => false,
'5.0' => true,
),
'date.sunrise_zenith' => array(
'4.4' => false,
'5.0' => true,
),
'date.sunset_zenith' => array(
'4.4' => false,
'5.0' => true,
),
'ibase.default_charset' => array(
'4.4' => false,
'5.0' => true,
),
'ibase.default_db' => array(
'4.4' => false,
'5.0' => true,
),
'mail.force_extra_parameters' => array(
'4.4' => false,
'5.0' => true,
),
'mime_magic.debug' => array(
'4.4' => false,
'5.0' => true,
),
'mysqli.max_links' => array(
'4.4' => false,
'5.0' => true,
),
'mysqli.default_port' => array(
'4.4' => false,
'5.0' => true,
),
'mysqli.default_socket' => array(
'4.4' => false,
'5.0' => true,
),
'mysqli.default_host' => array(
'4.4' => false,
'5.0' => true,
),
'mysqli.default_user' => array(
'4.4' => false,
'5.0' => true,
),
'mysqli.default_pw' => array(
'4.4' => false,
'5.0' => true,
),
'report_zend_debug' => array(
'4.4' => false,
'5.0' => true,
),
'session.hash_bits_per_character' => array(
'4.4' => false,
'5.0' => true,
),
'session.hash_function' => array(
'4.4' => false,
'5.0' => true,
),
'soap.wsdl_cache_dir' => array(
'4.4' => false,
'5.0' => true,
),
'soap.wsdl_cache_enabled' => array(
'4.4' => false,
'5.0' => true,
),
'soap.wsdl_cache_ttl' => array(
'4.4' => false,
'5.0' => true,
),
'sqlite.assoc_case' => array(
'4.4' => false,
'5.0' => true,
),
'tidy.clean_output' => array(
'4.4' => false,
'5.0' => true,
),
'tidy.default_config' => array(
'4.4' => false,
'5.0' => true,
),
'zend.ze1_compatibility_mode' => array(
'4.4' => false,
'5.0' => true,
),
'date.timezone' => array(
'5.0' => false,
'5.1' => true,
),
'detect_unicode' => array(
'5.0' => false,
'5.1' => true,
),
'fbsql.batchsize' => array(
'5.0' => false,
'5.1' => true,
'alternative' => 'fbsql.batchSize',
),
'realpath_cache_size' => array(
'5.0' => false,
'5.1' => true,
),
'realpath_cache_ttl' => array(
'5.0' => false,
'5.1' => true,
),
'mbstring.strict_detection' => array(
'5.1.1' => false,
'5.1.2' => true,
),
'mssql.charset' => array(
'5.1.1' => false,
'5.1.2' => true,
),
'gd.jpeg_ignore_warning' => array(
'5.1.2' => false,
'5.1.3' => true,
),
'fbsql.show_timestamp_decimals' => array(
'5.1.4' => false,
'5.1.5' => true,
),
'soap.wsdl_cache' => array(
'5.1.4' => false,
'5.1.5' => true,
),
'soap.wsdl_cache_limit' => array(
'5.1.4' => false,
'5.1.5' => true,
),
'allow_url_include' => array(
'5.1' => false,
'5.2' => true,
),
'filter.default' => array(
'5.1' => false,
'5.2' => true,
),
'filter.default_flags' => array(
'5.1' => false,
'5.2' => true,
),
'pcre.backtrack_limit' => array(
'5.1' => false,
'5.2' => true,
),
'pcre.recursion_limit' => array(
'5.1' => false,
'5.2' => true,
),
'session.cookie_httponly' => array(
'5.1' => false,
'5.2' => true,
),
'cgi.check_shebang_line' => array(
'5.2.0' => false,
'5.2.1' => true,
),
'max_input_nesting_level' => array(
'5.2.2' => false,
'5.2.3' => true,
),
'mysqli.allow_local_infile' => array(
'5.2.3' => false,
'5.2.4' => true,
),
'max_file_uploads' => array(
'5.2.11' => false,
'5.2.12' => true,
),
'cgi.discard_path' => array(
'5.2' => false,
'5.3' => true,
),
'exit_on_timeout' => array(
'5.2' => false,
'5.3' => true,
),
'intl.default_locale' => array(
'5.2' => false,
'5.3' => true,
),
'intl.error_level' => array(
'5.2' => false,
'5.3' => true,
),
'mail.add_x_header' => array(
'5.2' => false,
'5.3' => true,
),
'mail.log' => array(
'5.2' => false,
'5.3' => true,
),
'mbstring.http_output_conv_mimetype' => array(
'5.2' => false,
'5.3' => true,
),
'mysqli.allow_persistent' => array(
'5.2' => false,
'5.3' => true,
),
'mysqli.cache_size' => array(
'5.2' => false,
'5.3' => true,
),
'mysqli.max_persistent' => array(
'5.2' => false,
'5.3' => true,
),
'mysqlnd.collect_memory_statistics' => array(
'5.2' => false,
'5.3' => true,
),
'mysqlnd.collect_statistics' => array(
'5.2' => false,
'5.3' => true,
),
'mysqlnd.debug' => array(
'5.2' => false,
'5.3' => true,
),
'mysqlnd.net_read_buffer_size' => array(
'5.2' => false,
'5.3' => true,
),
'odbc.default_cursortype' => array(
'5.2' => false,
'5.3' => true,
),
'request_order' => array(
'5.2' => false,
'5.3' => true,
),
'user_ini.cache_ttl' => array(
'5.2' => false,
'5.3' => true,
),
'user_ini.filename' => array(
'5.2' => false,
'5.3' => true,
),
'zend.enable_gc' => array(
'5.2' => false,
'5.3' => true,
),
'curl.cainfo' => array(
'5.3.6' => false,
'5.3.7' => true,
),
'max_input_vars' => array(
'5.3.8' => false,
'5.3.9' => true,
),
'sqlite3.extension_dir' => array(
'5.3.10' => false,
'5.3.11' => true,
),
'cli.pager' => array(
'5.3' => false,
'5.4' => true,
),
'cli.prompt' => array(
'5.3' => false,
'5.4' => true,
),
'cli_server.color' => array(
'5.3' => false,
'5.4' => true,
),
'enable_post_data_reading' => array(
'5.3' => false,
'5.4' => true,
),
'mysqlnd.mempool_default_size' => array(
'5.3' => false,
'5.4' => true,
),
'mysqlnd.net_cmd_buffer_size' => array(
'5.3' => false,
'5.4' => true,
),
'mysqlnd.net_read_timeout' => array(
'5.3' => false,
'5.4' => true,
),
'phar.cache_list' => array(
'5.3' => false,
'5.4' => true,
),
'session.upload_progress.enabled' => array(
'5.3' => false,
'5.4' => true,
),
'session.upload_progress.cleanup' => array(
'5.3' => false,
'5.4' => true,
),
'session.upload_progress.name' => array(
'5.3' => false,
'5.4' => true,
),
'session.upload_progress.freq' => array(
'5.3' => false,
'5.4' => true,
),
'session.upload_progress.min_freq' => array(
'5.3' => false,
'5.4' => true,
),
'session.upload_progress.prefix' => array(
'5.3' => false,
'5.4' => true,
),
'windows_show_crt_warning' => array(
'5.3' => false,
'5.4' => true,
),
'zend.detect_unicode' => array(
'5.3' => false,
'5.4' => true,
'alternative' => 'detect_unicode',
),
'zend.multibyte' => array(
'5.3' => false,
'5.4' => true,
),
'zend.script_encoding' => array(
'5.3' => false,
'5.4' => true,
),
'zend.signal_check' => array(
'5.3' => false,
'5.4' => true,
),
'mysqlnd.log_mask' => array(
'5.3' => false,
'5.4' => true,
),
'intl.use_exceptions' => array(
'5.4' => false,
'5.5' => true,
),
'mysqlnd.sha256_server_public_key' => array(
'5.4' => false,
'5.5' => true,
),
'mysqlnd.trace_alloc' => array(
'5.4' => false,
'5.5' => true,
),
'sys_temp_dir' => array(
'5.4' => false,
'5.5' => true,
),
'xsl.security_prefs' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.enable' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.enable_cli' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.memory_consumption' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.interned_strings_buffer' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.max_accelerated_files' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.max_wasted_percentage' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.use_cwd' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.validate_timestamps' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.revalidate_freq' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.revalidate_path' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.save_comments' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.load_comments' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.fast_shutdown' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.enable_file_override' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.optimization_level' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.inherited_hack' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.dups_fix' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.blacklist_filename' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.max_file_size' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.consistency_checks' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.force_restart_timeout' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.error_log' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.log_verbosity_level' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.preferred_memory_model' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.protect_memory' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.mmap_base' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.restrict_api' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.file_update_protection' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.huge_code_pages' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.lockfile_path' => array(
'5.4' => false,
'5.5' => true,
),
'opcache.opt_debug_level' => array(
'5.4' => false,
'5.5' => true,
),
'session.use_strict_mode' => array(
'5.5.1' => false,
'5.5.2' => true,
),
'mysqli.rollback_on_cached_plink' => array(
'5.5' => false,
'5.6' => true,
),
'assert.exception' => array(
'5.6' => false,
'7.0' => true,
),
'pcre.jit' => array(
'5.6' => false,
'7.0' => true,
),
'session.lazy_write' => array(
'5.6' => false,
'7.0' => true,
),
'zend.assertions' => array(
'5.6' => false,
'7.0' => true,
),
'opcache.file_cache' => array(
'5.6' => false,
'7.0' => true,
),
'opcache.file_cache_only' => array(
'5.6' => false,
'7.0' => true,
),
'opcache.file_cache_consistency_checks' => array(
'5.6' => false,
'7.0' => true,
),
'opcache.file_cache_fallback' => array(
'5.6' => false,
'7.0' => true,
), // Windows only.
'opcache.validate_permission' => array(
'7.0.13' => false,
'7.0.14' => true,
),
'opcache.validate_root' => array(
'7.0.13' => false,
'7.0.14' => true,
),
'hard_timeout' => array(
'7.0' => false,
'7.1' => true,
),
'session.sid_length' => array(
'7.0' => false,
'7.1' => true,
),
'session.sid_bits_per_character' => array(
'7.0' => false,
'7.1' => true,
),
'session.trans_sid_hosts' => array(
'7.0' => false,
'7.1' => true,
),
'session.trans_sid_tags' => array(
'7.0' => false,
'7.1' => true,
),
'url_rewriter.hosts' => array(
'7.0' => false,
'7.1' => true,
),
// Introduced in PHP 7.1.25, 7.2.13, 7.3.0.
'imap.enable_insecure_rsh' => array(
'7.1.24' => false,
'7.1.25' => true,
),
'syslog.facility' => array(
'7.2' => false,
'7.3' => true,
),
'syslog.filter' => array(
'7.2' => false,
'7.3' => true,
),
'syslog.ident' => array(
'7.2' => false,
'7.3' => true,
),
'session.cookie_samesite' => array(
'7.2' => false,
'7.3' => true,
),
'ffi.enable' => array(
'7.3' => false,
'7.4' => true,
),
'ffi.preload' => array(
'7.3' => false,
'7.4' => true,
),
'opcache.cache_id' => array(
'7.3' => false,
'7.4' => true,
),
'opcache.preload' => array(
'7.3' => false,
'7.4' => true,
),
'zend.exception_ignore_args' => array(
'7.3' => false,
'7.4' => true,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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();
$ignore = array(
\T_DOUBLE_COLON => true,
\T_OBJECT_OPERATOR => true,
\T_FUNCTION => true,
\T_CONST => true,
);
$prevToken = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
// Not a call to a PHP function.
return;
}
$functionLc = strtolower($tokens[$stackPtr]['content']);
if (isset($this->iniFunctions[$functionLc]) === false) {
return;
}
$iniToken = $this->getFunctionCallParameter($phpcsFile, $stackPtr, $this->iniFunctions[$functionLc]);
if ($iniToken === false) {
return;
}
$filteredToken = $this->stripQuotes($iniToken['raw']);
if (isset($this->newIniDirectives[$filteredToken]) === false) {
return;
}
$itemInfo = array(
'name' => $filteredToken,
'functionLc' => $functionLc,
);
$this->handleFeature($phpcsFile, $iniToken['end'], $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newIniDirectives[$itemInfo['name']];
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array('alternative');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
$errorInfo['alternative'] = '';
if (isset($itemArray['alternative']) === true) {
$errorInfo['alternative'] = $itemArray['alternative'];
}
// Lower error level to warning if the function used was ini_get.
if ($errorInfo['error'] === true && $itemInfo['functionLc'] === 'ini_get') {
$errorInfo['error'] = false;
}
return $errorInfo;
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return "INI directive '%s' is not present in PHP version %s or earlier";
}
/**
* Allow for concrete child classes to filter the error message before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param string $error The error message which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return string
*/
protected function filterErrorMsg($error, array $itemInfo, array $errorInfo)
{
if ($errorInfo['alternative'] !== '') {
$error .= ". This directive was previously called '%s'.";
}
return $error;
}
/**
* Allow for concrete child classes to filter the error data before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param array $data The error data array which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return array
*/
protected function filterErrorData(array $data, array $itemInfo, array $errorInfo)
{
if ($errorInfo['alternative'] !== '') {
$data[] = $errorInfo['alternative'];
}
return $data;
}
}
@@ -0,0 +1,424 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\IniDirectives;
use PHPCompatibility\AbstractRemovedFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect the use of deprecated and removed INI directives through `ini_set()` or `ini_get()`.
*
* PHP version All
*
* @link https://www.php.net/manual/en/ini.list.php
* @link https://www.php.net/manual/en/ini.core.php
*
* @since 5.5
* @since 7.0.0 This sniff now throws a warning (deprecated) or an error (removed) depending
* on the `testVersion` set. Previously it would always throw a warning.
* @since 7.0.1 The sniff will now only throw warnings for `ini_get()`.
* @since 7.1.0 Now extends the `AbstractRemovedFeatureSniff` instead of the base `Sniff` class.
* @since 9.0.0 Renamed from `DeprecatedIniDirectivesSniff` to `RemovedIniDirectivesSniff`.
*/
class RemovedIniDirectivesSniff extends AbstractRemovedFeatureSniff
{
/**
* A list of deprecated/removed INI directives.
*
* The array lists : version number with false (deprecated) and true (removed).
* If's sufficient to list the first version where the ini directive was deprecated/removed.
*
* @since 5.5
* @since 7.0.3 Support for 'alternative' has been added.
*
* @var array(string)
*/
protected $deprecatedIniDirectives = array(
'fbsql.batchSize' => array(
'5.1' => true,
'alternative' => 'fbsql.batchsize',
),
'pfpro.defaulthost' => array(
'5.1' => true,
),
'pfpro.defaultport' => array(
'5.1' => true,
),
'pfpro.defaulttimeout' => array(
'5.1' => true,
),
'pfpro.proxyaddress' => array(
'5.1' => true,
),
'pfpro.proxyport' => array(
'5.1' => true,
),
'pfpro.proxylogon' => array(
'5.1' => true,
),
'pfpro.proxypassword' => array(
'5.1' => true,
),
'ifx.allow_persistent' => array(
'5.2.1' => true,
),
'ifx.blobinfile' => array(
'5.2.1' => true,
),
'ifx.byteasvarchar' => array(
'5.2.1' => true,
),
'ifx.charasvarchar' => array(
'5.2.1' => true,
),
'ifx.default_host' => array(
'5.2.1' => true,
),
'ifx.default_password' => array(
'5.2.1' => true,
),
'ifx.default_user' => array(
'5.2.1' => true,
),
'ifx.max_links' => array(
'5.2.1' => true,
),
'ifx.max_persistent' => array(
'5.2.1' => true,
),
'ifx.nullformat' => array(
'5.2.1' => true,
),
'ifx.textasvarchar' => array(
'5.2.1' => true,
),
'zend.ze1_compatibility_mode' => array(
'5.3' => true,
),
'allow_call_time_pass_reference' => array(
'5.3' => false,
'5.4' => true,
),
'define_syslog_variables' => array(
'5.3' => false,
'5.4' => true,
),
'detect_unicode' => array(
'5.4' => true,
'alternative' => 'zend.detect_unicode',
),
'highlight.bg' => array(
'5.3' => false,
'5.4' => true,
),
'magic_quotes_gpc' => array(
'5.3' => false,
'5.4' => true,
),
'magic_quotes_runtime' => array(
'5.3' => false,
'5.4' => true,
),
'magic_quotes_sybase' => array(
'5.3' => false,
'5.4' => true,
),
'mbstring.script_encoding' => array(
'5.4' => true,
'alternative' => 'zend.script_encoding',
),
'register_globals' => array(
'5.3' => false,
'5.4' => true,
),
'register_long_arrays' => array(
'5.3' => false,
'5.4' => true,
),
'safe_mode' => array(
'5.3' => false,
'5.4' => true,
),
'safe_mode_allowed_env_vars' => array(
'5.3' => false,
'5.4' => true,
),
'safe_mode_exec_dir' => array(
'5.3' => false,
'5.4' => true,
),
'safe_mode_gid' => array(
'5.3' => false,
'5.4' => true,
),
'safe_mode_include_dir' => array(
'5.3' => false,
'5.4' => true,
),
'safe_mode_protected_env_vars' => array(
'5.3' => false,
'5.4' => true,
),
'session.bug_compat_42' => array(
'5.3' => false,
'5.4' => true,
),
'session.bug_compat_warn' => array(
'5.3' => false,
'5.4' => true,
),
'y2k_compliance' => array(
'5.3' => false,
'5.4' => true,
),
'always_populate_raw_post_data' => array(
'5.6' => false,
'7.0' => true,
),
'iconv.input_encoding' => array(
'5.6' => false,
),
'iconv.output_encoding' => array(
'5.6' => false,
),
'iconv.internal_encoding' => array(
'5.6' => false,
),
'mbstring.http_input' => array(
'5.6' => false,
),
'mbstring.http_output' => array(
'5.6' => false,
),
'mbstring.internal_encoding' => array(
'5.6' => false,
),
'asp_tags' => array(
'7.0' => true,
),
'xsl.security_prefs' => array(
'7.0' => true,
),
'opcache.load_comments' => array(
'7.0' => true,
),
'mcrypt.algorithms_dir' => array(
'7.1' => false,
'7.2' => true,
),
'mcrypt.modes_dir' => array(
'7.1' => false,
'7.2' => true,
),
'session.entropy_file' => array(
'7.1' => true,
),
'session.entropy_length' => array(
'7.1' => true,
),
'session.hash_function' => array(
'7.1' => true,
),
'session.hash_bits_per_character' => array(
'7.1' => true,
),
'mbstring.func_overload' => array(
'7.2' => false,
),
'sql.safe_mode' => array(
'7.2' => true,
),
'track_errors' => array(
'7.2' => false,
),
'opcache.fast_shutdown' => array(
'7.2' => true,
),
'birdstep.max_links' => array(
'7.3' => true,
),
'opcache.inherited_hack' => array(
'5.3' => false, // Soft deprecated, i.e. ignored.
'7.3' => true,
),
'pdo_odbc.db2_instance_name' => array(
'7.3' => false, // Has been marked as deprecated in the manual from before this time. Now hard-deprecated.
),
'allow_url_include' => array(
'7.4' => false,
),
'ibase.allow_persistent' => array(
'7.4' => true,
),
'ibase.max_persistent' => array(
'7.4' => true,
),
'ibase.max_links' => array(
'7.4' => true,
),
'ibase.default_db' => array(
'7.4' => true,
),
'ibase.default_user' => array(
'7.4' => true,
),
'ibase.default_password' => array(
'7.4' => true,
),
'ibase.default_charset' => array(
'7.4' => true,
),
'ibase.timestampformat' => array(
'7.4' => true,
),
'ibase.dateformat' => array(
'7.4' => true,
),
'ibase.timeformat' => array(
'7.4' => true,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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();
$ignore = array(
\T_DOUBLE_COLON => true,
\T_OBJECT_OPERATOR => true,
\T_FUNCTION => true,
\T_CONST => true,
);
$prevToken = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
// Not a call to a PHP function.
return;
}
$functionLc = strtolower($tokens[$stackPtr]['content']);
if (isset($this->iniFunctions[$functionLc]) === false) {
return;
}
$iniToken = $this->getFunctionCallParameter($phpcsFile, $stackPtr, $this->iniFunctions[$functionLc]);
if ($iniToken === false) {
return;
}
$filteredToken = $this->stripQuotes($iniToken['raw']);
if (isset($this->deprecatedIniDirectives[$filteredToken]) === false) {
return;
}
$itemInfo = array(
'name' => $filteredToken,
'functionLc' => $functionLc,
);
$this->handleFeature($phpcsFile, $iniToken['end'], $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->deprecatedIniDirectives[$itemInfo['name']];
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
// Lower error level to warning if the function used was ini_get.
if ($errorInfo['error'] === true && $itemInfo['functionLc'] === 'ini_get') {
$errorInfo['error'] = false;
}
return $errorInfo;
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return "INI directive '%s' is ";
}
/**
* Get the error message template for suggesting an alternative for a specific sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getAlternativeOptionTemplate()
{
return str_replace('%s', "'%s'", parent::getAlternativeOptionTemplate());
}
}
@@ -0,0 +1,82 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\InitialValue;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect declaration of constants using the `const` keyword with a (constant) array value
* as supported since PHP 5.6.
*
* PHP version 5.6
*
* @link https://wiki.php.net/rfc/const_scalar_exprs
* @link https://www.php.net/manual/en/language.constants.syntax.php
*
* @since 7.1.4
* @since 9.0.0 Renamed from `ConstantArraysUsingConstSniff` to `NewConstantArraysUsingConstSniff`.
*/
class NewConstantArraysUsingConstSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.1.4
*
* @return array
*/
public function register()
{
return array(\T_CONST);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.5') !== true) {
return;
}
$tokens = $phpcsFile->getTokens();
$find = array(
\T_ARRAY => \T_ARRAY,
\T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
);
while (($hasArray = $phpcsFile->findNext($find, ($stackPtr + 1), null, false, null, true)) !== false) {
$phpcsFile->addError(
'Constant arrays using the "const" keyword are not allowed in PHP 5.5 or earlier',
$hasArray,
'Found'
);
// Skip past the content of the array.
$stackPtr = $hasArray;
if ($tokens[$hasArray]['code'] === \T_OPEN_SHORT_ARRAY && isset($tokens[$hasArray]['bracket_closer'])) {
$stackPtr = $tokens[$hasArray]['bracket_closer'];
} elseif ($tokens[$hasArray]['code'] === \T_ARRAY && isset($tokens[$hasArray]['parenthesis_closer'])) {
$stackPtr = $tokens[$hasArray]['parenthesis_closer'];
}
}
}
}
@@ -0,0 +1,101 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\InitialValue;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect declaration of constants using `define()` with a (constant) array value
* as supported since PHP 7.0.
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.define-array
* @link https://www.php.net/manual/en/language.constants.syntax.php
*
* @since 7.0.0
* @since 9.0.0 Renamed from `ConstantArraysUsingDefineSniff` to `NewConstantArraysUsingDefineSniff`.
*/
class NewConstantArraysUsingDefineSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
*
* @return array
*/
public function register()
{
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.6') !== true) {
return;
}
$tokens = $phpcsFile->getTokens();
$ignore = array(
\T_DOUBLE_COLON => true,
\T_OBJECT_OPERATOR => true,
\T_FUNCTION => true,
\T_CONST => true,
);
$prevToken = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
// Not a call to a PHP function.
return;
}
$functionLc = strtolower($tokens[$stackPtr]['content']);
if ($functionLc !== 'define') {
return;
}
$secondParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, 2);
if (isset($secondParam['start'], $secondParam['end']) === false) {
return;
}
$targetNestingLevel = 0;
if (isset($tokens[$secondParam['start']]['nested_parenthesis'])) {
$targetNestingLevel = \count($tokens[$secondParam['start']]['nested_parenthesis']);
}
$array = $phpcsFile->findNext(array(\T_ARRAY, \T_OPEN_SHORT_ARRAY), $secondParam['start'], ($secondParam['end'] + 1));
if ($array !== false) {
if ((isset($tokens[$array]['nested_parenthesis']) === false && $targetNestingLevel === 0) || \count($tokens[$array]['nested_parenthesis']) === $targetNestingLevel) {
$phpcsFile->addError(
'Constant arrays using define are not allowed in PHP 5.6 or earlier',
$array,
'Found'
);
}
}
}
}
@@ -0,0 +1,556 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\InitialValue;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect constant scalar expressions being used to set an initial value.
*
* Since PHP 5.6, it is now possible to provide a scalar expression involving
* numeric and string literals and/or constants in contexts where PHP previously
* expected a static value, such as constant and property declarations and
* default values for function parameters.
*
* PHP version 5.6
*
* @link https://www.php.net/manual/en/migration56.new-features.php#migration56.new-features.const-scalar-exprs
* @link https://wiki.php.net/rfc/const_scalar_exprs
*
* @since 8.2.0
*/
class NewConstantScalarExpressionsSniff extends Sniff
{
/**
* Error message.
*
* @since 8.2.0
*
* @var string
*/
const ERROR_PHRASE = 'Constant scalar expressions are not allowed %s in PHP 5.5 or earlier.';
/**
* Partial error phrases to be used in combination with the error message constant.
*
* @since 8.2.0
*
* @var array
*/
protected $errorPhrases = array(
'const' => 'when defining constants using the const keyword',
'property' => 'in property declarations',
'staticvar' => 'in static variable declarations',
'default' => 'in default function arguments',
);
/**
* Tokens which were allowed to be used in these declarations prior to PHP 5.6.
*
* This list will be enriched in the setProperties() method.
*
* @since 8.2.0
*
* @var array
*/
protected $safeOperands = array(
\T_LNUMBER => \T_LNUMBER,
\T_DNUMBER => \T_DNUMBER,
\T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
\T_TRUE => \T_TRUE,
\T_FALSE => \T_FALSE,
\T_NULL => \T_NULL,
\T_LINE => \T_LINE,
\T_FILE => \T_FILE,
\T_DIR => \T_DIR,
\T_FUNC_C => \T_FUNC_C,
\T_CLASS_C => \T_CLASS_C,
\T_TRAIT_C => \T_TRAIT_C,
\T_METHOD_C => \T_METHOD_C,
\T_NS_C => \T_NS_C,
// Special cases:
\T_NS_SEPARATOR => \T_NS_SEPARATOR,
/*
* This can be neigh anything, but for any usage except constants,
* the T_STRING will be combined with non-allowed tokens, so we should be good.
*/
\T_STRING => \T_STRING,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.2.0
*
* @return array
*/
public function register()
{
// Set the properties up only once.
$this->setProperties();
return array(
\T_CONST,
\T_VARIABLE,
\T_FUNCTION,
\T_CLOSURE,
\T_STATIC,
);
}
/**
* Make some adjustments to the $safeOperands property.
*
* @since 8.2.0
*
* @return void
*/
public function setProperties()
{
$this->safeOperands += Tokens::$heredocTokens;
$this->safeOperands += Tokens::$emptyTokens;
}
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 8.2.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('5.5') !== true);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return void|int Null or integer stack pointer to skip forward.
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->bowOutEarly() === true) {
return;
}
$tokens = $phpcsFile->getTokens();
switch ($tokens[$stackPtr]['type']) {
case 'T_FUNCTION':
case 'T_CLOSURE':
$params = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
if (empty($params)) {
// No parameters.
return;
}
$funcToken = $tokens[$stackPtr];
if (isset($funcToken['parenthesis_owner'], $funcToken['parenthesis_opener'], $funcToken['parenthesis_closer']) === false
|| $funcToken['parenthesis_owner'] !== $stackPtr
|| isset($tokens[$funcToken['parenthesis_opener']], $tokens[$funcToken['parenthesis_closer']]) === false
) {
// Hmm.. something is going wrong as these should all be available & valid.
return;
}
$opener = $funcToken['parenthesis_opener'];
$closer = $funcToken['parenthesis_closer'];
// Which nesting level is the one we are interested in ?
$nestedParenthesisCount = 1;
if (isset($tokens[$opener]['nested_parenthesis'])) {
$nestedParenthesisCount += \count($tokens[$opener]['nested_parenthesis']);
}
foreach ($params as $param) {
if (isset($param['default']) === false) {
continue;
}
$end = $param['token'];
while (($end = $phpcsFile->findNext(array(\T_COMMA, \T_CLOSE_PARENTHESIS), ($end + 1), ($closer + 1))) !== false) {
$maybeSkipTo = $this->isRealEndOfDeclaration($tokens, $end, $nestedParenthesisCount);
if ($maybeSkipTo !== true) {
$end = $maybeSkipTo;
continue;
}
// Ignore closing parenthesis/bracket if not 'ours'.
if ($tokens[$end]['code'] === \T_CLOSE_PARENTHESIS && $end !== $closer) {
continue;
}
// Ok, we've found the end of the param default value declaration.
break;
}
if ($this->isValidAssignment($phpcsFile, $param['token'], $end) === false) {
$this->throwError($phpcsFile, $param['token'], 'default', $param['content']);
}
}
/*
* No need for the sniff to be triggered by the T_VARIABLEs in the function
* definition as we've already examined them above, so let's skip over them.
*/
return $closer;
case 'T_VARIABLE':
case 'T_STATIC':
case 'T_CONST':
$type = 'const';
// Filter out non-property declarations.
if ($tokens[$stackPtr]['code'] === \T_VARIABLE) {
if ($this->isClassProperty($phpcsFile, $stackPtr) === false) {
return;
}
$type = 'property';
// Move back one token to have the same starting point as the others.
$stackPtr = ($stackPtr - 1);
}
// Filter out late static binding and class properties.
if ($tokens[$stackPtr]['code'] === \T_STATIC) {
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
if ($next === false || $tokens[$next]['code'] !== \T_VARIABLE) {
// Late static binding.
return;
}
if ($this->isClassProperty($phpcsFile, $next) === true) {
// Class properties are examined based on the T_VARIABLE token.
return;
}
unset($next);
$type = 'staticvar';
}
$endOfStatement = $phpcsFile->findNext(array(\T_SEMICOLON, \T_CLOSE_TAG), ($stackPtr + 1));
if ($endOfStatement === false) {
// No semi-colon - live coding.
return;
}
$targetNestingLevel = 0;
if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
$targetNestingLevel = \count($tokens[$stackPtr]['nested_parenthesis']);
}
// Examine each variable/constant in multi-declarations.
$start = $stackPtr;
$end = $stackPtr;
while (($end = $phpcsFile->findNext(array(\T_COMMA, \T_SEMICOLON, \T_OPEN_SHORT_ARRAY, \T_CLOSE_TAG), ($end + 1), ($endOfStatement + 1))) !== false) {
$maybeSkipTo = $this->isRealEndOfDeclaration($tokens, $end, $targetNestingLevel);
if ($maybeSkipTo !== true) {
$end = $maybeSkipTo;
continue;
}
$start = $phpcsFile->findNext(Tokens::$emptyTokens, ($start + 1), $end, true);
if ($start === false
|| ($tokens[$stackPtr]['code'] === \T_CONST && $tokens[$start]['code'] !== \T_STRING)
|| ($tokens[$stackPtr]['code'] !== \T_CONST && $tokens[$start]['code'] !== \T_VARIABLE)
) {
// Shouldn't be possible.
continue;
}
if ($this->isValidAssignment($phpcsFile, $start, $end) === false) {
// Create the "found" snippet.
$content = '';
$tokenCount = ($end - $start);
if ($tokenCount < 20) {
// Prevent large arrays from being added to the error message.
$content = $phpcsFile->getTokensAsString($start, ($tokenCount + 1));
}
$this->throwError($phpcsFile, $start, $type, $content);
}
$start = $end;
}
// Skip to the end of the statement to prevent duplicate messages for multi-declarations.
return $endOfStatement;
}
}
/**
* Is a value declared and is the value declared valid pre-PHP 5.6 ?
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
* @param int $end The end of the value definition.
* This will normally be a comma or semi-colon.
*
* @return bool
*/
protected function isValidAssignment(File $phpcsFile, $stackPtr, $end)
{
$tokens = $phpcsFile->getTokens();
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), $end, true);
if ($next === false || $tokens[$next]['code'] !== \T_EQUAL) {
// No value assigned.
return true;
}
return $this->isStaticValue($phpcsFile, $tokens, ($next + 1), ($end - 1));
}
/**
* Is a value declared and is the value declared constant as accepted in PHP 5.5 and lower ?
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param array $tokens The token stack of the current file.
* @param int $start The stackPtr from which to start examining.
* @param int $end The end of the value definition (inclusive),
* i.e. this token will be examined as part of
* the snippet.
* @param int $nestedArrays Optional. Array nesting level when examining
* the content of an array.
*
* @return bool
*/
protected function isStaticValue(File $phpcsFile, $tokens, $start, $end, $nestedArrays = 0)
{
$nextNonSimple = $phpcsFile->findNext($this->safeOperands, $start, ($end + 1), true);
if ($nextNonSimple === false) {
return true;
}
/*
* OK, so we have at least one token which needs extra examination.
*/
switch ($tokens[$nextNonSimple]['code']) {
case \T_MINUS:
case \T_PLUS:
if ($this->isNumber($phpcsFile, $start, $end, true) !== false) {
// Int or float with sign.
return true;
}
return false;
case \T_NAMESPACE:
case \T_PARENT:
case \T_SELF:
case \T_DOUBLE_COLON:
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonSimple + 1), ($end + 1), true);
if ($tokens[$nextNonSimple]['code'] === \T_NAMESPACE) {
// Allow only `namespace\...`.
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_NS_SEPARATOR) {
return false;
}
} elseif ($tokens[$nextNonSimple]['code'] === \T_PARENT
|| $tokens[$nextNonSimple]['code'] === \T_SELF
) {
// Allow only `parent::` and `self::`.
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_DOUBLE_COLON) {
return false;
}
} elseif ($tokens[$nextNonSimple]['code'] === \T_DOUBLE_COLON) {
// Allow only `T_STRING::T_STRING`.
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_STRING) {
return false;
}
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($nextNonSimple - 1), null, true);
// No need to worry about parent/self, that's handled above and
// the double colon is skipped over in that case.
if ($prevNonEmpty === false || $tokens[$prevNonEmpty]['code'] !== \T_STRING) {
return false;
}
}
// Examine what comes after the namespace/parent/self/double colon, if anything.
return $this->isStaticValue($phpcsFile, $tokens, ($nextNonEmpty + 1), $end, $nestedArrays);
case \T_ARRAY:
case \T_OPEN_SHORT_ARRAY:
++$nestedArrays;
$arrayItems = $this->getFunctionCallParameters($phpcsFile, $nextNonSimple);
if (empty($arrayItems) === false) {
foreach ($arrayItems as $item) {
// Check for a double arrow, but only if it's for this array item, not for a nested array.
$doubleArrow = false;
$maybeDoubleArrow = $phpcsFile->findNext(
array(\T_DOUBLE_ARROW, \T_ARRAY, \T_OPEN_SHORT_ARRAY),
$item['start'],
($item['end'] + 1)
);
if ($maybeDoubleArrow !== false && $tokens[$maybeDoubleArrow]['code'] === \T_DOUBLE_ARROW) {
// Double arrow is for this nesting level.
$doubleArrow = $maybeDoubleArrow;
}
if ($doubleArrow === false) {
if ($this->isStaticValue($phpcsFile, $tokens, $item['start'], $item['end'], $nestedArrays) === false) {
return false;
}
} else {
// Examine array key.
if ($this->isStaticValue($phpcsFile, $tokens, $item['start'], ($doubleArrow - 1), $nestedArrays) === false) {
return false;
}
// Examine array value.
if ($this->isStaticValue($phpcsFile, $tokens, ($doubleArrow + 1), $item['end'], $nestedArrays) === false) {
return false;
}
}
}
}
--$nestedArrays;
/*
* Find the end of the array.
* We already know we will have a valid closer as otherwise we wouldn't have been
* able to get the array items.
*/
$closer = ($nextNonSimple + 1);
if ($tokens[$nextNonSimple]['code'] === \T_OPEN_SHORT_ARRAY
&& isset($tokens[$nextNonSimple]['bracket_closer']) === true
) {
$closer = $tokens[$nextNonSimple]['bracket_closer'];
} else {
$maybeOpener = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonSimple + 1), ($end + 1), true);
if ($tokens[$maybeOpener]['code'] === \T_OPEN_PARENTHESIS) {
$opener = $maybeOpener;
if (isset($tokens[$opener]['parenthesis_closer']) === true) {
$closer = $tokens[$opener]['parenthesis_closer'];
}
}
}
if ($closer === $end) {
return true;
}
// Examine what comes after the array, if anything.
return $this->isStaticValue($phpcsFile, $tokens, ($closer + 1), $end, $nestedArrays);
}
// Ok, so this unsafe token was not one of the exceptions, i.e. this is a PHP 5.6+ syntax.
return false;
}
/**
* Throw an error if a scalar expression is found.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the token to link the error to.
* @param string $type Type of usage found.
* @param string $content Optional. The value for the declaration as found.
*
* @return void
*/
protected function throwError(File $phpcsFile, $stackPtr, $type, $content = '')
{
$error = static::ERROR_PHRASE;
$phrase = '';
$errorCode = 'Found';
if (isset($this->errorPhrases[$type]) === true) {
$errorCode = $this->stringToErrorCode($type) . 'Found';
$phrase = $this->errorPhrases[$type];
}
$data = array($phrase);
if (empty($content) === false) {
$error .= ' Found: %s';
$data[] = $content;
}
$phpcsFile->addError($error, $stackPtr, $errorCode, $data);
}
/**
* Helper function to find the end of multi variable/constant declarations.
*
* Checks whether a certain part of a declaration needs to be skipped over or
* if it is the real end of the declaration.
*
* @since 8.2.0
*
* @param array $tokens Token stack of the current file.
* @param int $endPtr The token to examine as a candidate end pointer.
* @param int $targetLevel Target nesting level.
*
* @return bool|int True if this is the real end. Int stackPtr to skip to if not.
*/
private function isRealEndOfDeclaration($tokens, $endPtr, $targetLevel)
{
// Ignore anything within short array definition brackets for now.
if ($tokens[$endPtr]['code'] === \T_OPEN_SHORT_ARRAY
&& (isset($tokens[$endPtr]['bracket_opener'])
&& $tokens[$endPtr]['bracket_opener'] === $endPtr)
&& isset($tokens[$endPtr]['bracket_closer'])
) {
// Skip forward to the end of the short array definition.
return $tokens[$endPtr]['bracket_closer'];
}
// Skip past comma's at a lower nesting level.
if ($tokens[$endPtr]['code'] === \T_COMMA) {
// Check if a comma is at the nesting level we're targetting.
$nestingLevel = 0;
if (isset($tokens[$endPtr]['nested_parenthesis']) === true) {
$nestingLevel = \count($tokens[$endPtr]['nested_parenthesis']);
}
if ($nestingLevel > $targetLevel) {
return $endPtr;
}
}
return true;
}
}
@@ -0,0 +1,100 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\InitialValue;
use PHPCompatibility\Sniffs\InitialValue\NewConstantScalarExpressionsSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect a heredoc being used to set an initial value.
*
* As of PHP 5.3.0, it's possible to initialize static variables, class properties
* and constants declared using the `const` keyword, using the Heredoc syntax.
* And while not documented, heredoc initialization can now also be used for function param defaults.
* See: https://3v4l.org/JVH8W
*
* These heredocs (still) cannot contain variables. That's, however, outside the scope of the
* PHPCompatibility library until such time as there is a PHP version in which this would be accepted.
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/migration53.new-features.php
* @link https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
*
* @since 7.1.4
* @since 8.2.0 Now extends the NewConstantScalarExpressionsSniff instead of the base Sniff class.
* @since 9.0.0 Renamed from `NewHeredocInitializeSniff` to `NewHeredocSniff`.
*/
class NewHeredocSniff extends NewConstantScalarExpressionsSniff
{
/**
* Error message.
*
* @since 8.2.0
*
* @var string
*/
const ERROR_PHRASE = 'Initializing %s using the Heredoc syntax was not supported in PHP 5.2 or earlier';
/**
* Partial error phrases to be used in combination with the error message constant.
*
* @since 8.2.0
*
* @var array
*/
protected $errorPhrases = array(
'const' => 'constants',
'property' => 'class properties',
'staticvar' => 'static variables',
'default' => 'default parameter values',
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 8.2.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('5.2') !== true);
}
/**
* Is a value declared and does the declared value not contain an heredoc ?
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
* @param int $end The end of the value definition.
*
* @return bool True if no heredoc (or assignment) is found, false otherwise.
*/
protected function isValidAssignment(File $phpcsFile, $stackPtr, $end)
{
$tokens = $phpcsFile->getTokens();
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), $end, true);
if ($next === false || $tokens[$next]['code'] !== \T_EQUAL) {
// No value assigned.
return true;
}
return ($phpcsFile->findNext(\T_START_HEREDOC, ($next + 1), $end, false, null, true) === false);
}
}
@@ -0,0 +1,103 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Interfaces;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
/**
* Detect classes which implement PHP native interfaces intended only for PHP internal use.
*
* PHP version 5.0+
*
* @link https://www.php.net/manual/en/class.traversable.php
* @link https://www.php.net/manual/en/class.throwable.php
* @link https://www.php.net/manual/en/class.datetimeinterface.php
*
* @since 7.0.3
*/
class InternalInterfacesSniff extends Sniff
{
/**
* A list of PHP internal interfaces, not intended to be implemented by userland classes.
*
* The array lists : the error message to use.
*
* @since 7.0.3
*
* @var array(string => string)
*/
protected $internalInterfaces = array(
'Traversable' => 'shouldn\'t be implemented directly, implement the Iterator or IteratorAggregate interface instead.',
'DateTimeInterface' => 'is intended for type hints only and is not implementable.',
'Throwable' => 'cannot be implemented directly, extend the Exception class instead.',
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.3
*
* @return array
*/
public function register()
{
// Handle case-insensitivity of interface names.
$this->internalInterfaces = $this->arrayKeysToLowercase($this->internalInterfaces);
$targets = array(\T_CLASS);
if (\defined('T_ANON_CLASS')) {
$targets[] = \T_ANON_CLASS;
}
return $targets;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.3
*
* @param \PHP_CodeSniffer_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)
{
$interfaces = PHPCSHelper::findImplementedInterfaceNames($phpcsFile, $stackPtr);
if (\is_array($interfaces) === false || $interfaces === array()) {
return;
}
foreach ($interfaces as $interface) {
$interface = ltrim($interface, '\\');
$interfaceLc = strtolower($interface);
if (isset($this->internalInterfaces[$interfaceLc]) === true) {
$error = 'The interface %s %s';
$errorCode = $this->stringToErrorCode($interfaceLc) . 'Found';
$data = array(
$interface,
$this->internalInterfaces[$interfaceLc],
);
$phpcsFile->addError($error, $stackPtr, $errorCode, $data);
}
}
}
}
@@ -0,0 +1,362 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Interfaces;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
/**
* Detect use of new PHP native interfaces and unsupported interface methods.
*
* PHP version 5.0+
*
* @since 7.0.3
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class..
* @since 7.1.4 Now also detects new interfaces when used as parameter type declarations.
* @since 8.2.0 Now also detects new interfaces when used as return type declarations.
*/
class NewInterfacesSniff extends AbstractNewFeatureSniff
{
/**
* A list of new interfaces, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the interface appears.
*
* @since 7.0.3
*
* @var array(string => array(string => bool))
*/
protected $newInterfaces = array(
'Traversable' => array(
'4.4' => false,
'5.0' => true,
),
'Reflector' => array(
'4.4' => false,
'5.0' => true,
),
'Countable' => array(
'5.0' => false,
'5.1' => true,
),
'OuterIterator' => array(
'5.0' => false,
'5.1' => true,
),
'RecursiveIterator' => array(
'5.0' => false,
'5.1' => true,
),
'SeekableIterator' => array(
'5.0' => false,
'5.1' => true,
),
'Serializable' => array(
'5.0' => false,
'5.1' => true,
),
'SplObserver' => array(
'5.0' => false,
'5.1' => true,
),
'SplSubject' => array(
'5.0' => false,
'5.1' => true,
),
'JsonSerializable' => array(
'5.3' => false,
'5.4' => true,
),
'SessionHandlerInterface' => array(
'5.3' => false,
'5.4' => true,
),
'DateTimeInterface' => array(
'5.4' => false,
'5.5' => true,
),
'SessionIdInterface' => array(
'5.5.0' => false,
'5.5.1' => true,
),
'Throwable' => array(
'5.6' => false,
'7.0' => true,
),
'SessionUpdateTimestampHandlerInterface' => array(
'5.6' => false,
'7.0' => true,
),
);
/**
* A list of methods which cannot be used in combination with particular interfaces.
*
* @since 7.0.3
*
* @var array(string => array(string => string))
*/
protected $unsupportedMethods = array(
'Serializable' => array(
'__sleep' => 'https://www.php.net/serializable',
'__wakeup' => 'https://www.php.net/serializable',
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.3
*
* @return array
*/
public function register()
{
// Handle case-insensitivity of interface names.
$this->newInterfaces = $this->arrayKeysToLowercase($this->newInterfaces);
$this->unsupportedMethods = $this->arrayKeysToLowercase($this->unsupportedMethods);
$targets = array(
\T_CLASS,
\T_FUNCTION,
\T_CLOSURE,
);
if (\defined('T_ANON_CLASS')) {
$targets[] = \T_ANON_CLASS;
}
if (\defined('T_RETURN_TYPE')) {
$targets[] = \T_RETURN_TYPE;
}
return $targets;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.3
*
* @param \PHP_CodeSniffer_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();
switch ($tokens[$stackPtr]['type']) {
case 'T_CLASS':
case 'T_ANON_CLASS':
$this->processClassToken($phpcsFile, $stackPtr);
break;
case 'T_FUNCTION':
case 'T_CLOSURE':
$this->processFunctionToken($phpcsFile, $stackPtr);
// Deal with older PHPCS versions which don't recognize return type hints
// as well as newer PHPCS versions (3.3.0+) where the tokenization has changed.
$returnTypeHint = $this->getReturnTypeHintToken($phpcsFile, $stackPtr);
if ($returnTypeHint !== false) {
$this->processReturnTypeToken($phpcsFile, $returnTypeHint);
}
break;
case 'T_RETURN_TYPE':
$this->processReturnTypeToken($phpcsFile, $stackPtr);
break;
default:
// Deliberately left empty.
break;
}
}
/**
* Processes this test for when a class token is encountered.
*
* - Detect classes implementing the new interfaces.
* - Detect classes implementing the new interfaces with unsupported functions.
*
* @since 7.1.4 Split off from the `process()` method.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processClassToken(File $phpcsFile, $stackPtr)
{
$interfaces = PHPCSHelper::findImplementedInterfaceNames($phpcsFile, $stackPtr);
if (\is_array($interfaces) === false || $interfaces === array()) {
return;
}
$tokens = $phpcsFile->getTokens();
$checkMethods = false;
if (isset($tokens[$stackPtr]['scope_closer'])) {
$checkMethods = true;
$scopeCloser = $tokens[$stackPtr]['scope_closer'];
}
foreach ($interfaces as $interface) {
$interface = ltrim($interface, '\\');
$interfaceLc = strtolower($interface);
if (isset($this->newInterfaces[$interfaceLc]) === true) {
$itemInfo = array(
'name' => $interface,
'nameLc' => $interfaceLc,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
if ($checkMethods === true && isset($this->unsupportedMethods[$interfaceLc]) === true) {
$nextFunc = $stackPtr;
while (($nextFunc = $phpcsFile->findNext(\T_FUNCTION, ($nextFunc + 1), $scopeCloser)) !== false) {
$funcName = $phpcsFile->getDeclarationName($nextFunc);
$funcNameLc = strtolower($funcName);
if ($funcNameLc === '') {
continue;
}
if (isset($this->unsupportedMethods[$interfaceLc][$funcNameLc]) === true) {
$error = 'Classes that implement interface %s do not support the method %s(). See %s';
$errorCode = $this->stringToErrorCode($interface) . 'UnsupportedMethod';
$data = array(
$interface,
$funcName,
$this->unsupportedMethods[$interfaceLc][$funcNameLc],
);
$phpcsFile->addError($error, $nextFunc, $errorCode, $data);
}
}
}
}
}
/**
* Processes this test for when a function token is encountered.
*
* - Detect new interfaces when used as a type hint.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processFunctionToken(File $phpcsFile, $stackPtr)
{
$typeHints = $this->getTypeHintsFromFunctionDeclaration($phpcsFile, $stackPtr);
if (empty($typeHints) || \is_array($typeHints) === false) {
return;
}
foreach ($typeHints as $hint) {
$typeHintLc = strtolower($hint);
if (isset($this->newInterfaces[$typeHintLc]) === true) {
$itemInfo = array(
'name' => $hint,
'nameLc' => $typeHintLc,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
}
}
/**
* Processes this test for when a return type token is encountered.
*
* - Detect new interfaces when used as a return type declaration.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processReturnTypeToken(File $phpcsFile, $stackPtr)
{
$returnTypeHint = $this->getReturnTypeHintName($phpcsFile, $stackPtr);
if (empty($returnTypeHint)) {
return;
}
$returnTypeHint = ltrim($returnTypeHint, '\\');
$returnTypeHintLc = strtolower($returnTypeHint);
if (isset($this->newInterfaces[$returnTypeHintLc]) === false) {
return;
}
// Still here ? Then this is a return type declaration using a new interface.
$itemInfo = array(
'name' => $returnTypeHint,
'nameLc' => $returnTypeHintLc,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newInterfaces[$itemInfo['nameLc']];
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'The built-in interface ' . parent::getErrorMsgTemplate();
}
}
@@ -0,0 +1,76 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Keywords;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect usage of `self`, `parent` and `static` and verify they are lowercase.
*
* Prior to PHP 5.5, cases existed where the `self`, `parent`, and `static` keywords
* were treated in a case sensitive fashion.
*
* PHP version 5.5
*
* @link https://www.php.net/manual/en/migration55.incompatible.php#migration55.incompatible.self-parent-static
*
* @since 7.1.4
*/
class CaseSensitiveKeywordsSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.1.4
*
* @return array
*/
public function register()
{
return array(
\T_SELF,
\T_STATIC,
\T_PARENT,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$tokenContentLC = strtolower($tokens[$stackPtr]['content']);
if ($tokenContentLC !== $tokens[$stackPtr]['content']) {
$phpcsFile->addError(
'The keyword \'%s\' was treated in a case-sensitive fashion in certain cases in PHP 5.4 or earlier. Use the lowercase version for consistent support.',
$stackPtr,
'NonLowercaseFound',
array($tokenContentLC)
);
}
}
}
@@ -0,0 +1,259 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Keywords;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detects the use of some reserved keywords to name a class, interface, trait or namespace.
*
* Emits errors for reserved words and warnings for soft-reserved words.
*
* PHP version 7.0+
*
* @link https://www.php.net/manual/en/reserved.other-reserved-words.php
* @link https://wiki.php.net/rfc/reserve_more_types_in_php_7
*
* @since 7.0.8
* @since 7.1.4 This sniff now throws a warning (soft reserved) or an error (reserved) depending
* on the `testVersion` set. Previously it would always throw an error.
*/
class ForbiddenNamesAsDeclaredSniff extends Sniff
{
/**
* List of tokens which can not be used as class, interface, trait names or as part of a namespace.
*
* @since 7.0.8
*
* @var array
*/
protected $forbiddenTokens = array(
\T_NULL => '7.0',
\T_TRUE => '7.0',
\T_FALSE => '7.0',
);
/**
* T_STRING keywords to recognize as forbidden names.
*
* @since 7.0.8
*
* @var array
*/
protected $forbiddenNames = array(
'null' => '7.0',
'true' => '7.0',
'false' => '7.0',
'bool' => '7.0',
'int' => '7.0',
'float' => '7.0',
'string' => '7.0',
'iterable' => '7.1',
'void' => '7.1',
'object' => '7.2',
);
/**
* T_STRING keywords to recognize as soft reserved names.
*
* Using any of these keywords to name a class, interface, trait or namespace
* is highly discouraged since they may be used in future versions of PHP.
*
* @since 7.0.8
*
* @var array
*/
protected $softReservedNames = array(
'resource' => '7.0',
'object' => '7.0',
'mixed' => '7.0',
'numeric' => '7.0',
);
/**
* Combined list of the two lists above.
*
* Used for quick check whether or not something is a reserved
* word.
* Set from the `register()` method.
*
* @since 7.0.8
*
* @var array
*/
private $allForbiddenNames = array();
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.8
*
* @return array
*/
public function register()
{
// Do the list merge only once.
$this->allForbiddenNames = array_merge($this->forbiddenNames, $this->softReservedNames);
$targets = array(
\T_CLASS,
\T_INTERFACE,
\T_TRAIT,
\T_NAMESPACE,
\T_STRING, // Compat for PHPCS < 2.4.0 and PHP < 5.3.
);
return $targets;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.8
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$tokenCode = $tokens[$stackPtr]['code'];
$tokenType = $tokens[$stackPtr]['type'];
$tokenContentLc = strtolower($tokens[$stackPtr]['content']);
// For string tokens we only care about 'trait' as that is the only one
// which may not be correctly recognized as it's own token.
// This only happens in older versions of PHP where the token doesn't exist yet as a keyword.
if ($tokenCode === \T_STRING && $tokenContentLc !== 'trait') {
return;
}
if (\in_array($tokenType, array('T_CLASS', 'T_INTERFACE', 'T_TRAIT'), true)) {
// Check for the declared name being a name which is not tokenized as T_STRING.
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty !== false && isset($this->forbiddenTokens[$tokens[$nextNonEmpty]['code']]) === true) {
$name = $tokens[$nextNonEmpty]['content'];
} else {
// Get the declared name if it's a T_STRING.
$name = $phpcsFile->getDeclarationName($stackPtr);
}
unset($nextNonEmpty);
if (isset($name) === false || \is_string($name) === false || $name === '') {
return;
}
$nameLc = strtolower($name);
if (isset($this->allForbiddenNames[$nameLc]) === false) {
return;
}
} elseif ($tokenCode === \T_NAMESPACE) {
$namespaceName = $this->getDeclaredNamespaceName($phpcsFile, $stackPtr);
if ($namespaceName === false || $namespaceName === '') {
return;
}
$namespaceParts = explode('\\', $namespaceName);
foreach ($namespaceParts as $namespacePart) {
$partLc = strtolower($namespacePart);
if (isset($this->allForbiddenNames[$partLc]) === true) {
$name = $namespacePart;
$nameLc = $partLc;
break;
}
}
} elseif ($tokenCode === \T_STRING) {
// Traits which are not yet tokenized as T_TRAIT.
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false) {
return;
}
$nextNonEmptyCode = $tokens[$nextNonEmpty]['code'];
if ($nextNonEmptyCode !== \T_STRING && isset($this->forbiddenTokens[$nextNonEmptyCode]) === true) {
$name = $tokens[$nextNonEmpty]['content'];
$nameLc = strtolower($tokens[$nextNonEmpty]['content']);
} elseif ($nextNonEmptyCode === \T_STRING) {
$endOfStatement = $phpcsFile->findNext(array(\T_SEMICOLON, \T_OPEN_CURLY_BRACKET), ($stackPtr + 1));
if ($endOfStatement === false) {
return;
}
do {
$nextNonEmptyLc = strtolower($tokens[$nextNonEmpty]['content']);
if (isset($this->allForbiddenNames[$nextNonEmptyLc]) === true) {
$name = $tokens[$nextNonEmpty]['content'];
$nameLc = $nextNonEmptyLc;
break;
}
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $endOfStatement, true);
} while ($nextNonEmpty !== false);
}
unset($nextNonEmptyCode, $nextNonEmptyLc, $endOfStatement);
}
if (isset($name, $nameLc) === false) {
return;
}
// Still here, so this is one of the reserved words.
// Build up the error message.
$error = "'%s' is a";
$isError = null;
$errorCode = $this->stringToErrorCode($nameLc) . 'Found';
$data = array(
$nameLc,
);
if (isset($this->softReservedNames[$nameLc]) === true
&& $this->supportsAbove($this->softReservedNames[$nameLc]) === true
) {
$error .= ' soft reserved keyword as of PHP version %s';
$isError = false;
$data[] = $this->softReservedNames[$nameLc];
}
if (isset($this->forbiddenNames[$nameLc]) === true
&& $this->supportsAbove($this->forbiddenNames[$nameLc]) === true
) {
if (isset($isError) === true) {
$error .= ' and a';
}
$error .= ' reserved keyword as of PHP version %s';
$isError = true;
$data[] = $this->forbiddenNames[$nameLc];
}
if (isset($isError) === true) {
$error .= ' and should not be used to name a class, interface or trait or as part of a namespace (%s)';
$data[] = $tokens[$stackPtr]['type'];
$this->addMessage($phpcsFile, $error, $stackPtr, $isError, $errorCode, $data);
}
}
}
@@ -0,0 +1,188 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Keywords;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Prohibits the use of reserved keywords invoked as functions.
*
* PHP version All
*
* @link https://www.php.net/manual/en/reserved.keywords.php
*
* @since 5.5
*/
class ForbiddenNamesAsInvokedFunctionsSniff extends Sniff
{
/**
* List of tokens to register.
*
* @since 5.5
*
* @var array
*/
protected $targetedTokens = array(
\T_ABSTRACT => '5.0',
\T_CALLABLE => '5.4',
\T_CATCH => '5.0',
\T_FINAL => '5.0',
\T_FINALLY => '5.5',
\T_GOTO => '5.3',
\T_IMPLEMENTS => '5.0',
\T_INTERFACE => '5.0',
\T_INSTANCEOF => '5.0',
\T_INSTEADOF => '5.4',
\T_NAMESPACE => '5.3',
\T_PRIVATE => '5.0',
\T_PROTECTED => '5.0',
\T_PUBLIC => '5.0',
\T_TRAIT => '5.4',
\T_TRY => '5.0',
);
/**
* T_STRING keywords to recognize as targetted tokens.
*
* Compatibility for PHP versions where the keyword is not yet recognized
* as its own token and for PHPCS versions which change the token to
* T_STRING when used in a method call.
*
* @since 5.5
*
* @var array
*/
protected $targetedStringTokens = array(
'abstract' => '5.0',
'callable' => '5.4',
'catch' => '5.0',
'final' => '5.0',
'finally' => '5.5',
'goto' => '5.3',
'implements' => '5.0',
'interface' => '5.0',
'instanceof' => '5.0',
'insteadof' => '5.4',
'namespace' => '5.3',
'private' => '5.0',
'protected' => '5.0',
'public' => '5.0',
'trait' => '5.4',
'try' => '5.0',
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
$tokens = array_keys($this->targetedTokens);
$tokens[] = \T_STRING;
return $tokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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();
$tokenCode = $tokens[$stackPtr]['code'];
$tokenContentLc = strtolower($tokens[$stackPtr]['content']);
$isString = false;
/*
* For string tokens we only care if the string is a reserved word used
* as a function. This only happens in older versions of PHP where the
* token doesn't exist yet for that keyword or in later versions when the
* token is used in a method invocation.
*/
if ($tokenCode === \T_STRING
&& (isset($this->targetedStringTokens[$tokenContentLc]) === false)
) {
return;
}
if ($tokenCode === \T_STRING) {
$isString = true;
}
// Make sure this is a function call.
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($next === false || $tokens[$next]['code'] !== \T_OPEN_PARENTHESIS) {
// Not a function call.
return;
}
// This sniff isn't concerned about function declarations.
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($prev !== false && $tokens[$prev]['code'] === \T_FUNCTION) {
return;
}
/*
* Deal with PHP 7 relaxing the rules.
* "As of PHP 7.0.0 these keywords are allowed as property, constant, and method names
* of classes, interfaces and traits...", i.e. they can be invoked as a method call.
*
* Only needed for those keywords which we sniff out via T_STRING.
*/
if (($tokens[$prev]['code'] === \T_OBJECT_OPERATOR || $tokens[$prev]['code'] === \T_DOUBLE_COLON)
&& $this->supportsBelow('5.6') === false
) {
return;
}
// For the word catch, it is valid to have an open parenthesis
// after it, but only if it is preceded by a right curly brace.
if ($tokenCode === \T_CATCH) {
if ($prev !== false && $tokens[$prev]['code'] === \T_CLOSE_CURLY_BRACKET) {
// Ok, it's fine.
return;
}
}
if ($isString === true) {
$version = $this->targetedStringTokens[$tokenContentLc];
} else {
$version = $this->targetedTokens[$tokenCode];
}
if ($this->supportsAbove($version)) {
$error = "'%s' is a reserved keyword introduced in PHP version %s and cannot be invoked as a function (%s)";
$errorCode = $this->stringToErrorCode($tokenContentLc) . 'Found';
$data = array(
$tokenContentLc,
$version,
$tokens[$stackPtr]['type'],
);
$phpcsFile->addError($error, $stackPtr, $errorCode, $data);
}
}
}
@@ -0,0 +1,442 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Keywords;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detects the use of reserved keywords as class, function, namespace or constant names.
*
* PHP version All
*
* @link https://www.php.net/manual/en/reserved.keywords.php
*
* @since 5.5
*/
class ForbiddenNamesSniff extends Sniff
{
/**
* A list of keywords that can not be used as function, class and namespace name or constant name.
* Mentions since which version it's not allowed.
*
* @since 5.5
*
* @var array(string => string)
*/
protected $invalidNames = array(
'abstract' => '5.0',
'and' => 'all',
'array' => 'all',
'as' => 'all',
'break' => 'all',
'callable' => '5.4',
'case' => 'all',
'catch' => '5.0',
'class' => 'all',
'clone' => '5.0',
'const' => 'all',
'continue' => 'all',
'declare' => 'all',
'default' => 'all',
'die' => 'all',
'do' => 'all',
'echo' => 'all',
'else' => 'all',
'elseif' => 'all',
'empty' => 'all',
'enddeclare' => 'all',
'endfor' => 'all',
'endforeach' => 'all',
'endif' => 'all',
'endswitch' => 'all',
'endwhile' => 'all',
'eval' => 'all',
'exit' => 'all',
'extends' => 'all',
'final' => '5.0',
'finally' => '5.5',
'for' => 'all',
'foreach' => 'all',
'function' => 'all',
'global' => 'all',
'goto' => '5.3',
'if' => 'all',
'implements' => '5.0',
'include' => 'all',
'include_once' => 'all',
'instanceof' => '5.0',
'insteadof' => '5.4',
'interface' => '5.0',
'isset' => 'all',
'list' => 'all',
'namespace' => '5.3',
'new' => 'all',
'or' => 'all',
'print' => 'all',
'private' => '5.0',
'protected' => '5.0',
'public' => '5.0',
'require' => 'all',
'require_once' => 'all',
'return' => 'all',
'static' => 'all',
'switch' => 'all',
'throw' => '5.0',
'trait' => '5.4',
'try' => '5.0',
'unset' => 'all',
'use' => 'all',
'var' => 'all',
'while' => 'all',
'xor' => 'all',
'yield' => '5.5',
'__class__' => 'all',
'__dir__' => '5.3',
'__file__' => 'all',
'__function__' => 'all',
'__method__' => 'all',
'__namespace__' => '5.3',
);
/**
* A list of keywords that can follow use statements.
*
* @since 7.0.1
*
* @var array(string => string)
*/
protected $validUseNames = array(
'const' => true,
'function' => true,
);
/**
* Scope modifiers and other keywords allowed in trait use statements.
*
* @since 7.1.4
*
* @var array
*/
private $allowedModifiers = array();
/**
* Targeted tokens.
*
* @since 5.5
*
* @var array
*/
protected $targetedTokens = array(
\T_CLASS,
\T_FUNCTION,
\T_NAMESPACE,
\T_STRING,
\T_CONST,
\T_USE,
\T_AS,
\T_EXTENDS,
\T_INTERFACE,
\T_TRAIT,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
$this->allowedModifiers = Tokens::$scopeModifiers;
$this->allowedModifiers[\T_FINAL] = \T_FINAL;
$tokens = $this->targetedTokens;
if (\defined('T_ANON_CLASS')) {
$tokens[] = \T_ANON_CLASS;
}
return $tokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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();
/*
* We distinguish between the class, function and namespace names vs the define statements.
*/
if ($tokens[$stackPtr]['type'] === 'T_STRING') {
$this->processString($phpcsFile, $stackPtr, $tokens);
} else {
$this->processNonString($phpcsFile, $stackPtr, $tokens);
}
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
* @param array $tokens The stack of tokens that make up
* the file.
*
* @return void
*/
public function processNonString(File $phpcsFile, $stackPtr, $tokens)
{
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false) {
return;
}
/*
* Deal with anonymous classes - `class` before a reserved keyword is sometimes
* misidentified as `T_ANON_CLASS`.
* In PHPCS < 2.3.4 these were tokenized as T_CLASS no matter what.
*/
if ($tokens[$stackPtr]['type'] === 'T_ANON_CLASS' || $tokens[$stackPtr]['type'] === 'T_CLASS') {
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($prevNonEmpty !== false && $tokens[$prevNonEmpty]['type'] === 'T_NEW') {
return;
}
}
/*
* PHP 5.6 allows for use const and use function, but only if followed by the function/constant name.
* - `use function HelloWorld` => move to the next token (HelloWorld) to verify.
* - `use const HelloWorld` => move to the next token (HelloWorld) to verify.
*/
elseif ($tokens[$stackPtr]['type'] === 'T_USE'
&& isset($this->validUseNames[strtolower($tokens[$nextNonEmpty]['content'])]) === true
) {
$maybeUseNext = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true, null, true);
if ($maybeUseNext !== false && $this->isEndOfUseStatement($tokens[$maybeUseNext]) === false) {
$nextNonEmpty = $maybeUseNext;
}
}
/*
* Deal with visibility modifiers.
* - `use HelloWorld { sayHello as protected; }` => valid, bow out.
* - `use HelloWorld { sayHello as private myPrivateHello; }` => move to the next token to verify.
*/
elseif ($tokens[$stackPtr]['type'] === 'T_AS'
&& isset($this->allowedModifiers[$tokens[$nextNonEmpty]['code']]) === true
&& $phpcsFile->hasCondition($stackPtr, \T_USE) === true
) {
$maybeUseNext = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true, null, true);
if ($maybeUseNext === false || $this->isEndOfUseStatement($tokens[$maybeUseNext]) === true) {
return;
}
$nextNonEmpty = $maybeUseNext;
}
/*
* Deal with foreach ( ... as list() ).
*/
elseif ($tokens[$stackPtr]['type'] === 'T_AS'
&& isset($tokens[$stackPtr]['nested_parenthesis']) === true
&& $tokens[$nextNonEmpty]['code'] === \T_LIST
) {
$parentheses = array_reverse($tokens[$stackPtr]['nested_parenthesis'], true);
foreach ($parentheses as $open => $close) {
if (isset($tokens[$open]['parenthesis_owner'])
&& $tokens[$tokens[$open]['parenthesis_owner']]['code'] === \T_FOREACH
) {
return;
}
}
}
/*
* Deal with functions declared to return by reference.
*/
elseif ($tokens[$stackPtr]['type'] === 'T_FUNCTION'
&& $tokens[$nextNonEmpty]['type'] === 'T_BITWISE_AND'
) {
$maybeUseNext = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true, null, true);
if ($maybeUseNext === false) {
// Live coding.
return;
}
$nextNonEmpty = $maybeUseNext;
}
/*
* Deal with nested namespaces.
*/
elseif ($tokens[$stackPtr]['type'] === 'T_NAMESPACE') {
if ($tokens[$stackPtr + 1]['code'] === \T_NS_SEPARATOR) {
// Not a namespace declaration, but use of, i.e. `namespace\someFunction();`.
return;
}
$endToken = $phpcsFile->findNext(array(\T_SEMICOLON, \T_OPEN_CURLY_BRACKET), ($stackPtr + 1), null, false, null, true);
$namespaceName = trim($phpcsFile->getTokensAsString(($stackPtr + 1), ($endToken - $stackPtr - 1)));
if (empty($namespaceName) === true) {
return;
}
$namespaceParts = explode('\\', $namespaceName);
foreach ($namespaceParts as $namespacePart) {
$partLc = strtolower($namespacePart);
if (isset($this->invalidNames[$partLc]) === false) {
continue;
}
// Find the token position of the part which matched.
for ($i = ($stackPtr + 1); $i < $endToken; $i++) {
if ($tokens[$i]['content'] === $namespacePart) {
$nextNonEmpty = $i;
break;
}
}
}
unset($i, $namespacePart, $partLc);
}
$nextContentLc = strtolower($tokens[$nextNonEmpty]['content']);
if (isset($this->invalidNames[$nextContentLc]) === false) {
return;
}
/*
* Deal with PHP 7 relaxing the rules.
* "As of PHP 7.0.0 these keywords are allowed as property, constant, and method names
* of classes, interfaces and traits, except that class may not be used as constant name."
*/
if ((($tokens[$stackPtr]['type'] === 'T_FUNCTION'
&& $this->inClassScope($phpcsFile, $stackPtr, false) === true)
|| ($tokens[$stackPtr]['type'] === 'T_CONST'
&& $this->isClassConstant($phpcsFile, $stackPtr) === true
&& $nextContentLc !== 'class'))
&& $this->supportsBelow('5.6') === false
) {
return;
}
if ($this->supportsAbove($this->invalidNames[$nextContentLc])) {
$data = array(
$tokens[$nextNonEmpty]['content'],
$this->invalidNames[$nextContentLc],
);
$this->addError($phpcsFile, $stackPtr, $tokens[$nextNonEmpty]['content'], $data);
}
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
* @param array $tokens The stack of tokens that make up
* the file.
*
* @return void
*/
public function processString(File $phpcsFile, $stackPtr, $tokens)
{
$tokenContentLc = strtolower($tokens[$stackPtr]['content']);
/*
* Special case for PHP versions where the target is not yet identified as
* its own token, but presents as T_STRING.
* - trait keyword in PHP < 5.4
*/
if (version_compare(\PHP_VERSION_ID, '50400', '<') && $tokenContentLc === 'trait') {
$this->processNonString($phpcsFile, $stackPtr, $tokens);
return;
}
// Look for any define/defined tokens (both T_STRING ones, blame Tokenizer).
if ($tokenContentLc !== 'define' && $tokenContentLc !== 'defined') {
return;
}
// Retrieve the define(d) constant name.
$firstParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, 1);
if ($firstParam === false) {
return;
}
$defineName = $this->stripQuotes($firstParam['raw']);
$defineNameLc = strtolower($defineName);
if (isset($this->invalidNames[$defineNameLc]) && $this->supportsAbove($this->invalidNames[$defineNameLc])) {
$data = array(
$defineName,
$this->invalidNames[$defineNameLc],
);
$this->addError($phpcsFile, $stackPtr, $defineNameLc, $data);
}
}
/**
* Add the error message.
*
* @since 7.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
* @param string $content The token content found.
* @param array $data The data to pass into the error message.
*
* @return void
*/
protected function addError(File $phpcsFile, $stackPtr, $content, $data)
{
$error = "Function name, class name, namespace name or constant name can not be reserved keyword '%s' (since version %s)";
$errorCode = $this->stringToErrorCode($content) . 'Found';
$phpcsFile->addError($error, $stackPtr, $errorCode, $data);
}
/**
* Check if the current token code is for a token which can be considered
* the end of a (partial) use statement.
*
* @since 7.0.8
*
* @param int $token The current token information.
*
* @return bool
*/
protected function isEndOfUseStatement($token)
{
return \in_array($token['code'], array(\T_CLOSE_CURLY_BRACKET, \T_SEMICOLON, \T_COMMA), true);
}
}
@@ -0,0 +1,391 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Keywords;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect use of new PHP keywords.
*
* PHP version All
*
* @link https://wiki.php.net/rfc/heredoc-with-double-quotes
* @link https://wiki.php.net/rfc/horizontalreuse (traits)
* @link https://wiki.php.net/rfc/generators
* @link https://wiki.php.net/rfc/finally
* @link https://wiki.php.net/rfc/generator-delegation
*
* @since 5.5
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class..
*/
class NewKeywordsSniff extends AbstractNewFeatureSniff
{
/**
* A list of new keywords, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the last version which did not contain the keyword.
*
* Description will be used as part of the error message.
* Condition is the name of a callback method within this class or the parent class
* which checks whether the token complies with a certain condition.
* The callback function will be passed the $phpcsFile and the $stackPtr.
* The callback function should return `true` if the condition is met and the
* error should *not* be thrown.
*
* @since 5.5
* @since 7.0.3 Support for 'condition' has been added.
*
* @var array(string => array(string => bool|string))
*/
protected $newKeywords = array(
'T_HALT_COMPILER' => array(
'5.0' => false,
'5.1' => true,
'description' => '"__halt_compiler" keyword',
),
'T_CONST' => array(
'5.2' => false,
'5.3' => true,
'description' => '"const" keyword',
'condition' => 'isClassConstant', // Keyword is only new when not in class context.
),
'T_CALLABLE' => array(
'5.3' => false,
'5.4' => true,
'description' => '"callable" keyword',
'content' => 'callable',
),
'T_DIR' => array(
'5.2' => false,
'5.3' => true,
'description' => '__DIR__ magic constant',
'content' => '__DIR__',
),
'T_GOTO' => array(
'5.2' => false,
'5.3' => true,
'description' => '"goto" keyword',
'content' => 'goto',
),
'T_INSTEADOF' => array(
'5.3' => false,
'5.4' => true,
'description' => '"insteadof" keyword (for traits)',
'content' => 'insteadof',
),
'T_NAMESPACE' => array(
'5.2' => false,
'5.3' => true,
'description' => '"namespace" keyword',
'content' => 'namespace',
),
'T_NS_C' => array(
'5.2' => false,
'5.3' => true,
'description' => '__NAMESPACE__ magic constant',
'content' => '__NAMESPACE__',
),
'T_USE' => array(
'5.2' => false,
'5.3' => true,
'description' => '"use" keyword (for traits/namespaces/anonymous functions)',
),
'T_START_NOWDOC' => array(
'5.2' => false,
'5.3' => true,
'description' => 'nowdoc functionality',
),
'T_END_NOWDOC' => array(
'5.2' => false,
'5.3' => true,
'description' => 'nowdoc functionality',
),
'T_START_HEREDOC' => array(
'5.2' => false,
'5.3' => true,
'description' => '(Double) quoted Heredoc identifier',
'condition' => 'isNotQuoted', // Heredoc is only new with quoted identifier.
),
'T_TRAIT' => array(
'5.3' => false,
'5.4' => true,
'description' => '"trait" keyword',
'content' => 'trait',
),
'T_TRAIT_C' => array(
'5.3' => false,
'5.4' => true,
'description' => '__TRAIT__ magic constant',
'content' => '__TRAIT__',
),
// The specifics for distinguishing between 'yield' and 'yield from' are dealt
// with in the translation logic.
// This token has to be placed above the `T_YIELD` token in this array to allow for this.
'T_YIELD_FROM' => array(
'5.6' => false,
'7.0' => true,
'description' => '"yield from" keyword (for generators)',
'content' => 'yield',
),
'T_YIELD' => array(
'5.4' => false,
'5.5' => true,
'description' => '"yield" keyword (for generators)',
'content' => 'yield',
),
'T_FINALLY' => array(
'5.4' => false,
'5.5' => true,
'description' => '"finally" keyword (in exception handling)',
'content' => 'finally',
),
);
/**
* Translation table for T_STRING tokens.
*
* Will be set up from the register() method.
*
* @since 7.0.5
*
* @var array(string => string)
*/
protected $translateContentToToken = array();
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
$tokens = array();
$translate = array();
foreach ($this->newKeywords as $token => $versions) {
if (\defined($token)) {
$tokens[] = constant($token);
}
if (isset($versions['content'])) {
$translate[strtolower($versions['content'])] = $token;
}
}
/*
* Deal with tokens not recognized by the PHP version the sniffer is run
* under and (not correctly) compensated for by PHPCS.
*/
if (empty($translate) === false) {
$this->translateContentToToken = $translate;
$tokens[] = \T_STRING;
}
return $tokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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();
$tokenType = $tokens[$stackPtr]['type'];
// Allow for dealing with multi-token keywords, like "yield from".
$end = $stackPtr;
// Translate T_STRING token if necessary.
if ($tokens[$stackPtr]['type'] === 'T_STRING') {
$content = strtolower($tokens[$stackPtr]['content']);
if (isset($this->translateContentToToken[$content]) === false) {
// Not one of the tokens we're looking for.
return;
}
$tokenType = $this->translateContentToToken[$content];
}
/*
* Special case: distinguish between `yield` and `yield from`.
*
* PHPCS currently (at least up to v 3.0.1) does not backfill for the
* `yield` nor the `yield from` keywords.
* See: https://github.com/squizlabs/PHP_CodeSniffer/issues/1524
*
* In PHP < 5.5, both `yield` as well as `from` are tokenized as T_STRING.
* In PHP 5.5 - 5.6, `yield` is tokenized as T_YIELD and `from` as T_STRING,
* but the `T_YIELD_FROM` token *is* defined in PHP.
* In PHP 7.0+ both are tokenized as their respective token, however,
* a multi-line "yield from" is tokenized as two tokens.
*/
if ($tokenType === 'T_YIELD') {
$nextToken = $phpcsFile->findNext(\T_WHITESPACE, ($end + 1), null, true);
if ($tokens[$nextToken]['code'] === \T_STRING
&& $tokens[$nextToken]['content'] === 'from'
) {
$tokenType = 'T_YIELD_FROM';
$end = $nextToken;
}
unset($nextToken);
}
if ($tokenType === 'T_YIELD_FROM' && $tokens[($stackPtr - 1)]['type'] === 'T_YIELD_FROM') {
// Multi-line "yield from", no need to report it twice.
return;
}
if (isset($this->newKeywords[$tokenType]) === false) {
return;
}
$nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true);
$prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($prevToken !== false
&& ($tokens[$prevToken]['code'] === \T_DOUBLE_COLON
|| $tokens[$prevToken]['code'] === \T_OBJECT_OPERATOR)
) {
// Class property of the same name as one of the keywords. Ignore.
return;
}
// Skip attempts to use keywords as functions or class names - the former
// will be reported by ForbiddenNamesAsInvokedFunctionsSniff, whilst the
// latter will be (partially) reported by the ForbiddenNames sniff.
// Either type will result in false-positives when targetting lower versions
// of PHP where the name was not reserved, unless we explicitly check for
// them.
if (($nextToken === false
|| $tokens[$nextToken]['type'] !== 'T_OPEN_PARENTHESIS')
&& ($prevToken === false
|| $tokens[$prevToken]['type'] !== 'T_CLASS'
|| $tokens[$prevToken]['type'] !== 'T_INTERFACE')
) {
// Skip based on token scope condition.
if (isset($this->newKeywords[$tokenType]['condition'])
&& \call_user_func(array($this, $this->newKeywords[$tokenType]['condition']), $phpcsFile, $stackPtr) === true
) {
return;
}
$itemInfo = array(
'name' => $tokenType,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newKeywords[$itemInfo['name']];
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array(
'description',
'condition',
'content',
);
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
$errorInfo['description'] = $itemArray['description'];
return $errorInfo;
}
/**
* Allow for concrete child classes to filter the error data before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param array $data The error data array which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return array
*/
protected function filterErrorData(array $data, array $itemInfo, array $errorInfo)
{
$data[0] = $errorInfo['description'];
return $data;
}
/**
* Callback for the quoted heredoc identifier condition.
*
* A double quoted identifier will have the opening quote on position 3
* in the string: `<<<"ID"`.
*
* @since 8.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return bool
*/
public function isNotQuoted(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
return ($tokens[$stackPtr]['content'][3] !== '"');
}
}
@@ -0,0 +1,91 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\LanguageConstructs;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Verify that nothing but variables are passed to empty().
*
* Prior to PHP 5.5, `empty()` only supported variables; anything else resulted in a parse error.
*
* PHP version 5.5
*
* @link https://wiki.php.net/rfc/empty_isset_exprs
* @link https://www.php.net/manual/en/function.empty.php
*
* @since 7.0.4
* @since 9.0.0 The "is the parameter a variable" determination has been abstracted out
* and moved to a separate method `Sniff::isVariable()`.
* @since 9.0.0 Renamed from `EmptyNonVariableSniff` to `NewEmptyNonVariableSniff`.
*/
class NewEmptyNonVariableSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.4
*
* @return array
*/
public function register()
{
return array(\T_EMPTY);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.4
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$open = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
if ($open === false
|| $tokens[$open]['code'] !== \T_OPEN_PARENTHESIS
|| isset($tokens[$open]['parenthesis_closer']) === false
) {
return;
}
$close = $tokens[$open]['parenthesis_closer'];
$nestingLevel = 0;
if ($close !== ($open + 1) && isset($tokens[$open + 1]['nested_parenthesis'])) {
$nestingLevel = \count($tokens[$open + 1]['nested_parenthesis']);
}
if ($this->isVariable($phpcsFile, ($open + 1), $close, $nestingLevel) === true) {
return;
}
$phpcsFile->addError(
'Only variables can be passed to empty() prior to PHP 5.5.',
$stackPtr,
'Found'
);
}
}
@@ -0,0 +1,159 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\LanguageConstructs;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect use of new PHP language constructs.
*
* PHP version All
*
* @link https://wiki.php.net/rfc/namespaceseparator
* @link https://wiki.php.net/rfc/variadics
* @link https://wiki.php.net/rfc/argument_unpacking
*
* @since 5.6
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class..
* @since 9.0.0 Detection for new operator tokens has been moved to the `NewOperators` sniff.
*/
class NewLanguageConstructsSniff extends AbstractNewFeatureSniff
{
/**
* A list of new language constructs, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the keyword appears.
*
* @since 5.6
*
* @var array(string => array(string => bool|string))
*/
protected $newConstructs = array(
'T_NS_SEPARATOR' => array(
'5.2' => false,
'5.3' => true,
'description' => 'the \ operator (for namespaces)',
),
'T_ELLIPSIS' => array(
'5.5' => false,
'5.6' => true,
'description' => 'the ... spread operator',
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.6
*
* @return array
*/
public function register()
{
$tokens = array();
foreach ($this->newConstructs as $token => $versions) {
$tokens[] = constant($token);
}
return $tokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.6
*
* @param \PHP_CodeSniffer_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();
$tokenType = $tokens[$stackPtr]['type'];
$itemInfo = array(
'name' => $tokenType,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newConstructs[$itemInfo['name']];
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array('description');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
$errorInfo['description'] = $itemArray['description'];
return $errorInfo;
}
/**
* Allow for concrete child classes to filter the error data before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param array $data The error data array which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return array
*/
protected function filterErrorData(array $data, array $itemInfo, array $errorInfo)
{
$data[0] = $errorInfo['description'];
return $data;
}
}
@@ -0,0 +1,188 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Lists;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect code affected by the changed list assignment order in PHP 7.0+.
*
* The `list()` construct no longer assigns variables in reverse order.
* This affects all list constructs where non-unique variables are used.
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.variable-handling.list.order
* @link https://wiki.php.net/rfc/abstract_syntax_tree#changes_to_list
* @link https://www.php.net/manual/en/function.list.php
*
* @since 9.0.0
*/
class AssignmentOrderSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.0.0
*
* @return array
*/
public function register()
{
return array(
\T_LIST,
\T_OPEN_SHORT_ARRAY,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return void|int Void if not a valid list. If a list construct has been
* examined, the stack pointer to the list closer to skip
* passed any nested lists which don't need to be examined again.
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY
&& $this->isShortList($phpcsFile, $stackPtr) === false
) {
// Short array, not short list.
return;
}
if ($tokens[$stackPtr]['code'] === \T_LIST) {
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false
|| $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS
|| isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false
) {
// Parse error or live coding.
return;
}
$opener = $nextNonEmpty;
$closer = $tokens[$nextNonEmpty]['parenthesis_closer'];
} else {
// Short list syntax.
$opener = $stackPtr;
if (isset($tokens[$stackPtr]['bracket_closer'])) {
$closer = $tokens[$stackPtr]['bracket_closer'];
}
}
if (isset($opener, $closer) === false) {
return;
}
/*
* OK, so we have the opener & closer, now we need to check all the variables in the
* list() to see if there are duplicates as that's the problem.
*/
$hasVars = $phpcsFile->findNext(array(\T_VARIABLE, \T_DOLLAR), ($opener + 1), $closer);
if ($hasVars === false) {
// Empty list, not our concern.
return ($closer + 1);
}
// Set the variable delimiters based on the list type being examined.
$stopPoints = array(\T_COMMA);
if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY) {
$stopPoints[] = \T_CLOSE_SHORT_ARRAY;
} else {
$stopPoints[] = \T_CLOSE_PARENTHESIS;
}
$listVars = array();
$lastStopPoint = $opener;
/*
* Create a list of all variables used within the `list()` construct.
* We're not concerned with whether these are nested or not, as any duplicate
* variable name used will be problematic, independent of nesting.
*/
do {
$nextStopPoint = $phpcsFile->findNext($stopPoints, ($lastStopPoint + 1), $closer);
if ($nextStopPoint === false) {
$nextStopPoint = $closer;
}
// Also detect this in PHP 7.1 keyed lists.
$hasDoubleArrow = $phpcsFile->findNext(\T_DOUBLE_ARROW, ($lastStopPoint + 1), $nextStopPoint);
if ($hasDoubleArrow !== false) {
$lastStopPoint = $hasDoubleArrow;
}
// Find the start of the variable, allowing for variable variables.
$nextStartPoint = $phpcsFile->findNext(array(\T_VARIABLE, \T_DOLLAR), ($lastStopPoint + 1), $nextStopPoint);
if ($nextStartPoint === false) {
// Skip past empty bits in the list, i.e. `list( $a, , ,)`.
$lastStopPoint = $nextStopPoint;
continue;
}
/*
* Gather the content of all non-empty tokens to determine the "variable name".
* Variable name in this context includes array or object property syntaxes, such
* as `$a['name']` and `$b->property`.
*/
$varContent = '';
for ($i = $nextStartPoint; $i < $nextStopPoint; $i++) {
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) {
continue;
}
$varContent .= $tokens[$i]['content'];
}
if ($varContent !== '') {
$listVars[] = $varContent;
}
$lastStopPoint = $nextStopPoint;
} while ($lastStopPoint < $closer);
if (empty($listVars)) {
// Shouldn't be possible, but just in case.
return ($closer + 1);
}
// Verify that all variables used in the list() construct are unique.
if (\count($listVars) !== \count(array_unique($listVars))) {
$phpcsFile->addError(
'list() will assign variable from left-to-right since PHP 7.0. Ensure all variables in list() are unique to prevent unexpected results.',
$stackPtr,
'Affected'
);
}
return ($closer + 1);
}
}
@@ -0,0 +1,116 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Lists;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Support for empty `list()` expressions has been removed in PHP 7.0.
*
* PHP version 7.0
*
* @link https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.variable-handling.list.empty
* @link https://wiki.php.net/rfc/abstract_syntax_tree#changes_to_list
* @link https://www.php.net/manual/en/function.list.php
*
* @since 7.0.0
*/
class ForbiddenEmptyListAssignmentSniff extends Sniff
{
/**
* List of tokens to disregard when determining whether the list() is empty.
*
* @since 7.0.3
*
* @var array
*/
protected $ignoreTokens = array();
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
*
* @return array
*/
public function register()
{
// Set up a list of tokens to disregard when determining whether the list() is empty.
// Only needs to be set up once.
$this->ignoreTokens = Tokens::$emptyTokens;
$this->ignoreTokens[\T_COMMA] = \T_COMMA;
$this->ignoreTokens[\T_OPEN_PARENTHESIS] = \T_OPEN_PARENTHESIS;
$this->ignoreTokens[\T_CLOSE_PARENTHESIS] = \T_CLOSE_PARENTHESIS;
return array(
\T_LIST,
\T_OPEN_SHORT_ARRAY,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY) {
if ($this->isShortList($phpcsFile, $stackPtr) === false) {
return;
}
$open = $stackPtr;
$close = $tokens[$stackPtr]['bracket_closer'];
} else {
// T_LIST.
$open = $phpcsFile->findNext(\T_OPEN_PARENTHESIS, $stackPtr, null, false, null, true);
if ($open === false || isset($tokens[$open]['parenthesis_closer']) === false) {
return;
}
$close = $tokens[$open]['parenthesis_closer'];
}
$error = true;
if (($close - $open) > 1) {
for ($cnt = $open + 1; $cnt < $close; $cnt++) {
if (isset($this->ignoreTokens[$tokens[$cnt]['code']]) === false) {
$error = false;
break;
}
}
}
if ($error === true) {
$phpcsFile->addError(
'Empty list() assignments are not allowed since PHP 7.0',
$stackPtr,
'Found'
);
}
}
}
@@ -0,0 +1,227 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Lists;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Since PHP 7.1, you can specify keys in `list()`, or its new shorthand `[]` syntax.
*
* PHP version 7.1
*
* @link https://wiki.php.net/rfc/list_keys
* @link https://www.php.net/manual/en/function.list.php
*
* @since 9.0.0
*/
class NewKeyedListSniff extends Sniff
{
/**
* Tokens which represent the start of a list construct.
*
* @since 9.0.0
*
* @var array
*/
protected $sniffTargets = array(
\T_LIST => \T_LIST,
\T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
);
/**
* The token(s) within the list construct which is being targeted.
*
* @since 9.0.0
*
* @var array
*/
protected $targetsInList = array(
\T_DOUBLE_ARROW => \T_DOUBLE_ARROW,
);
/**
* All tokens needed to walk through the list construct and
* determine whether the target token is contained within.
*
* Set by the setUpAllTargets() method which is called from within register().
*
* @since 9.0.0
*
* @var array
*/
protected $allTargets;
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.0.0
*
* @return array
*/
public function register()
{
$this->setUpAllTargets();
return $this->sniffTargets;
}
/**
* Prepare the $allTargets array only once.
*
* @since 9.0.0
*
* @return void
*/
public function setUpAllTargets()
{
$this->allTargets = $this->sniffTargets + $this->targetsInList;
}
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('7.0') === false);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->bowOutEarly() === true) {
return;
}
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY
&& $this->isShortList($phpcsFile, $stackPtr) === false
) {
// Short array, not short list.
return;
}
if ($tokens[$stackPtr]['code'] === \T_LIST) {
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false
|| $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS
|| isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false
) {
// Parse error or live coding.
return;
}
$opener = $nextNonEmpty;
$closer = $tokens[$nextNonEmpty]['parenthesis_closer'];
} else {
// Short list syntax.
$opener = $stackPtr;
if (isset($tokens[$stackPtr]['bracket_closer'])) {
$closer = $tokens[$stackPtr]['bracket_closer'];
}
}
if (isset($opener, $closer) === false) {
return;
}
$this->examineList($phpcsFile, $opener, $closer);
}
/**
* Examine the contents of a list construct to determine whether an error needs to be thrown.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $opener The position of the list open token.
* @param int $closer The position of the list close token.
*
* @return void
*/
protected function examineList(File $phpcsFile, $opener, $closer)
{
$start = $opener;
while (($start = $this->hasTargetInList($phpcsFile, $start, $closer)) !== false) {
$phpcsFile->addError(
'Specifying keys in list constructs is not supported in PHP 7.0 or earlier.',
$start,
'Found'
);
}
}
/**
* Check whether a certain target token exists within a list construct.
*
* Skips past nested list constructs, so these can be examined based on their own token.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $start The position of the list open token or a token
* within the list to start (resume) the examination from.
* @param int $closer The position of the list close token.
*
* @return int|bool Stack pointer to the target token if encountered. False otherwise.
*/
protected function hasTargetInList(File $phpcsFile, $start, $closer)
{
$tokens = $phpcsFile->getTokens();
for ($i = ($start + 1); $i < $closer; $i++) {
if (isset($this->allTargets[$tokens[$i]['code']]) === false) {
continue;
}
if (isset($this->targetsInList[$tokens[$i]['code']]) === true) {
return $i;
}
// Skip past nested list constructs.
if ($tokens[$i]['code'] === \T_LIST) {
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
if ($nextNonEmpty !== false
&& $tokens[$nextNonEmpty]['code'] === \T_OPEN_PARENTHESIS
&& isset($tokens[$nextNonEmpty]['parenthesis_closer']) === true
) {
$i = $tokens[$nextNonEmpty]['parenthesis_closer'];
}
} elseif ($tokens[$i]['code'] === \T_OPEN_SHORT_ARRAY
&& isset($tokens[$i]['bracket_closer'])
) {
$i = $tokens[$i]['bracket_closer'];
}
}
return false;
}
}
@@ -0,0 +1,74 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Lists;
use PHPCompatibility\Sniffs\Lists\NewKeyedListSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect reference assignments in array destructuring using (short) list.
*
* PHP version 7.3
*
* @link https://www.php.net/manual/en/migration73.new-features.php#migration73.new-features.core.destruct-reference
* @link https://wiki.php.net/rfc/list_reference_assignment
* @link https://www.php.net/manual/en/function.list.php
*
* @since 9.0.0
*/
class NewListReferenceAssignmentSniff extends NewKeyedListSniff
{
/**
* The token(s) within the list construct which is being targeted.
*
* @since 9.0.0
*
* @var array
*/
protected $targetsInList = array(
\T_BITWISE_AND => \T_BITWISE_AND,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('7.2') === false);
}
/**
* Examine the contents of a list construct to determine whether an error needs to be thrown.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $opener The position of the list open token.
* @param int $closer The position of the list close token.
*
* @return void
*/
protected function examineList(File $phpcsFile, $opener, $closer)
{
$start = $opener;
while (($start = $this->hasTargetInList($phpcsFile, $start, $closer)) !== false) {
$phpcsFile->addError(
'Reference assignments within list constructs are not supported in PHP 7.2 or earlier.',
$start,
'Found'
);
}
}
}
@@ -0,0 +1,84 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Lists;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect short list syntax for symmetric array destructuring.
*
* "The shorthand array syntax (`[]`) may now be used to destructure arrays for
* assignments (including within `foreach`), as an alternative to the existing
* `list()` syntax, which is still supported."
*
* PHP version 7.1
*
* @link https://www.php.net/manual/en/migration71.new-features.php#migration71.new-features.symmetric-array-destructuring
* @link https://wiki.php.net/rfc/short_list_syntax
*
* @since 9.0.0
*/
class NewShortListSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.0.0
*
* @return array
*/
public function register()
{
return array(\T_OPEN_SHORT_ARRAY);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->supportsBelow('7.0') === false) {
return;
}
if ($this->isShortList($phpcsFile, $stackPtr) === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$closer = $tokens[$stackPtr]['bracket_closer'];
$hasVariable = $phpcsFile->findNext(\T_VARIABLE, ($stackPtr + 1), $closer);
if ($hasVariable === false) {
// List syntax is only valid if there are variables in it.
return;
}
$phpcsFile->addError(
'The shorthand list syntax "[]" to destructure arrays is not available in PHP 7.0 or earlier.',
$stackPtr,
'Found'
);
return ($closer + 1);
}
}
@@ -0,0 +1,103 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\MethodUse;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* As of PHP 5.3, the `__toString()` magic method can no longer be passed arguments.
*
* Sister-sniff to `PHPCompatibility.FunctionDeclarations.ForbiddenToStringParameters`.
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/migration53.incompatible.php
* @link https://www.php.net/manual/en/language.oop5.magic.php#object.tostring
*
* @since 9.2.0
*/
class ForbiddenToStringParametersSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.2.0
*
* @return array
*/
public function register()
{
return array(
\T_DOUBLE_COLON,
\T_OBJECT_OPERATOR,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.2.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('5.3') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_STRING) {
/*
* Not a method call.
*
* Note: This disregards method calls with the method name in a variable, like:
* $method = '__toString';
* $obj->$method();
* However, that would be very hard to examine reliably anyway.
*/
return;
}
if (strtolower($tokens[$nextNonEmpty]['content']) !== '__tostring') {
// Not a call to the __toString() method.
return;
}
$openParens = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true);
if ($openParens === false || $tokens[$openParens]['code'] !== \T_OPEN_PARENTHESIS) {
// Not a method call.
return;
}
$closeParens = $phpcsFile->findNext(Tokens::$emptyTokens, ($openParens + 1), null, true);
if ($closeParens === false || $tokens[$closeParens]['code'] === \T_CLOSE_PARENTHESIS) {
// Not a method call.
return;
}
// If we're still here, then this is a call to the __toString() magic method passing parameters.
$phpcsFile->addError(
'The __toString() magic method will no longer accept passed arguments since PHP 5.3',
$stackPtr,
'Passed'
);
}
}
@@ -0,0 +1,117 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\MethodUse;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect direct calls to the `__clone()` magic method, which is allowed since PHP 7.0.
*
* "Doing calls like `$obj->__clone()` is now allowed. This was the only magic method
* that had a compile-time check preventing some calls to it, which doesn't make sense.
* If we allow all other magic methods to be called, there's no reason to forbid this one."
*
* PHP version 7.0
*
* @link https://wiki.php.net/rfc/abstract_syntax_tree#directly_calling_clone_is_allowed
* @link https://www.php.net/manual/en/language.oop5.cloning.php
*
* @since 9.1.0
*/
class NewDirectCallsToCloneSniff extends Sniff
{
/**
* Tokens which indicate class internal use.
*
* @since 9.3.2
*
* @var array
*/
protected $classInternal = array(
\T_PARENT => true,
\T_SELF => true,
\T_STATIC => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.1.0
*
* @return array
*/
public function register()
{
return array(
\T_DOUBLE_COLON,
\T_OBJECT_OPERATOR,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.1.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.6') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_STRING) {
/*
* Not a method call.
*
* Note: This disregards method calls with the method name in a variable, like:
* $method = '__clone';
* $obj->$method();
* However, that would be very hard to examine reliably anyway.
*/
return;
}
if (strtolower($tokens[$nextNonEmpty]['content']) !== '__clone') {
// Not a call to the __clone() method.
return;
}
$nextNextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true);
if ($nextNextNonEmpty === false || $tokens[$nextNextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
// Not a method call.
return;
}
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($prevNonEmpty === false || isset($this->classInternal[$tokens[$prevNonEmpty]['code']])) {
// Class internal call to __clone().
return;
}
$phpcsFile->addError(
'Direct calls to the __clone() magic method are not allowed in PHP 5.6 or earlier.',
$nextNonEmpty,
'Found'
);
}
}
@@ -0,0 +1,147 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Miscellaneous;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* PHP 7.4 now supports stand-alone PHP tags at the end of a file (without new line).
*
* > `<?php` at the end of the file (without trailing newline) will now be
* > interpreted as an opening PHP tag. Previously it was interpreted either as
* > `<? php` and resulted in a syntax error (with short_open_tag=1) or was
* > interpreted as a literal `<?php` string (with short_open_tag=0).
*
* {@internal Due to an issue with the Tokenizer, this sniff will not work correctly
* on PHP 5.3 in combination with PHPCS < 2.6.0 when short_open_tag is `On`.
* As this is causing "Undefined offset" notices, there is nothing we can
* do to work-around this.}
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.incompatible.php#migration74.incompatible.core.php-tag
* @link https://github.com/php/php-src/blob/30de357fa14480468132bbc22a272aeb91789ba8/UPGRADING#L37-L40
*
* @since 9.3.0
*/
class NewPHPOpenTagEOFSniff extends Sniff
{
/**
* Whether or not short open tags is enabled on the install running the sniffs.
*
* @since 9.3.0
*
* @var bool
*/
protected $shortOpenTags;
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.3.0
*
* @return array
*/
public function register()
{
$targets = array(
\T_OPEN_TAG_WITH_ECHO,
);
$this->shortOpenTags = (bool) ini_get('short_open_tag');
if ($this->shortOpenTags === false) {
$targets[] = \T_INLINE_HTML;
} else {
$targets[] = \T_STRING;
}
if (version_compare(\PHP_VERSION_ID, '70399', '>')) {
$targets[] = \T_OPEN_TAG;
}
return $targets;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('7.3') === false) {
return;
}
if ($stackPtr !== ($phpcsFile->numTokens - 1)) {
// We're only interested in the last token in the file.
return;
}
$tokens = $phpcsFile->getTokens();
$contents = $tokens[$stackPtr]['content'];
$error = false;
switch ($tokens[$stackPtr]['code']) {
case \T_INLINE_HTML:
// PHP < 7.4 with short open tags off.
if ($contents === '<?php') {
$error = true;
} elseif ($contents === '<?=') {
// Also cover short open echo tags in PHP 5.3 with short open tags off.
$error = true;
}
break;
case \T_STRING:
// PHP < 7.4 with short open tags on.
if ($contents === 'php'
&& $tokens[($stackPtr - 1)]['code'] === \T_OPEN_TAG
&& $tokens[($stackPtr - 1)]['content'] === '<?'
) {
$error = true;
}
break;
case \T_OPEN_TAG_WITH_ECHO:
// PHP 5.4+.
if (rtrim($contents) === '<?=') {
$error = true;
}
break;
case \T_OPEN_TAG:
// PHP 7.4+.
if ($contents === '<?php') {
$error = true;
}
break;
}
if ($error === true) {
$phpcsFile->addError(
'A PHP open tag at the end of a file, without trailing newline, was not supported in PHP 7.3 or earlier and would result in a syntax error or be interpreted as a literal string',
$stackPtr,
'Found'
);
}
}
}
@@ -0,0 +1,172 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Miscellaneous;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Check for use of alternative PHP tags, support for which was removed in PHP 7.0.
*
* {@internal Based on `Generic.PHP.DisallowAlternativePHPTags` by Juliette Reinders Folmer
* (with permission) which was merged into PHPCS 2.7.0.}
*
* PHP version 7.0
*
* @link https://wiki.php.net/rfc/remove_alternative_php_tags
* @link https://www.php.net/manual/en/language.basic-syntax.phptags.php
*
* @since 7.0.4
*/
class RemovedAlternativePHPTagsSniff extends Sniff
{
/**
* Whether ASP tags are enabled or not.
*
* @since 7.0.4
*
* @var bool
*/
private $aspTags = false;
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.4
*
* @return array
*/
public function register()
{
if (version_compare(\PHP_VERSION_ID, '70000', '<') === true) {
// phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.asp_tagsRemoved
$this->aspTags = (bool) ini_get('asp_tags');
}
return array(
\T_OPEN_TAG,
\T_OPEN_TAG_WITH_ECHO,
\T_INLINE_HTML,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.4
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$openTag = $tokens[$stackPtr];
$content = trim($openTag['content']);
if ($content === '' || $content === '<?php') {
return;
}
if ($openTag['code'] === \T_OPEN_TAG || $openTag['code'] === \T_OPEN_TAG_WITH_ECHO) {
if ($content === '<%' || $content === '<%=') {
$data = array(
'ASP',
$content,
);
$errorCode = 'ASPOpenTagFound';
} elseif (strpos($content, '<script ') !== false) {
$data = array(
'Script',
$content,
);
$errorCode = 'ScriptOpenTagFound';
} else {
return;
}
}
// Account for incorrect script open tags.
// The "(?:<s)?" in the regex is to work-around a bug in the tokenizer in PHP 5.2.
elseif ($openTag['code'] === \T_INLINE_HTML
&& preg_match('`((?:<s)?cript (?:[^>]+)?language=[\'"]?php[\'"]?(?:[^>]+)?>)`i', $content, $match) === 1
) {
$found = $match[1];
$data = array(
'Script',
$found,
);
$errorCode = 'ScriptOpenTagFound';
}
if (isset($errorCode, $data)) {
$phpcsFile->addError(
'%s style opening tags have been removed in PHP 7.0. Found "%s"',
$stackPtr,
$errorCode,
$data
);
return;
}
// If we're still here, we can't be sure if what we found was really intended as ASP open tags.
if ($openTag['code'] === \T_INLINE_HTML && $this->aspTags === false) {
if (strpos($content, '<%') !== false) {
$error = 'Possible use of ASP style opening tags detected. ASP style opening tags have been removed in PHP 7.0. Found: %s';
$snippet = $this->getSnippet($content, '<%');
$data = array('<%' . $snippet);
$phpcsFile->addWarning($error, $stackPtr, 'MaybeASPOpenTagFound', $data);
}
}
}
/**
* Get a snippet from a HTML token.
*
* @since 7.0.4
*
* @param string $content The content of the HTML token.
* @param string $startAt Partial string to use as a starting point for the snippet.
* @param int $length The target length of the snippet to get. Defaults to 25.
*
* @return string
*/
protected function getSnippet($content, $startAt = '', $length = 25)
{
$startPos = 0;
if ($startAt !== '') {
$startPos = strpos($content, $startAt);
if ($startPos !== false) {
$startPos += \strlen($startAt);
}
}
$snippet = substr($content, $startPos, $length);
if ((\strlen($content) - $startPos) > $length) {
$snippet .= '...';
}
return $snippet;
}
}
@@ -0,0 +1,250 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Miscellaneous;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Check for valid integer types and values.
*
* Checks:
* - PHP 5.4 introduced binary integers.
* - PHP 7.0 removed tolerance for invalid octals. These were truncated prior to PHP 7
* and give a parse error since PHP 7.
* - PHP 7.0 removed support for recognizing hexadecimal numeric strings as numeric.
* Type juggling and recognition was inconsistent prior to PHP 7. As of PHP 7, they
* are no longer treated as numeric.
*
* PHP version 5.4+
*
* @link https://wiki.php.net/rfc/binnotation4ints
* @link https://wiki.php.net/rfc/remove_hex_support_in_numeric_strings
* @link https://www.php.net/manual/en/language.types.integer.php
*
* @since 7.0.3
* @since 7.0.8 This sniff now throws a warning instead of an error for invalid binary integers.
*/
class ValidIntegersSniff extends Sniff
{
/**
* Whether PHPCS is run on a PHP < 5.4.
*
* @since 7.0.3
*
* @var bool
*/
protected $isLowPHPVersion = false;
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.3
*
* @return array
*/
public function register()
{
$this->isLowPHPVersion = version_compare(\PHP_VERSION_ID, '50400', '<');
return array(
\T_LNUMBER, // Binary, octal integers.
\T_CONSTANT_ENCAPSED_STRING, // Hex numeric string.
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.3
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$token = $tokens[$stackPtr];
if ($this->couldBeBinaryInteger($tokens, $stackPtr) === true) {
if ($this->supportsBelow('5.3')) {
$error = 'Binary integer literals were not present in PHP version 5.3 or earlier. Found: %s';
if ($this->isLowPHPVersion === false) {
$data = array($token['content']);
} else {
$data = array($this->getBinaryInteger($phpcsFile, $tokens, $stackPtr));
}
$phpcsFile->addError($error, $stackPtr, 'BinaryIntegerFound', $data);
}
if ($this->isInvalidBinaryInteger($tokens, $stackPtr) === true) {
$error = 'Invalid binary integer detected. Found: %s';
$data = array($this->getBinaryInteger($phpcsFile, $tokens, $stackPtr));
$phpcsFile->addWarning($error, $stackPtr, 'InvalidBinaryIntegerFound', $data);
}
return;
}
$isError = $this->supportsAbove('7.0');
$data = array( $token['content'] );
if ($this->isInvalidOctalInteger($tokens, $stackPtr) === true) {
$this->addMessage(
$phpcsFile,
'Invalid octal integer detected. Prior to PHP 7 this would lead to a truncated number. From PHP 7 onwards this causes a parse error. Found: %s',
$stackPtr,
$isError,
'InvalidOctalIntegerFound',
$data
);
return;
}
if ($this->isHexidecimalNumericString($tokens, $stackPtr) === true) {
$this->addMessage(
$phpcsFile,
'The behaviour of hexadecimal numeric strings was inconsistent prior to PHP 7 and support has been removed in PHP 7. Found: %s',
$stackPtr,
$isError,
'HexNumericStringFound',
$data
);
return;
}
}
/**
* Could the current token potentially be a binary integer ?
*
* @since 7.0.3
*
* @param array $tokens Token stack.
* @param int $stackPtr The current position in the token stack.
*
* @return bool
*/
private function couldBeBinaryInteger($tokens, $stackPtr)
{
$token = $tokens[$stackPtr];
if ($token['code'] !== \T_LNUMBER) {
return false;
}
if ($this->isLowPHPVersion === false) {
return (preg_match('`^0b[0-1]+$`iD', $token['content']) === 1);
}
// Pre-5.4, binary strings are tokenized as T_LNUMBER (0) + T_STRING ("b01010101").
// At this point, we don't yet care whether it's a valid binary int, that's a separate check.
else {
return($token['content'] === '0' && $tokens[$stackPtr + 1]['code'] === \T_STRING && preg_match('`^b[0-9]+$`iD', $tokens[$stackPtr + 1]['content']) === 1);
}
}
/**
* Is the current token an invalid binary integer ?
*
* @since 7.0.3
*
* @param array $tokens Token stack.
* @param int $stackPtr The current position in the token stack.
*
* @return bool
*/
private function isInvalidBinaryInteger($tokens, $stackPtr)
{
if ($this->couldBeBinaryInteger($tokens, $stackPtr) === false) {
return false;
}
if ($this->isLowPHPVersion === false) {
// If it's an invalid binary int, the token will be split into two T_LNUMBER tokens.
return ($tokens[$stackPtr + 1]['code'] === \T_LNUMBER);
} else {
return (preg_match('`^b[0-1]+$`iD', $tokens[$stackPtr + 1]['content']) === 0);
}
}
/**
* Retrieve the content of the tokens which together look like a binary integer.
*
* @since 7.0.3
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param array $tokens Token stack.
* @param int $stackPtr The position of the current token in
* the stack.
*
* @return string
*/
private function getBinaryInteger(File $phpcsFile, $tokens, $stackPtr)
{
$length = 2; // PHP < 5.4 T_LNUMBER + T_STRING.
if ($this->isLowPHPVersion === false) {
$i = $stackPtr;
while ($tokens[$i]['code'] === \T_LNUMBER) {
$i++;
}
$length = ($i - $stackPtr);
}
return $phpcsFile->getTokensAsString($stackPtr, $length);
}
/**
* Is the current token an invalid octal integer ?
*
* @since 7.0.3
*
* @param array $tokens Token stack.
* @param int $stackPtr The current position in the token stack.
*
* @return bool
*/
private function isInvalidOctalInteger($tokens, $stackPtr)
{
$token = $tokens[$stackPtr];
if ($token['code'] === \T_LNUMBER && preg_match('`^0[0-7]*[8-9]+[0-9]*$`D', $token['content']) === 1) {
return true;
}
return false;
}
/**
* Is the current token a hexidecimal numeric string ?
*
* @since 7.0.3
*
* @param array $tokens Token stack.
* @param int $stackPtr The current position in the token stack.
*
* @return bool
*/
private function isHexidecimalNumericString($tokens, $stackPtr)
{
$token = $tokens[$stackPtr];
if ($token['code'] === \T_CONSTANT_ENCAPSED_STRING && preg_match('`^0x[a-f0-9]+$`iD', $this->stripQuotes($token['content'])) === 1) {
return true;
}
return false;
}
}
@@ -0,0 +1,199 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Operators;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect code affected by the change in operator precedence of concatenation in PHP 8.0.
*
* In PHP < 8.0 the operator precedence of `.`, `+` and `-` are the same.
* As of PHP 8.0, the operator precedence of the concatenation operator will be
* lowered to be right below the `<<` and `>>` operators.
*
* As of PHP 7.4, a deprecation warning will be thrown upon encountering an
* unparenthesized expression containing an `.` before a `+` or `-`.
*
* PHP version 7.4
* PHP version 8.0
*
* @link https://wiki.php.net/rfc/concatenation_precedence
* @link https://www.php.net/manual/en/language.operators.precedence.php
*
* @since 9.2.0
*/
class ChangedConcatOperatorPrecedenceSniff extends Sniff
{
/**
* List of tokens with a lower operator precedence than concatenation in PHP >= 8.0.
*
* @since 9.2.0
*
* @var array
*/
private $tokensWithLowerPrecedence = array(
'T_BITWISE_AND' => true,
'T_BITWISE_XOR' => true,
'T_BITWISE_OR' => true,
'T_COALESCE' => true,
'T_INLINE_THEN' => true,
'T_INLINE_ELSE' => true,
'T_YIELD_FROM' => true,
'T_YIELD' => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.2.0
*
* @return array
*/
public function register()
{
return array(
\T_PLUS,
\T_MINUS,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.2.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.4') === false) {
return;
}
if ($this->isUnaryPlusMinus($phpcsFile, $stackPtr) === true) {
return;
}
$tokens = $phpcsFile->getTokens();
for ($i = ($stackPtr - 1); $stackPtr >= 0; $i--) {
if ($tokens[$i]['code'] === \T_STRING_CONCAT) {
// Found one.
break;
}
if ($tokens[$i]['code'] === \T_SEMICOLON
|| $tokens[$i]['code'] === \T_OPEN_CURLY_BRACKET
|| $tokens[$i]['code'] === \T_OPEN_TAG
|| $tokens[$i]['code'] === \T_OPEN_TAG_WITH_ECHO
|| $tokens[$i]['code'] === \T_COMMA
|| $tokens[$i]['code'] === \T_COLON
|| $tokens[$i]['code'] === \T_CASE
) {
// If we reached any of the above tokens, we've reached the end of
// the statement without encountering a concatenation operator.
return;
}
if ($tokens[$i]['code'] === \T_OPEN_CURLY_BRACKET
&& isset($tokens[$i]['bracket_closer'])
&& $tokens[$i]['bracket_closer'] > $stackPtr
) {
// No need to look any further, this is plus/minus within curly braces
// and we've reached the open curly.
return;
}
if ($tokens[$i]['code'] === \T_OPEN_PARENTHESIS
&& isset($tokens[$i]['parenthesis_closer'])
&& $tokens[$i]['parenthesis_closer'] > $stackPtr
) {
// No need to look any further, this is plus/minus within parenthesis
// and we've reached the open parenthesis.
return;
}
if (($tokens[$i]['code'] === \T_OPEN_SHORT_ARRAY
|| $tokens[$i]['code'] === \T_OPEN_SQUARE_BRACKET)
&& isset($tokens[$i]['bracket_closer'])
&& $tokens[$i]['bracket_closer'] > $stackPtr
) {
// No need to look any further, this is plus/minus within a short array
// or array key square brackets and we've reached the opener.
return;
}
if ($tokens[$i]['code'] === \T_CLOSE_CURLY_BRACKET) {
if (isset($tokens[$i]['scope_owner'])) {
// Different scope, we've passed the start of the statement.
return;
}
if (isset($tokens[$i]['bracket_opener'])) {
$i = $tokens[$i]['bracket_opener'];
}
continue;
}
if ($tokens[$i]['code'] === \T_CLOSE_PARENTHESIS
&& isset($tokens[$i]['parenthesis_opener'])
) {
// Skip over statements in parenthesis, including long arrays.
$i = $tokens[$i]['parenthesis_opener'];
continue;
}
if (($tokens[$i]['code'] === \T_CLOSE_SQUARE_BRACKET
|| $tokens[$i]['code'] === \T_CLOSE_SHORT_ARRAY)
&& isset($tokens[$i]['bracket_opener'])
) {
// Skip over array keys and short arrays.
$i = $tokens[$i]['bracket_opener'];
continue;
}
// Check for chain being broken by a token with a lower precedence.
if (isset(Tokens::$booleanOperators[$tokens[$i]['code']]) === true
|| isset(Tokens::$assignmentTokens[$tokens[$i]['code']]) === true
) {
return;
}
if (isset($this->tokensWithLowerPrecedence[$tokens[$i]['type']]) === true) {
if ($tokens[$i]['code'] === \T_BITWISE_AND
&& $phpcsFile->isReference($i) === true
) {
continue;
}
return;
}
}
$message = 'Using an unparenthesized expression containing a "." before a "+" or "-" has been deprecated in PHP 7.4';
$isError = false;
if ($this->supportsAbove('8.0') === true) {
$message .= ' and removed in PHP 8.0';
$isError = true;
}
$this->addMessage($phpcsFile, $message, $i, $isError);
}
}
@@ -0,0 +1,109 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Operators;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Bitwise shifts by negative number will throw an ArithmeticError since PHP 7.0.
*
* PHP version 7.0
*
* @link https://wiki.php.net/rfc/integer_semantics
* @link https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.integers.negative-bitshift
*
* @since 7.0.0
*/
class ForbiddenNegativeBitshiftSniff extends Sniff
{
/**
* Potential end tokens for which the end pointer has to be set back by one.
*
* {@internal The PHPCS `findEndOfStatement()` method is not completely consistent
* in how it returns the statement end. This is just a simple way to bypass
* the inconsistency for our purposes.}
*
* @since 8.2.0
*
* @var array
*/
private $inclusiveStopPoints = array(
\T_COLON => true,
\T_COMMA => true,
\T_DOUBLE_ARROW => true,
\T_SEMICOLON => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
* @since 8.2.0 Now registers all bitshift tokens, not just bitshift right (`T_SR`).
*
* @return array
*/
public function register()
{
return array(
\T_SL,
\T_SL_EQUAL,
\T_SR,
\T_SR_EQUAL,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
// Determine the start and end of the part of the statement we need to examine.
$start = ($stackPtr + 1);
$next = $phpcsFile->findNext(Tokens::$emptyTokens, $start, null, true);
if ($next !== false && $tokens[$next]['code'] === \T_OPEN_PARENTHESIS) {
$start = ($next + 1);
}
$end = PHPCSHelper::findEndOfStatement($phpcsFile, $start);
if (isset($this->inclusiveStopPoints[$tokens[$end]['code']]) === true) {
--$end;
}
if ($this->isNegativeNumber($phpcsFile, $start, $end, true) !== true) {
// Not a negative number or undetermined.
return;
}
$phpcsFile->addError(
'Bitwise shifts by negative number will throw an ArithmeticError in PHP 7.0. Found: %s',
$stackPtr,
'Found',
array($phpcsFile->getTokensAsString($start, ($end - $start + 1)))
);
}
}
@@ -0,0 +1,317 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Operators;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect use of new PHP operators.
*
* PHP version All
*
* @link https://wiki.php.net/rfc/pow-operator
* @link https://wiki.php.net/rfc/combined-comparison-operator
* @link https://wiki.php.net/rfc/isset_ternary
* @link https://wiki.php.net/rfc/null_coalesce_equal_operator
*
* @since 9.0.0 Detection of new operators was originally included in the
* `NewLanguageConstruct` sniff (since 5.6).
*/
class NewOperatorsSniff extends AbstractNewFeatureSniff
{
/**
* A list of new operators, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the operator appears.
*
* @since 5.6
*
* @var array(string => array(string => bool|string))
*/
protected $newOperators = array(
'T_POW' => array(
'5.5' => false,
'5.6' => true,
'description' => 'power operator (**)',
), // Identified in PHP < 5.6 icw PHPCS < 2.4.0 as T_MULTIPLY + T_MULTIPLY.
'T_POW_EQUAL' => array(
'5.5' => false,
'5.6' => true,
'description' => 'power assignment operator (**=)',
), // Identified in PHP < 5.6 icw PHPCS < 2.6.0 as T_MULTIPLY + T_MUL_EQUAL.
'T_SPACESHIP' => array(
'5.6' => false,
'7.0' => true,
'description' => 'spaceship operator (<=>)',
), // Identified in PHP < 7.0 icw PHPCS < 2.5.1 as T_IS_SMALLER_OR_EQUAL + T_GREATER_THAN.
'T_COALESCE' => array(
'5.6' => false,
'7.0' => true,
'description' => 'null coalescing operator (??)',
), // Identified in PHP < 7.0 icw PHPCS < 2.6.2 as T_INLINE_THEN + T_INLINE_THEN.
'T_COALESCE_EQUAL' => array(
'7.3' => false,
'7.4' => true,
'description' => 'null coalesce equal operator (??=)',
), // Identified in PHP < 7.0 icw PHPCS < 2.6.2 as T_INLINE_THEN + T_INLINE_THEN + T_EQUAL and between PHPCS 2.6.2 and PHPCS 2.8.1 as T_COALESCE + T_EQUAL.
);
/**
* A list of new operators which are not recognized in older PHPCS versions.
*
* The array lists an alternative token to listen for.
*
* @since 7.0.3
*
* @var array(string => int)
*/
protected $newOperatorsPHPCSCompat = array(
'T_POW' => \T_MULTIPLY,
'T_POW_EQUAL' => \T_MUL_EQUAL,
'T_SPACESHIP' => \T_GREATER_THAN,
'T_COALESCE' => \T_INLINE_THEN,
'T_COALESCE_EQUAL' => \T_EQUAL,
);
/**
* Token translation table for older PHPCS versions.
*
* The 'before' index lists the token which would have to be directly before the
* token found for it to be one of the new operators.
* The 'real_token' index indicates which operator was found in that case.
*
* If the token combination has multi-layer complexity, such as is the case
* with T_COALESCE(_EQUAL), a 'callback' index is added instead pointing to a
* separate function which can determine whether this is the targetted token across
* PHP and PHPCS versions.
*
* {@internal 'before' was chosen rather than 'after' as that allowed for a 1-on-1
* translation list with the current tokens.}
*
* @since 7.0.3
*
* @var array(string => array(string => string))
*/
protected $PHPCSCompatTranslate = array(
'T_MULTIPLY' => array(
'before' => 'T_MULTIPLY',
'real_token' => 'T_POW',
),
'T_MUL_EQUAL' => array(
'before' => 'T_MULTIPLY',
'real_token' => 'T_POW_EQUAL',
),
'T_GREATER_THAN' => array(
'before' => 'T_IS_SMALLER_OR_EQUAL',
'real_token' => 'T_SPACESHIP',
),
'T_INLINE_THEN' => array(
'callback' => 'isTCoalesce',
'real_token' => 'T_COALESCE',
),
'T_EQUAL' => array(
'callback' => 'isTCoalesceEqual',
'real_token' => 'T_COALESCE_EQUAL',
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.6
*
* @return array
*/
public function register()
{
$tokens = array();
foreach ($this->newOperators as $token => $versions) {
if (\defined($token)) {
$tokens[] = constant($token);
} elseif (isset($this->newOperatorsPHPCSCompat[$token])) {
$tokens[] = $this->newOperatorsPHPCSCompat[$token];
}
}
return $tokens;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.6
*
* @param \PHP_CodeSniffer_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();
$tokenType = $tokens[$stackPtr]['type'];
// Translate older PHPCS token combis for new operators to the actual operator.
if (isset($this->newOperators[$tokenType]) === false) {
if (isset($this->PHPCSCompatTranslate[$tokenType])
&& ((isset($this->PHPCSCompatTranslate[$tokenType]['before'], $tokens[$stackPtr - 1]) === true
&& $tokens[$stackPtr - 1]['type'] === $this->PHPCSCompatTranslate[$tokenType]['before'])
|| (isset($this->PHPCSCompatTranslate[$tokenType]['callback']) === true
&& \call_user_func(array($this, $this->PHPCSCompatTranslate[$tokenType]['callback']), $tokens, $stackPtr) === true))
) {
$tokenType = $this->PHPCSCompatTranslate[$tokenType]['real_token'];
}
} elseif ($tokenType === 'T_COALESCE') {
// Make sure that T_COALESCE is not confused with T_COALESCE_EQUAL.
if (isset($tokens[($stackPtr + 1)]) !== false && $tokens[($stackPtr + 1)]['code'] === \T_EQUAL) {
// Ignore as will be dealt with via the T_EQUAL token.
return;
}
}
// If the translation did not yield one of the tokens we are looking for, bow out.
if (isset($this->newOperators[$tokenType]) === false) {
return;
}
$itemInfo = array(
'name' => $tokenType,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newOperators[$itemInfo['name']];
}
/**
* Get an array of the non-PHP-version array keys used in a sub-array.
*
* @since 7.1.0
*
* @return array
*/
protected function getNonVersionArrayKeys()
{
return array('description');
}
/**
* Retrieve the relevant detail (version) information for use in an error message.
*
* @since 7.1.0
*
* @param array $itemArray Version and other information about the item.
* @param array $itemInfo Base information about the item.
*
* @return array
*/
public function getErrorInfo(array $itemArray, array $itemInfo)
{
$errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
$errorInfo['description'] = $itemArray['description'];
return $errorInfo;
}
/**
* Filter the error data before it's passed to PHPCS.
*
* @since 7.1.0
*
* @param array $data The error data array which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return array
*/
protected function filterErrorData(array $data, array $itemInfo, array $errorInfo)
{
$data[0] = $errorInfo['description'];
return $data;
}
/**
* Callback function to determine whether a T_EQUAL token is really a T_COALESCE_EQUAL token.
*
* @since 7.1.2
*
* @param array $tokens The token stack.
* @param int $stackPtr The current position in the token stack.
*
* @return bool
*/
private function isTCoalesceEqual($tokens, $stackPtr)
{
if ($tokens[$stackPtr]['code'] !== \T_EQUAL || isset($tokens[($stackPtr - 1)]) === false) {
// Function called for wrong token or token has no predecessor.
return false;
}
if ($tokens[($stackPtr - 1)]['type'] === 'T_COALESCE') {
return true;
}
if ($tokens[($stackPtr - 1)]['type'] === 'T_INLINE_THEN'
&& (isset($tokens[($stackPtr - 2)]) && $tokens[($stackPtr - 2)]['type'] === 'T_INLINE_THEN')
) {
return true;
}
return false;
}
/**
* Callback function to determine whether a T_INLINE_THEN token is really a T_COALESCE token.
*
* @since 7.1.2
*
* @param array $tokens The token stack.
* @param int $stackPtr The current position in the token stack.
*
* @return bool
*/
private function isTCoalesce($tokens, $stackPtr)
{
if ($tokens[$stackPtr]['code'] !== \T_INLINE_THEN || isset($tokens[($stackPtr - 1)]) === false) {
// Function called for wrong token or token has no predecessor.
return false;
}
if ($tokens[($stackPtr - 1)]['code'] === \T_INLINE_THEN) {
// Make sure not to confuse it with the T_COALESCE_EQUAL token.
if (isset($tokens[($stackPtr + 1)]) === false || $tokens[($stackPtr + 1)]['code'] !== \T_EQUAL) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,73 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Operators;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
/**
* Detect usage of the short ternary (elvis) operator as introduced in PHP 5.3.
*
* Performs checks on ternary operators, specifically that the middle expression
* is not omitted for versions that don't support this.
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/migration53.new-features.php
* @link https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.ternary
*
* @since 7.0.0
* @since 7.0.8 This sniff now throws an error instead of a warning.
* @since 9.0.0 Renamed from `TernaryOperatorsSniff` to `NewShortTernarySniff`.
*/
class NewShortTernarySniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.0
*
* @return array
*/
public function register()
{
return array(\T_INLINE_THEN);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsBelow('5.2') === false) {
return;
}
if ($this->isShortTernary($phpcsFile, $stackPtr) === false) {
return;
}
$phpcsFile->addError(
'Middle may not be omitted from ternary operators in PHP < 5.3',
$stackPtr,
'MiddleMissing'
);
}
}
@@ -0,0 +1,157 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Operators;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* The left-associativity of the ternary operator is deprecated in PHP 7.4 and
* removed in PHP 8.0.
*
* PHP version 7.4
* PHP version 8.0
*
* @link https://www.php.net/manual/en/migration74.deprecated.php#migration74.deprecated.core.nested-ternary
* @link https://wiki.php.net/rfc/ternary_associativity
* @link https://github.com/php/php-src/pull/4017
*
* @since 9.2.0
*/
class RemovedTernaryAssociativitySniff extends Sniff
{
/**
* List of tokens with a lower operator precedence than ternary.
*
* @since 9.2.0
*
* @var array
*/
private $tokensWithLowerPrecedence = array(
'T_YIELD_FROM' => true,
'T_YIELD' => true,
'T_LOGICAL_AND' => true,
'T_LOGICAL_OR' => true,
'T_LOGICAL_XOR' => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.2.0
*
* @return array
*/
public function register()
{
return array(\T_INLINE_THEN);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.2.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$endOfStatement = PHPCSHelper::findEndOfStatement($phpcsFile, $stackPtr);
if ($tokens[$endOfStatement]['code'] !== \T_SEMICOLON
&& $tokens[$endOfStatement]['code'] !== \T_COLON
&& $tokens[$endOfStatement]['code'] !== \T_COMMA
&& $tokens[$endOfStatement]['code'] !== \T_DOUBLE_ARROW
&& $tokens[$endOfStatement]['code'] !== \T_OPEN_TAG
&& $tokens[$endOfStatement]['code'] !== \T_CLOSE_TAG
) {
// End of statement is last non-empty before close brace, so make sure we examine that token too.
++$endOfStatement;
}
$ternaryCount = 0;
$elseCount = 0;
$shortTernaryCount = 0;
// Start at $stackPtr so we don't need duplicate code for short ternary determination.
for ($i = $stackPtr; $i < $endOfStatement; $i++) {
if (($tokens[$i]['code'] === \T_OPEN_SHORT_ARRAY
|| $tokens[$i]['code'] === \T_OPEN_SQUARE_BRACKET
|| $tokens[$i]['code'] === \T_OPEN_CURLY_BRACKET)
&& isset($tokens[$i]['bracket_closer'])
) {
// Skip over short arrays, array access keys and curlies.
$i = $tokens[$i]['bracket_closer'];
continue;
}
if ($tokens[$i]['code'] === \T_OPEN_PARENTHESIS
&& isset($tokens[$i]['parenthesis_closer'])
) {
// Skip over anything between parentheses.
$i = $tokens[$i]['parenthesis_closer'];
continue;
}
// Check for operators with lower operator precedence.
if (isset(Tokens::$assignmentTokens[$tokens[$i]['code']])
|| isset($this->tokensWithLowerPrecedence[$tokens[$i]['code']])
) {
break;
}
if ($tokens[$i]['code'] === \T_INLINE_THEN) {
++$ternaryCount;
if ($this->isShortTernary($phpcsFile, $i) === true) {
++$shortTernaryCount;
}
continue;
}
if ($tokens[$i]['code'] === \T_INLINE_ELSE) {
if (($ternaryCount - $elseCount) >= 2) {
// This is the `else` for a ternary in the middle part of a previous ternary.
--$ternaryCount;
} else {
++$elseCount;
}
continue;
}
}
if ($ternaryCount > 1 && $ternaryCount === $elseCount && $ternaryCount > $shortTernaryCount) {
$message = 'The left-associativity of the ternary operator has been deprecated in PHP 7.4';
$isError = false;
if ($this->supportsAbove('8.0') === true) {
$message .= ' and removed in PHP 8.0';
$isError = true;
}
$message .= '. Multiple consecutive ternaries detected. Use parenthesis to clarify the order in which the operations should be executed';
$this->addMessage($phpcsFile, $message, $stackPtr, $isError);
}
}
}
@@ -0,0 +1,84 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect: Passing `null` to `get_class()` is no longer allowed as of PHP 7.2.
* This will now result in an `E_WARNING` being thrown.
*
* PHP version 7.2
*
* @link https://wiki.php.net/rfc/get_class_disallow_null_parameter
* @link https://www.php.net/manual/en/function.get-class.php#refsect1-function.get-class-changelog
*
* @since 9.0.0
*/
class ForbiddenGetClassNullSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.0.0
*
* @var array
*/
protected $targetFunctions = array(
'get_class' => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsAbove('7.2') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[1]) === false) {
return;
}
if ($parameters[1]['raw'] !== 'null') {
return;
}
$phpcsFile->addError(
'Passing "null" as the $object to get_class() is not allowed since PHP 7.2.',
$parameters[1]['start'],
'Found'
);
}
}
@@ -0,0 +1,113 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* Since PHP 5.3.4, `strip_tags()` ignores self-closing XHTML tags in allowable_tags
*
* PHP version 5.3.4
*
* @link https://www.php.net/manual/en/function.strip-tags.php#refsect1-function.strip-tags-changelog
*
* @since 9.3.0
*/
class ForbiddenStripTagsSelfClosingXHTMLSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'strip_tags' => true,
);
/**
* Text string tokens to examine.
*
* @since 9.3.0
*
* @var array
*/
private $textStringTokens = array(
\T_CONSTANT_ENCAPSED_STRING => true,
\T_DOUBLE_QUOTED_STRING => true,
\T_INLINE_HTML => true,
\T_HEREDOC => true,
\T_NOWDOC => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsAbove('5.4') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[2]) === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$targetParam = $parameters[2];
for ($i = $targetParam['start']; $i <= $targetParam['end']; $i++) {
if ($tokens[$i]['code'] === \T_STRING
|| $tokens[$i]['code'] === \T_VARIABLE
) {
// Variable, constant, function call. Ignore as undetermined.
return;
}
if (isset($this->textStringTokens[$tokens[$i]['code']]) === true
&& strpos($tokens[$i]['content'], '/>') !== false
) {
$phpcsFile->addError(
'Self-closing XHTML tags are ignored. Only non-self-closing tags should be used in the strip_tags() $allowable_tags parameter since PHP 5.3.4. Found: %s',
$i,
'Found',
array($targetParam['raw'])
);
// Only throw one error per function call.
return;
}
}
}
}
@@ -0,0 +1,116 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* In PHP 5.2 and lower, the `$initial` parameter for `array_reduce()` had to be an integer.
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/migration53.other.php#migration53.other
* @link https://www.php.net/manual/en/function.array-reduce.php#refsect1-function.array-reduce-changelog
*
* @since 9.0.0
*/
class NewArrayReduceInitialTypeSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.0.0
*
* @var array
*/
protected $targetFunctions = array(
'array_reduce' => true,
);
/**
* Tokens which, for the purposes of this sniff, indicate that there is
* a variable element to the value passed.
*
* @since 9.0.0
*
* @var array
*/
private $variableValueTokens = array(
\T_VARIABLE,
\T_STRING,
\T_SELF,
\T_PARENT,
\T_STATIC,
\T_DOUBLE_QUOTED_STRING,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('5.2') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[3]) === false) {
return;
}
$targetParam = $parameters[3];
if ($this->isNumber($phpcsFile, $targetParam['start'], $targetParam['end'], true) !== false) {
return;
}
if ($this->isNumericCalculation($phpcsFile, $targetParam['start'], $targetParam['end']) === true) {
return;
}
$error = 'Passing a non-integer as the value for $initial to array_reduce() is not supported in PHP 5.2 or lower.';
if ($phpcsFile->findNext($this->variableValueTokens, $targetParam['start'], ($targetParam['end'] + 1)) === false) {
$phpcsFile->addError(
$error . ' Found %s',
$targetParam['start'],
'InvalidTypeFound',
array($targetParam['raw'])
);
} else {
$phpcsFile->addWarning(
$error . ' Variable value found. Found %s',
$targetParam['start'],
'VariableFound',
array($targetParam['raw'])
);
}
}
}
@@ -0,0 +1,119 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* Check for valid values for the `fopen()` `$mode` parameter.
*
* PHP version 5.2+
*
* @link https://www.php.net/manual/en/function.fopen.php#refsect1-function.fopen-changelog
*
* @since 9.0.0
*/
class NewFopenModesSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.0.0
*
* @var array
*/
protected $targetFunctions = array(
'fopen' => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
// Version used here should be (above) the highest version from the `newModes` control,
// structure below, i.e. the last PHP version in which a new mode was introduced.
return ($this->supportsBelow('7.1') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[2]) === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$targetParam = $parameters[2];
$errors = array();
for ($i = $targetParam['start']; $i <= $targetParam['end']; $i++) {
if ($tokens[$i]['code'] !== \T_CONSTANT_ENCAPSED_STRING) {
continue;
}
if (strpos($tokens[$i]['content'], 'c+') !== false && $this->supportsBelow('5.2.5')) {
$errors['cplusFound'] = array(
'c+',
'5.2.5',
$targetParam['raw'],
);
} elseif (strpos($tokens[$i]['content'], 'c') !== false && $this->supportsBelow('5.2.5')) {
$errors['cFound'] = array(
'c',
'5.2.5',
$targetParam['raw'],
);
}
if (strpos($tokens[$i]['content'], 'e') !== false && $this->supportsBelow('7.0.15')) {
$errors['eFound'] = array(
'e',
'7.0.15',
$targetParam['raw'],
);
}
}
if (empty($errors) === true) {
return;
}
foreach ($errors as $errorCode => $errorData) {
$phpcsFile->addError(
'Passing "%s" as the $mode to fopen() is not supported in PHP %s or lower. Found %s',
$targetParam['start'],
$errorCode,
$errorData
);
}
}
}
@@ -0,0 +1,92 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* As of PHP 5.4, the default character set for `htmlspecialchars()`, `htmlentities()`
* and `html_entity_decode()` is now `UTF-8`, instead of `ISO-8859-1`.
*
* PHP version 5.4
*
* @link https://www.php.net/manual/en/migration54.other.php
* @link https://www.php.net/manual/en/function.html-entity-decode.php#refsect1-function.html-entity-decode-changelog
* @link https://www.php.net/manual/en/function.htmlentities.php#refsect1-function.htmlentities-changelog
* @link https://www.php.net/manual/en/function.htmlspecialchars.php#refsect1-function.htmlspecialchars-changelog
*
* @since 9.3.0
*/
class NewHTMLEntitiesEncodingDefaultSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* Key is the function name, value the 1-based parameter position of
* the $encoding parameter.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'html_entity_decode' => 3,
'htmlentities' => 3,
'htmlspecialchars' => 3,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* Note: This sniff should only trigger errors when both PHP 5.3 or lower,
* as well as PHP 5.4 or higher need to be supported within the application.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('5.3') === false || $this->supportsAbove('5.4') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
$functionLC = strtolower($functionName);
if (isset($parameters[$this->targetFunctions[$functionLC]]) === true) {
return;
}
$phpcsFile->addError(
'The default value of the $encoding parameter for %s() was changed from ISO-8859-1 to UTF-8 in PHP 5.4. For cross-version compatibility, the $encoding parameter should be explicitly set.',
$stackPtr,
'NotSet',
array($functionName)
);
}
}
@@ -0,0 +1,185 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractNewFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect the use of newly introduced hash algorithms.
*
* PHP version 5.2+
*
* @link https://www.php.net/manual/en/function.hash-algos.php#refsect1-function.hash-algos-changelog
*
* @since 7.0.7
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` instead of the base `Sniff` class..
*/
class NewHashAlgorithmsSniff extends AbstractNewFeatureSniff
{
/**
* A list of new hash algorithms, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the hash algorithm appears.
*
* @since 7.0.7
*
* @var array(string => array(string => bool))
*/
protected $newAlgorithms = array(
'md2' => array(
'5.2' => false,
'5.3' => true,
),
'ripemd256' => array(
'5.2' => false,
'5.3' => true,
),
'ripemd320' => array(
'5.2' => false,
'5.3' => true,
),
'salsa10' => array(
'5.2' => false,
'5.3' => true,
),
'salsa20' => array(
'5.2' => false,
'5.3' => true,
),
'snefru256' => array(
'5.2' => false,
'5.3' => true,
),
'sha224' => array(
'5.2' => false,
'5.3' => true,
),
'joaat' => array(
'5.3' => false,
'5.4' => true,
),
'fnv132' => array(
'5.3' => false,
'5.4' => true,
),
'fnv164' => array(
'5.3' => false,
'5.4' => true,
),
'gost-crypto' => array(
'5.5' => false,
'5.6' => true,
),
'sha512/224' => array(
'7.0' => false,
'7.1' => true,
),
'sha512/256' => array(
'7.0' => false,
'7.1' => true,
),
'sha3-224' => array(
'7.0' => false,
'7.1' => true,
),
'sha3-256' => array(
'7.0' => false,
'7.1' => true,
),
'sha3-384' => array(
'7.0' => false,
'7.1' => true,
),
'sha3-512' => array(
'7.0' => false,
'7.1' => true,
),
'crc32c' => array(
'7.3' => false,
'7.4' => true,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 7.0.7
*
* @return array
*/
public function register()
{
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 7.0.7
*
* @param \PHP_CodeSniffer_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)
{
$algo = $this->getHashAlgorithmParameter($phpcsFile, $stackPtr);
if (empty($algo) || \is_string($algo) === false) {
return;
}
// Bow out if not one of the algorithms we're targetting.
if (isset($this->newAlgorithms[$algo]) === false) {
return;
}
// Check if the algorithm used is new.
$itemInfo = array(
'name' => $algo,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->newAlgorithms[$itemInfo['name']];
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'The %s hash algorithm is not present in PHP version %s or earlier';
}
}
@@ -0,0 +1,91 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* The default value for the `$variant` parameter has changed from `INTL_IDNA_VARIANT_2003`
* to `INTL_IDNA_VARIANT_UTS46` in PHP 7.4.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.incompatible.php#migration74.incompatible.intl
* @link https://wiki.php.net/rfc/deprecate-and-remove-intl_idna_variant_2003
* @link https://www.php.net/manual/en/function.idn-to-ascii.php
* @link https://www.php.net/manual/en/function.idn-to-utf8.php
*
* @since 9.3.0
*/
class NewIDNVariantDefaultSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* Key is the function name, value the 1-based parameter position of
* the $variant parameter.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'idn_to_ascii' => 3,
'idn_to_utf8' => 3,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* Note: This sniff should only trigger errors when both PHP 7.3 or lower,
* as well as PHP 7.4 or higher need to be supported within the application.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('7.3') === false || $this->supportsAbove('7.4') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
$functionLC = strtolower($functionName);
if (isset($parameters[$this->targetFunctions[$functionLC]]) === true) {
return;
}
$error = 'The default value of the %s() $variant parameter has changed from INTL_IDNA_VARIANT_2003 to INTL_IDNA_VARIANT_UTS46 in PHP 7.4. For optimal cross-version compatibility, the $variant parameter should be explicitly set.';
$phpcsFile->addError(
$error,
$stackPtr,
'NotSet',
array($functionName)
);
}
}
@@ -0,0 +1,231 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect calls to Iconv and Mbstring functions with the optional `$charset`/`$encoding` parameter not set.
*
* The default value for the iconv `$charset` and the MbString $encoding` parameters was changed
* in PHP 5.6 to the value of `default_charset`, which defaults to `UTF-8`.
*
* Previously, the iconv functions would default to the value of `iconv.internal_encoding`;
* The Mbstring functions would default to the return value of `mb_internal_encoding()`.
* In both case, this would normally come down to `ISO-8859-1`.
*
* PHP version 5.6
*
* @link https://www.php.net/manual/en/migration56.new-features.php#migration56.new-features.default-encoding
* @link https://www.php.net/manual/en/migration56.deprecated.php#migration56.deprecated.iconv-mbstring-encoding
* @link https://wiki.php.net/rfc/default_encoding
*
* @since 9.3.0
*/
class NewIconvMbstringCharsetDefaultSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* Only those functions where the charset/encoding parameter is optional need to be listed.
*
* Key is the function name, value the 1-based parameter position of
* the $charset/$encoding parameter.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'iconv_mime_decode_headers' => 3,
'iconv_mime_decode' => 3,
'iconv_mime_encode' => 3, // Special case.
'iconv_strlen' => 2,
'iconv_strpos' => 4,
'iconv_strrpos' => 3,
'iconv_substr' => 4,
'mb_check_encoding' => 2,
'mb_chr' => 2,
'mb_convert_case' => 3,
'mb_convert_encoding' => 3,
'mb_convert_kana' => 3,
'mb_decode_numericentity' => 3,
'mb_encode_numericentity' => 3,
'mb_ord' => 2,
'mb_scrub' => 2,
'mb_strcut' => 4,
'mb_stripos' => 4,
'mb_stristr' => 4,
'mb_strlen' => 2,
'mb_strpos' => 4,
'mb_strrchr' => 4,
'mb_strrichr' => 4,
'mb_strripos' => 4,
'mb_strrpos' => 4,
'mb_strstr' => 4,
'mb_strtolower' => 2,
'mb_strtoupper' => 2,
'mb_strwidth' => 2,
'mb_substr_count' => 3,
'mb_substr' => 4,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* Note: This sniff should only trigger errors when both PHP 5.5 or lower,
* as well as PHP 5.6 or higher need to be supported within the application.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('5.5') === false || $this->supportsAbove('5.6') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
$functionLC = strtolower($functionName);
if ($functionLC === 'iconv_mime_encode') {
// Special case the iconv_mime_encode() function.
return $this->processIconvMimeEncode($phpcsFile, $stackPtr, $functionName, $parameters);
}
if (isset($parameters[$this->targetFunctions[$functionLC]]) === true) {
return;
}
$paramName = '$encoding';
if (strpos($functionLC, 'iconv_') === 0) {
$paramName = '$charset';
} elseif ($functionLC === 'mb_convert_encoding') {
$paramName = '$from_encoding';
}
$error = 'The default value of the %1$s parameter for %2$s() was changed from ISO-8859-1 to UTF-8 in PHP 5.6. For cross-version compatibility, the %1$s parameter should be explicitly set.';
$data = array(
$paramName,
$functionName,
);
$phpcsFile->addError($error, $stackPtr, 'NotSet', $data);
}
/**
* Process the parameters of a matched call to the iconv_mime_encode() function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processIconvMimeEncode(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
$error = 'The default value of the %s parameter index for iconv_mime_encode() was changed from ISO-8859-1 to UTF-8 in PHP 5.6. For cross-version compatibility, the %s should be explicitly set.';
$functionLC = strtolower($functionName);
if (isset($parameters[$this->targetFunctions[$functionLC]]) === false) {
$phpcsFile->addError(
$error,
$stackPtr,
'PreferencesNotSet',
array(
'$preferences[\'input/output-charset\']',
'$preferences[\'input-charset\'] and $preferences[\'output-charset\'] indexes',
)
);
return;
}
$tokens = $phpcsFile->getTokens();
$targetParam = $parameters[$this->targetFunctions[$functionLC]];
$firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $targetParam['start'], ($targetParam['end'] + 1), true);
if ($firstNonEmpty === false) {
// Parse error or live coding.
return;
}
if ($tokens[$firstNonEmpty]['code'] === \T_ARRAY
|| $tokens[$firstNonEmpty]['code'] === \T_OPEN_SHORT_ARRAY
) {
$hasInputCharset = preg_match('`([\'"])input-charset\1\s*=>`', $targetParam['raw']);
$hasOutputCharset = preg_match('`([\'"])output-charset\1\s*=>`', $targetParam['raw']);
if ($hasInputCharset === 1 && $hasOutputCharset === 1) {
// Both input as well as output charset are set.
return;
}
if ($hasInputCharset !== 1) {
$phpcsFile->addError(
$error,
$firstNonEmpty,
'InputPreferenceNotSet',
array(
'$preferences[\'input-charset\']',
'$preferences[\'input-charset\'] index',
)
);
}
if ($hasOutputCharset !== 1) {
$phpcsFile->addError(
$error,
$firstNonEmpty,
'OutputPreferenceNotSet',
array(
'$preferences[\'output-charset\']',
'$preferences[\'output-charset\'] index',
)
);
}
return;
}
// The $preferences parameter was passed, but it was a variable/constant/output of a function call.
$phpcsFile->addWarning(
$error,
$firstNonEmpty,
'Undetermined',
array(
'$preferences[\'input/output-charset\']',
'$preferences[\'input-charset\'] and $preferences[\'output-charset\'] indexes',
)
);
}
}
@@ -0,0 +1,129 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect negative string offsets as parameters passed to functions where this
* was not allowed prior to PHP 7.1.
*
* PHP version 7.1
*
* @link https://wiki.php.net/rfc/negative-string-offsets
*
* @since 9.0.0
*/
class NewNegativeStringOffsetSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.0.0
*
* @var array Function name => 1-based parameter offset of the affected parameters => parameter name.
*/
protected $targetFunctions = array(
'file_get_contents' => array(
4 => 'offset',
),
'grapheme_extract' => array(
4 => 'start',
),
'grapheme_stripos' => array(
3 => 'offset',
),
'grapheme_strpos' => array(
3 => 'offset',
),
'iconv_strpos' => array(
3 => 'offset',
),
'mb_ereg_search_setpos' => array(
1 => 'position',
),
'mb_strimwidth' => array(
2 => 'start',
3 => 'width',
),
'mb_stripos' => array(
3 => 'offset',
),
'mb_strpos' => array(
3 => 'offset',
),
'stripos' => array(
3 => 'offset',
),
'strpos' => array(
3 => 'offset',
),
'substr_count' => array(
3 => 'offset',
4 => 'length',
),
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('7.0') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
$functionLC = strtolower($functionName);
foreach ($this->targetFunctions[$functionLC] as $pos => $name) {
if (isset($parameters[$pos]) === false) {
continue;
}
$targetParam = $parameters[$pos];
if ($this->isNegativeNumber($phpcsFile, $targetParam['start'], $targetParam['end']) === false) {
continue;
}
$phpcsFile->addError(
'Negative string offsets were not supported for the $%s parameter in %s() in PHP 7.0 or lower. Found %s',
$targetParam['start'],
'Found',
array(
$name,
$functionName,
$targetParam['raw'],
)
);
}
}
}
@@ -0,0 +1,127 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\Sniffs\ParameterValues\RemovedPCREModifiersSniff;
use PHP_CodeSniffer_File as File;
/**
* Check for the use of newly added regex modifiers for PCRE functions.
*
* Initially just checks for the PHP 7.2 new `J` modifier.
*
* PHP version 7.2+
*
* @link https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php
* @link https://www.php.net/manual/en/migration72.new-features.php#migration72.new-features.pcre
*
* @since 8.2.0
* @since 9.0.0 Renamed from `PCRENewModifiersSniff` to `NewPCREModifiersSniff`.
*/
class NewPCREModifiersSniff extends RemovedPCREModifiersSniff
{
/**
* Functions to check for.
*
* @since 8.2.0
*
* @var array
*/
protected $targetFunctions = array(
'preg_filter' => true,
'preg_grep' => true,
'preg_match_all' => true,
'preg_match' => true,
'preg_replace_callback_array' => true,
'preg_replace_callback' => true,
'preg_replace' => true,
'preg_split' => true,
);
/**
* Array listing newly introduced regex modifiers.
*
* The key should be the modifier (case-sensitive!).
* The value should be the PHP version in which the modifier was introduced.
*
* @since 8.2.0
*
* @var array
*/
protected $newModifiers = array(
'J' => array(
'7.1' => false,
'7.2' => true,
),
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 8.2.0
*
* @return bool
*/
protected function bowOutEarly()
{
// Version used here should be the highest version from the `$newModifiers` array,
// i.e. the last PHP version in which a new modifier was introduced.
return ($this->supportsBelow('7.2') === false);
}
/**
* Examine the regex modifier string.
*
* @since 8.2.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
* @param string $functionName The function which contained the pattern.
* @param string $modifiers The regex modifiers found.
*
* @return void
*/
protected function examineModifiers(File $phpcsFile, $stackPtr, $functionName, $modifiers)
{
$error = 'The PCRE regex modifier "%s" is not present in PHP version %s or earlier';
foreach ($this->newModifiers as $modifier => $versionArray) {
if (strpos($modifiers, $modifier) === false) {
continue;
}
$notInVersion = '';
foreach ($versionArray as $version => $present) {
if ($notInVersion === '' && $present === false
&& $this->supportsBelow($version) === true
) {
$notInVersion = $version;
}
}
if ($notInVersion === '') {
continue;
}
$errorCode = $modifier . 'ModifierFound';
$data = array(
$modifier,
$notInVersion,
);
$phpcsFile->addError($error, $stackPtr, $errorCode, $data);
}
}
}
@@ -0,0 +1,132 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* Check for valid values for the `$format` passed to `pack()`.
*
* PHP version 5.4+
*
* @link https://www.php.net/manual/en/function.pack.php#refsect1-function.pack-changelog
*
* @since 9.0.0
*/
class NewPackFormatSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.0.0
*
* @var array
*/
protected $targetFunctions = array(
'pack' => true,
);
/**
* List of new format character codes added to pack().
*
* @since 9.0.0
*
* @var array Regex pattern => Version array.
*/
protected $newFormats = array(
'`([Z])`' => array(
'5.4' => false,
'5.5' => true,
),
'`([qQJP])`' => array(
'5.6.2' => false,
'5.6.3' => true,
),
'`([eEgG])`' => array(
'7.0.14' => false,
'7.0.15' => true, // And 7.1.1.
),
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsBelow('7.1') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[1]) === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$targetParam = $parameters[1];
for ($i = $targetParam['start']; $i <= $targetParam['end']; $i++) {
if ($tokens[$i]['code'] !== \T_CONSTANT_ENCAPSED_STRING
&& $tokens[$i]['code'] !== \T_DOUBLE_QUOTED_STRING
) {
continue;
}
$content = $tokens[$i]['content'];
if ($tokens[$i]['code'] === \T_DOUBLE_QUOTED_STRING) {
$content = $this->stripVariables($content);
}
foreach ($this->newFormats as $pattern => $versionArray) {
if (preg_match($pattern, $content, $matches) !== 1) {
continue;
}
foreach ($versionArray as $version => $present) {
if ($present === false && $this->supportsBelow($version) === true) {
$phpcsFile->addError(
'Passing the $format(s) "%s" to pack() is not supported in PHP %s or lower. Found %s',
$targetParam['start'],
'NewFormatFound',
array(
$matches[1],
$version,
$targetParam['raw'],
)
);
continue 2;
}
}
}
}
}
}
@@ -0,0 +1,125 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* The constant value of the password hash algorithm constants has changed in PHP 7.4.
*
* Applications using the constants `PASSWORD_DEFAULT`, `PASSWORD_BCRYPT`,
* `PASSWORD_ARGON2I`, and `PASSWORD_ARGON2ID` will continue to function correctly.
* Using an integer will still work, but will produce a deprecation warning.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.incompatible.php#migration74.incompatible.core.password-algorithm-constants
* @link https://wiki.php.net/rfc/password_registry
*
* @since 9.3.0
*/
class NewPasswordAlgoConstantValuesSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* Key is the function name, value the 1-based parameter position of
* the $algo parameter.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'password_hash' => 2,
'password_needs_rehash' => 2,
);
/**
* Tokens types which indicate that the parameter passed is not the PHP native constant.
*
* @since 9.3.0
*
* @var array
*/
private $invalidTokenTypes = array(
\T_NULL => true,
\T_TRUE => true,
\T_FALSE => true,
\T_LNUMBER => true,
\T_DNUMBER => true,
\T_CONSTANT_ENCAPSED_STRING => true,
\T_DOUBLE_QUOTED_STRING => true,
\T_HEREDOC => true,
\T_NOWDOC => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsAbove('7.4') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
$functionLC = strtolower($functionName);
if (isset($parameters[$this->targetFunctions[$functionLC]]) === false) {
return;
}
$targetParam = $parameters[$this->targetFunctions[$functionLC]];
$tokens = $phpcsFile->getTokens();
for ($i = $targetParam['start']; $i <= $targetParam['end']; $i++) {
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
continue;
}
if (isset($this->invalidTokenTypes[$tokens[$i]['code']]) === true) {
$phpcsFile->addWarning(
'The value of the password hash algorithm constants has changed in PHP 7.4. Pass a PHP native constant to the %s() function instead of using the value of the constant. Found: %s',
$stackPtr,
'NotAlgoConstant',
array(
$functionName,
$targetParam['raw'],
)
);
break;
}
}
}
}
@@ -0,0 +1,136 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* As of PHP 7.4, `proc_open()` now also accepts an array instead of a string for the command.
*
* In that case, the process will be opened directly (without going through a shell) and
* PHP will take care of any necessary argument escaping.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.standard.proc-open
* @link https://www.php.net/manual/en/function.proc-open.php
*
* @since 9.3.0
*/
class NewProcOpenCmdArraySniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'proc_open' => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return false;
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[1]) === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$targetParam = $parameters[1];
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $targetParam['start'], $targetParam['end'], true);
if ($nextNonEmpty === false) {
// Shouldn't be possible.
return;
}
if ($tokens[$nextNonEmpty]['code'] !== \T_ARRAY
&& $tokens[$nextNonEmpty]['code'] !== \T_OPEN_SHORT_ARRAY
) {
// Not passed as an array.
return;
}
if ($this->supportsBelow('7.3') === true) {
$phpcsFile->addError(
'The proc_open() function did not accept $cmd to be passed in array format in PHP 7.3 and earlier.',
$nextNonEmpty,
'Found'
);
}
if ($this->supportsAbove('7.4') === true) {
if (strpos($targetParam['raw'], 'escapeshellarg(') === false) {
// Efficiency: prevent needlessly walking the array.
return;
}
$items = $this->getFunctionCallParameters($phpcsFile, $nextNonEmpty);
if (empty($items)) {
return;
}
foreach ($items as $item) {
for ($i = $item['start']; $i <= $item['end']; $i++) {
if ($tokens[$i]['code'] !== \T_STRING
|| $tokens[$i]['content'] !== 'escapeshellarg'
) {
continue;
}
// @todo Potential future enhancement: check if it's a call to the PHP native function.
$phpcsFile->addWarning(
'When passing proc_open() the $cmd parameter as an array, PHP will take care of any necessary argument escaping. Found: %s',
$i,
'Invalid',
array($item['raw'])
);
// Only throw one error per array item.
break;
}
}
}
}
}
@@ -0,0 +1,152 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* As of PHP 7.4, `strip_tags()` now also accepts an array of `$allowable_tags`.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.standard.strip-tags
* @link https://www.php.net/manual/en/function.strip-tags.php
*
* @since 9.3.0
*/
class NewStripTagsAllowableTagsArraySniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'strip_tags' => true,
);
/**
* Text string tokens to examine.
*
* @since 9.3.0
*
* @var array
*/
private $textStringTokens = array(
\T_CONSTANT_ENCAPSED_STRING => true,
\T_DOUBLE_QUOTED_STRING => true,
\T_INLINE_HTML => true,
\T_HEREDOC => true,
\T_NOWDOC => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return false;
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[2]) === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$targetParam = $parameters[2];
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $targetParam['start'], $targetParam['end'], true);
if ($nextNonEmpty === false) {
// Shouldn't be possible.
return;
}
if ($tokens[$nextNonEmpty]['code'] !== \T_ARRAY
&& $tokens[$nextNonEmpty]['code'] !== \T_OPEN_SHORT_ARRAY
) {
// Not passed as a hard-coded array.
return;
}
if ($this->supportsBelow('7.3') === true) {
$phpcsFile->addError(
'The strip_tags() function did not accept $allowable_tags to be passed in array format in PHP 7.3 and earlier.',
$nextNonEmpty,
'Found'
);
}
if ($this->supportsAbove('7.4') === true) {
if (strpos($targetParam['raw'], '>') === false) {
// Efficiency: prevent needlessly walking the array.
return;
}
$items = $this->getFunctionCallParameters($phpcsFile, $nextNonEmpty);
if (empty($items)) {
return;
}
foreach ($items as $item) {
for ($i = $item['start']; $i <= $item['end']; $i++) {
if ($tokens[$i]['code'] === \T_STRING
|| $tokens[$i]['code'] === \T_VARIABLE
) {
// Variable, constant, function call. Ignore complete item as undetermined.
break;
}
if (isset($this->textStringTokens[$tokens[$i]['code']]) === true
&& strpos($tokens[$i]['content'], '>') !== false
) {
$phpcsFile->addWarning(
'When passing strip_tags() the $allowable_tags parameter as an array, the tags should not be enclosed in <> brackets. Found: %s',
$i,
'Invalid',
array($item['raw'])
);
// Only throw one error per array item.
break;
}
}
}
}
}
}
@@ -0,0 +1,117 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractRemovedFeatureSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect the use of deprecated and removed hash algorithms.
*
* PHP version 5.4
*
* @link https://www.php.net/manual/en/function.hash-algos.php#refsect1-function.hash-algos-changelog
*
* @since 5.5
* @since 7.1.0 Now extends the `AbstractRemovedFeatureSniff` instead of the base `Sniff` class.
*/
class RemovedHashAlgorithmsSniff extends AbstractRemovedFeatureSniff
{
/**
* A list of removed hash algorithms, which were present in older versions.
*
* The array lists : version number with false (deprecated) and true (removed).
* If's sufficient to list the first version where the hash algorithm was deprecated/removed.
*
* @since 7.0.7
*
* @var array(string => array(string => bool))
*/
protected $removedAlgorithms = array(
'salsa10' => array(
'5.4' => true,
),
'salsa20' => array(
'5.4' => true,
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
return array(\T_STRING);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer_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)
{
$algo = $this->getHashAlgorithmParameter($phpcsFile, $stackPtr);
if (empty($algo) || \is_string($algo) === false) {
return;
}
// Bow out if not one of the algorithms we're targetting.
if (isset($this->removedAlgorithms[$algo]) === false) {
return;
}
$itemInfo = array(
'name' => $algo,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @since 7.1.0
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->removedAlgorithms[$itemInfo['name']];
}
/**
* Get the error message template for this sniff.
*
* @since 7.1.0
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return 'The %s hash algorithm is ';
}
}
@@ -0,0 +1,86 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect passing deprecated `$type` values to `iconv_get_encoding()`.
*
* "The iconv and mbstring configuration options related to encoding have been
* deprecated in favour of default_charset."
*
* {@internal It is unclear which mbstring functions should be targetted, so for now,
* only the iconv function is handled.}
*
* PHP version 5.6
*
* @link https://www.php.net/manual/en/migration56.deprecated.php#migration56.deprecated.iconv-mbstring-encoding
* @link https://wiki.php.net/rfc/default_encoding
*
* @since 9.0.0
*/
class RemovedIconvEncodingSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.0.0
*
* @var array
*/
protected $targetFunctions = array(
'iconv_set_encoding' => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsAbove('5.6') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[1]) === false) {
return;
}
$phpcsFile->addWarning(
'All previously accepted values for the $type parameter of iconv_set_encoding() have been deprecated since PHP 5.6. Found %s',
$parameters[1]['start'],
'DeprecatedValueFound',
$parameters[1]['raw']
);
}
}
@@ -0,0 +1,323 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Passing the `$glue` and `$pieces` parameters to `implode()` in reverse order has
* been deprecated in PHP 7.4.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.deprecated.php#migration74.deprecated.core.implode-reverse-parameters
* @link https://wiki.php.net/rfc/deprecations_php_7_4#implode_parameter_order_mix
* @link https://php.net/manual/en/function.implode.php
*
* @since 9.3.0
*/
class RemovedImplodeFlexibleParamOrderSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'implode' => true,
'join' => true,
);
/**
* List of PHP native constants which should be recognized as text strings.
*
* @since 9.3.0
*
* @var array
*/
private $constantStrings = array(
'DIRECTORY_SEPARATOR' => true,
'PHP_EOL' => true,
);
/**
* List of PHP native functions which should be recognized as returning an array.
*
* Note: The array_*() functions will always be taken into account.
*
* @since 9.3.0
*
* @var array
*/
private $arrayFunctions = array(
'compact' => true,
'explode' => true,
'range' => true,
);
/**
* List of PHP native array functions which should *not* be recognized as returning an array.
*
* @since 9.3.0
*
* @var array
*/
private $arrayFunctionExceptions = array(
'array_key_exists' => true,
'array_key_first' => true,
'array_key_last' => true,
'array_multisort' => true,
'array_pop' => true,
'array_product' => true,
'array_push' => true,
'array_search' => true,
'array_shift' => true,
'array_sum' => true,
'array_unshift' => true,
'array_walk_recursive' => true,
'array_walk' => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsAbove('7.4') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[2]) === false) {
// Only one parameter, this must be $pieces. Bow out.
return;
}
$tokens = $phpcsFile->getTokens();
/*
* Examine the first parameter.
* If there is any indication that this is an array declaration, we have an error.
*/
$targetParam = $parameters[1];
$start = $targetParam['start'];
$end = ($targetParam['end'] + 1);
$isOnlyText = true;
$firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true);
if ($firstNonEmpty === false) {
// Parse error. Shouldn't be possible.
return;
}
if ($tokens[$firstNonEmpty]['code'] === \T_OPEN_PARENTHESIS) {
$start = ($firstNonEmpty + 1);
$end = $tokens[$firstNonEmpty]['parenthesis_closer'];
}
$hasTernary = $phpcsFile->findNext(\T_INLINE_THEN, $start, $end);
if ($hasTernary !== false
&& isset($tokens[$start]['nested_parenthesis'], $tokens[$hasTernary]['nested_parenthesis'])
&& count($tokens[$start]['nested_parenthesis']) === count($tokens[$hasTernary]['nested_parenthesis'])
) {
$start = ($hasTernary + 1);
}
for ($i = $start; $i < $end; $i++) {
$tokenCode = $tokens[$i]['code'];
if (isset(Tokens::$emptyTokens[$tokenCode])) {
continue;
}
if ($tokenCode === \T_STRING && isset($this->constantStrings[$tokens[$i]['content']])) {
continue;
}
if ($hasTernary !== false && $tokenCode === \T_INLINE_ELSE) {
continue;
}
if (isset(Tokens::$stringTokens[$tokenCode]) === false) {
$isOnlyText = false;
}
if ($tokenCode === \T_ARRAY || $tokenCode === \T_OPEN_SHORT_ARRAY || $tokenCode === \T_ARRAY_CAST) {
$this->throwNotice($phpcsFile, $stackPtr, $functionName);
return;
}
if ($tokenCode === \T_STRING) {
/*
* Check for specific functions which return an array (i.e. $pieces).
*/
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), $end, true);
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
continue;
}
$nameLc = strtolower($tokens[$i]['content']);
if (isset($this->arrayFunctions[$nameLc]) === false
&& (strpos($nameLc, 'array_') !== 0
|| isset($this->arrayFunctionExceptions[$nameLc]) === true)
) {
continue;
}
// Now make sure it's the PHP native function being called.
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), $start, true);
if ($tokens[$prevNonEmpty]['code'] === \T_DOUBLE_COLON
|| $tokens[$prevNonEmpty]['code'] === \T_OBJECT_OPERATOR
) {
// Method call, not a call to the PHP native function.
continue;
}
if ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR
&& $tokens[$prevNonEmpty - 1]['code'] === \T_STRING
) {
// Namespaced function.
continue;
}
// Ok, so we know that there is an array function in the first param.
// 99.9% chance that this is $pieces, not $glue.
$this->throwNotice($phpcsFile, $stackPtr, $functionName);
return;
}
}
if ($isOnlyText === true) {
// First parameter only contained text string tokens, i.e. glue.
return;
}
/*
* Examine the second parameter.
*/
$targetParam = $parameters[2];
$start = $targetParam['start'];
$end = ($targetParam['end'] + 1);
$firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true);
if ($firstNonEmpty === false) {
// Parse error. Shouldn't be possible.
return;
}
if ($tokens[$firstNonEmpty]['code'] === \T_OPEN_PARENTHESIS) {
$start = ($firstNonEmpty + 1);
$end = $tokens[$firstNonEmpty]['parenthesis_closer'];
}
$hasTernary = $phpcsFile->findNext(\T_INLINE_THEN, $start, $end);
if ($hasTernary !== false
&& isset($tokens[$start]['nested_parenthesis'], $tokens[$hasTernary]['nested_parenthesis'])
&& count($tokens[$start]['nested_parenthesis']) === count($tokens[$hasTernary]['nested_parenthesis'])
) {
$start = ($hasTernary + 1);
}
for ($i = $start; $i < $end; $i++) {
$tokenCode = $tokens[$i]['code'];
if (isset(Tokens::$emptyTokens[$tokenCode])) {
continue;
}
if ($tokenCode === \T_ARRAY || $tokenCode === \T_OPEN_SHORT_ARRAY || $tokenCode === \T_ARRAY_CAST) {
// Found an array, $pieces is second.
return;
}
if ($tokenCode === \T_STRING && isset($this->constantStrings[$tokens[$i]['content']])) {
// One of the special cased, PHP native string constants found.
$this->throwNotice($phpcsFile, $stackPtr, $functionName);
return;
}
if ($tokenCode === \T_STRING || $tokenCode === \T_VARIABLE) {
// Function call, constant or variable encountered.
// No matter what this is combined with, we won't be able to reliably determine the value.
return;
}
if ($tokenCode === \T_CONSTANT_ENCAPSED_STRING
|| $tokenCode === \T_DOUBLE_QUOTED_STRING
|| $tokenCode === \T_HEREDOC
|| $tokenCode === \T_NOWDOC
) {
$this->throwNotice($phpcsFile, $stackPtr, $functionName);
return;
}
}
}
/**
* Throw the error/warning.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
*
* @return void
*/
protected function throwNotice(File $phpcsFile, $stackPtr, $functionName)
{
$message = 'Passing the $glue and $pieces parameters in reverse order to %s has been deprecated since PHP 7.4';
$isError = false;
$errorCode = 'Deprecated';
$data = array($functionName);
/*
Support for the deprecated behaviour is expected to be removed in PHP 8.0.
Once this has been implemented, this section should be uncommented.
if ($this->supportsAbove('8.0') === true) {
$message .= ' and is removed since PHP 8.0';
$isError = true;
$errorCode = 'Removed';
}
*/
$message .= '; $glue should be the first parameter and $pieces the second';
$this->addMessage($phpcsFile, $message, $stackPtr, $isError, $errorCode, $data);
}
}
@@ -0,0 +1,149 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect passing `$encoding` to `mb_strrpos()` as 3rd argument.
*
* The `$encoding` parameter was moved from the third position to the fourth in PHP 5.2.0.
* For backward compatibility, `$encoding` could be specified as the third parameter, but doing
* so is deprecated and will be removed in the future.
*
* Between PHP 5.2 and PHP 7.3, this was a deprecation in documentation only.
* As of PHP 7.4, a deprecation warning will be thrown if an encoding is passed as the 3rd
* argument.
* As of PHP 8, the argument is expected to change to accept an integer only.
*
* PHP version 5.2
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.deprecated.php#migration74.deprecated.mbstring
* @link https://wiki.php.net/rfc/deprecations_php_7_4#mb_strrpos_with_encoding_as_3rd_argument
* @link https://www.php.net/manual/en/function.mb-strrpos.php
*
* @since 9.3.0
*/
class RemovedMbStrrposEncodingThirdParamSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.3.0
*
* @var array
*/
protected $targetFunctions = array(
'mb_strrpos' => true,
);
/**
* Tokens which should be recognized as text.
*
* @since 9.3.0
*
* @var array
*/
private $textStringTokens = array(
\T_CONSTANT_ENCAPSED_STRING,
\T_DOUBLE_QUOTED_STRING,
\T_HEREDOC,
\T_NOWDOC,
);
/**
* Tokens which should be recognized as numbers.
*
* @since 9.3.0
*
* @var array
*/
private $numberTokens = array(
\T_LNUMBER => \T_LNUMBER,
\T_DNUMBER => \T_DNUMBER,
\T_MINUS => \T_MINUS,
\T_PLUS => \T_PLUS,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.3.0
*
* @return bool
*/
protected function bowOutEarly()
{
return $this->supportsAbove('5.2') === false;
}
/**
* Process the parameters of a matched function.
*
* @since 9.3.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[3]) === false) {
// Optional third parameter not set.
return;
}
if (isset($parameters[4]) === true) {
// Encoding set as fourth parameter.
return;
}
$targetParam = $parameters[3];
$targets = $this->numberTokens + Tokens::$emptyTokens;
$nonNumber = $phpcsFile->findNext($targets, $targetParam['start'], ($targetParam['end'] + 1), true);
if ($nonNumber === false) {
return;
}
if ($this->isNumericCalculation($phpcsFile, $targetParam['start'], $targetParam['end']) === true) {
return;
}
$hasString = $phpcsFile->findNext($this->textStringTokens, $targetParam['start'], ($targetParam['end'] + 1));
if ($hasString === false) {
// No text strings found. Undetermined.
return;
}
$error = 'Passing the encoding to mb_strrpos() as third parameter is soft deprecated since PHP 5.2';
if ($this->supportsAbove('7.4') === true) {
$error .= ' and hard deprecated since PHP 7.4';
}
$error .= '. Use an explicit 0 as the offset in the third parameter.';
$phpcsFile->addWarning(
$error,
$targetParam['start'],
'Deprecated'
);
}
}
@@ -0,0 +1,135 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Check for use of deprecated and removed regex modifiers for MbString regex functions.
*
* Initially just checks for the PHP 7.1 deprecated `e` modifier.
*
* PHP version 7.1
*
* @link https://wiki.php.net/rfc/deprecate_mb_ereg_replace_eval_option
* @link https://www.php.net/manual/en/function.mb-regex-set-options.php
*
* @since 7.0.5
* @since 7.0.8 This sniff now throws a warning instead of an error as the functionality is
* only deprecated (for now).
* @since 8.2.0 Now extends the `AbstractFunctionCallParameterSniff` instead of the base `Sniff` class.
* @since 9.0.0 Renamed from `MbstringReplaceEModifierSniff` to `RemovedMbstringModifiersSniff`.
*/
class RemovedMbstringModifiersSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* Key is the function name, value the parameter position of the options parameter.
*
* @since 7.0.5
* @since 8.2.0 Renamed from `$functions` to `$targetFunctions`.
*
* @var array
*/
protected $targetFunctions = array(
'mb_ereg_replace' => 4,
'mb_eregi_replace' => 4,
'mb_regex_set_options' => 1,
'mbereg_replace' => 4, // Undocumented, but valid function alias.
'mberegi_replace' => 4, // Undocumented, but valid function alias.
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 8.2.0
*
* @return bool
*/
protected function bowOutEarly()
{
// Version used here should be the highest version from the `$newModifiers` array,
// i.e. the last PHP version in which a new modifier was introduced.
return ($this->supportsAbove('7.1') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 7.0.5
* @since 8.2.0 Renamed from `process()` to `processParameters()` and removed
* logic superfluous now the sniff extends the abstract.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
$tokens = $phpcsFile->getTokens();
$functionNameLc = strtolower($functionName);
// Check whether the options parameter in the function call is passed.
if (isset($parameters[$this->targetFunctions[$functionNameLc]]) === false) {
return;
}
$optionsParam = $parameters[$this->targetFunctions[$functionNameLc]];
$stringToken = $phpcsFile->findNext(Tokens::$stringTokens, $optionsParam['start'], $optionsParam['end'] + 1);
if ($stringToken === false) {
// No string token found in the options parameter, so skip it (e.g. variable passed in).
return;
}
$options = '';
/*
* Get the content of any string tokens in the options parameter and remove the quotes and variables.
*/
for ($i = $stringToken; $i <= $optionsParam['end']; $i++) {
if (isset(Tokens::$stringTokens[$tokens[$i]['code']]) === false) {
continue;
}
$content = $this->stripQuotes($tokens[$i]['content']);
if ($tokens[$i]['code'] === \T_DOUBLE_QUOTED_STRING) {
$content = $this->stripVariables($content);
}
$content = trim($content);
if (empty($content) === false) {
$options .= $content;
}
}
if (strpos($options, 'e') !== false) {
$error = 'The Mbstring regex "e" modifier is deprecated since PHP 7.1.';
// The alternative mb_ereg_replace_callback() function is only available since 5.4.1.
if ($this->supportsBelow('5.4.1') === false) {
$error .= ' Use mb_ereg_replace_callback() instead (PHP 5.4.1+).';
}
$phpcsFile->addWarning($error, $stackPtr, 'Deprecated');
}
}
}
@@ -0,0 +1,121 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\ParameterValues;
use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHP_CodeSniffer_File as File;
/**
* Detect usage of non-cryptographic hashes.
*
* "The `hash_hmac()`, `hash_hmac_file()`, `hash_pbkdf2()`, and `hash_init()`
* (with `HASH_HMAC`) functions no longer accept non-cryptographic hashes."
*
* PHP version 7.2
*
* @link https://www.php.net/manual/en/migration72.incompatible.php#migration72.incompatible.hash-functions
*
* @since 9.0.0
*/
class RemovedNonCryptoHashSniff extends AbstractFunctionCallParameterSniff
{
/**
* Functions to check for.
*
* @since 9.0.0
*
* @var array
*/
protected $targetFunctions = array(
'hash_hmac' => true,
'hash_hmac_file' => true,
'hash_init' => true,
'hash_pbkdf2' => true,
);
/**
* List of the non-cryptographic hashes.
*
* @since 9.0.0
*
* @var array
*/
protected $disabledCryptos = array(
'adler32' => true,
'crc32' => true,
'crc32b' => true,
'fnv132' => true,
'fnv1a32' => true,
'fnv164' => true,
'fnv1a64' => true,
'joaat' => true,
);
/**
* Do a version check to determine if this sniff needs to run at all.
*
* @since 9.0.0
*
* @return bool
*/
protected function bowOutEarly()
{
return ($this->supportsAbove('7.2') === false);
}
/**
* Process the parameters of a matched function.
*
* @since 9.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack.
* @param string $functionName The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
{
if (isset($parameters[1]) === false) {
return;
}
$targetParam = $parameters[1];
if (isset($this->disabledCryptos[$this->stripQuotes($targetParam['raw'])]) === false) {
return;
}
if (strtolower($functionName) === 'hash_init'
&& (isset($parameters[2]) === false
|| ($parameters[2]['raw'] !== 'HASH_HMAC'
&& $parameters[2]['raw'] !== (string) \HASH_HMAC))
) {
// For hash_init(), these hashes are only disabled with HASH_HMAC set.
return;
}
$phpcsFile->addError(
'Non-cryptographic hashes are no longer accepted by function %s() since PHP 7.2. Found: %s',
$targetParam['start'],
$this->stringToErrorCode($functionName),
array(
$functionName,
$targetParam['raw'],
)
);
}
}

Some files were not shown because too many files have changed in this diff Show More