true, 'join' => true, ); /** * List of PHP native constants which should be recognized as text strings. * * @since 9.3.0 * * @var array */ private $constantStrings = array( 'DIRECTORY_SEPARATOR' => true, 'PHP_EOL' => true, ); /** * List of PHP native functions which should be recognized as returning an array. * * Note: The array_*() functions will always be taken into account. * * @since 9.3.0 * * @var array */ private $arrayFunctions = array( 'compact' => true, 'explode' => true, 'range' => true, ); /** * List of PHP native array functions which should *not* be recognized as returning an array. * * @since 9.3.0 * * @var array */ private $arrayFunctionExceptions = array( 'array_key_exists' => true, 'array_key_first' => true, 'array_key_last' => true, 'array_multisort' => true, 'array_pop' => true, 'array_product' => true, 'array_push' => true, 'array_search' => true, 'array_shift' => true, 'array_sum' => true, 'array_unshift' => true, 'array_walk_recursive' => true, 'array_walk' => true, ); /** * Do a version check to determine if this sniff needs to run at all. * * @since 9.3.0 * * @return bool */ protected function bowOutEarly() { return ($this->supportsAbove('7.4') === false); } /** * Process the parameters of a matched function. * * @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. * @param string $functionName The token content (function name) which was matched. * @param array $parameters Array with information about the parameters. * * @return int|void Integer stack pointer to skip forward or void to continue * normal file processing. */ public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters) { if (isset($parameters[2]) === false) { // Only one parameter, this must be $pieces. Bow out. return; } $tokens = $phpcsFile->getTokens(); /* * Examine the first parameter. * If there is any indication that this is an array declaration, we have an error. */ $targetParam = $parameters[1]; $start = $targetParam['start']; $end = ($targetParam['end'] + 1); $isOnlyText = true; $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true); if ($firstNonEmpty === false) { // Parse error. Shouldn't be possible. return; } if ($tokens[$firstNonEmpty]['code'] === \T_OPEN_PARENTHESIS) { $start = ($firstNonEmpty + 1); $end = $tokens[$firstNonEmpty]['parenthesis_closer']; } $hasTernary = $phpcsFile->findNext(\T_INLINE_THEN, $start, $end); if ($hasTernary !== false && isset($tokens[$start]['nested_parenthesis'], $tokens[$hasTernary]['nested_parenthesis']) && count($tokens[$start]['nested_parenthesis']) === count($tokens[$hasTernary]['nested_parenthesis']) ) { $start = ($hasTernary + 1); } for ($i = $start; $i < $end; $i++) { $tokenCode = $tokens[$i]['code']; if (isset(Tokens::$emptyTokens[$tokenCode])) { continue; } if ($tokenCode === \T_STRING && isset($this->constantStrings[$tokens[$i]['content']])) { continue; } if ($hasTernary !== false && $tokenCode === \T_INLINE_ELSE) { continue; } if (isset(Tokens::$stringTokens[$tokenCode]) === false) { $isOnlyText = false; } if ($tokenCode === \T_ARRAY || $tokenCode === \T_OPEN_SHORT_ARRAY || $tokenCode === \T_ARRAY_CAST) { $this->throwNotice($phpcsFile, $stackPtr, $functionName); return; } if ($tokenCode === \T_STRING) { /* * Check for specific functions which return an array (i.e. $pieces). */ $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), $end, true); if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) { continue; } $nameLc = strtolower($tokens[$i]['content']); if (isset($this->arrayFunctions[$nameLc]) === false && (strpos($nameLc, 'array_') !== 0 || isset($this->arrayFunctionExceptions[$nameLc]) === true) ) { continue; } // Now make sure it's the PHP native function being called. $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), $start, true); if ($tokens[$prevNonEmpty]['code'] === \T_DOUBLE_COLON || $tokens[$prevNonEmpty]['code'] === \T_OBJECT_OPERATOR ) { // Method call, not a call to the PHP native function. continue; } if ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR && $tokens[$prevNonEmpty - 1]['code'] === \T_STRING ) { // Namespaced function. continue; } // Ok, so we know that there is an array function in the first param. // 99.9% chance that this is $pieces, not $glue. $this->throwNotice($phpcsFile, $stackPtr, $functionName); return; } } if ($isOnlyText === true) { // First parameter only contained text string tokens, i.e. glue. return; } /* * Examine the second parameter. */ $targetParam = $parameters[2]; $start = $targetParam['start']; $end = ($targetParam['end'] + 1); $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true); if ($firstNonEmpty === false) { // Parse error. Shouldn't be possible. return; } if ($tokens[$firstNonEmpty]['code'] === \T_OPEN_PARENTHESIS) { $start = ($firstNonEmpty + 1); $end = $tokens[$firstNonEmpty]['parenthesis_closer']; } $hasTernary = $phpcsFile->findNext(\T_INLINE_THEN, $start, $end); if ($hasTernary !== false && isset($tokens[$start]['nested_parenthesis'], $tokens[$hasTernary]['nested_parenthesis']) && count($tokens[$start]['nested_parenthesis']) === count($tokens[$hasTernary]['nested_parenthesis']) ) { $start = ($hasTernary + 1); } for ($i = $start; $i < $end; $i++) { $tokenCode = $tokens[$i]['code']; if (isset(Tokens::$emptyTokens[$tokenCode])) { continue; } if ($tokenCode === \T_ARRAY || $tokenCode === \T_OPEN_SHORT_ARRAY || $tokenCode === \T_ARRAY_CAST) { // Found an array, $pieces is second. return; } if ($tokenCode === \T_STRING && isset($this->constantStrings[$tokens[$i]['content']])) { // One of the special cased, PHP native string constants found. $this->throwNotice($phpcsFile, $stackPtr, $functionName); return; } if ($tokenCode === \T_STRING || $tokenCode === \T_VARIABLE) { // Function call, constant or variable encountered. // No matter what this is combined with, we won't be able to reliably determine the value. return; } if ($tokenCode === \T_CONSTANT_ENCAPSED_STRING || $tokenCode === \T_DOUBLE_QUOTED_STRING || $tokenCode === \T_HEREDOC || $tokenCode === \T_NOWDOC ) { $this->throwNotice($phpcsFile, $stackPtr, $functionName); return; } } } /** * Throw the error/warning. * * @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. * @param string $functionName The token content (function name) which was matched. * * @return void */ protected function throwNotice(File $phpcsFile, $stackPtr, $functionName) { $message = 'Passing the $glue and $pieces parameters in reverse order to %s has been deprecated since PHP 7.4'; $isError = false; $errorCode = 'Deprecated'; $data = array($functionName); /* Support for the deprecated behaviour is expected to be removed in PHP 8.0. Once this has been implemented, this section should be uncommented. if ($this->supportsAbove('8.0') === true) { $message .= ' and is removed since PHP 8.0'; $isError = true; $errorCode = 'Removed'; } */ $message .= '; $glue should be the first parameter and $pieces the second'; $this->addMessage($phpcsFile, $message, $stackPtr, $isError, $errorCode, $data); } }