123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- <?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;
- }
- }
|