Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
276 lines
9.6 KiB
PHP
Executable File
276 lines
9.6 KiB
PHP
Executable File
<?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\AttributeNotation;
|
|
|
|
use PhpCsFixer\AbstractFixer;
|
|
use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
|
|
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
|
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
|
|
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
|
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
|
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
|
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
|
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
|
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
|
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
|
use PhpCsFixer\Tokenizer\Analyzer\Analysis\AttributeAnalysis;
|
|
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
|
|
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
|
|
use PhpCsFixer\Tokenizer\Analyzer\FullyQualifiedNameAnalyzer;
|
|
use PhpCsFixer\Tokenizer\FCT;
|
|
use PhpCsFixer\Tokenizer\Token;
|
|
use PhpCsFixer\Tokenizer\Tokens;
|
|
use Symfony\Component\OptionsResolver\Options;
|
|
|
|
/**
|
|
* @phpstan-import-type _AttributeItems from AttributeAnalysis
|
|
*
|
|
* @phpstan-type _AutogeneratedInputConfiguration array{
|
|
* order?: list<string>,
|
|
* sort_algorithm?: 'alpha'|'custom',
|
|
* }
|
|
* @phpstan-type _AutogeneratedComputedConfiguration array{
|
|
* order: array<string, int>,
|
|
* sort_algorithm: 'alpha'|'custom',
|
|
* }
|
|
*
|
|
* @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
|
|
*
|
|
* @author HypeMC <hypemc@gmail.com>
|
|
*/
|
|
final class OrderedAttributesFixer extends AbstractFixer implements ConfigurableFixerInterface
|
|
{
|
|
/** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
|
|
use ConfigurableFixerTrait;
|
|
|
|
public const ORDER_ALPHA = 'alpha';
|
|
public const ORDER_CUSTOM = 'custom';
|
|
|
|
private const SUPPORTED_SORT_ALGORITHMS = [
|
|
self::ORDER_ALPHA,
|
|
self::ORDER_CUSTOM,
|
|
];
|
|
|
|
public function getDefinition(): FixerDefinitionInterface
|
|
{
|
|
return new FixerDefinition(
|
|
'Sorts attributes using the configured sort algorithm.',
|
|
[
|
|
new VersionSpecificCodeSample(
|
|
<<<'EOL'
|
|
<?php
|
|
|
|
#[Foo]
|
|
#[Bar(3)]
|
|
#[Qux(new Bar(5))]
|
|
#[Corge(a: 'test')]
|
|
class Sample1 {}
|
|
|
|
#[
|
|
Foo,
|
|
Bar(3),
|
|
Qux(new Bar(5)),
|
|
Corge(a: 'test'),
|
|
]
|
|
class Sample2 {}
|
|
|
|
EOL,
|
|
new VersionSpecification(8_00_00),
|
|
),
|
|
new VersionSpecificCodeSample(
|
|
<<<'EOL'
|
|
<?php
|
|
|
|
use A\B\Foo;
|
|
use A\B\Bar as BarAlias;
|
|
use A\B as AB;
|
|
|
|
#[Foo]
|
|
#[BarAlias(3)]
|
|
#[AB\Qux(new Bar(5))]
|
|
#[\A\B\Corge(a: 'test')]
|
|
class Sample1 {}
|
|
|
|
EOL,
|
|
new VersionSpecification(8_00_00),
|
|
['sort_algorithm' => self::ORDER_CUSTOM, 'order' => ['A\B\Qux', 'A\B\Bar', 'A\B\Corge']],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*
|
|
* Must run after FullyQualifiedStrictTypesFixer.
|
|
*/
|
|
public function getPriority(): int
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public function isCandidate(Tokens $tokens): bool
|
|
{
|
|
return $tokens->isTokenKindFound(FCT::T_ATTRIBUTE);
|
|
}
|
|
|
|
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
|
{
|
|
$fixerName = $this->getName();
|
|
|
|
return new FixerConfigurationResolver([
|
|
(new FixerOptionBuilder('sort_algorithm', 'How the attributes should be sorted.'))
|
|
->setAllowedValues(self::SUPPORTED_SORT_ALGORITHMS)
|
|
->setDefault(self::ORDER_ALPHA)
|
|
->setNormalizer(static function (Options $options, string $value) use ($fixerName): string {
|
|
if (self::ORDER_CUSTOM === $value && [] === $options['order']) {
|
|
throw new InvalidFixerConfigurationException(
|
|
$fixerName,
|
|
'The custom order strategy requires providing `order` option with a list of attributes\'s FQNs.'
|
|
);
|
|
}
|
|
|
|
return $value;
|
|
})
|
|
->getOption(),
|
|
(new FixerOptionBuilder('order', 'A list of FQCNs of attributes defining the desired order used when custom sorting algorithm is configured.'))
|
|
->setAllowedTypes(['string[]'])
|
|
->setDefault([])
|
|
->setNormalizer(static function (Options $options, array $value) use ($fixerName): array {
|
|
if ($value !== array_unique($value)) {
|
|
throw new InvalidFixerConfigurationException($fixerName, 'The list includes attributes that are not unique.');
|
|
}
|
|
|
|
return array_flip(array_values(
|
|
array_map(static fn (string $attribute): string => ltrim($attribute, '\\'), $value),
|
|
));
|
|
})
|
|
->getOption(),
|
|
]);
|
|
}
|
|
|
|
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
|
{
|
|
$fullyQualifiedNameAnalyzer = new FullyQualifiedNameAnalyzer($tokens);
|
|
|
|
$index = 0;
|
|
|
|
while (null !== $index = $tokens->getNextTokenOfKind($index, [[\T_ATTRIBUTE]])) {
|
|
/** @var _AttributeItems $elements */
|
|
$elements = array_map(fn (AttributeAnalysis $attributeAnalysis): array => [
|
|
'name' => $this->sortAttributes($fullyQualifiedNameAnalyzer, $tokens, $attributeAnalysis->getStartIndex(), $attributeAnalysis->getAttributes()),
|
|
'start' => $attributeAnalysis->getStartIndex(),
|
|
'end' => $attributeAnalysis->getEndIndex(),
|
|
], AttributeAnalyzer::collect($tokens, $index));
|
|
|
|
$endIndex = end($elements)['end'];
|
|
|
|
try {
|
|
if (1 === \count($elements)) {
|
|
continue;
|
|
}
|
|
|
|
$sortedElements = $this->sortElements($elements);
|
|
|
|
if ($elements === $sortedElements) {
|
|
continue;
|
|
}
|
|
|
|
$this->sortTokens($tokens, $index, $endIndex, $sortedElements);
|
|
} finally {
|
|
$index = $endIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param _AttributeItems $attributes
|
|
*/
|
|
private function sortAttributes(FullyQualifiedNameAnalyzer $fullyQualifiedNameAnalyzer, Tokens $tokens, int $index, array $attributes): string
|
|
{
|
|
if (1 === \count($attributes)) {
|
|
return $this->getAttributeName($fullyQualifiedNameAnalyzer, $attributes[0]['name'], $attributes[0]['start']);
|
|
}
|
|
|
|
foreach ($attributes as &$attribute) {
|
|
$attribute['name'] = $this->getAttributeName($fullyQualifiedNameAnalyzer, $attribute['name'], $attribute['start']);
|
|
}
|
|
|
|
$sortedElements = $this->sortElements($attributes);
|
|
|
|
if ($attributes === $sortedElements) {
|
|
return $attributes[0]['name'];
|
|
}
|
|
|
|
$this->sortTokens($tokens, $index + 1, end($attributes)['end'], $sortedElements, new Token(','));
|
|
|
|
return $sortedElements[0]['name'];
|
|
}
|
|
|
|
private function getAttributeName(FullyQualifiedNameAnalyzer $fullyQualifiedNameAnalyzer, string $name, int $index): string
|
|
{
|
|
if (self::ORDER_CUSTOM === $this->configuration['sort_algorithm']) {
|
|
return $fullyQualifiedNameAnalyzer->getFullyQualifiedName($name, $index, NamespaceUseAnalysis::TYPE_CLASS);
|
|
}
|
|
|
|
return ltrim($name, '\\');
|
|
}
|
|
|
|
/**
|
|
* @param _AttributeItems $elements
|
|
*
|
|
* @return _AttributeItems
|
|
*/
|
|
private function sortElements(array $elements): array
|
|
{
|
|
usort($elements, function (array $a, array $b): int {
|
|
$sortAlgorithm = $this->configuration['sort_algorithm'];
|
|
|
|
if (self::ORDER_ALPHA === $sortAlgorithm) {
|
|
return $a['name'] <=> $b['name'];
|
|
}
|
|
|
|
if (self::ORDER_CUSTOM === $sortAlgorithm) {
|
|
return
|
|
($this->configuration['order'][$a['name']] ?? \PHP_INT_MAX)
|
|
<=> ($this->configuration['order'][$b['name']] ?? \PHP_INT_MAX);
|
|
}
|
|
|
|
throw new \InvalidArgumentException(\sprintf('Invalid sort algorithm "%s" provided.', $sortAlgorithm));
|
|
});
|
|
|
|
return $elements;
|
|
}
|
|
|
|
/**
|
|
* @param _AttributeItems $elements
|
|
*/
|
|
private function sortTokens(Tokens $tokens, int $startIndex, int $endIndex, array $elements, ?Token $delimiter = null): void
|
|
{
|
|
$replaceTokens = [];
|
|
|
|
foreach ($elements as $pos => $element) {
|
|
for ($i = $element['start']; $i <= $element['end']; ++$i) {
|
|
$replaceTokens[] = clone $tokens[$i];
|
|
}
|
|
if (null !== $delimiter && $pos !== \count($elements) - 1) {
|
|
$replaceTokens[] = clone $delimiter;
|
|
}
|
|
}
|
|
|
|
$tokens->overrideRange($startIndex, $endIndex, $replaceTokens);
|
|
}
|
|
}
|