200 lines
6.6 KiB
PHP
200 lines
6.6 KiB
PHP
|
<?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\Syntax;
|
||
|
|
||
|
use PHPCompatibility\Sniff;
|
||
|
use PHP_CodeSniffer_File as File;
|
||
|
use PHP_CodeSniffer_Tokens as Tokens;
|
||
|
|
||
|
/**
|
||
|
* Detect array and string literal dereferencing.
|
||
|
*
|
||
|
* As of PHP 5.5, array and string literals can now be dereferenced directly to
|
||
|
* access individual elements and characters.
|
||
|
*
|
||
|
* As of PHP 7.0, this also works when using curly braces for the dereferencing.
|
||
|
* While unclear, this most likely has to do with the Uniform Variable Syntax changes.
|
||
|
*
|
||
|
* PHP version 5.5
|
||
|
* PHP version 7.0
|
||
|
*
|
||
|
* @link https://www.php.net/manual/en/migration55.new-features.php#migration55.new-features.const-dereferencing
|
||
|
* @link https://wiki.php.net/rfc/constdereference
|
||
|
* @link https://wiki.php.net/rfc/uniform_variable_syntax
|
||
|
* @link https://www.php.net/manual/en/language.types.array.php#example-63
|
||
|
*
|
||
|
* {@internal The reason for splitting the logic of this sniff into different methods is
|
||
|
* to allow re-use of the logic by the PHP 7.4 `RemovedCurlyBraceArrayAccess` sniff.}
|
||
|
*
|
||
|
* @since 7.1.4
|
||
|
* @since 9.3.0 Now also detects dereferencing using curly braces.
|
||
|
*/
|
||
|
class NewArrayStringDereferencingSniff 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_ARRAY,
|
||
|
\T_OPEN_SHORT_ARRAY,
|
||
|
\T_CONSTANT_ENCAPSED_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.6') === false) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$dereferencing = $this->isArrayStringDereferencing($phpcsFile, $stackPtr);
|
||
|
if (empty($dereferencing)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$tokens = $phpcsFile->getTokens();
|
||
|
$supports54 = $this->supportsBelow('5.4');
|
||
|
|
||
|
foreach ($dereferencing['braces'] as $openBrace => $closeBrace) {
|
||
|
if ($supports54 === true
|
||
|
&& ($tokens[$openBrace]['type'] === 'T_OPEN_SQUARE_BRACKET'
|
||
|
|| $tokens[$openBrace]['type'] === 'T_OPEN_SHORT_ARRAY') // Work around bug #1381 in PHPCS 2.8.1 and lower.
|
||
|
) {
|
||
|
$phpcsFile->addError(
|
||
|
'Direct array dereferencing of %s is not present in PHP version 5.4 or earlier',
|
||
|
$openBrace,
|
||
|
'Found',
|
||
|
array($dereferencing['type'])
|
||
|
);
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// PHP 7.0 Array/string dereferencing using curly braces.
|
||
|
if ($tokens[$openBrace]['type'] === 'T_OPEN_CURLY_BRACKET') {
|
||
|
$phpcsFile->addError(
|
||
|
'Direct array dereferencing of %s using curly braces is not present in PHP version 5.6 or earlier',
|
||
|
$openBrace,
|
||
|
'FoundUsingCurlies',
|
||
|
array($dereferencing['type'])
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Check if this string/array is being dereferenced.
|
||
|
*
|
||
|
* @since 9.3.0 Logic 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 array Array containing the type of access and stack pointers to the
|
||
|
* open/close braces involved in the array/string dereferencing;
|
||
|
* or an empty array if no array/string dereferencing was detected.
|
||
|
*/
|
||
|
public function isArrayStringDereferencing(File $phpcsFile, $stackPtr)
|
||
|
{
|
||
|
$tokens = $phpcsFile->getTokens();
|
||
|
|
||
|
switch ($tokens[$stackPtr]['code']) {
|
||
|
case \T_CONSTANT_ENCAPSED_STRING:
|
||
|
$type = 'string literals';
|
||
|
$end = $stackPtr;
|
||
|
break;
|
||
|
|
||
|
case \T_ARRAY:
|
||
|
if (isset($tokens[$stackPtr]['parenthesis_closer']) === false) {
|
||
|
// Live coding.
|
||
|
return array();
|
||
|
} else {
|
||
|
$type = 'arrays';
|
||
|
$end = $tokens[$stackPtr]['parenthesis_closer'];
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case \T_OPEN_SHORT_ARRAY:
|
||
|
if (isset($tokens[$stackPtr]['bracket_closer']) === false) {
|
||
|
// Live coding.
|
||
|
return array();
|
||
|
} else {
|
||
|
$type = 'arrays';
|
||
|
$end = $tokens[$stackPtr]['bracket_closer'];
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (isset($type, $end) === false) {
|
||
|
// Shouldn't happen, but for some reason did.
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
$braces = array();
|
||
|
|
||
|
do {
|
||
|
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true, null, true);
|
||
|
if ($nextNonEmpty === false) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ($tokens[$nextNonEmpty]['type'] === 'T_OPEN_SQUARE_BRACKET'
|
||
|
|| $tokens[$nextNonEmpty]['type'] === 'T_OPEN_CURLY_BRACKET' // PHP 7.0+.
|
||
|
|| $tokens[$nextNonEmpty]['type'] === 'T_OPEN_SHORT_ARRAY' // Work around bug #1381 in PHPCS 2.8.1 and lower.
|
||
|
) {
|
||
|
if (isset($tokens[$nextNonEmpty]['bracket_closer']) === false) {
|
||
|
// Live coding or parse error.
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$braces[$nextNonEmpty] = $tokens[$nextNonEmpty]['bracket_closer'];
|
||
|
|
||
|
// Continue, just in case there is nested array access, i.e. `array(1, 2, 3)[$i][$j];`.
|
||
|
$end = $tokens[$nextNonEmpty]['bracket_closer'];
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// If we're still here, we've reached the end of the variable.
|
||
|
break;
|
||
|
|
||
|
} while (true);
|
||
|
|
||
|
if (empty($braces)) {
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
return array(
|
||
|
'type' => $type,
|
||
|
'braces' => $braces,
|
||
|
);
|
||
|
}
|
||
|
}
|