*/ public function register() { return [\T_USE]; } /** * Processes this test, when one of its tokens is encountered. * * @since 1.1.0 * * @param \PHP_CodeSniffer\Files\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 (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { // Closure or trait use statement. Bow out. return; } $useStatements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr); $ooCount = \count($useStatements['name']); $functionCount = \count($useStatements['function']); $constantCount = \count($useStatements['const']); $totalCount = $ooCount + $functionCount + $constantCount; if ($totalCount === 0) { // There must have been a parse error. Bow out. return; } // End of statement will always be found, otherwise the import statement parsing would have failed. $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); $groupStart = $phpcsFile->findNext(\T_OPEN_USE_GROUP, ($stackPtr + 1), $endOfStatement); if ($groupStart === false) { // Not a group use statement. Just record the metric. if ($totalCount === 1) { $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'single import'); } else { $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'multi import'); } return; } if ($totalCount === 1 || ($ooCount !== 0 && $functionCount === 0 && $constantCount === 0) || ($ooCount === 0 && $functionCount !== 0 && $constantCount === 0) || ($ooCount === 0 && $functionCount === 0 && $constantCount !== 0) ) { // Not a *mixed* group use statement. $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'group use, single type'); return; } $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'group use, multi type'); // Build up the error message. $foundPhrases = []; if ($ooCount > 1) { $foundPhrases[] = \sprintf('%d namespaces/OO names', $ooCount); } elseif ($ooCount === 1) { $foundPhrases[] = \sprintf('%d namespace/OO name', $ooCount); } if ($functionCount > 1) { $foundPhrases[] = \sprintf('%d functions', $functionCount); } elseif ($functionCount === 1) { $foundPhrases[] = \sprintf('%d function', $functionCount); } if ($constantCount > 1) { $foundPhrases[] = \sprintf('%d constants', $constantCount); } elseif ($constantCount === 1) { $foundPhrases[] = \sprintf('%d constant', $constantCount); } if (\count($foundPhrases) === 2) { $found = \implode(' and ', $foundPhrases); } else { $found = \array_shift($foundPhrases) . ', '; $found .= \implode(' and ', $foundPhrases); } $error = 'Group use statements should import one type of construct.' . ' Mixed group use statement found importing %s.'; $code = 'Found'; $data = [$found]; $hasComment = $phpcsFile->findNext(Tokens::$commentTokens, ($stackPtr + 1), $endOfStatement); if ($hasComment !== false) { // Don't attempt to auto-fix is there are comments or PHPCS annotations in the statement. $phpcsFile->addError($error, $stackPtr, $code, $data); return; } $fix = $phpcsFile->addFixableError($error, $stackPtr, $code, $data); if ($fix === false) { return; } /* * Fix it. * * This fixer complies with the following (arbitrary) requirements: * - It will re-use the original base "group" name, i.e. the part before \{. * - It take take aliases into account, but only when something is aliased to a different name. * Aliases re-using the original name will be removed. * - The fix will not add a trailing comma after the last group use sub-statement. * This is a PHP 7.2+ feature. * If a standard wants to enforce trailing commas, they should use a separate sniff for that. * - If there is only 1 statement of a certain type, the replacement will be a single * import use statement, not a group use statement. */ $phpcsFile->fixer->beginChangeset(); // Ensure that a potential close PHP tag ending the statement is not removed. $tokens = $phpcsFile->getTokens(); $endRemoval = $endOfStatement; if ($tokens[$endOfStatement]['code'] !== \T_SEMICOLON) { $endRemoval = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($endOfStatement - 1), null, true); } // Remove old statement with the exception of the `use` keyword. for ($i = ($stackPtr + 1); $i <= $endRemoval; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } // Build up the new use import statements. $newStatements = []; $useIndent = \str_repeat(' ', ($tokens[$stackPtr]['column'] - 1)); $insideIndent = $useIndent . \str_repeat(' ', 4); $baseGroupName = GetTokensAsString::noEmpties($phpcsFile, ($stackPtr + 1), ($groupStart - 1)); foreach ($useStatements as $type => $statements) { $count = \count($statements); if ($count === 0) { continue; } $typeName = $type . ' '; if ($type === 'name') { $typeName = ''; } if ($count === 1) { $fqName = \reset($statements); $alias = \key($statements); $newStatement = 'use ' . $typeName . $fqName; $unqualifiedName = \ltrim(\substr($fqName, \strrpos($fqName, '\\')), '\\'); if ($unqualifiedName !== $alias) { $newStatement .= ' as ' . $alias; } $newStatement .= ';'; $newStatements[] = $newStatement; continue; } // Multiple statements, add a single-type group use statement. $newStatement = 'use ' . $typeName . $baseGroupName . '{' . $phpcsFile->eolChar; foreach ($statements as $alias => $fqName) { $partialName = \str_replace($baseGroupName, '', $fqName); $newStatement .= $insideIndent . $partialName; $unqualifiedName = \ltrim(\substr($partialName, \strrpos($partialName, '\\')), '\\'); if ($unqualifiedName !== $alias) { $newStatement .= ' as ' . $alias; } $newStatement .= ',' . $phpcsFile->eolChar; } // Remove trailing comma after last statement as that's PHP 7.2+. $newStatement = \rtrim($newStatement, ',' . $phpcsFile->eolChar); $newStatement .= $phpcsFile->eolChar . $useIndent . '};'; $newStatements[] = $newStatement; } $replacement = \implode($phpcsFile->eolChar . $useIndent, $newStatements); $phpcsFile->fixer->replaceToken($stackPtr, $replacement); $phpcsFile->fixer->endChangeset(); } }