File "GroupImportFixer.php"
Full Path: /var/www/html/back/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GroupImportFixer.php
File size: 13.35 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz RumiĆski <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Fixer\Import;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Utils;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
/**
* @phpstan-type _AutogeneratedInputConfiguration array{
* group_types?: list<string>,
* }
* @phpstan-type _AutogeneratedComputedConfiguration array{
* group_types: list<string>,
* }
*
* @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
*
* @author Volodymyr Kupriienko <vldmr.kuprienko@gmail.com>
* @author Greg Korba <greg@codito.dev>
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise.
*/
final class GroupImportFixer extends AbstractFixer implements ConfigurableFixerInterface
{
/** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
use ConfigurableFixerTrait;
/** @internal */
public const GROUP_CLASSY = 'classy';
/** @internal */
public const GROUP_CONSTANTS = 'constants';
/** @internal */
public const GROUP_FUNCTIONS = 'functions';
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'There MUST be group use for the same namespaces.',
[
new CodeSample(
"<?php\nuse Foo\\Bar;\nuse Foo\\Baz;\n",
),
new CodeSample(
<<<'PHP'
<?php
use A\Foo;
use function B\foo;
use A\Bar;
use function B\bar;
PHP,
['group_types' => [self::GROUP_CLASSY]],
),
],
);
}
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(\T_USE);
}
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
$allowedTypes = [self::GROUP_CLASSY, self::GROUP_FUNCTIONS, self::GROUP_CONSTANTS];
return new FixerConfigurationResolver([
(new FixerOptionBuilder('group_types', 'Defines the order of import types.'))
->setAllowedTypes(['string[]'])
->setAllowedValues([static function (array $types) use ($allowedTypes): bool {
foreach ($types as $type) {
if (!\in_array($type, $allowedTypes, true)) {
throw new InvalidOptionsException(
\sprintf(
'Invalid group type: %s, allowed types: %s.',
$type,
Utils::naturalLanguageJoin($allowedTypes),
),
);
}
}
return true;
}])
->setDefault($allowedTypes)
->getOption(),
]);
}
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$useWithSameNamespaces = $this->getSameNamespacesByType($tokens);
if ([] === $useWithSameNamespaces) {
return;
}
$typeMap = [
NamespaceUseAnalysis::TYPE_CLASS => self::GROUP_CLASSY,
NamespaceUseAnalysis::TYPE_FUNCTION => self::GROUP_FUNCTIONS,
NamespaceUseAnalysis::TYPE_CONSTANT => self::GROUP_CONSTANTS,
];
// As a first step we need to remove all the use statements for the enabled import types.
// We can't add new group imports yet, because we need to operate on previously determined token indices for all types.
foreach ($useWithSameNamespaces as $type => $uses) {
if (!\in_array($typeMap[$type], $this->configuration['group_types'], true)) {
continue;
}
$this->removeSingleUseStatements($uses, $tokens);
}
foreach ($useWithSameNamespaces as $type => $uses) {
if (!\in_array($typeMap[$type], $this->configuration['group_types'], true)) {
continue;
}
$this->addGroupUseStatements($uses, $tokens);
}
}
/**
* Gets namespace use analyzers with same namespaces.
*
* @return array<NamespaceUseAnalysis::TYPE_*, non-empty-list<NamespaceUseAnalysis>>
*/
private function getSameNamespacesByType(Tokens $tokens): array
{
$useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens);
if (0 === \count($useDeclarations)) {
return [];
}
$allNamespaceAndType = array_map(
fn (NamespaceUseAnalysis $useDeclaration): string => $this->getNamespaceNameWithSlash($useDeclaration).$useDeclaration->getType(),
$useDeclarations,
);
$sameNamespaces = array_filter(array_count_values($allNamespaceAndType), static fn (int $count): bool => $count > 1);
$sameNamespaces = array_keys($sameNamespaces);
$sameNamespaceAnalysis = array_filter($useDeclarations, function (NamespaceUseAnalysis $useDeclaration) use ($sameNamespaces): bool {
$namespaceNameAndType = $this->getNamespaceNameWithSlash($useDeclaration).$useDeclaration->getType();
return \in_array($namespaceNameAndType, $sameNamespaces, true);
});
usort($sameNamespaceAnalysis, function (NamespaceUseAnalysis $a, NamespaceUseAnalysis $b): int {
$namespaceA = $this->getNamespaceNameWithSlash($a);
$namespaceB = $this->getNamespaceNameWithSlash($b);
$namespaceDifference = \strlen($namespaceA) <=> \strlen($namespaceB);
return 0 !== $namespaceDifference ? $namespaceDifference : $a->getFullName() <=> $b->getFullName();
});
$sameNamespaceAnalysisByType = [];
foreach ($sameNamespaceAnalysis as $analysis) {
$sameNamespaceAnalysisByType[$analysis->getType()][] = $analysis;
}
ksort($sameNamespaceAnalysisByType);
return $sameNamespaceAnalysisByType;
}
/**
* @param list<NamespaceUseAnalysis> $statements
*/
private function removeSingleUseStatements(array $statements, Tokens $tokens): void
{
foreach ($statements as $useDeclaration) {
$index = $useDeclaration->getStartIndex();
$endIndex = $useDeclaration->getEndIndex();
$useStatementTokens = [\T_USE, \T_WHITESPACE, \T_STRING, \T_NS_SEPARATOR, \T_AS, CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT];
while ($index !== $endIndex) {
if ($tokens[$index]->isGivenKind($useStatementTokens)) {
$tokens->clearAt($index);
}
++$index;
}
if (isset($tokens[$index]) && $tokens[$index]->equals(';')) {
$tokens->clearAt($index);
}
++$index;
if (isset($tokens[$index]) && $tokens[$index]->isGivenKind(\T_WHITESPACE)) {
$tokens->clearAt($index);
}
}
}
/**
* @param list<NamespaceUseAnalysis> $statements
*/
private function addGroupUseStatements(array $statements, Tokens $tokens): void
{
$currentUseDeclaration = null;
$insertIndex = $statements[0]->getStartIndex();
// If group import was inserted in place of removed imports, it may have more tokens than before,
// and indices stored in imports of another type can be out-of-sync, and can point in the middle of group import.
// Let's move the pointer to the closest empty token (erased single import).
if (null !== $tokens[$insertIndex]->getId() || '' !== $tokens[$insertIndex]->getContent()) {
do {
++$insertIndex;
} while (null !== $tokens[$insertIndex]->getId() || '' !== $tokens[$insertIndex]->getContent());
}
foreach ($statements as $index => $useDeclaration) {
if ($this->areDeclarationsDifferent($currentUseDeclaration, $useDeclaration)) {
$currentUseDeclaration = $useDeclaration;
$insertIndex += $this->createNewGroup(
$tokens,
$insertIndex,
$useDeclaration,
rtrim($this->getNamespaceNameWithSlash($currentUseDeclaration), '\\'),
);
} else {
$newTokens = [
new Token(','),
new Token([\T_WHITESPACE, ' ']),
];
if ($useDeclaration->isAliased()) {
$tokens->insertAt($insertIndex, $newTokens);
$insertIndex += \count($newTokens);
$newTokens = [];
$insertIndex += $this->insertToGroupUseWithAlias($tokens, $insertIndex, $useDeclaration);
}
$newTokens[] = new Token([\T_STRING, $useDeclaration->getShortName()]);
if (!isset($statements[$index + 1]) || $this->areDeclarationsDifferent($currentUseDeclaration, $statements[$index + 1])) {
$newTokens[] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']);
$newTokens[] = new Token(';');
$newTokens[] = new Token([\T_WHITESPACE, "\n"]);
}
$tokens->insertAt($insertIndex, $newTokens);
$insertIndex += \count($newTokens);
}
}
}
private function getNamespaceNameWithSlash(NamespaceUseAnalysis $useDeclaration): string
{
$position = strrpos($useDeclaration->getFullName(), '\\');
if (false === $position || 0 === $position) {
return $useDeclaration->getFullName();
}
return substr($useDeclaration->getFullName(), 0, $position + 1);
}
/**
* Insert use with alias to the group.
*/
private function insertToGroupUseWithAlias(Tokens $tokens, int $insertIndex, NamespaceUseAnalysis $useDeclaration): int
{
$newTokens = [
new Token([\T_STRING, substr($useDeclaration->getFullName(), (int) strripos($useDeclaration->getFullName(), '\\') + 1)]),
new Token([\T_WHITESPACE, ' ']),
new Token([\T_AS, 'as']),
new Token([\T_WHITESPACE, ' ']),
];
$tokens->insertAt($insertIndex, $newTokens);
return \count($newTokens);
}
/**
* Creates new use statement group.
*/
private function createNewGroup(Tokens $tokens, int $insertIndex, NamespaceUseAnalysis $useDeclaration, string $currentNamespace): int
{
$insertedTokens = 0;
$newTokens = [
new Token([\T_USE, 'use']),
new Token([\T_WHITESPACE, ' ']),
];
if ($useDeclaration->isFunction() || $useDeclaration->isConstant()) {
$importStatementParams = $useDeclaration->isFunction()
? [CT::T_FUNCTION_IMPORT, 'function']
: [CT::T_CONST_IMPORT, 'const'];
$newTokens[] = new Token($importStatementParams);
$newTokens[] = new Token([\T_WHITESPACE, ' ']);
}
$namespaceParts = explode('\\', $currentNamespace);
foreach ($namespaceParts as $part) {
$newTokens[] = new Token([\T_STRING, $part]);
$newTokens[] = new Token([\T_NS_SEPARATOR, '\\']);
}
$newTokens[] = new Token([CT::T_GROUP_IMPORT_BRACE_OPEN, '{']);
$newTokensCount = \count($newTokens);
$tokens->insertAt($insertIndex, $newTokens);
$insertedTokens += $newTokensCount;
$insertIndex += $newTokensCount;
if ($useDeclaration->isAliased()) {
$inserted = $this->insertToGroupUseWithAlias($tokens, $insertIndex + 1, $useDeclaration) + 1;
$insertedTokens += $inserted;
$insertIndex += $inserted;
}
$tokens->insertAt($insertIndex, new Token([\T_STRING, $useDeclaration->getShortName()]));
return ++$insertedTokens;
}
/**
* Check if namespace use analyses are different.
*/
private function areDeclarationsDifferent(?NamespaceUseAnalysis $analysis1, ?NamespaceUseAnalysis $analysis2): bool
{
if (null === $analysis1 || null === $analysis2) {
return true;
}
$namespaceName1 = $this->getNamespaceNameWithSlash($analysis1);
$namespaceName2 = $this->getNamespaceNameWithSlash($analysis2);
return $namespaceName1 !== $namespaceName2 || $analysis1->getType() !== $analysis2->getType();
}
}