基础代码

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
File diff suppressed because it is too large Load Diff
+165
View File
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
@@ -0,0 +1,73 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* PHPCS cross-version compatibility helper.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*
* @since 8.0.0
*/
/*
* Alias a number of PHPCS 3.x classes to their PHPCS 2.x equivalents.
*
* This file is auto-loaded by PHPCS 3.x before any sniffs are loaded
* through the PHPCS 3.x `<autoload>` ruleset directive.
*
* {@internal The PHPCS file have been reorganized in PHPCS 3.x, quite
* a few "old" classes have been split and spread out over several "new"
* classes. In other words, this will only work for a limited number
* of classes.}
*
* {@internal The `class_exists` wrappers are needed to play nice with other
* external PHPCS standards creating cross-version compatibility in the same
* manner.}
*/
if (defined('PHPCOMPATIBILITY_PHPCS_ALIASES_SET') === false) {
if (interface_exists('\PHP_CodeSniffer_Sniff') === false) {
class_alias('PHP_CodeSniffer\Sniffs\Sniff', '\PHP_CodeSniffer_Sniff');
}
if (class_exists('\PHP_CodeSniffer_File') === false) {
class_alias('PHP_CodeSniffer\Files\File', '\PHP_CodeSniffer_File');
}
if (class_exists('\PHP_CodeSniffer_Tokens') === false) {
class_alias('PHP_CodeSniffer\Util\Tokens', '\PHP_CodeSniffer_Tokens');
}
if (class_exists('\PHP_CodeSniffer_Exception') === false) {
class_alias('PHP_CodeSniffer\Exceptions\RuntimeException', '\PHP_CodeSniffer_Exception');
}
if (class_exists('\PHP_CodeSniffer_Standards_AbstractScopeSniff') === false) {
class_alias('PHP_CodeSniffer\Sniffs\AbstractScopeSniff', '\PHP_CodeSniffer_Standards_AbstractScopeSniff');
}
if (class_exists('\Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff') === false) {
class_alias('PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\CamelCapsFunctionNameSniff', '\Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff');
}
define('PHPCOMPATIBILITY_PHPCS_ALIASES_SET', true);
/*
* Register an autoloader.
*
* {@internal When `installed_paths` is set via the ruleset, this autoloader
* is needed to run the sniffs.
* Upstream issue: {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/1591} }
*
* @since 8.0.0
*/
spl_autoload_register(function ($class) {
// Only try & load our own classes.
if (stripos($class, 'PHPCompatibility') !== 0) {
return;
}
$file = realpath(__DIR__) . DIRECTORY_SEPARATOR . strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';
if (file_exists($file)) {
include_once $file;
}
});
}
@@ -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);
}
}

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