基础代码

This commit is contained in:
2021-02-26 22:23:13 +08:00
parent 7884df52f0
commit a719feebba
2585 changed files with 328263 additions and 0 deletions
@@ -0,0 +1,259 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2009-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 the use of call time pass by reference.
*
* This behaviour has been deprecated in PHP 5.3 and removed in PHP 5.4.
*
* PHP version 5.4
*
* @link https://wiki.php.net/rfc/calltimebyref
* @link https://www.php.net/manual/en/language.references.pass.php
*
* @since 5.5
* @since 7.0.8 This sniff now throws a warning (deprecated) or an error (removed) depending
* on the `testVersion` set. Previously it would always throw an error.
*/
class ForbiddenCallTimePassByReferenceSniff extends Sniff
{
/**
* Tokens that represent assignments or equality comparisons.
*
* Near duplicate of Tokens::$assignmentTokens + Tokens::$equalityTokens.
* Copied in for PHPCS cross-version compatibility.
*
* @since 8.1.0
*
* @var array
*/
private $assignOrCompare = array(
// Equality tokens.
'T_IS_EQUAL' => true,
'T_IS_NOT_EQUAL' => true,
'T_IS_IDENTICAL' => true,
'T_IS_NOT_IDENTICAL' => true,
'T_IS_SMALLER_OR_EQUAL' => true,
'T_IS_GREATER_OR_EQUAL' => true,
// Assignment tokens.
'T_EQUAL' => true,
'T_AND_EQUAL' => true,
'T_OR_EQUAL' => true,
'T_CONCAT_EQUAL' => true,
'T_DIV_EQUAL' => true,
'T_MINUS_EQUAL' => true,
'T_POW_EQUAL' => true,
'T_MOD_EQUAL' => true,
'T_MUL_EQUAL' => true,
'T_PLUS_EQUAL' => true,
'T_XOR_EQUAL' => true,
'T_DOUBLE_ARROW' => true,
'T_SL_EQUAL' => true,
'T_SR_EQUAL' => true,
'T_COALESCE_EQUAL' => true,
'T_ZSR_EQUAL' => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
return array(
\T_STRING,
\T_VARIABLE,
);
}
/**
* 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.3') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
// Skip tokens that are the names of functions or classes
// within their definitions. For example: function myFunction...
// "myFunction" is T_STRING but we should skip because it is not a
// function or method *call*.
$findTokens = Tokens::$emptyTokens;
$findTokens[] = \T_BITWISE_AND;
$prevNonEmpty = $phpcsFile->findPrevious(
$findTokens,
($stackPtr - 1),
null,
true
);
if ($prevNonEmpty !== false && \in_array($tokens[$prevNonEmpty]['type'], array('T_FUNCTION', 'T_CLASS', 'T_INTERFACE', 'T_TRAIT'), true)) {
return;
}
// If the next non-whitespace token after the function or method call
// is not an opening parenthesis then it can't really be a *call*.
$openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($openBracket === false || $tokens[$openBracket]['code'] !== \T_OPEN_PARENTHESIS
|| isset($tokens[$openBracket]['parenthesis_closer']) === false
) {
return;
}
// Get the function call parameters.
$parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
if (\count($parameters) === 0) {
return;
}
// Which nesting level is the one we are interested in ?
$nestedParenthesisCount = 1;
if (isset($tokens[$openBracket]['nested_parenthesis'])) {
$nestedParenthesisCount = \count($tokens[$openBracket]['nested_parenthesis']) + 1;
}
foreach ($parameters as $parameter) {
if ($this->isCallTimePassByReferenceParam($phpcsFile, $parameter, $nestedParenthesisCount) === true) {
// T_BITWISE_AND represents a pass-by-reference.
$error = 'Using a call-time pass-by-reference is deprecated since PHP 5.3';
$isError = false;
$errorCode = 'Deprecated';
if ($this->supportsAbove('5.4')) {
$error .= ' and prohibited since PHP 5.4';
$isError = true;
$errorCode = 'NotAllowed';
}
$this->addMessage($phpcsFile, $error, $parameter['start'], $isError, $errorCode);
}
}
}
/**
* Determine whether a parameter is passed by reference.
*
* @since 7.0.6 Split off from the `process()` method.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param array $parameter Information on the current parameter
* to be examined.
* @param int $nestingLevel Target nesting level.
*
* @return bool
*/
protected function isCallTimePassByReferenceParam(File $phpcsFile, $parameter, $nestingLevel)
{
$tokens = $phpcsFile->getTokens();
$searchStartToken = $parameter['start'] - 1;
$searchEndToken = $parameter['end'] + 1;
$nextVariable = $searchStartToken;
do {
$nextVariable = $phpcsFile->findNext(array(\T_VARIABLE, \T_OPEN_SHORT_ARRAY, \T_CLOSURE), ($nextVariable + 1), $searchEndToken);
if ($nextVariable === false) {
return false;
}
// Ignore anything within short array definition brackets.
if ($tokens[$nextVariable]['type'] === 'T_OPEN_SHORT_ARRAY'
&& (isset($tokens[$nextVariable]['bracket_opener'])
&& $tokens[$nextVariable]['bracket_opener'] === $nextVariable)
&& isset($tokens[$nextVariable]['bracket_closer'])
) {
// Skip forward to the end of the short array definition.
$nextVariable = $tokens[$nextVariable]['bracket_closer'];
continue;
}
// Skip past closures passed as function parameters.
if ($tokens[$nextVariable]['type'] === 'T_CLOSURE'
&& (isset($tokens[$nextVariable]['scope_condition'])
&& $tokens[$nextVariable]['scope_condition'] === $nextVariable)
&& isset($tokens[$nextVariable]['scope_closer'])
) {
// Skip forward to the end of the closure declaration.
$nextVariable = $tokens[$nextVariable]['scope_closer'];
continue;
}
// Make sure the variable belongs directly to this function call
// and is not inside a nested function call or array.
if (isset($tokens[$nextVariable]['nested_parenthesis']) === false
|| (\count($tokens[$nextVariable]['nested_parenthesis']) !== $nestingLevel)
) {
continue;
}
// Checking this: $value = my_function(...[*]$arg...).
$tokenBefore = $phpcsFile->findPrevious(
Tokens::$emptyTokens,
($nextVariable - 1),
$searchStartToken,
true
);
if ($tokenBefore === false || $tokens[$tokenBefore]['code'] !== \T_BITWISE_AND) {
// Nothing before the token or no &.
continue;
}
if ($phpcsFile->isReference($tokenBefore) === false) {
continue;
}
// Checking this: $value = my_function(...[*]&$arg...).
$tokenBefore = $phpcsFile->findPrevious(
Tokens::$emptyTokens,
($tokenBefore - 1),
$searchStartToken,
true
);
// Prevent false positive on assign by reference and compare with reference
// within function call parameters.
if (isset($this->assignOrCompare[$tokens[$tokenBefore]['type']])) {
continue;
}
// The found T_BITWISE_AND represents a pass-by-reference.
return true;
} while ($nextVariable < $searchEndToken);
// This code should never be reached, but here in case of weird bugs.
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\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,
);
}
}
@@ -0,0 +1,142 @@
<?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;
/**
* Using the spread operator for unpacking arrays in array expressions is available since PHP 7.4.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.unpack-inside-array
* @link https://wiki.php.net/rfc/spread_operator_for_array
*
* @since 9.2.0
*/
class NewArrayUnpackingSniff 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_ARRAY,
\T_OPEN_SHORT_ARRAY,
);
}
/**
* 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();
/*
* Determine the array opener & closer.
*/
$closer = $phpcsFile->numTokens;
if ($tokens[$stackPtr]['code'] === \T_ARRAY) {
if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
return;
}
$opener = $tokens[$stackPtr]['parenthesis_opener'];
if (isset($tokens[$opener]['parenthesis_closer'])) {
$closer = $tokens[$opener]['parenthesis_closer'];
}
} else {
// Short array syntax.
$opener = $stackPtr;
if (isset($tokens[$stackPtr]['bracket_closer'])) {
$closer = $tokens[$stackPtr]['bracket_closer'];
}
}
$nestingLevel = 0;
if (isset($tokens[($opener + 1)]['nested_parenthesis'])) {
$nestingLevel = count($tokens[($opener + 1)]['nested_parenthesis']);
}
for ($i = $opener; $i < $closer;) {
$i = $phpcsFile->findNext(array(\T_ELLIPSIS, \T_OPEN_SHORT_ARRAY, \T_ARRAY), ($i + 1), $closer);
if ($i === false) {
return;
}
if ($tokens[$i]['code'] === \T_OPEN_SHORT_ARRAY) {
if (isset($tokens[$i]['bracket_closer']) === false) {
// Live coding, unfinished nested array, handle this when the array opener
// of the nested array is passed.
return;
}
// Skip over nested short arrays. These will be handled when the array opener
// of the nested array is passed.
$i = $tokens[$i]['bracket_closer'];
continue;
}
if ($tokens[$i]['code'] === \T_ARRAY) {
if (isset($tokens[$i]['parenthesis_closer']) === false) {
// Live coding, unfinished nested array, handle this when the array opener
// of the nested array is passed.
return;
}
// Skip over nested long arrays. These will be handled when the array opener
// of the nested array is passed.
$i = $tokens[$i]['parenthesis_closer'];
continue;
}
// Ensure this is not function call variable unpacking.
if (isset($tokens[$i]['nested_parenthesis'])
&& count($tokens[$i]['nested_parenthesis']) > $nestingLevel
) {
continue;
}
// Ok, found one.
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
$snippet = trim($phpcsFile->getTokensAsString($i, (($nextNonEmpty - $i) + 1)));
$phpcsFile->addError(
'Array unpacking within array declarations using the spread operator is not supported in PHP 7.3 or earlier. Found: %s',
$i,
'Found',
array($snippet)
);
}
}
}
@@ -0,0 +1,192 @@
<?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 class member access on object instantiation/cloning.
*
* PHP 5.4: Class member access on instantiation has been added, e.g. `(new Foo)->bar()`.
* PHP 7.0: Class member access on cloning has been added, e.g. `(clone $foo)->bar()`.
*
* As of PHP 7.0, class member access on instantiation also works when using curly braces.
* While unclear, this most likely has to do with the Uniform Variable Syntax changes.
*
* PHP version 5.4
* PHP version 7.0
*
* @link https://www.php.net/manual/en/language.oop5.basic.php#example-177
* @link https://www.php.net/manual/en/language.oop5.cloning.php#language.oop5.traits.properties.example
* @link https://www.php.net/manual/en/migration54.new-features.php
* @link https://wiki.php.net/rfc/instance-method-call
* @link https://wiki.php.net/rfc/uniform_variable_syntax
*
* {@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 8.2.0
* @since 9.3.0 Now also detects class member access on instantiation using curly braces.
*/
class NewClassMemberAccessSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.2.0
*
* @return array
*/
public function register()
{
return array(
\T_NEW,
\T_CLONE,
);
}
/**
* 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->supportsBelow('5.6') === false) {
return;
}
$pointers = $this->isClassMemberAccess($phpcsFile, $stackPtr);
if (empty($pointers)) {
return;
}
$tokens = $phpcsFile->getTokens();
$supports53 = $this->supportsBelow('5.3');
$error = 'Class member access on object %s was not supported in PHP %s or earlier';
$data = array('instantiation', '5.3');
$errorCode = 'OnNewFound';
if ($tokens[$stackPtr]['code'] === \T_CLONE) {
$data = array('cloning', '5.6');
$errorCode = 'OnCloneFound';
}
foreach ($pointers as $open => $close) {
$itemData = $data;
$itemErrorCode = $errorCode;
if ($tokens[$stackPtr]['code'] === \T_NEW
&& $tokens[$open]['code'] !== \T_OPEN_CURLY_BRACKET
) {
if ($supports53 === true) {
$phpcsFile->addError($error, $open, $itemErrorCode, $itemData);
}
continue;
}
if ($tokens[$stackPtr]['code'] === \T_NEW
&& $tokens[$open]['code'] === \T_OPEN_CURLY_BRACKET
) {
// Non-curlies was already handled above.
$itemData = array('instantiation using curly braces', '5.6');
$itemErrorCode = 'OnNewFoundUsingCurlies';
}
$phpcsFile->addError($error, $open, $itemErrorCode, $itemData);
}
}
/**
* Check if the class being instantiated/cloned 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 stack pointers to the object operator or
* the open/close braces involved in the class member access;
* or an empty array if no class member access was detected.
*/
public function isClassMemberAccess(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['nested_parenthesis']) === false) {
// The `new className/clone $a` has to be in parentheses, without is not supported.
return array();
}
$parenthesisCloser = end($tokens[$stackPtr]['nested_parenthesis']);
$parenthesisOpener = key($tokens[$stackPtr]['nested_parenthesis']);
if (isset($tokens[$parenthesisOpener]['parenthesis_owner']) === true) {
// If there is an owner, these parentheses are for a different purpose.
return array();
}
$prevBeforeParenthesis = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($parenthesisOpener - 1), null, true);
if ($prevBeforeParenthesis !== false && $tokens[$prevBeforeParenthesis]['code'] === \T_STRING) {
// This is most likely a function call with the new/cloned object as a parameter.
return array();
}
$braces = array();
$end = $parenthesisCloser;
do {
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true, null, true);
if ($nextNonEmpty === false) {
break;
}
if ($tokens[$nextNonEmpty]['code'] === \T_OBJECT_OPERATOR) {
// No need to walk any further if this is object access.
$braces[$nextNonEmpty] = true;
break;
}
if ($tokens[$nextNonEmpty]['code'] === \T_OPEN_SQUARE_BRACKET
|| $tokens[$nextNonEmpty]['code'] === \T_OPEN_CURLY_BRACKET // PHP 7.0+.
) {
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. `(new Foo())[1][0];`.
$end = $tokens[$nextNonEmpty]['bracket_closer'];
continue;
}
// If we're still here, we've reached the end.
break;
} while (true);
return $braces;
}
}
@@ -0,0 +1,89 @@
<?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 dynamic access to static methods and properties, as well as class constants.
*
* As of PHP 5.3, static properties and methods as well as class constants
* can be accessed using a dynamic (variable) class name.
*
* PHP version 5.3
*
* @link https://www.php.net/manual/en/migration53.new-features.php
*
* @since 8.1.0
* @since 9.0.0 Renamed from `DynamicAccessToStaticSniff` to `NewDynamicAccessToStaticSniff`.
*/
class NewDynamicAccessToStaticSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.1.0
*
* @return array
*/
public function register()
{
return array(
\T_DOUBLE_COLON,
);
}
/**
* 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->supportsBelow('5.2') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
// Disregard `static::` as well. Late static binding is reported by another sniff.
if ($tokens[$prevNonEmpty]['code'] === \T_SELF
|| $tokens[$prevNonEmpty]['code'] === \T_PARENT
|| $tokens[$prevNonEmpty]['code'] === \T_STATIC
) {
return;
}
if ($tokens[$prevNonEmpty]['code'] === \T_STRING) {
$prevPrevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevNonEmpty - 1), null, true);
if ($tokens[$prevPrevNonEmpty]['code'] !== \T_OBJECT_OPERATOR) {
return;
}
}
$phpcsFile->addError(
'Static class properties and methods, as well as class constants, could not be accessed using a dynamic (variable) classname in PHP 5.2 or earlier.',
$stackPtr,
'Found'
);
}
}
@@ -0,0 +1,255 @@
<?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 PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
/**
* Detect usage of flexible heredoc/nowdoc and related cross-version incompatibilities.
*
* As of PHP 7.3:
* - The body and the closing marker of a heredoc/nowdoc can be indented;
* - The closing marker no longer needs to be on a line by itself;
* - The heredoc/nowdoc body may no longer contain the closing marker at the
* start of any of its lines.
*
* PHP version 7.3
*
* @link https://www.php.net/manual/en/migration73.new-features.php#migration73.new-features.core.heredoc
* @link https://wiki.php.net/rfc/flexible_heredoc_nowdoc_syntaxes
*
* @since 9.0.0
*/
class NewFlexibleHeredocNowdocSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.0.0
*
* @return array
*/
public function register()
{
$targets = array(
\T_END_HEREDOC,
\T_END_NOWDOC,
);
if (version_compare(\PHP_VERSION_ID, '70299', '>') === false) {
// Start identifier of a PHP 7.3 flexible heredoc/nowdoc.
$targets[] = \T_STRING;
}
return $targets;
}
/**
* 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)
{
/*
* Due to a tokenizer bug which gets hit when the PHP 7.3 heredoc/nowdoc syntax
* is used, this part of the sniff cannot possibly work on PHPCS < 2.6.0.
* See upstream issue #928.
*/
if ($this->supportsBelow('7.2') === true && version_compare(PHPCSHelper::getVersion(), '2.6.0', '>=')) {
$this->detectIndentedNonStandAloneClosingMarker($phpcsFile, $stackPtr);
}
$tokens = $phpcsFile->getTokens();
if ($this->supportsAbove('7.3') === true && $tokens[$stackPtr]['code'] !== \T_STRING) {
$this->detectClosingMarkerInBody($phpcsFile, $stackPtr);
}
}
/**
* Detect indented and/or non-stand alone closing markers.
*
* @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
*/
protected function detectIndentedNonStandAloneClosingMarker(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$indentError = 'Heredoc/nowdoc with an indented closing marker is not supported in PHP 7.2 or earlier.';
$indentErrorCode = 'IndentedClosingMarker';
$trailingError = 'Having code - other than a semi-colon or new line - after the closing marker of a heredoc/nowdoc is not supported in PHP 7.2 or earlier.';
$trailingErrorCode = 'ClosingMarkerNoNewLine';
if (version_compare(\PHP_VERSION_ID, '70299', '>') === true) {
/*
* Check for indented closing marker.
*/
if (ltrim($tokens[$stackPtr]['content']) !== $tokens[$stackPtr]['content']) {
$phpcsFile->addError($indentError, $stackPtr, $indentErrorCode);
}
/*
* Check for tokens after the closing marker.
*/
$nextNonWhitespace = $phpcsFile->findNext(array(\T_WHITESPACE, \T_SEMICOLON), ($stackPtr + 1), null, true);
if ($tokens[$stackPtr]['line'] === $tokens[$nextNonWhitespace]['line']) {
$phpcsFile->addError($trailingError, $stackPtr, $trailingErrorCode);
}
} else {
// For PHP < 7.3, we're only interested in T_STRING tokens.
if ($tokens[$stackPtr]['code'] !== \T_STRING) {
return;
}
if (preg_match('`^<<<([\'"]?)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\1[\r\n]+`', $tokens[$stackPtr]['content'], $matches) !== 1) {
// Not the start of a PHP 7.3 flexible heredoc/nowdoc.
return;
}
$identifier = $matches[2];
for ($i = ($stackPtr + 1); $i <= $phpcsFile->numTokens; $i++) {
if ($tokens[$i]['code'] !== \T_ENCAPSED_AND_WHITESPACE) {
continue;
}
$trimmed = ltrim($tokens[$i]['content']);
if (strpos($trimmed, $identifier) !== 0) {
continue;
}
// OK, we've found the PHP 7.3 flexible heredoc/nowdoc closing marker.
/*
* Check for indented closing marker.
*/
if ($trimmed !== $tokens[$i]['content']) {
// Indent found before closing marker.
$phpcsFile->addError($indentError, $i, $indentErrorCode);
}
/*
* Check for tokens after the closing marker.
*/
// Remove the identifier.
$afterMarker = substr($trimmed, \strlen($identifier));
// Remove a potential semi-colon at the beginning of what's left of the string.
$afterMarker = ltrim($afterMarker, ';');
// Remove new line characters at the end of the string.
$afterMarker = rtrim($afterMarker, "\r\n");
if ($afterMarker !== '') {
$phpcsFile->addError($trailingError, $i, $trailingErrorCode);
}
break;
}
}
}
/**
* Detect heredoc/nowdoc identifiers at the start of lines in the heredoc/nowdoc body.
*
* @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
*/
protected function detectClosingMarkerInBody(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$error = 'The body of a heredoc/nowdoc can not contain the heredoc/nowdoc closing marker as text at the start of a line since PHP 7.3.';
$errorCode = 'ClosingMarkerNoNewLine';
if (version_compare(\PHP_VERSION_ID, '70299', '>') === true) {
$nextNonWhitespace = $phpcsFile->findNext(\T_WHITESPACE, ($stackPtr + 1), null, true, null, true);
if ($nextNonWhitespace === false
|| $tokens[$nextNonWhitespace]['code'] === \T_SEMICOLON
|| (($tokens[$nextNonWhitespace]['code'] === \T_COMMA
|| $tokens[$nextNonWhitespace]['code'] === \T_STRING_CONCAT)
&& $tokens[$nextNonWhitespace]['line'] !== $tokens[$stackPtr]['line'])
) {
// This is most likely a correctly identified closing marker.
return;
}
// The real closing tag has to be before the next heredoc/nowdoc.
$nextHereNowDoc = $phpcsFile->findNext(array(\T_START_HEREDOC, \T_START_NOWDOC), ($stackPtr + 1));
if ($nextHereNowDoc === false) {
$nextHereNowDoc = null;
}
$identifier = trim($tokens[$stackPtr]['content']);
$realClosingMarker = $stackPtr;
while (($realClosingMarker = $phpcsFile->findNext(\T_STRING, ($realClosingMarker + 1), $nextHereNowDoc, false, $identifier)) !== false) {
$prevNonWhitespace = $phpcsFile->findPrevious(\T_WHITESPACE, ($realClosingMarker - 1), null, true);
if ($prevNonWhitespace === false
|| $tokens[$prevNonWhitespace]['line'] === $tokens[$realClosingMarker]['line']
) {
// Marker text found, but not at the start of the line.
continue;
}
// The original T_END_HEREDOC/T_END_NOWDOC was most likely incorrect as we've found
// a possible alternative closing marker.
$phpcsFile->addError($error, $stackPtr, $errorCode);
break;
}
} else {
if (isset($tokens[$stackPtr]['scope_closer'], $tokens[$stackPtr]['scope_opener']) === true
&& $tokens[$stackPtr]['scope_closer'] === $stackPtr
) {
$opener = $tokens[$stackPtr]['scope_opener'];
} else {
// PHPCS < 3.0.2 did not add scope_* values for Nowdocs.
$opener = $phpcsFile->findPrevious(\T_START_NOWDOC, ($stackPtr - 1));
if ($opener === false) {
return;
}
}
$quotedIdentifier = preg_quote($tokens[$stackPtr]['content'], '`');
// Throw an error for each line in the body which starts with the identifier.
for ($i = ($opener + 1); $i < $stackPtr; $i++) {
if (preg_match('`^[ \t]*' . $quotedIdentifier . '\b`', $tokens[$i]['content']) === 1) {
$phpcsFile->addError($error, $i, $errorCode);
}
}
}
}
}
@@ -0,0 +1,187 @@
<?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 function array dereferencing as introduced in PHP 5.4.
*
* PHP 5.4 supports direct array dereferencing on the return of a method/function call.
*
* 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.4
* PHP version 7.0
*
* @link https://www.php.net/manual/en/language.types.array.php#example-63
* @link https://www.php.net/manual/en/migration54.new-features.php
* @link https://wiki.php.net/rfc/functionarraydereferencing
* @link https://wiki.php.net/rfc/uniform_variable_syntax
*
* {@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.0.0
* @since 9.3.0 Now also detects dereferencing using curly braces.
*/
class NewFunctionArrayDereferencingSniff 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') === false) {
return;
}
$dereferencing = $this->isFunctionArrayDereferencing($phpcsFile, $stackPtr);
if (empty($dereferencing)) {
return;
}
$tokens = $phpcsFile->getTokens();
$supports53 = $this->supportsBelow('5.3');
foreach ($dereferencing as $openBrace => $closeBrace) {
if ($supports53 === true
&& $tokens[$openBrace]['type'] === 'T_OPEN_SQUARE_BRACKET'
) {
$phpcsFile->addError(
'Function array dereferencing is not present in PHP version 5.3 or earlier',
$openBrace,
'Found'
);
continue;
}
// PHP 7.0 function array dereferencing using curly braces.
if ($tokens[$openBrace]['type'] === 'T_OPEN_CURLY_BRACKET') {
$phpcsFile->addError(
'Function array dereferencing using curly braces is not present in PHP version 5.6 or earlier',
$openBrace,
'FoundUsingCurlies'
);
}
}
}
/**
* Check if the return of a function/method call 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 stack pointers to the open/close braces
* involved in the function dereferencing;
* or an empty array if no function dereferencing was detected.
*/
public function isFunctionArrayDereferencing(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Next non-empty token should be the open parenthesis.
$openParenthesis = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
if ($openParenthesis === false || $tokens[$openParenthesis]['code'] !== \T_OPEN_PARENTHESIS) {
return array();
}
// Don't throw errors during live coding.
if (isset($tokens[$openParenthesis]['parenthesis_closer']) === false) {
return array();
}
// Is this T_STRING really a function or method call ?
$prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($prevToken !== false
&& \in_array($tokens[$prevToken]['code'], array(\T_DOUBLE_COLON, \T_OBJECT_OPERATOR), true) === false
) {
if ($tokens[$prevToken]['code'] === \T_BITWISE_AND) {
// This may be a function declared by reference.
$prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevToken - 1), null, true);
}
$ignore = array(
\T_FUNCTION => true,
\T_CONST => true,
\T_USE => true,
\T_NEW => true,
\T_CLASS => true,
\T_INTERFACE => true,
);
if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
// Not a call to a PHP function or method.
return array();
}
}
$current = $tokens[$openParenthesis]['parenthesis_closer'];
$braces = array();
do {
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 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+.
) {
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. `echo $foo->bar()[0][2];`.
$current = $tokens[$nextNonEmpty]['bracket_closer'];
continue;
}
// If we're still here, we've reached the end of the function call.
break;
} while (true);
return $braces;
}
}
@@ -0,0 +1,120 @@
<?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 trailing comma's in function calls, `isset()` and `unset()` as allowed since PHP 7.3.
*
* PHP version 7.3
*
* @link https://www.php.net/manual/en/migration73.new-features.php#migration73.new-features.core.trailing-commas
* @link https://wiki.php.net/rfc/trailing-comma-function-calls
*
* @since 8.2.0
* @since 9.0.0 Renamed from `NewTrailingCommaSniff` to `NewFunctionCallTrailingCommaSniff`.
*/
class NewFunctionCallTrailingCommaSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 8.2.0
*
* @return array
*/
public function register()
{
return array(
\T_STRING,
\T_VARIABLE,
\T_ISSET,
\T_UNSET,
);
}
/**
* 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->supportsBelow('7.2') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS
|| isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false
) {
return;
}
if ($tokens[$stackPtr]['code'] === \T_STRING) {
$ignore = array(
\T_FUNCTION => true,
\T_CONST => true,
\T_USE => true,
);
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if (isset($ignore[$tokens[$prevNonEmpty]['code']]) === true) {
// Not a function call.
return;
}
}
$closer = $tokens[$nextNonEmpty]['parenthesis_closer'];
$lastInParenthesis = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($closer - 1), $nextNonEmpty, true);
if ($tokens[$lastInParenthesis]['code'] !== \T_COMMA) {
return;
}
$data = array();
switch ($tokens[$stackPtr]['code']) {
case \T_ISSET:
$data[] = 'calls to isset()';
$errorCode = 'FoundInIsset';
break;
case \T_UNSET:
$data[] = 'calls to unset()';
$errorCode = 'FoundInUnset';
break;
default:
$data[] = 'function calls';
$errorCode = 'FoundInFunctionCall';
break;
}
$phpcsFile->addError(
'Trailing comma\'s are not allowed in %s in PHP 7.2 or earlier',
$lastInParenthesis,
$errorCode,
$data
);
}
}
@@ -0,0 +1,77 @@
<?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;
/**
* Detect use of short array syntax which is available since PHP 5.4.
*
* PHP version 5.4
*
* @link https://wiki.php.net/rfc/shortsyntaxforarrays
* @link https://www.php.net/manual/en/language.types.array.php#language.types.array.syntax
*
* @since 7.0.0
* @since 9.0.0 Renamed from `ShortArraySniff` to `NewShortArraySniff`.
*/
class NewShortArraySniff 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_OPEN_SHORT_ARRAY,
\T_CLOSE_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->supportsBelow('5.3') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$token = $tokens[$stackPtr];
$error = '%s is not supported in PHP 5.3 or lower';
$data = array();
if ($token['type'] === 'T_OPEN_SHORT_ARRAY') {
$data[] = 'Short array syntax (open)';
} elseif ($token['type'] === 'T_CLOSE_SHORT_ARRAY') {
$data[] = 'Short array syntax (close)';
}
$phpcsFile->addError($error, $stackPtr, 'Found', $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\Syntax;
use PHPCompatibility\Sniff;
use PHPCompatibility\Sniffs\Syntax\NewArrayStringDereferencingSniff;
use PHPCompatibility\Sniffs\Syntax\NewClassMemberAccessSniff;
use PHPCompatibility\Sniffs\Syntax\NewFunctionArrayDereferencingSniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Using the curly brace syntax to access array or string offsets has been deprecated in PHP 7.4.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.deprecated.php#migration74.deprecated.core.array-string-access-curly-brace
* @link https://wiki.php.net/rfc/deprecate_curly_braces_array_access
*
* @since 9.3.0
*/
class RemovedCurlyBraceArrayAccessSniff extends Sniff
{
/**
* Instance of the NewArrayStringDereferencing sniff.
*
* @since 9.3.0
*
* @var \PHPCompatibility\Sniffs\Syntax\NewArrayStringDereferencingSniff
*/
private $newArrayStringDereferencing;
/**
* Target tokens as register by the NewArrayStringDereferencing sniff.
*
* @since 9.3.0
*
* @var array
*/
private $newArrayStringDereferencingTargets;
/**
* Instance of the NewClassMemberAccess sniff.
*
* @since 9.3.0
*
* @var \PHPCompatibility\Sniffs\Syntax\NewClassMemberAccessSniff
*/
private $newClassMemberAccess;
/**
* Target tokens as register by the NewClassMemberAccess sniff.
*
* @since 9.3.0
*
* @var array
*/
private $newClassMemberAccessTargets;
/**
* Instance of the NewFunctionArrayDereferencing sniff.
*
* @since 9.3.0
*
* @var \PHPCompatibility\Sniffs\Syntax\NewFunctionArrayDereferencingSniff
*/
private $newFunctionArrayDereferencing;
/**
* Target tokens as register by the NewFunctionArrayDereferencing sniff.
*
* @since 9.3.0
*
* @var array
*/
private $newFunctionArrayDereferencingTargets;
/**
* Constructor.
*
* @since 9.3.0
*/
public function __construct()
{
$this->newArrayStringDereferencing = new NewArrayStringDereferencingSniff();
$this->newClassMemberAccess = new NewClassMemberAccessSniff();
$this->newFunctionArrayDereferencing = new NewFunctionArrayDereferencingSniff();
}
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.3.0
*
* @return array
*/
public function register()
{
$targets = array(
array(
\T_VARIABLE,
\T_STRING, // Constants.
),
);
// Registers T_ARRAY, T_OPEN_SHORT_ARRAY and T_CONSTANT_ENCAPSED_STRING.
$additionalTargets = $this->newArrayStringDereferencing->register();
$this->newArrayStringDereferencingTargets = array_flip($additionalTargets);
$targets[] = $additionalTargets;
// Registers T_NEW and T_CLONE.
$additionalTargets = $this->newClassMemberAccess->register();
$this->newClassMemberAccessTargets = array_flip($additionalTargets);
$targets[] = $additionalTargets;
// Registers T_STRING.
$additionalTargets = $this->newFunctionArrayDereferencing->register();
$this->newFunctionArrayDereferencingTargets = array_flip($additionalTargets);
$targets[] = $additionalTargets;
return call_user_func_array('array_merge', $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->supportsAbove('7.4') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$braces = array();
// Note: Overwriting braces in each `if` is fine as only one will match anyway.
if ($tokens[$stackPtr]['code'] === \T_VARIABLE) {
$braces = $this->isVariableArrayAccess($phpcsFile, $stackPtr);
}
if (isset($this->newArrayStringDereferencingTargets[$tokens[$stackPtr]['code']])) {
$dereferencing = $this->newArrayStringDereferencing->isArrayStringDereferencing($phpcsFile, $stackPtr);
if (isset($dereferencing['braces'])) {
$braces = $dereferencing['braces'];
}
}
if (isset($this->newClassMemberAccessTargets[$tokens[$stackPtr]['code']])) {
$braces = $this->newClassMemberAccess->isClassMemberAccess($phpcsFile, $stackPtr);
}
if (isset($this->newFunctionArrayDereferencingTargets[$tokens[$stackPtr]['code']])) {
$braces = $this->newFunctionArrayDereferencing->isFunctionArrayDereferencing($phpcsFile, $stackPtr);
}
if (empty($braces) && $tokens[$stackPtr]['code'] === \T_STRING) {
$braces = $this->isConstantArrayAccess($phpcsFile, $stackPtr);
}
if (empty($braces)) {
return;
}
foreach ($braces as $open => $close) {
// Some of the functions will sniff for both curlies as well as square braces.
if ($tokens[$open]['code'] !== \T_OPEN_CURLY_BRACKET) {
continue;
}
// Make sure there is something between the braces, otherwise it's still not curly brace array access.
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($open + 1), $close, true);
if ($nextNonEmpty === false) {
// Nothing between the brackets. Parse error. Ignore.
continue;
}
// OK, so we've found curly brace array access.
$snippet = $phpcsFile->getTokensAsString($stackPtr, (($close - $stackPtr) + 1));
$fix = $phpcsFile->addFixableWarning(
'Curly brace syntax for accessing array elements and string offsets has been deprecated in PHP 7.4. Found: %s',
$open,
'Found',
array($snippet)
);
if ($fix === true) {
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->replaceToken($open, '[');
$phpcsFile->fixer->replaceToken($close, ']');
$phpcsFile->fixer->endChangeset();
}
}
}
/**
* Determine whether a variable is being dereferenced using curly brace syntax.
*
* @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 array An array with the stack pointers to the open/close braces of
* the curly brace array access, or an empty array if no curly
* brace array access was detected.
*/
protected function isVariableArrayAccess(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$current = $stackPtr;
$braces = array();
do {
$current = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), null, true);
if ($current === false) {
break;
}
// Skip over square bracket array access. Bracket styles can be mixed.
if ($tokens[$current]['code'] === \T_OPEN_SQUARE_BRACKET
&& isset($tokens[$current]['bracket_closer']) === true
&& $current === $tokens[$current]['bracket_opener']
) {
$current = $tokens[$current]['bracket_closer'];
continue;
}
// Handle property access.
if ($tokens[$current]['code'] === \T_OBJECT_OPERATOR) {
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), null, true);
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_STRING) {
// Live coding or parse error.
break;
}
$current = $nextNonEmpty;
continue;
}
if ($tokens[$current]['code'] === \T_OPEN_CURLY_BRACKET) {
if (isset($tokens[$current]['bracket_closer']) === false) {
// Live coding or parse error.
break;
}
$braces[$current] = $tokens[$current]['bracket_closer'];
// Continue, just in case there is nested access using curly braces, i.e. `$a{$i}{$j};`.
$current = $tokens[$current]['bracket_closer'];
continue;
}
// If we're still here, we've reached the end of the variable.
break;
} while (true);
return $braces;
}
/**
* Determine whether a T_STRING is a constant being dereferenced using curly brace syntax.
*
* {@internal Note: the first braces for array access to a constant, for some unknown reason,
* can never be curlies, but have to be square brackets.
* Subsequent braces can be curlies.}
*
* @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 array An array with the stack pointers to the open/close braces of
* the curly brace array access, or an empty array if no curly
* brace array access was detected.
*/
protected function isConstantArrayAccess(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($this->isUseOfGlobalConstant($phpcsFile, $stackPtr) === false
&& $tokens[$prevNonEmpty]['code'] !== \T_DOUBLE_COLON // Class constant access.
) {
return array();
}
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false) {
return array();
}
if ($tokens[$nextNonEmpty]['code'] !== \T_OPEN_SQUARE_BRACKET
|| isset($tokens[$nextNonEmpty]['bracket_closer']) === false
) {
// Array access for constants must start with square brackets.
return array();
}
$current = $tokens[$nextNonEmpty]['bracket_closer'];
$braces = array();
do {
$current = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), null, true);
if ($current === false) {
break;
}
// Skip over square bracket array access. Bracket styles can be mixed.
if ($tokens[$current]['code'] === \T_OPEN_SQUARE_BRACKET
&& isset($tokens[$current]['bracket_closer']) === true
&& $current === $tokens[$current]['bracket_opener']
) {
$current = $tokens[$current]['bracket_closer'];
continue;
}
if ($tokens[$current]['code'] === \T_OPEN_CURLY_BRACKET) {
if (isset($tokens[$current]['bracket_closer']) === false) {
// Live coding or parse error.
break;
}
$braces[$current] = $tokens[$current]['bracket_closer'];
// Continue, just in case there is nested access using curly braces, i.e. `$a{$i}{$j};`.
$current = $tokens[$current]['bracket_closer'];
continue;
}
// If we're still here, we've reached the end of the variable.
break;
} while (true);
return $braces;
}
}
@@ -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\Syntax;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect the use of assigning the return value of `new` by reference.
*
* This syntax has been deprecated since PHP 5.3 and removed in PHP 7.0.
*
* PHP version 5.3
* PHP version 7.0
*
* @link https://wiki.php.net/rfc/remove_deprecated_functionality_in_php7
*
* @since 5.5
* @since 9.0.0 Renamed from `DeprecatedNewReferenceSniff` to `RemovedNewReferenceSniff`.
*/
class RemovedNewReferenceSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
*
* @return array
*/
public function register()
{
return array(\T_NEW);
}
/**
* 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.3') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($prevNonEmpty === false || $tokens[$prevNonEmpty]['type'] !== 'T_BITWISE_AND') {
return;
}
$error = 'Assigning the return value of new by reference is deprecated in PHP 5.3';
$isError = false;
$errorCode = 'Deprecated';
if ($this->supportsAbove('7.0') === true) {
$error .= ' and has been removed in PHP 7.0';
$isError = true;
$errorCode = 'Removed';
}
$this->addMessage($phpcsFile, $error, $stackPtr, $isError, $errorCode);
}
}