> */ private $keysSeenLt8 = []; /** * Keep track of which array keys have been seen already on PHP >= 8.0. * * @since 1.0.0 * * @var array> */ private $keysSeenGt8 = []; /** * Keep track of the maximum seen integer key to know what the next value will be for * array items without a key on PHP < 8.0. * * @since 1.0.0 * * @var int */ private $currentMaxIntKeyLt8; /** * Keep track of the maximum seen integer key to know what the next value will be for * array items without a key on PHP >= 8.0. * * @since 1.0.0 * * @var int */ private $currentMaxIntKeyGt8; /** * PHP version as configured or -1 if unknown. * * @since 1.0.0 * * @var int */ private $phpVersion; /** * Process every part of the array declaration. * * This contains the default logic for the sniff, but can be overloaded in a concrete child class * if needed. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the * token was found. * * @return void */ public function processArray(File $phpcsFile) { // Reset properties before processing this array. $this->keysSeenLt8 = []; $this->keysSeenGt8 = []; if (isset($this->phpVersion) === false) { // Set default value to prevent this code from running every time the sniff is triggered. $this->phpVersion = -1; $phpVersion = Helper::getConfigData('php_version'); if ($phpVersion !== null) { $this->phpVersion = (int) $phpVersion; } } unset($this->currentMaxIntKeyLt8, $this->currentMaxIntKeyGt8); parent::processArray($phpcsFile); } /** * Process the tokens in an array key. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the * token was found. * @param int $startPtr The stack pointer to the first token in the "key" part of * an array item. * @param int $endPtr The stack pointer to the last token in the "key" part of * an array item. * @param int $itemNr Which item in the array is being handled. * * @return void */ public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) { $key = $this->getActualArrayKey($phpcsFile, $startPtr, $endPtr); if (isset($key) === false) { // Key could not be determined. return; } $integerKey = \is_int($key); $errorMsg = 'Duplicate array key found. The value will be overwritten%s.' . ' The %s array key "%s" was first seen on line %d'; $errorCode = 'Found'; $errors = []; $baseData = [ ($integerKey === true) ? 'integer' : 'string', $key, ]; /* * Check if we've seen the key before. */ if (($this->phpVersion === -1 || $this->phpVersion < 80000) && isset($this->keysSeenLt8[$key]) === true ) { $errors['phplt8'] = [ 'data_subset' => $baseData, 'error_suffix' => '', 'code_suffix' => '', ]; if ($integerKey === true) { $errors['phplt8']['error_suffix'] = ' when using PHP < 8.0'; $errors['phplt8']['code_suffix'] = 'ForPHPlt80'; } $firstSeen = $this->keysSeenLt8[$key]; $firstNonEmptyFirstSeen = $phpcsFile->findNext(Tokens::$emptyTokens, $firstSeen['ptr'], null, true); $errors['phplt8']['data_subset'][] = $this->tokens[$firstNonEmptyFirstSeen]['line']; } if (($this->phpVersion === -1 || $this->phpVersion >= 80000) && isset($this->keysSeenGt8[$key]) === true ) { $errors['phpgt8'] = [ 'data_subset' => $baseData, 'error_suffix' => '', 'code_suffix' => '', ]; if ($integerKey === true) { $errors['phpgt8']['error_suffix'] = ' when using PHP >= 8.0'; $errors['phpgt8']['code_suffix'] = 'ForPHPgte80'; } $firstSeen = $this->keysSeenGt8[$key]; $firstNonEmptyFirstSeen = $phpcsFile->findNext(Tokens::$emptyTokens, $firstSeen['ptr'], null, true); $errors['phpgt8']['data_subset'][] = $this->tokens[$firstNonEmptyFirstSeen]['line']; } /* * Throw the error(s). * * If no PHP version was passed, throw errors both for PHP < 8.0 and PHP >= 8.0. * If a PHP version was set, only throw the error appropriate for the selected PHP version. * If both errors would effectively be the same, only throw one. */ if ($errors !== []) { $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); if (isset($errors['phplt8'], $errors['phpgt8']) && $errors['phplt8']['data_subset'] === $errors['phpgt8']['data_subset'] ) { // Only throw the error once if it would be the same for PHP < 8.0 and PHP >= 8.0. $data = $errors['phplt8']['data_subset']; \array_unshift($data, ''); $phpcsFile->addError($errorMsg, $firstNonEmpty, $errorCode, $data); return; } if (isset($errors['phplt8'])) { $code = $errorCode . $errors['phplt8']['code_suffix']; $data = $errors['phplt8']['data_subset']; \array_unshift($data, $errors['phplt8']['error_suffix']); $phpcsFile->addError($errorMsg, $firstNonEmpty, $code, $data); } if (isset($errors['phpgt8'])) { $code = $errorCode . $errors['phpgt8']['code_suffix']; $data = $errors['phpgt8']['data_subset']; \array_unshift($data, $errors['phpgt8']['error_suffix']); $phpcsFile->addError($errorMsg, $firstNonEmpty, $code, $data); } return; } /* * Key not seen before. Add to arrays. */ $this->keysSeenLt8[$key] = [ 'item' => $itemNr, 'ptr' => $startPtr, ]; $this->keysSeenGt8[$key] = [ 'item' => $itemNr, 'ptr' => $startPtr, ]; if ($integerKey === true) { if ((isset($this->currentMaxIntKeyLt8) === false && $key > -1) || (isset($this->currentMaxIntKeyLt8) === true && $key > $this->currentMaxIntKeyLt8) ) { $this->currentMaxIntKeyLt8 = $key; } if (isset($this->currentMaxIntKeyGt8) === false || $key > $this->currentMaxIntKeyGt8 ) { $this->currentMaxIntKeyGt8 = $key; } } } /** * Process an array item without an array key. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the * token was found. * @param int $startPtr The stack pointer to the first token in the array item, * which in this case will be the first token of the array * value part of the array item. * @param int $itemNr Which item in the array is being handled. * * @return void */ public function processNoKey(File $phpcsFile, $startPtr, $itemNr) { // Track the key for PHP < 8.0. if (isset($this->currentMaxIntKeyLt8) === false) { $this->currentMaxIntKeyLt8 = -1; } ++$this->currentMaxIntKeyLt8; $this->keysSeenLt8[$this->currentMaxIntKeyLt8] = [ 'item' => $itemNr, 'ptr' => $startPtr, ]; // Track the key for PHP 8.0+. if (isset($this->currentMaxIntKeyGt8) === false) { $this->currentMaxIntKeyGt8 = -1; } ++$this->currentMaxIntKeyGt8; $this->keysSeenGt8[$this->currentMaxIntKeyGt8] = [ 'item' => $itemNr, 'ptr' => $startPtr, ]; } }