xarxaprod-wp-theme/vendor/phpcompatibility/php-compatibility/PHPCompatibility/Sniffs/ControlStructures/DiscouragedSwitchContinueSn...

239 lines
8.1 KiB
PHP
Raw Permalink Normal View History

2024-01-09 16:13:20 +01:00
<?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);
}
}