Missing dependancies
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
<?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\Console;
|
||||
|
||||
use PhpCsFixer\Console\Command\DescribeCommand;
|
||||
use PhpCsFixer\Console\Command\FixCommand;
|
||||
use PhpCsFixer\Console\Command\HelpCommand;
|
||||
use PhpCsFixer\Console\Command\ListFilesCommand;
|
||||
use PhpCsFixer\Console\Command\ListSetsCommand;
|
||||
use PhpCsFixer\Console\Command\SelfUpdateCommand;
|
||||
use PhpCsFixer\Console\SelfUpdate\GithubClient;
|
||||
use PhpCsFixer\Console\SelfUpdate\NewVersionChecker;
|
||||
use PhpCsFixer\PharChecker;
|
||||
use PhpCsFixer\ToolInfo;
|
||||
use PhpCsFixer\Utils;
|
||||
use Symfony\Component\Console\Application as BaseApplication;
|
||||
use Symfony\Component\Console\Command\ListCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Application extends BaseApplication
|
||||
{
|
||||
public const VERSION = '3.13.0';
|
||||
public const VERSION_CODENAME = 'Oliva';
|
||||
|
||||
private ToolInfo $toolInfo;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('PHP CS Fixer', self::VERSION);
|
||||
|
||||
$this->toolInfo = new ToolInfo();
|
||||
|
||||
// in alphabetical order
|
||||
$this->add(new DescribeCommand());
|
||||
$this->add(new FixCommand($this->toolInfo));
|
||||
$this->add(new ListFilesCommand($this->toolInfo));
|
||||
$this->add(new ListSetsCommand());
|
||||
$this->add(new SelfUpdateCommand(
|
||||
new NewVersionChecker(new GithubClient()),
|
||||
$this->toolInfo,
|
||||
new PharChecker()
|
||||
));
|
||||
}
|
||||
|
||||
public static function getMajorVersion(): int
|
||||
{
|
||||
return (int) explode('.', self::VERSION)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function doRun(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$stdErr = $output instanceof ConsoleOutputInterface
|
||||
? $output->getErrorOutput()
|
||||
: ($input->hasParameterOption('--format', true) && 'txt' !== $input->getParameterOption('--format', null, true) ? null : $output)
|
||||
;
|
||||
|
||||
if (null !== $stdErr) {
|
||||
$warningsDetector = new WarningsDetector($this->toolInfo);
|
||||
$warningsDetector->detectOldVendor();
|
||||
$warningsDetector->detectOldMajor();
|
||||
$warnings = $warningsDetector->getWarnings();
|
||||
|
||||
if (\count($warnings) > 0) {
|
||||
foreach ($warnings as $warning) {
|
||||
$stdErr->writeln(sprintf($stdErr->isDecorated() ? '<bg=yellow;fg=black;>%s</>' : '%s', $warning));
|
||||
}
|
||||
$stdErr->writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
$result = parent::doRun($input, $output);
|
||||
|
||||
if (
|
||||
null !== $stdErr
|
||||
&& $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE
|
||||
) {
|
||||
$triggeredDeprecations = Utils::getTriggeredDeprecations();
|
||||
|
||||
if (\count($triggeredDeprecations) > 0) {
|
||||
$stdErr->writeln('');
|
||||
$stdErr->writeln($stdErr->isDecorated() ? '<bg=yellow;fg=black;>Detected deprecations in use:</>' : 'Detected deprecations in use:');
|
||||
foreach ($triggeredDeprecations as $deprecation) {
|
||||
$stdErr->writeln(sprintf('- %s', $deprecation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLongVersion(): string
|
||||
{
|
||||
$commit = '@git-commit@';
|
||||
$versionCommit = '';
|
||||
|
||||
if ('@'.'git-commit@' !== $commit) { /** @phpstan-ignore-line as `$commit` is replaced during phar building */
|
||||
$versionCommit = substr($commit, 0, 7);
|
||||
}
|
||||
|
||||
return implode('', [
|
||||
parent::getLongVersion(),
|
||||
$versionCommit ? sprintf(' <info>(%s)</info>', $versionCommit) : '', // @phpstan-ignore-line to avoid `Ternary operator condition is always true|false.`
|
||||
self::VERSION_CODENAME ? sprintf(' <info>%s</info>', self::VERSION_CODENAME) : '', // @phpstan-ignore-line to avoid `Ternary operator condition is always true|false.`
|
||||
' by <comment>Fabien Potencier</comment> and <comment>Dariusz Ruminski</comment>.',
|
||||
"\nPHP runtime: <info>".PHP_VERSION.'</info>',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDefaultCommands(): array
|
||||
{
|
||||
return [new HelpCommand(), new ListCommand()];
|
||||
}
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
use PhpCsFixer\Differ\DiffConsoleFormatter;
|
||||
use PhpCsFixer\Differ\FullDiffer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
|
||||
use PhpCsFixer\Fixer\FixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\AliasedFixerOption;
|
||||
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
|
||||
use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption;
|
||||
use PhpCsFixer\FixerDefinition\CodeSampleInterface;
|
||||
use PhpCsFixer\FixerDefinition\FileSpecificCodeSampleInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSampleInterface;
|
||||
use PhpCsFixer\FixerFactory;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\RuleSet\RuleSets;
|
||||
use PhpCsFixer\StdinFileInfo;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Utils;
|
||||
use PhpCsFixer\WordMatcher;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
#[AsCommand(name: 'describe')]
|
||||
final class DescribeCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'describe';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $setNames;
|
||||
|
||||
private FixerFactory $fixerFactory;
|
||||
|
||||
/**
|
||||
* @var array<string, FixerInterface>
|
||||
*/
|
||||
private $fixers;
|
||||
|
||||
public function __construct(?FixerFactory $fixerFactory = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (null === $fixerFactory) {
|
||||
$fixerFactory = new FixerFactory();
|
||||
$fixerFactory->registerBuiltInFixers();
|
||||
}
|
||||
|
||||
$this->fixerFactory = $fixerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDefinition(
|
||||
[
|
||||
new InputArgument('name', InputArgument::REQUIRED, 'Name of rule / set.'),
|
||||
]
|
||||
)
|
||||
->setDescription('Describe rule / ruleset.')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity() && $output instanceof ConsoleOutputInterface) {
|
||||
$stdErr = $output->getErrorOutput();
|
||||
$stdErr->writeln($this->getApplication()->getLongVersion());
|
||||
}
|
||||
|
||||
$name = $input->getArgument('name');
|
||||
|
||||
try {
|
||||
if (str_starts_with($name, '@')) {
|
||||
$this->describeSet($output, $name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->describeRule($output, $name);
|
||||
} catch (DescribeNameNotFoundException $e) {
|
||||
$matcher = new WordMatcher(
|
||||
'set' === $e->getType() ? $this->getSetNames() : array_keys($this->getFixers())
|
||||
);
|
||||
|
||||
$alternative = $matcher->match($name);
|
||||
|
||||
$this->describeList($output, $e->getType());
|
||||
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'%s "%s" not found.%s',
|
||||
ucfirst($e->getType()),
|
||||
$name,
|
||||
null === $alternative ? '' : ' Did you mean "'.$alternative.'"?'
|
||||
));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function describeRule(OutputInterface $output, string $name): void
|
||||
{
|
||||
$fixers = $this->getFixers();
|
||||
|
||||
if (!isset($fixers[$name])) {
|
||||
throw new DescribeNameNotFoundException($name, 'rule');
|
||||
}
|
||||
|
||||
/** @var FixerInterface $fixer */
|
||||
$fixer = $fixers[$name];
|
||||
|
||||
$definition = $fixer->getDefinition();
|
||||
|
||||
$summary = $definition->getSummary();
|
||||
|
||||
if ($fixer instanceof DeprecatedFixerInterface) {
|
||||
$successors = $fixer->getSuccessorsNames();
|
||||
$message = [] === $successors
|
||||
? 'will be removed on next major version'
|
||||
: sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors));
|
||||
$message = Preg::replace('/(`.+?`)/', '<info>$1</info>', $message);
|
||||
$summary .= sprintf(' <error>DEPRECATED</error>: %s.', $message);
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('<info>Description of</info> %s <info>rule</info>.', $name));
|
||||
|
||||
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
|
||||
$output->writeln(sprintf('Fixer class: <comment>%s</comment>.', \get_class($fixer)));
|
||||
}
|
||||
|
||||
$output->writeln($summary);
|
||||
|
||||
$description = $definition->getDescription();
|
||||
|
||||
if (null !== $description) {
|
||||
$output->writeln($description);
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
|
||||
if ($fixer->isRisky()) {
|
||||
$output->writeln('<error>Fixer applying this rule is risky.</error>');
|
||||
|
||||
$riskyDescription = $definition->getRiskyDescription();
|
||||
|
||||
if (null !== $riskyDescription) {
|
||||
$output->writeln($riskyDescription);
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
if ($fixer instanceof ConfigurableFixerInterface) {
|
||||
$configurationDefinition = $fixer->getConfigurationDefinition();
|
||||
$options = $configurationDefinition->getOptions();
|
||||
|
||||
$output->writeln(sprintf('Fixer is configurable using following option%s:', 1 === \count($options) ? '' : 's'));
|
||||
|
||||
foreach ($options as $option) {
|
||||
$line = '* <info>'.OutputFormatter::escape($option->getName()).'</info>';
|
||||
$allowed = HelpCommand::getDisplayableAllowedValues($option);
|
||||
|
||||
if (null === $allowed) {
|
||||
$allowed = array_map(
|
||||
static fn (string $type): string => '<comment>'.$type.'</comment>',
|
||||
$option->getAllowedTypes(),
|
||||
);
|
||||
} else {
|
||||
$allowed = array_map(static function ($value): string {
|
||||
return $value instanceof AllowedValueSubset
|
||||
? 'a subset of <comment>'.HelpCommand::toString($value->getAllowedValues()).'</comment>'
|
||||
: '<comment>'.HelpCommand::toString($value).'</comment>';
|
||||
}, $allowed);
|
||||
}
|
||||
|
||||
$line .= ' ('.implode(', ', $allowed).')';
|
||||
|
||||
$description = Preg::replace('/(`.+?`)/', '<info>$1</info>', OutputFormatter::escape($option->getDescription()));
|
||||
$line .= ': '.lcfirst(Preg::replace('/\.$/', '', $description)).'; ';
|
||||
|
||||
if ($option->hasDefault()) {
|
||||
$line .= sprintf(
|
||||
'defaults to <comment>%s</comment>',
|
||||
HelpCommand::toString($option->getDefault())
|
||||
);
|
||||
} else {
|
||||
$line .= '<comment>required</comment>';
|
||||
}
|
||||
|
||||
if ($option instanceof DeprecatedFixerOption) {
|
||||
$line .= '. <error>DEPRECATED</error>: '.Preg::replace(
|
||||
'/(`.+?`)/',
|
||||
'<info>$1</info>',
|
||||
OutputFormatter::escape(lcfirst($option->getDeprecationMessage()))
|
||||
);
|
||||
}
|
||||
|
||||
if ($option instanceof AliasedFixerOption) {
|
||||
$line .= '; <error>DEPRECATED</error> alias: <comment>'.$option->getAlias().'</comment>';
|
||||
}
|
||||
|
||||
$output->writeln($line);
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
/** @var CodeSampleInterface[] $codeSamples */
|
||||
$codeSamples = array_filter($definition->getCodeSamples(), static function (CodeSampleInterface $codeSample): bool {
|
||||
if ($codeSample instanceof VersionSpecificCodeSampleInterface) {
|
||||
return $codeSample->isSuitableFor(\PHP_VERSION_ID);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (0 === \count($codeSamples)) {
|
||||
$output->writeln([
|
||||
'Fixing examples cannot be demonstrated on the current PHP version.',
|
||||
'',
|
||||
]);
|
||||
} else {
|
||||
$output->writeln('Fixing examples:');
|
||||
|
||||
$differ = new FullDiffer();
|
||||
$diffFormatter = new DiffConsoleFormatter(
|
||||
$output->isDecorated(),
|
||||
sprintf(
|
||||
'<comment> ---------- begin diff ----------</comment>%s%%s%s<comment> ----------- end diff -----------</comment>',
|
||||
PHP_EOL,
|
||||
PHP_EOL
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($codeSamples as $index => $codeSample) {
|
||||
$old = $codeSample->getCode();
|
||||
$tokens = Tokens::fromCode($old);
|
||||
|
||||
$configuration = $codeSample->getConfiguration();
|
||||
|
||||
if ($fixer instanceof ConfigurableFixerInterface) {
|
||||
$fixer->configure($configuration ?? []);
|
||||
}
|
||||
|
||||
$file = $codeSample instanceof FileSpecificCodeSampleInterface
|
||||
? $codeSample->getSplFileInfo()
|
||||
: new StdinFileInfo();
|
||||
|
||||
$fixer->fix($file, $tokens);
|
||||
|
||||
$diff = $differ->diff($old, $tokens->generateCode());
|
||||
|
||||
if ($fixer instanceof ConfigurableFixerInterface) {
|
||||
if (null === $configuration) {
|
||||
$output->writeln(sprintf(' * Example #%d. Fixing with the <comment>default</comment> configuration.', $index + 1));
|
||||
} else {
|
||||
$output->writeln(sprintf(' * Example #%d. Fixing with configuration: <comment>%s</comment>.', $index + 1, HelpCommand::toString($codeSample->getConfiguration())));
|
||||
}
|
||||
} else {
|
||||
$output->writeln(sprintf(' * Example #%d.', $index + 1));
|
||||
}
|
||||
|
||||
$output->writeln([$diffFormatter->format($diff, ' %s'), '']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function describeSet(OutputInterface $output, string $name): void
|
||||
{
|
||||
if (!\in_array($name, $this->getSetNames(), true)) {
|
||||
throw new DescribeNameNotFoundException($name, 'set');
|
||||
}
|
||||
|
||||
$ruleSetDefinitions = RuleSets::getSetDefinitions();
|
||||
$fixers = $this->getFixers();
|
||||
|
||||
$output->writeln(sprintf('<info>Description of the</info> %s <info>set.</info>', $ruleSetDefinitions[$name]->getName()));
|
||||
$output->writeln($this->replaceRstLinks($ruleSetDefinitions[$name]->getDescription()));
|
||||
|
||||
if ($ruleSetDefinitions[$name]->isRisky()) {
|
||||
$output->writeln('This set contains <error>risky</error> rules.');
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
|
||||
$help = '';
|
||||
|
||||
foreach ($ruleSetDefinitions[$name]->getRules() as $rule => $config) {
|
||||
if (str_starts_with($rule, '@')) {
|
||||
$set = $ruleSetDefinitions[$rule];
|
||||
$help .= sprintf(
|
||||
" * <info>%s</info>%s\n | %s\n\n",
|
||||
$rule,
|
||||
$set->isRisky() ? ' <error>risky</error>' : '',
|
||||
$this->replaceRstLinks($set->getDescription())
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var FixerInterface $fixer */
|
||||
$fixer = $fixers[$rule];
|
||||
|
||||
$definition = $fixer->getDefinition();
|
||||
$help .= sprintf(
|
||||
" * <info>%s</info>%s\n | %s\n%s\n",
|
||||
$rule,
|
||||
$fixer->isRisky() ? ' <error>risky</error>' : '',
|
||||
$definition->getSummary(),
|
||||
true !== $config ? sprintf(" <comment>| Configuration: %s</comment>\n", HelpCommand::toString($config)) : ''
|
||||
);
|
||||
}
|
||||
|
||||
$output->write($help);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, FixerInterface>
|
||||
*/
|
||||
private function getFixers(): array
|
||||
{
|
||||
if (null !== $this->fixers) {
|
||||
return $this->fixers;
|
||||
}
|
||||
|
||||
$fixers = [];
|
||||
|
||||
foreach ($this->fixerFactory->getFixers() as $fixer) {
|
||||
$fixers[$fixer->getName()] = $fixer;
|
||||
}
|
||||
|
||||
$this->fixers = $fixers;
|
||||
ksort($this->fixers);
|
||||
|
||||
return $this->fixers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getSetNames(): array
|
||||
{
|
||||
if (null !== $this->setNames) {
|
||||
return $this->setNames;
|
||||
}
|
||||
|
||||
$this->setNames = RuleSets::getSetDefinitionNames();
|
||||
|
||||
return $this->setNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type 'rule'|'set'
|
||||
*/
|
||||
private function describeList(OutputInterface $output, string $type): void
|
||||
{
|
||||
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) {
|
||||
$describe = [
|
||||
'sets' => $this->getSetNames(),
|
||||
'rules' => $this->getFixers(),
|
||||
];
|
||||
} elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
|
||||
$describe = 'set' === $type ? ['sets' => $this->getSetNames()] : ['rules' => $this->getFixers()];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var string[] $items */
|
||||
foreach ($describe as $list => $items) {
|
||||
$output->writeln(sprintf('<comment>Defined %s:</comment>', $list));
|
||||
|
||||
foreach ($items as $name => $item) {
|
||||
$output->writeln(sprintf('* <info>%s</info>', \is_string($name) ? $name : $item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function replaceRstLinks(string $content): string
|
||||
{
|
||||
return Preg::replaceCallback(
|
||||
'/(`[^<]+<[^>]+>`_)/',
|
||||
static function (array $matches) {
|
||||
return Preg::replaceCallback(
|
||||
'/`(.*)<(.*)>`_/',
|
||||
static function (array $matches): string {
|
||||
return $matches[1].'('.$matches[2].')';
|
||||
},
|
||||
$matches[1]
|
||||
);
|
||||
},
|
||||
$content
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class DescribeNameNotFoundException extends \InvalidArgumentException
|
||||
{
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* 'rule'|'set'.
|
||||
*/
|
||||
private string $type;
|
||||
|
||||
public function __construct(string $name, string $type)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
use PhpCsFixer\Documentation\DocumentationLocator;
|
||||
use PhpCsFixer\Documentation\FixerDocumentGenerator;
|
||||
use PhpCsFixer\Documentation\ListDocumentGenerator;
|
||||
use PhpCsFixer\Documentation\RuleSetDocumentationGenerator;
|
||||
use PhpCsFixer\FixerFactory;
|
||||
use PhpCsFixer\RuleSet\RuleSets;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[AsCommand(name: 'documentation')]
|
||||
final class DocumentationCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'documentation';
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setAliases(['doc'])
|
||||
->setDescription('Dumps the documentation of the project into its "/doc" directory.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$filesystem = new Filesystem();
|
||||
$locator = new DocumentationLocator();
|
||||
|
||||
$fixerFactory = new FixerFactory();
|
||||
$fixerFactory->registerBuiltInFixers();
|
||||
$fixers = $fixerFactory->getFixers();
|
||||
|
||||
$setDefinitions = RuleSets::getSetDefinitions();
|
||||
|
||||
$fixerDocumentGenerator = new FixerDocumentGenerator($locator);
|
||||
$ruleSetDocumentationGenerator = new RuleSetDocumentationGenerator($locator);
|
||||
$listDocumentGenerator = new ListDocumentGenerator($locator);
|
||||
|
||||
// Array of existing fixer docs.
|
||||
// We first override existing files, and then we will delete files that are no longer needed.
|
||||
// We cannot remove all files first, as generation of docs is re-using existing docs to extract code-samples for
|
||||
// VersionSpecificCodeSample under incompatible PHP version.
|
||||
$docForFixerRelativePaths = [];
|
||||
|
||||
foreach ($fixers as $fixer) {
|
||||
$docForFixerRelativePaths[] = $locator->getFixerDocumentationFileRelativePath($fixer);
|
||||
$filesystem->dumpFile(
|
||||
$locator->getFixerDocumentationFilePath($fixer),
|
||||
$fixerDocumentGenerator->generateFixerDocumentation($fixer)
|
||||
);
|
||||
}
|
||||
|
||||
/** @var SplFileInfo $file */
|
||||
foreach (
|
||||
(new Finder())->files()
|
||||
->in($locator->getFixersDocumentationDirectoryPath())
|
||||
->notPath($docForFixerRelativePaths) as $file
|
||||
) {
|
||||
$filesystem->remove($file->getPathname());
|
||||
}
|
||||
|
||||
// Fixer doc. index
|
||||
|
||||
$filesystem->dumpFile(
|
||||
$locator->getFixersDocumentationIndexFilePath(),
|
||||
$fixerDocumentGenerator->generateFixersDocumentationIndex($fixers)
|
||||
);
|
||||
|
||||
// RuleSet docs.
|
||||
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ((new Finder())->files()->in($locator->getRuleSetsDocumentationDirectoryPath()) as $file) {
|
||||
$filesystem->remove($file->getPathname());
|
||||
}
|
||||
|
||||
$paths = [];
|
||||
|
||||
foreach ($setDefinitions as $name => $definition) {
|
||||
$path = $locator->getRuleSetsDocumentationFilePath($name);
|
||||
$paths[$name] = $path;
|
||||
$filesystem->dumpFile($path, $ruleSetDocumentationGenerator->generateRuleSetsDocumentation($definition, $fixers));
|
||||
}
|
||||
|
||||
// RuleSet doc. index
|
||||
|
||||
$filesystem->dumpFile(
|
||||
$locator->getRuleSetsDocumentationIndexFilePath(),
|
||||
$ruleSetDocumentationGenerator->generateRuleSetsDocumentationIndex($paths)
|
||||
);
|
||||
|
||||
// List file / Appendix
|
||||
|
||||
$filesystem->dumpFile(
|
||||
$locator->getListingFilePath(),
|
||||
$listDocumentGenerator->generateListingDocumentation($fixers)
|
||||
);
|
||||
|
||||
$output->writeln('Docs updated.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
use PhpCsFixer\Config;
|
||||
use PhpCsFixer\ConfigInterface;
|
||||
use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
|
||||
use PhpCsFixer\Console\ConfigurationResolver;
|
||||
use PhpCsFixer\Console\Output\ErrorOutput;
|
||||
use PhpCsFixer\Console\Output\NullOutput;
|
||||
use PhpCsFixer\Console\Output\ProcessOutput;
|
||||
use PhpCsFixer\Console\Report\FixReport\ReportSummary;
|
||||
use PhpCsFixer\Error\ErrorsManager;
|
||||
use PhpCsFixer\Runner\Runner;
|
||||
use PhpCsFixer\ToolInfoInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Terminal;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
#[AsCommand(name: 'fix')]
|
||||
final class FixCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'fix';
|
||||
|
||||
private EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
private ErrorsManager $errorsManager;
|
||||
|
||||
private Stopwatch $stopwatch;
|
||||
|
||||
private ConfigInterface $defaultConfig;
|
||||
|
||||
private ToolInfoInterface $toolInfo;
|
||||
|
||||
public function __construct(ToolInfoInterface $toolInfo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->eventDispatcher = new EventDispatcher();
|
||||
$this->errorsManager = new ErrorsManager();
|
||||
$this->stopwatch = new Stopwatch();
|
||||
$this->defaultConfig = new Config();
|
||||
$this->toolInfo = $toolInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Override here to only generate the help copy when used.
|
||||
*/
|
||||
public function getHelp(): string
|
||||
{
|
||||
return <<<'EOF'
|
||||
The <info>%command.name%</info> command tries to fix as much coding standards
|
||||
problems as possible on a given file or files in a given directory and its subdirectories:
|
||||
|
||||
<info>$ php %command.full_name% /path/to/dir</info>
|
||||
<info>$ php %command.full_name% /path/to/file</info>
|
||||
|
||||
By default <comment>--path-mode</comment> is set to `override`, which means, that if you specify the path to a file or a directory via
|
||||
command arguments, then the paths provided to a `Finder` in config file will be ignored. You can use <comment>--path-mode=intersection</comment>
|
||||
to merge paths from the config file and from the argument:
|
||||
|
||||
<info>$ php %command.full_name% --path-mode=intersection /path/to/dir</info>
|
||||
|
||||
The <comment>--format</comment> option for the output format. Supported formats are `txt` (default one), `json`, `xml`, `checkstyle`, `junit` and `gitlab`.
|
||||
|
||||
NOTE: the output for the following formats are generated in accordance with schemas
|
||||
|
||||
* `checkstyle` follows the common `"checkstyle" XML schema </doc/schemas/fix/checkstyle.xsd>`_
|
||||
* `json` follows the `own JSON schema </doc/schemas/fix/schema.json>`_
|
||||
* `junit` follows the `JUnit XML schema from Jenkins </doc/schemas/fix/junit-10.xsd>`_
|
||||
* `xml` follows the `own XML schema </doc/schemas/fix/xml.xsd>`_
|
||||
|
||||
The <comment>--quiet</comment> Do not output any message.
|
||||
|
||||
The <comment>--verbose</comment> option will show the applied rules. When using the `txt` format it will also display progress notifications.
|
||||
|
||||
NOTE: if there is an error like "errors reported during linting after fixing", you can use this to be even more verbose for debugging purpose
|
||||
|
||||
* `-v`: verbose
|
||||
* `-vv`: very verbose
|
||||
* `-vvv`: debug
|
||||
|
||||
The <comment>--rules</comment> option limits the rules to apply to the
|
||||
project:
|
||||
|
||||
EOF. /* @TODO: 4.0 - change to @PER */ <<<'EOF'
|
||||
|
||||
<info>$ php %command.full_name% /path/to/project --rules=@PSR12</info>
|
||||
|
||||
By default the PSR-12 rules are used.
|
||||
|
||||
The <comment>--rules</comment> option lets you choose the exact rules to
|
||||
apply (the rule names must be separated by a comma):
|
||||
|
||||
<info>$ php %command.full_name% /path/to/dir --rules=line_ending,full_opening_tag,indentation_type</info>
|
||||
|
||||
You can also exclude the rules you don't want by placing a dash in front of the rule name, if this is more convenient,
|
||||
using <comment>-name_of_fixer</comment>:
|
||||
|
||||
<info>$ php %command.full_name% /path/to/dir --rules=-full_opening_tag,-indentation_type</info>
|
||||
|
||||
When using combinations of exact and exclude rules, applying exact rules along with above excluded results:
|
||||
|
||||
<info>$ php %command.full_name% /path/to/project --rules=@Symfony,-@PSR1,-blank_line_before_statement,strict_comparison</info>
|
||||
|
||||
Complete configuration for rules can be supplied using a `json` formatted string.
|
||||
|
||||
<info>$ php %command.full_name% /path/to/project --rules='{"concat_space": {"spacing": "none"}}'</info>
|
||||
|
||||
The <comment>--dry-run</comment> flag will run the fixer without making changes to your files.
|
||||
|
||||
The <comment>--diff</comment> flag can be used to let the fixer output all the changes it makes.
|
||||
|
||||
The <comment>--allow-risky</comment> option (pass `yes` or `no`) allows you to set whether risky rules may run. Default value is taken from config file.
|
||||
A rule is considered risky if it could change code behaviour. By default no risky rules are run.
|
||||
|
||||
The <comment>--stop-on-violation</comment> flag stops the execution upon first file that needs to be fixed.
|
||||
|
||||
The <comment>--show-progress</comment> option allows you to choose the way process progress is rendered:
|
||||
|
||||
* <comment>none</comment>: disables progress output;
|
||||
* <comment>dots</comment>: multiline progress output with number of files and percentage on each line.
|
||||
|
||||
If the option is not provided, it defaults to <comment>dots</comment> unless a config file that disables output is used, in which case it defaults to <comment>none</comment>. This option has no effect if the verbosity of the command is less than <comment>verbose</comment>.
|
||||
|
||||
<info>$ php %command.full_name% --verbose --show-progress=dots</info>
|
||||
|
||||
By using <command>--using-cache</command> option with `yes` or `no` you can set if the caching
|
||||
mechanism should be used.
|
||||
|
||||
The command can also read from standard input, in which case it won't
|
||||
automatically fix anything:
|
||||
|
||||
<info>$ cat foo.php | php %command.full_name% --diff -</info>
|
||||
|
||||
Finally, if you don't need BC kept on CLI level, you might use `PHP_CS_FIXER_FUTURE_MODE` to start using options that
|
||||
would be default in next MAJOR release and to forbid using deprecated configuration:
|
||||
|
||||
<info>$ PHP_CS_FIXER_FUTURE_MODE=1 php %command.full_name% -v --diff</info>
|
||||
|
||||
Exit code
|
||||
---------
|
||||
|
||||
Exit code of the fix command is built using following bit flags:
|
||||
|
||||
* 0 - OK.
|
||||
* 1 - General error (or PHP minimal requirement not matched).
|
||||
* 4 - Some files have invalid syntax (only in dry-run mode).
|
||||
* 8 - Some files need fixing (only in dry-run mode).
|
||||
* 16 - Configuration error of the application.
|
||||
* 32 - Configuration error of a Fixer.
|
||||
* 64 - Exception raised within the application.
|
||||
|
||||
EOF
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDefinition(
|
||||
[
|
||||
new InputArgument('path', InputArgument::IS_ARRAY, 'The path.'),
|
||||
new InputOption('path-mode', '', InputOption::VALUE_REQUIRED, 'Specify path mode (can be override or intersection).', ConfigurationResolver::PATH_MODE_OVERRIDE),
|
||||
new InputOption('allow-risky', '', InputOption::VALUE_REQUIRED, 'Are risky fixers allowed (can be yes or no).'),
|
||||
new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php-cs-fixer.php file.'),
|
||||
new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'),
|
||||
new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'The rules.'),
|
||||
new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Does cache should be used (can be yes or no).'),
|
||||
new InputOption('cache-file', '', InputOption::VALUE_REQUIRED, 'The path to the cache file.'),
|
||||
new InputOption('diff', '', InputOption::VALUE_NONE, 'Also produce diff for each file.'),
|
||||
new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.'),
|
||||
new InputOption('stop-on-violation', '', InputOption::VALUE_NONE, 'Stop execution on first violation.'),
|
||||
new InputOption('show-progress', '', InputOption::VALUE_REQUIRED, 'Type of progress indicator (none, dots).'),
|
||||
]
|
||||
)
|
||||
->setDescription('Fixes a directory or a file.')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$verbosity = $output->getVerbosity();
|
||||
|
||||
$passedConfig = $input->getOption('config');
|
||||
$passedRules = $input->getOption('rules');
|
||||
|
||||
if (null !== $passedConfig && null !== $passedRules) {
|
||||
throw new InvalidConfigurationException('Passing both `--config` and `--rules` options is not allowed.');
|
||||
}
|
||||
|
||||
$resolver = new ConfigurationResolver(
|
||||
$this->defaultConfig,
|
||||
[
|
||||
'allow-risky' => $input->getOption('allow-risky'),
|
||||
'config' => $passedConfig,
|
||||
'dry-run' => $input->getOption('dry-run'),
|
||||
'rules' => $passedRules,
|
||||
'path' => $input->getArgument('path'),
|
||||
'path-mode' => $input->getOption('path-mode'),
|
||||
'using-cache' => $input->getOption('using-cache'),
|
||||
'cache-file' => $input->getOption('cache-file'),
|
||||
'format' => $input->getOption('format'),
|
||||
'diff' => $input->getOption('diff'),
|
||||
'stop-on-violation' => $input->getOption('stop-on-violation'),
|
||||
'verbosity' => $verbosity,
|
||||
'show-progress' => $input->getOption('show-progress'),
|
||||
],
|
||||
getcwd(),
|
||||
$this->toolInfo
|
||||
);
|
||||
|
||||
$reporter = $resolver->getReporter();
|
||||
|
||||
$stdErr = $output instanceof ConsoleOutputInterface
|
||||
? $output->getErrorOutput()
|
||||
: ('txt' === $reporter->getFormat() ? $output : null)
|
||||
;
|
||||
|
||||
if (null !== $stdErr) {
|
||||
if (OutputInterface::VERBOSITY_VERBOSE <= $verbosity) {
|
||||
$stdErr->writeln($this->getApplication()->getLongVersion());
|
||||
}
|
||||
|
||||
$configFile = $resolver->getConfigFile();
|
||||
$stdErr->writeln(sprintf('Loaded config <comment>%s</comment>%s.', $resolver->getConfig()->getName(), null === $configFile ? '' : ' from "'.$configFile.'"'));
|
||||
|
||||
if ($resolver->getUsingCache()) {
|
||||
$cacheFile = $resolver->getCacheFile();
|
||||
|
||||
if (is_file($cacheFile)) {
|
||||
$stdErr->writeln(sprintf('Using cache file "%s".', $cacheFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$progressType = $resolver->getProgress();
|
||||
$finder = $resolver->getFinder();
|
||||
|
||||
if (null !== $stdErr && $resolver->configFinderIsOverridden()) {
|
||||
$stdErr->writeln(
|
||||
sprintf($stdErr->isDecorated() ? '<bg=yellow;fg=black;>%s</>' : '%s', 'Paths from configuration file have been overridden by paths provided as command arguments.')
|
||||
);
|
||||
}
|
||||
|
||||
if ('none' === $progressType || null === $stdErr) {
|
||||
$progressOutput = new NullOutput();
|
||||
} else {
|
||||
$finder = new \ArrayIterator(iterator_to_array($finder));
|
||||
$progressOutput = new ProcessOutput(
|
||||
$stdErr,
|
||||
$this->eventDispatcher,
|
||||
(new Terminal())->getWidth(),
|
||||
\count($finder)
|
||||
);
|
||||
}
|
||||
|
||||
$runner = new Runner(
|
||||
$finder,
|
||||
$resolver->getFixers(),
|
||||
$resolver->getDiffer(),
|
||||
'none' !== $progressType ? $this->eventDispatcher : null,
|
||||
$this->errorsManager,
|
||||
$resolver->getLinter(),
|
||||
$resolver->isDryRun(),
|
||||
$resolver->getCacheManager(),
|
||||
$resolver->getDirectory(),
|
||||
$resolver->shouldStopOnViolation()
|
||||
);
|
||||
|
||||
$this->stopwatch->start('fixFiles');
|
||||
$changed = $runner->fix();
|
||||
$this->stopwatch->stop('fixFiles');
|
||||
|
||||
$progressOutput->printLegend();
|
||||
|
||||
$fixEvent = $this->stopwatch->getEvent('fixFiles');
|
||||
|
||||
$reportSummary = new ReportSummary(
|
||||
$changed,
|
||||
$fixEvent->getDuration(),
|
||||
$fixEvent->getMemory(),
|
||||
OutputInterface::VERBOSITY_VERBOSE <= $verbosity,
|
||||
$resolver->isDryRun(),
|
||||
$output->isDecorated()
|
||||
);
|
||||
|
||||
$output->isDecorated()
|
||||
? $output->write($reporter->generate($reportSummary))
|
||||
: $output->write($reporter->generate($reportSummary), false, OutputInterface::OUTPUT_RAW)
|
||||
;
|
||||
|
||||
$invalidErrors = $this->errorsManager->getInvalidErrors();
|
||||
$exceptionErrors = $this->errorsManager->getExceptionErrors();
|
||||
$lintErrors = $this->errorsManager->getLintErrors();
|
||||
|
||||
if (null !== $stdErr) {
|
||||
$errorOutput = new ErrorOutput($stdErr);
|
||||
|
||||
if (\count($invalidErrors) > 0) {
|
||||
$errorOutput->listErrors('linting before fixing', $invalidErrors);
|
||||
}
|
||||
|
||||
if (\count($exceptionErrors) > 0) {
|
||||
$errorOutput->listErrors('fixing', $exceptionErrors);
|
||||
}
|
||||
|
||||
if (\count($lintErrors) > 0) {
|
||||
$errorOutput->listErrors('linting after fixing', $lintErrors);
|
||||
}
|
||||
}
|
||||
|
||||
$exitStatusCalculator = new FixCommandExitStatusCalculator();
|
||||
|
||||
return $exitStatusCalculator->calculate(
|
||||
$resolver->isDryRun(),
|
||||
\count($changed) > 0,
|
||||
\count($invalidErrors) > 0,
|
||||
\count($exceptionErrors) > 0,
|
||||
\count($lintErrors) > 0
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class FixCommandExitStatusCalculator
|
||||
{
|
||||
// Exit status 1 is reserved for environment constraints not matched.
|
||||
public const EXIT_STATUS_FLAG_HAS_INVALID_FILES = 4;
|
||||
public const EXIT_STATUS_FLAG_HAS_CHANGED_FILES = 8;
|
||||
public const EXIT_STATUS_FLAG_HAS_INVALID_CONFIG = 16;
|
||||
public const EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG = 32;
|
||||
public const EXIT_STATUS_FLAG_EXCEPTION_IN_APP = 64;
|
||||
|
||||
public function calculate(bool $isDryRun, bool $hasChangedFiles, bool $hasInvalidErrors, bool $hasExceptionErrors, bool $hasLintErrorsAfterFixing): int
|
||||
{
|
||||
$exitStatus = 0;
|
||||
|
||||
if ($isDryRun) {
|
||||
if ($hasChangedFiles) {
|
||||
$exitStatus |= self::EXIT_STATUS_FLAG_HAS_CHANGED_FILES;
|
||||
}
|
||||
|
||||
if ($hasInvalidErrors) {
|
||||
$exitStatus |= self::EXIT_STATUS_FLAG_HAS_INVALID_FILES;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasExceptionErrors || $hasLintErrorsAfterFixing) {
|
||||
$exitStatus |= self::EXIT_STATUS_FLAG_EXCEPTION_IN_APP;
|
||||
}
|
||||
|
||||
return $exitStatus;
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\HelpCommand as BaseHelpCommand;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
#[AsCommand(name: 'help')]
|
||||
final class HelpCommand extends BaseHelpCommand
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'help';
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function toString($value): string
|
||||
{
|
||||
return \is_array($value)
|
||||
? static::arrayToString($value)
|
||||
: static::scalarToString($value)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed values of the given option that can be converted to a string.
|
||||
*
|
||||
* @return null|list<AllowedValueSubset|mixed>
|
||||
*/
|
||||
public static function getDisplayableAllowedValues(FixerOptionInterface $option): ?array
|
||||
{
|
||||
$allowed = $option->getAllowedValues();
|
||||
|
||||
if (null !== $allowed) {
|
||||
$allowed = array_filter($allowed, static function ($value): bool {
|
||||
return !$value instanceof \Closure;
|
||||
});
|
||||
|
||||
usort($allowed, static function ($valueA, $valueB): int {
|
||||
if ($valueA instanceof AllowedValueSubset) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($valueB instanceof AllowedValueSubset) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return strcasecmp(
|
||||
self::toString($valueA),
|
||||
self::toString($valueB)
|
||||
);
|
||||
});
|
||||
|
||||
if (0 === \count($allowed)) {
|
||||
$allowed = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$output->getFormatter()->setStyle('url', new OutputFormatterStyle('blue'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function scalarToString($value): string
|
||||
{
|
||||
$str = var_export($value, true);
|
||||
|
||||
return Preg::replace('/\bNULL\b/', 'null', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $value
|
||||
*/
|
||||
private static function arrayToString(array $value): string
|
||||
{
|
||||
if (0 === \count($value)) {
|
||||
return '[]';
|
||||
}
|
||||
|
||||
$isHash = !array_is_list($value);
|
||||
$str = '[';
|
||||
|
||||
foreach ($value as $k => $v) {
|
||||
if ($isHash) {
|
||||
$str .= static::scalarToString($k).' => ';
|
||||
}
|
||||
|
||||
$str .= \is_array($v)
|
||||
? static::arrayToString($v).', '
|
||||
: static::scalarToString($v).', '
|
||||
;
|
||||
}
|
||||
|
||||
return substr($str, 0, -2).']';
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
use PhpCsFixer\Config;
|
||||
use PhpCsFixer\ConfigInterface;
|
||||
use PhpCsFixer\Console\ConfigurationResolver;
|
||||
use PhpCsFixer\ToolInfoInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Markus Staab <markus.staab@redaxo.org>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
#[AsCommand(name: 'list-files')]
|
||||
final class ListFilesCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'list-files';
|
||||
|
||||
private ConfigInterface $defaultConfig;
|
||||
|
||||
private ToolInfoInterface $toolInfo;
|
||||
|
||||
public function __construct(ToolInfoInterface $toolInfo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->defaultConfig = new Config();
|
||||
$this->toolInfo = $toolInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDefinition(
|
||||
[
|
||||
new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php-cs-fixer.php file.'),
|
||||
]
|
||||
)
|
||||
->setDescription('List all files being fixed by the given config.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$passedConfig = $input->getOption('config');
|
||||
$cwd = getcwd();
|
||||
|
||||
$resolver = new ConfigurationResolver(
|
||||
$this->defaultConfig,
|
||||
[
|
||||
'config' => $passedConfig,
|
||||
],
|
||||
$cwd,
|
||||
$this->toolInfo
|
||||
);
|
||||
|
||||
$finder = $resolver->getFinder();
|
||||
|
||||
/** @var \SplFileInfo $file */
|
||||
foreach ($finder as $file) {
|
||||
if ($file->isFile()) {
|
||||
$relativePath = str_replace($cwd, '.', $file->getRealPath());
|
||||
// unify directory separators across operating system
|
||||
$relativePath = str_replace('/', \DIRECTORY_SEPARATOR, $relativePath);
|
||||
|
||||
$output->writeln(escapeshellarg($relativePath));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
|
||||
use PhpCsFixer\Console\Report\ListSetsReport\ReporterFactory;
|
||||
use PhpCsFixer\Console\Report\ListSetsReport\ReporterInterface;
|
||||
use PhpCsFixer\Console\Report\ListSetsReport\ReportSummary;
|
||||
use PhpCsFixer\Console\Report\ListSetsReport\TextReporter;
|
||||
use PhpCsFixer\RuleSet\RuleSets;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
#[AsCommand(name: 'list-sets')]
|
||||
final class ListSetsCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'list-sets';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDefinition(
|
||||
[
|
||||
new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.', (new TextReporter())->getFormat()),
|
||||
]
|
||||
)
|
||||
->setDescription('List all available RuleSets.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$reporter = $this->resolveReporterWithFactory(
|
||||
$input->getOption('format'),
|
||||
new ReporterFactory()
|
||||
);
|
||||
|
||||
$reportSummary = new ReportSummary(
|
||||
array_values(RuleSets::getSetDefinitions())
|
||||
);
|
||||
|
||||
$report = $reporter->generate($reportSummary);
|
||||
|
||||
$output->isDecorated()
|
||||
? $output->write(OutputFormatter::escape($report))
|
||||
: $output->write($report, false, OutputInterface::OUTPUT_RAW)
|
||||
;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function resolveReporterWithFactory(string $format, ReporterFactory $factory): ReporterInterface
|
||||
{
|
||||
try {
|
||||
$factory->registerBuiltInReporters();
|
||||
$reporter = $factory->getReporter($format);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
$formats = $factory->getFormats();
|
||||
sort($formats);
|
||||
|
||||
throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats)));
|
||||
}
|
||||
|
||||
return $reporter;
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
<?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\Console\Command;
|
||||
|
||||
use PhpCsFixer\Console\SelfUpdate\NewVersionCheckerInterface;
|
||||
use PhpCsFixer\PharCheckerInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\ToolInfoInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Igor Wiedler <igor@wiedler.ch>
|
||||
* @author Stephane PY <py.stephane1@gmail.com>
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
#[AsCommand(name: 'self-update')]
|
||||
final class SelfUpdateCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'self-update';
|
||||
|
||||
private NewVersionCheckerInterface $versionChecker;
|
||||
|
||||
private ToolInfoInterface $toolInfo;
|
||||
|
||||
private PharCheckerInterface $pharChecker;
|
||||
|
||||
public function __construct(
|
||||
NewVersionCheckerInterface $versionChecker,
|
||||
ToolInfoInterface $toolInfo,
|
||||
PharCheckerInterface $pharChecker
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->versionChecker = $versionChecker;
|
||||
$this->toolInfo = $toolInfo;
|
||||
$this->pharChecker = $pharChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setAliases(['selfupdate'])
|
||||
->setDefinition(
|
||||
[
|
||||
new InputOption('--force', '-f', InputOption::VALUE_NONE, 'Force update to next major version if available.'),
|
||||
]
|
||||
)
|
||||
->setDescription('Update php-cs-fixer.phar to the latest stable version.')
|
||||
->setHelp(
|
||||
<<<'EOT'
|
||||
The <info>%command.name%</info> command replace your php-cs-fixer.phar by the
|
||||
latest version released on:
|
||||
<comment>https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases</comment>
|
||||
|
||||
<info>$ php php-cs-fixer.phar %command.name%</info>
|
||||
|
||||
EOT
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity() && $output instanceof ConsoleOutputInterface) {
|
||||
$stdErr = $output->getErrorOutput();
|
||||
$stdErr->writeln($this->getApplication()->getLongVersion());
|
||||
}
|
||||
|
||||
if (!$this->toolInfo->isInstalledAsPhar()) {
|
||||
$output->writeln('<error>Self-update is available only for PHAR version.</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$currentVersion = $this->getApplication()->getVersion();
|
||||
Preg::match('/^v?(?<major>\d+)\./', $currentVersion, $matches);
|
||||
$currentMajor = (int) $matches['major'];
|
||||
|
||||
try {
|
||||
$latestVersion = $this->versionChecker->getLatestVersion();
|
||||
$latestVersionOfCurrentMajor = $this->versionChecker->getLatestVersionOfMajor($currentMajor);
|
||||
} catch (\Exception $exception) {
|
||||
$output->writeln(sprintf(
|
||||
'<error>Unable to determine newest version: %s</error>',
|
||||
$exception->getMessage()
|
||||
));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (1 !== $this->versionChecker->compareVersions($latestVersion, $currentVersion)) {
|
||||
$output->writeln('<info>PHP CS Fixer is already up-to-date.</info>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$remoteTag = $latestVersion;
|
||||
|
||||
if (
|
||||
0 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $latestVersion)
|
||||
&& true !== $input->getOption('force')
|
||||
) {
|
||||
$output->writeln(sprintf('<info>A new major version of PHP CS Fixer is available</info> (<comment>%s</comment>)', $latestVersion));
|
||||
$output->writeln(sprintf('<info>Before upgrading please read</info> https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/%s/UPGRADE-v%s.md', $latestVersion, $currentMajor + 1));
|
||||
$output->writeln('<info>If you are ready to upgrade run this command with</info> <comment>-f</comment>');
|
||||
$output->writeln('<info>Checking for new minor/patch version...</info>');
|
||||
|
||||
if (1 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $currentVersion)) {
|
||||
$output->writeln('<info>No minor update for PHP CS Fixer.</info>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$remoteTag = $latestVersionOfCurrentMajor;
|
||||
}
|
||||
|
||||
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
|
||||
|
||||
if (!is_writable($localFilename)) {
|
||||
$output->writeln(sprintf('<error>No permission to update</error> "%s" <error>file.</error>', $localFilename));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$tempFilename = \dirname($localFilename).'/'.basename($localFilename, '.phar').'-tmp.phar';
|
||||
$remoteFilename = $this->toolInfo->getPharDownloadUri($remoteTag);
|
||||
|
||||
if (false === @copy($remoteFilename, $tempFilename)) {
|
||||
$output->writeln(sprintf('<error>Unable to download new version</error> %s <error>from the server.</error>', $remoteTag));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
chmod($tempFilename, 0777 & ~umask());
|
||||
|
||||
$pharInvalidityReason = $this->pharChecker->checkFileValidity($tempFilename);
|
||||
if (null !== $pharInvalidityReason) {
|
||||
unlink($tempFilename);
|
||||
$output->writeln(sprintf('<error>The download of</error> %s <error>is corrupt (%s).</error>', $remoteTag, $pharInvalidityReason));
|
||||
$output->writeln('<error>Please re-run the "self-update" command to try again.</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
rename($tempFilename, $localFilename);
|
||||
|
||||
$output->writeln(sprintf('<info>PHP CS Fixer updated</info> (<comment>%s</comment> -> <comment>%s</comment>)', $currentVersion, $remoteTag));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+961
@@ -0,0 +1,961 @@
|
||||
<?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\Console;
|
||||
|
||||
use PhpCsFixer\Cache\CacheManagerInterface;
|
||||
use PhpCsFixer\Cache\Directory;
|
||||
use PhpCsFixer\Cache\DirectoryInterface;
|
||||
use PhpCsFixer\Cache\FileCacheManager;
|
||||
use PhpCsFixer\Cache\FileHandler;
|
||||
use PhpCsFixer\Cache\NullCacheManager;
|
||||
use PhpCsFixer\Cache\Signature;
|
||||
use PhpCsFixer\ConfigInterface;
|
||||
use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
|
||||
use PhpCsFixer\Console\Command\HelpCommand;
|
||||
use PhpCsFixer\Console\Report\FixReport\ReporterFactory;
|
||||
use PhpCsFixer\Console\Report\FixReport\ReporterInterface;
|
||||
use PhpCsFixer\Differ\DifferInterface;
|
||||
use PhpCsFixer\Differ\NullDiffer;
|
||||
use PhpCsFixer\Differ\UnifiedDiffer;
|
||||
use PhpCsFixer\Finder;
|
||||
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
|
||||
use PhpCsFixer\Fixer\FixerInterface;
|
||||
use PhpCsFixer\FixerFactory;
|
||||
use PhpCsFixer\Linter\Linter;
|
||||
use PhpCsFixer\Linter\LinterInterface;
|
||||
use PhpCsFixer\RuleSet\RuleSet;
|
||||
use PhpCsFixer\RuleSet\RuleSetInterface;
|
||||
use PhpCsFixer\StdinFileInfo;
|
||||
use PhpCsFixer\ToolInfoInterface;
|
||||
use PhpCsFixer\Utils;
|
||||
use PhpCsFixer\WhitespacesFixerConfig;
|
||||
use PhpCsFixer\WordMatcher;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Finder\Finder as SymfonyFinder;
|
||||
|
||||
/**
|
||||
* The resolver that resolves configuration to use by command line options and config.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Katsuhiro Ogawa <ko.fivestar@gmail.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ConfigurationResolver
|
||||
{
|
||||
public const PATH_MODE_OVERRIDE = 'override';
|
||||
public const PATH_MODE_INTERSECTION = 'intersection';
|
||||
|
||||
/**
|
||||
* @var null|bool
|
||||
*/
|
||||
private $allowRisky;
|
||||
|
||||
/**
|
||||
* @var null|ConfigInterface
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $configFile;
|
||||
|
||||
private string $cwd;
|
||||
|
||||
private ConfigInterface $defaultConfig;
|
||||
|
||||
/**
|
||||
* @var null|ReporterInterface
|
||||
*/
|
||||
private $reporter;
|
||||
|
||||
/**
|
||||
* @var null|bool
|
||||
*/
|
||||
private $isStdIn;
|
||||
|
||||
/**
|
||||
* @var null|bool
|
||||
*/
|
||||
private $isDryRun;
|
||||
|
||||
/**
|
||||
* @var null|FixerInterface[]
|
||||
*/
|
||||
private $fixers;
|
||||
|
||||
/**
|
||||
* @var null|bool
|
||||
*/
|
||||
private $configFinderIsOverridden;
|
||||
|
||||
private ToolInfoInterface $toolInfo;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $options = [
|
||||
'allow-risky' => null,
|
||||
'cache-file' => null,
|
||||
'config' => null,
|
||||
'diff' => null,
|
||||
'dry-run' => null,
|
||||
'format' => null,
|
||||
'path' => [],
|
||||
'path-mode' => self::PATH_MODE_OVERRIDE,
|
||||
'rules' => null,
|
||||
'show-progress' => null,
|
||||
'stop-on-violation' => null,
|
||||
'using-cache' => null,
|
||||
'verbosity' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $cacheFile;
|
||||
|
||||
/**
|
||||
* @var null|CacheManagerInterface
|
||||
*/
|
||||
private $cacheManager;
|
||||
|
||||
/**
|
||||
* @var null|DifferInterface
|
||||
*/
|
||||
private $differ;
|
||||
|
||||
/**
|
||||
* @var null|Directory
|
||||
*/
|
||||
private $directory;
|
||||
|
||||
/**
|
||||
* @var null|iterable<\SplFileInfo>
|
||||
*/
|
||||
private ?iterable $finder = null;
|
||||
|
||||
private ?string $format = null;
|
||||
|
||||
/**
|
||||
* @var null|Linter
|
||||
*/
|
||||
private $linter;
|
||||
|
||||
/**
|
||||
* @var null|list<string>
|
||||
*/
|
||||
private ?array $path = null;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $progress;
|
||||
|
||||
/**
|
||||
* @var null|RuleSet
|
||||
*/
|
||||
private $ruleSet;
|
||||
|
||||
/**
|
||||
* @var null|bool
|
||||
*/
|
||||
private $usingCache;
|
||||
|
||||
/**
|
||||
* @var FixerFactory
|
||||
*/
|
||||
private $fixerFactory;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
ConfigInterface $config,
|
||||
array $options,
|
||||
string $cwd,
|
||||
ToolInfoInterface $toolInfo
|
||||
) {
|
||||
$this->defaultConfig = $config;
|
||||
$this->cwd = $cwd;
|
||||
$this->toolInfo = $toolInfo;
|
||||
|
||||
foreach ($options as $name => $value) {
|
||||
$this->setOption($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function getCacheFile(): ?string
|
||||
{
|
||||
if (!$this->getUsingCache()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (null === $this->cacheFile) {
|
||||
if (null === $this->options['cache-file']) {
|
||||
$this->cacheFile = $this->getConfig()->getCacheFile();
|
||||
} else {
|
||||
$this->cacheFile = $this->options['cache-file'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cacheFile;
|
||||
}
|
||||
|
||||
public function getCacheManager(): CacheManagerInterface
|
||||
{
|
||||
if (null === $this->cacheManager) {
|
||||
$cacheFile = $this->getCacheFile();
|
||||
|
||||
if (null === $cacheFile) {
|
||||
$this->cacheManager = new NullCacheManager();
|
||||
} else {
|
||||
$this->cacheManager = new FileCacheManager(
|
||||
new FileHandler($cacheFile),
|
||||
new Signature(
|
||||
PHP_VERSION,
|
||||
$this->toolInfo->getVersion(),
|
||||
$this->getConfig()->getIndent(),
|
||||
$this->getConfig()->getLineEnding(),
|
||||
$this->getRules()
|
||||
),
|
||||
$this->isDryRun(),
|
||||
$this->getDirectory()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cacheManager;
|
||||
}
|
||||
|
||||
public function getConfig(): ConfigInterface
|
||||
{
|
||||
if (null === $this->config) {
|
||||
foreach ($this->computeConfigFiles() as $configFile) {
|
||||
if (!file_exists($configFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$configFileBasename = basename($configFile);
|
||||
$deprecatedConfigs = [
|
||||
'.php_cs' => '.php-cs-fixer.php',
|
||||
'.php_cs.dist' => '.php-cs-fixer.dist.php',
|
||||
];
|
||||
|
||||
if (isset($deprecatedConfigs[$configFileBasename])) {
|
||||
throw new InvalidConfigurationException("Configuration file `{$configFileBasename}` is outdated, rename to `{$deprecatedConfigs[$configFileBasename]}`.");
|
||||
}
|
||||
|
||||
$this->config = self::separatedContextLessInclude($configFile);
|
||||
$this->configFile = $configFile;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (null === $this->config) {
|
||||
$this->config = $this->defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function getConfigFile(): ?string
|
||||
{
|
||||
if (null === $this->configFile) {
|
||||
$this->getConfig();
|
||||
}
|
||||
|
||||
return $this->configFile;
|
||||
}
|
||||
|
||||
public function getDiffer(): DifferInterface
|
||||
{
|
||||
if (null === $this->differ) {
|
||||
if ($this->options['diff']) {
|
||||
$this->differ = new UnifiedDiffer();
|
||||
} else {
|
||||
$this->differ = new NullDiffer();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->differ;
|
||||
}
|
||||
|
||||
public function getDirectory(): DirectoryInterface
|
||||
{
|
||||
if (null === $this->directory) {
|
||||
$path = $this->getCacheFile();
|
||||
if (null === $path) {
|
||||
$absolutePath = $this->cwd;
|
||||
} else {
|
||||
$filesystem = new Filesystem();
|
||||
|
||||
$absolutePath = $filesystem->isAbsolutePath($path)
|
||||
? $path
|
||||
: $this->cwd.\DIRECTORY_SEPARATOR.$path;
|
||||
}
|
||||
|
||||
$this->directory = new Directory(\dirname($absolutePath));
|
||||
}
|
||||
|
||||
return $this->directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FixerInterface[] An array of FixerInterface
|
||||
*/
|
||||
public function getFixers(): array
|
||||
{
|
||||
if (null === $this->fixers) {
|
||||
$this->fixers = $this->createFixerFactory()
|
||||
->useRuleSet($this->getRuleSet())
|
||||
->setWhitespacesConfig(new WhitespacesFixerConfig($this->config->getIndent(), $this->config->getLineEnding()))
|
||||
->getFixers()
|
||||
;
|
||||
|
||||
if (false === $this->getRiskyAllowed()) {
|
||||
$riskyFixers = array_map(
|
||||
static function (FixerInterface $fixer): string {
|
||||
return $fixer->getName();
|
||||
},
|
||||
array_filter(
|
||||
$this->fixers,
|
||||
static function (FixerInterface $fixer): bool {
|
||||
return $fixer->isRisky();
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (\count($riskyFixers) > 0) {
|
||||
throw new InvalidConfigurationException(sprintf('The rules contain risky fixers ("%s"), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', implode('", "', $riskyFixers)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fixers;
|
||||
}
|
||||
|
||||
public function getLinter(): LinterInterface
|
||||
{
|
||||
if (null === $this->linter) {
|
||||
$this->linter = new Linter();
|
||||
}
|
||||
|
||||
return $this->linter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPath(): array
|
||||
{
|
||||
if (null === $this->path) {
|
||||
$filesystem = new Filesystem();
|
||||
$cwd = $this->cwd;
|
||||
|
||||
if (1 === \count($this->options['path']) && '-' === $this->options['path'][0]) {
|
||||
$this->path = $this->options['path'];
|
||||
} else {
|
||||
$this->path = array_map(
|
||||
static function (string $rawPath) use ($cwd, $filesystem): string {
|
||||
$path = trim($rawPath);
|
||||
|
||||
if ('' === $path) {
|
||||
throw new InvalidConfigurationException("Invalid path: \"{$rawPath}\".");
|
||||
}
|
||||
|
||||
$absolutePath = $filesystem->isAbsolutePath($path)
|
||||
? $path
|
||||
: $cwd.\DIRECTORY_SEPARATOR.$path;
|
||||
|
||||
if (!file_exists($absolutePath)) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'The path "%s" is not readable.',
|
||||
$path
|
||||
));
|
||||
}
|
||||
|
||||
return $absolutePath;
|
||||
},
|
||||
$this->options['path']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidConfigurationException
|
||||
*/
|
||||
public function getProgress(): string
|
||||
{
|
||||
if (null === $this->progress) {
|
||||
if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) {
|
||||
$progressType = $this->options['show-progress'];
|
||||
$progressTypes = ['none', 'dots'];
|
||||
|
||||
if (null === $progressType) {
|
||||
$progressType = $this->getConfig()->getHideProgress() ? 'none' : 'dots';
|
||||
} elseif (!\in_array($progressType, $progressTypes, true)) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'The progress type "%s" is not defined, supported are "%s".',
|
||||
$progressType,
|
||||
implode('", "', $progressTypes)
|
||||
));
|
||||
}
|
||||
|
||||
$this->progress = $progressType;
|
||||
} else {
|
||||
$this->progress = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->progress;
|
||||
}
|
||||
|
||||
public function getReporter(): ReporterInterface
|
||||
{
|
||||
if (null === $this->reporter) {
|
||||
$reporterFactory = new ReporterFactory();
|
||||
$reporterFactory->registerBuiltInReporters();
|
||||
|
||||
$format = $this->getFormat();
|
||||
|
||||
try {
|
||||
$this->reporter = $reporterFactory->getReporter($format);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
$formats = $reporterFactory->getFormats();
|
||||
sort($formats);
|
||||
|
||||
throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats)));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->reporter;
|
||||
}
|
||||
|
||||
public function getRiskyAllowed(): bool
|
||||
{
|
||||
if (null === $this->allowRisky) {
|
||||
if (null === $this->options['allow-risky']) {
|
||||
$this->allowRisky = $this->getConfig()->getRiskyAllowed();
|
||||
} else {
|
||||
$this->allowRisky = $this->resolveOptionBooleanValue('allow-risky');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->allowRisky;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns rules.
|
||||
*
|
||||
* @return array<string, array<string, mixed>|bool>
|
||||
*/
|
||||
public function getRules(): array
|
||||
{
|
||||
return $this->getRuleSet()->getRules();
|
||||
}
|
||||
|
||||
public function getUsingCache(): bool
|
||||
{
|
||||
if (null === $this->usingCache) {
|
||||
if (null === $this->options['using-cache']) {
|
||||
$this->usingCache = $this->getConfig()->getUsingCache();
|
||||
} else {
|
||||
$this->usingCache = $this->resolveOptionBooleanValue('using-cache');
|
||||
}
|
||||
}
|
||||
|
||||
$this->usingCache = $this->usingCache && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer());
|
||||
|
||||
return $this->usingCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<\SplFileInfo>
|
||||
*/
|
||||
public function getFinder(): iterable
|
||||
{
|
||||
if (null === $this->finder) {
|
||||
$this->finder = $this->resolveFinder();
|
||||
}
|
||||
|
||||
return $this->finder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns dry-run flag.
|
||||
*/
|
||||
public function isDryRun(): bool
|
||||
{
|
||||
if (null === $this->isDryRun) {
|
||||
if ($this->isStdIn()) {
|
||||
// Can't write to STDIN
|
||||
$this->isDryRun = true;
|
||||
} else {
|
||||
$this->isDryRun = $this->options['dry-run'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->isDryRun;
|
||||
}
|
||||
|
||||
public function shouldStopOnViolation(): bool
|
||||
{
|
||||
return $this->options['stop-on-violation'];
|
||||
}
|
||||
|
||||
public function configFinderIsOverridden(): bool
|
||||
{
|
||||
if (null === $this->configFinderIsOverridden) {
|
||||
$this->resolveFinder();
|
||||
}
|
||||
|
||||
return $this->configFinderIsOverridden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute file candidates for config file.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function computeConfigFiles(): array
|
||||
{
|
||||
$configFile = $this->options['config'];
|
||||
|
||||
if (null !== $configFile) {
|
||||
if (false === file_exists($configFile) || false === is_readable($configFile)) {
|
||||
throw new InvalidConfigurationException(sprintf('Cannot read config file "%s".', $configFile));
|
||||
}
|
||||
|
||||
return [$configFile];
|
||||
}
|
||||
|
||||
$path = $this->getPath();
|
||||
|
||||
if ($this->isStdIn() || 0 === \count($path)) {
|
||||
$configDir = $this->cwd;
|
||||
} elseif (1 < \count($path)) {
|
||||
throw new InvalidConfigurationException('For multiple paths config parameter is required.');
|
||||
} elseif (!is_file($path[0])) {
|
||||
$configDir = $path[0];
|
||||
} else {
|
||||
$dirName = pathinfo($path[0], PATHINFO_DIRNAME);
|
||||
$configDir = $dirName ?: $path[0];
|
||||
}
|
||||
|
||||
$candidates = [
|
||||
$configDir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php',
|
||||
$configDir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php',
|
||||
$configDir.\DIRECTORY_SEPARATOR.'.php_cs', // old v2 config, present here only to throw nice error message later
|
||||
$configDir.\DIRECTORY_SEPARATOR.'.php_cs.dist', // old v2 config, present here only to throw nice error message later
|
||||
];
|
||||
|
||||
if ($configDir !== $this->cwd) {
|
||||
$candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php';
|
||||
$candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php';
|
||||
$candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs'; // old v2 config, present here only to throw nice error message later
|
||||
$candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs.dist'; // old v2 config, present here only to throw nice error message later
|
||||
}
|
||||
|
||||
return $candidates;
|
||||
}
|
||||
|
||||
private function createFixerFactory(): FixerFactory
|
||||
{
|
||||
if (null === $this->fixerFactory) {
|
||||
$fixerFactory = new FixerFactory();
|
||||
$fixerFactory->registerBuiltInFixers();
|
||||
$fixerFactory->registerCustomFixers($this->getConfig()->getCustomFixers());
|
||||
|
||||
$this->fixerFactory = $fixerFactory;
|
||||
}
|
||||
|
||||
return $this->fixerFactory;
|
||||
}
|
||||
|
||||
private function getFormat(): string
|
||||
{
|
||||
if (null === $this->format) {
|
||||
$this->format = $this->options['format'] ?? $this->getConfig()->getFormat();
|
||||
}
|
||||
|
||||
return $this->format;
|
||||
}
|
||||
|
||||
private function getRuleSet(): RuleSetInterface
|
||||
{
|
||||
if (null === $this->ruleSet) {
|
||||
$rules = $this->parseRules();
|
||||
$this->validateRules($rules);
|
||||
|
||||
$this->ruleSet = new RuleSet($rules);
|
||||
}
|
||||
|
||||
return $this->ruleSet;
|
||||
}
|
||||
|
||||
private function isStdIn(): bool
|
||||
{
|
||||
if (null === $this->isStdIn) {
|
||||
$this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0];
|
||||
}
|
||||
|
||||
return $this->isStdIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @param iterable<T> $iterable
|
||||
*
|
||||
* @return \Traversable<T>
|
||||
*/
|
||||
private function iterableToTraversable(iterable $iterable): \Traversable
|
||||
{
|
||||
return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function parseRules(): array
|
||||
{
|
||||
if (null === $this->options['rules']) {
|
||||
return $this->getConfig()->getRules();
|
||||
}
|
||||
|
||||
$rules = trim($this->options['rules']);
|
||||
if ('' === $rules) {
|
||||
throw new InvalidConfigurationException('Empty rules value is not allowed.');
|
||||
}
|
||||
|
||||
if (str_starts_with($rules, '{')) {
|
||||
$rules = json_decode($rules, true);
|
||||
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new InvalidConfigurationException(sprintf('Invalid JSON rules input: "%s".', json_last_error_msg()));
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
$rules = [];
|
||||
|
||||
foreach (explode(',', $this->options['rules']) as $rule) {
|
||||
$rule = trim($rule);
|
||||
|
||||
if ('' === $rule) {
|
||||
throw new InvalidConfigurationException('Empty rule name is not allowed.');
|
||||
}
|
||||
|
||||
if (str_starts_with($rule, '-')) {
|
||||
$rules[substr($rule, 1)] = false;
|
||||
} else {
|
||||
$rules[$rule] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $rules
|
||||
*
|
||||
* @throws InvalidConfigurationException
|
||||
*/
|
||||
private function validateRules(array $rules): void
|
||||
{
|
||||
/**
|
||||
* Create a ruleset that contains all configured rules, even when they originally have been disabled.
|
||||
*
|
||||
* @see RuleSet::resolveSet()
|
||||
*/
|
||||
$ruleSet = [];
|
||||
|
||||
foreach ($rules as $key => $value) {
|
||||
if (\is_int($key)) {
|
||||
throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value));
|
||||
}
|
||||
|
||||
$ruleSet[$key] = true;
|
||||
}
|
||||
|
||||
$ruleSet = new RuleSet($ruleSet);
|
||||
|
||||
$configuredFixers = array_keys($ruleSet->getRules());
|
||||
|
||||
$fixers = $this->createFixerFactory()->getFixers();
|
||||
|
||||
$availableFixers = array_map(static fn (FixerInterface $fixer): string => $fixer->getName(), $fixers);
|
||||
|
||||
$unknownFixers = array_diff($configuredFixers, $availableFixers);
|
||||
|
||||
if (\count($unknownFixers) > 0) {
|
||||
$renamedRules = [
|
||||
'blank_line_before_return' => [
|
||||
'new_name' => 'blank_line_before_statement',
|
||||
'config' => ['statements' => ['return']],
|
||||
],
|
||||
'final_static_access' => [
|
||||
'new_name' => 'self_static_accessor',
|
||||
],
|
||||
'hash_to_slash_comment' => [
|
||||
'new_name' => 'single_line_comment_style',
|
||||
'config' => ['comment_types' => ['hash']],
|
||||
],
|
||||
'lowercase_constants' => [
|
||||
'new_name' => 'constant_case',
|
||||
'config' => ['case' => 'lower'],
|
||||
],
|
||||
'no_extra_consecutive_blank_lines' => [
|
||||
'new_name' => 'no_extra_blank_lines',
|
||||
],
|
||||
'no_multiline_whitespace_before_semicolons' => [
|
||||
'new_name' => 'multiline_whitespace_before_semicolons',
|
||||
],
|
||||
'no_short_echo_tag' => [
|
||||
'new_name' => 'echo_tag_syntax',
|
||||
'config' => ['format' => 'long'],
|
||||
],
|
||||
'php_unit_ordered_covers' => [
|
||||
'new_name' => 'phpdoc_order_by_value',
|
||||
'config' => ['annotations' => ['covers']],
|
||||
],
|
||||
'phpdoc_inline_tag' => [
|
||||
'new_name' => 'general_phpdoc_tag_rename, phpdoc_inline_tag_normalizer and phpdoc_tag_type',
|
||||
],
|
||||
'pre_increment' => [
|
||||
'new_name' => 'increment_style',
|
||||
'config' => ['style' => 'pre'],
|
||||
],
|
||||
'psr0' => [
|
||||
'new_name' => 'psr_autoloading',
|
||||
'config' => ['dir' => 'x'],
|
||||
],
|
||||
'psr4' => [
|
||||
'new_name' => 'psr_autoloading',
|
||||
],
|
||||
'silenced_deprecation_error' => [
|
||||
'new_name' => 'error_suppression',
|
||||
],
|
||||
'trailing_comma_in_multiline_array' => [
|
||||
'new_name' => 'trailing_comma_in_multiline',
|
||||
'config' => ['elements' => ['arrays']],
|
||||
],
|
||||
];
|
||||
|
||||
$message = 'The rules contain unknown fixers: ';
|
||||
$hasOldRule = false;
|
||||
|
||||
foreach ($unknownFixers as $unknownFixer) {
|
||||
if (isset($renamedRules[$unknownFixer])) { // Check if present as old renamed rule
|
||||
$hasOldRule = true;
|
||||
$message .= sprintf(
|
||||
'"%s" is renamed (did you mean "%s"?%s), ',
|
||||
$unknownFixer,
|
||||
$renamedRules[$unknownFixer]['new_name'],
|
||||
isset($renamedRules[$unknownFixer]['config']) ? ' (note: use configuration "'.HelpCommand::toString($renamedRules[$unknownFixer]['config']).'")' : ''
|
||||
);
|
||||
} else { // Go to normal matcher if it is not a renamed rule
|
||||
$matcher = new WordMatcher($availableFixers);
|
||||
$alternative = $matcher->match($unknownFixer);
|
||||
$message .= sprintf(
|
||||
'"%s"%s, ',
|
||||
$unknownFixer,
|
||||
null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$message = substr($message, 0, -2).'.';
|
||||
|
||||
if ($hasOldRule) {
|
||||
$message .= "\nFor more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless.";
|
||||
}
|
||||
|
||||
throw new InvalidConfigurationException($message);
|
||||
}
|
||||
|
||||
foreach ($fixers as $fixer) {
|
||||
$fixerName = $fixer->getName();
|
||||
if (isset($rules[$fixerName]) && $fixer instanceof DeprecatedFixerInterface) {
|
||||
$successors = $fixer->getSuccessorsNames();
|
||||
$messageEnd = [] === $successors
|
||||
? sprintf(' and will be removed in version %d.0.', Application::getMajorVersion() + 1)
|
||||
: sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors)));
|
||||
|
||||
Utils::triggerDeprecation(new \RuntimeException("Rule \"{$fixerName}\" is deprecated{$messageEnd}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply path on config instance.
|
||||
*
|
||||
* @return iterable<\SplFileInfo>
|
||||
*/
|
||||
private function resolveFinder(): iterable
|
||||
{
|
||||
$this->configFinderIsOverridden = false;
|
||||
|
||||
if ($this->isStdIn()) {
|
||||
return new \ArrayIterator([new StdinFileInfo()]);
|
||||
}
|
||||
|
||||
$modes = [self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION];
|
||||
|
||||
if (!\in_array(
|
||||
$this->options['path-mode'],
|
||||
$modes,
|
||||
true
|
||||
)) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'The path-mode "%s" is not defined, supported are "%s".',
|
||||
$this->options['path-mode'],
|
||||
implode('", "', $modes)
|
||||
));
|
||||
}
|
||||
|
||||
$isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode'];
|
||||
|
||||
$paths = array_filter(array_map(
|
||||
static function (string $path) {
|
||||
return realpath($path);
|
||||
},
|
||||
$this->getPath()
|
||||
));
|
||||
|
||||
if (0 === \count($paths)) {
|
||||
if ($isIntersectionPathMode) {
|
||||
return new \ArrayIterator([]);
|
||||
}
|
||||
|
||||
return $this->iterableToTraversable($this->getConfig()->getFinder());
|
||||
}
|
||||
|
||||
$pathsByType = [
|
||||
'file' => [],
|
||||
'dir' => [],
|
||||
];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (is_file($path)) {
|
||||
$pathsByType['file'][] = $path;
|
||||
} else {
|
||||
$pathsByType['dir'][] = $path.\DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
||||
|
||||
$nestedFinder = null;
|
||||
$currentFinder = $this->iterableToTraversable($this->getConfig()->getFinder());
|
||||
|
||||
try {
|
||||
$nestedFinder = $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
if ($isIntersectionPathMode) {
|
||||
if (null === $nestedFinder) {
|
||||
throw new InvalidConfigurationException(
|
||||
'Cannot create intersection with not-fully defined Finder in configuration file.'
|
||||
);
|
||||
}
|
||||
|
||||
return new \CallbackFilterIterator(
|
||||
new \IteratorIterator($nestedFinder),
|
||||
static function (\SplFileInfo $current) use ($pathsByType): bool {
|
||||
$currentRealPath = $current->getRealPath();
|
||||
|
||||
if (\in_array($currentRealPath, $pathsByType['file'], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($pathsByType['dir'] as $path) {
|
||||
if (str_starts_with($currentRealPath, $path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $this->getConfigFile() && null !== $nestedFinder) {
|
||||
$this->configFinderIsOverridden = true;
|
||||
}
|
||||
|
||||
if ($currentFinder instanceof SymfonyFinder && null === $nestedFinder) {
|
||||
// finder from configuration Symfony finder and it is not fully defined, we may fulfill it
|
||||
return $currentFinder->in($pathsByType['dir'])->append($pathsByType['file']);
|
||||
}
|
||||
|
||||
return Finder::create()->in($pathsByType['dir'])->append($pathsByType['file']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set option that will be resolved.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function setOption(string $name, $value): void
|
||||
{
|
||||
if (!\array_key_exists($name, $this->options)) {
|
||||
throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name));
|
||||
}
|
||||
|
||||
$this->options[$name] = $value;
|
||||
}
|
||||
|
||||
private function resolveOptionBooleanValue(string $optionName): bool
|
||||
{
|
||||
$value = $this->options[$optionName];
|
||||
|
||||
if (!\is_string($value)) {
|
||||
throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName));
|
||||
}
|
||||
|
||||
if ('yes' === $value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('no' === $value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new InvalidConfigurationException(sprintf('Expected "yes" or "no" for option "%s", got "%s".', $optionName, $value));
|
||||
}
|
||||
|
||||
private static function separatedContextLessInclude(string $path): ConfigInterface
|
||||
{
|
||||
$config = include $path;
|
||||
|
||||
// verify that the config has an instance of Config
|
||||
if (!$config instanceof ConfigInterface) {
|
||||
throw new InvalidConfigurationException(sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $path, \is_object($config) ? \get_class($config) : \gettype($config)));
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?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\Console\Output;
|
||||
|
||||
use PhpCsFixer\Differ\DiffConsoleFormatter;
|
||||
use PhpCsFixer\Error\Error;
|
||||
use PhpCsFixer\Linter\LintingException;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ErrorOutput
|
||||
{
|
||||
private OutputInterface $output;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isDecorated;
|
||||
|
||||
public function __construct(OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->isDecorated = $output->isDecorated();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Error[] $errors
|
||||
*/
|
||||
public function listErrors(string $process, array $errors): void
|
||||
{
|
||||
$this->output->writeln(['', sprintf(
|
||||
'Files that were not fixed due to errors reported during %s:',
|
||||
$process
|
||||
)]);
|
||||
|
||||
$showDetails = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE;
|
||||
$showTrace = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG;
|
||||
foreach ($errors as $i => $error) {
|
||||
$this->output->writeln(sprintf('%4d) %s', $i + 1, $error->getFilePath()));
|
||||
$e = $error->getSource();
|
||||
if (!$showDetails || null === $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = sprintf('[%s]', \get_class($e));
|
||||
$message = $e->getMessage();
|
||||
$code = $e->getCode();
|
||||
if (0 !== $code) {
|
||||
$message .= " ({$code})";
|
||||
}
|
||||
|
||||
$length = max(\strlen($class), \strlen($message));
|
||||
$lines = [
|
||||
'',
|
||||
$class,
|
||||
$message,
|
||||
'',
|
||||
];
|
||||
|
||||
$this->output->writeln('');
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (\strlen($line) < $length) {
|
||||
$line .= str_repeat(' ', $length - \strlen($line));
|
||||
}
|
||||
|
||||
$this->output->writeln(sprintf(' <error> %s </error>', $this->prepareOutput($line)));
|
||||
}
|
||||
|
||||
if ($showTrace && !$e instanceof LintingException) { // stack trace of lint exception is of no interest
|
||||
$this->output->writeln('');
|
||||
$stackTrace = $e->getTrace();
|
||||
foreach ($stackTrace as $trace) {
|
||||
if (isset($trace['class']) && \Symfony\Component\Console\Command\Command::class === $trace['class'] && 'run' === $trace['function']) {
|
||||
$this->output->writeln(' [ ... ]');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$this->outputTrace($trace);
|
||||
}
|
||||
}
|
||||
|
||||
if (Error::TYPE_LINT === $error->getType() && 0 < \count($error->getAppliedFixers())) {
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln(sprintf(' Applied fixers: <comment>%s</comment>', implode(', ', $error->getAppliedFixers())));
|
||||
|
||||
$diff = $error->getDiff();
|
||||
if (!empty($diff)) {
|
||||
$diffFormatter = new DiffConsoleFormatter(
|
||||
$this->isDecorated,
|
||||
sprintf(
|
||||
'<comment> ---------- begin diff ----------</comment>%s%%s%s<comment> ----------- end diff -----------</comment>',
|
||||
PHP_EOL,
|
||||
PHP_EOL
|
||||
)
|
||||
);
|
||||
|
||||
$this->output->writeln($diffFormatter->format($diff));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* function?: string,
|
||||
* line?: int,
|
||||
* file?: string,
|
||||
* class?: class-string,
|
||||
* type?: '::'|'->',
|
||||
* args?: mixed[],
|
||||
* object?: object,
|
||||
* } $trace
|
||||
*/
|
||||
private function outputTrace(array $trace): void
|
||||
{
|
||||
if (isset($trace['class'], $trace['type'], $trace['function'])) {
|
||||
$this->output->writeln(sprintf(
|
||||
' <comment>%s</comment>%s<comment>%s()</comment>',
|
||||
$this->prepareOutput($trace['class']),
|
||||
$this->prepareOutput($trace['type']),
|
||||
$this->prepareOutput($trace['function'])
|
||||
));
|
||||
} elseif (isset($trace['function'])) {
|
||||
$this->output->writeln(sprintf(' <comment>%s()</comment>', $this->prepareOutput($trace['function'])));
|
||||
}
|
||||
|
||||
if (isset($trace['file'])) {
|
||||
$this->output->writeln(sprintf(' in <info>%s</info> at line <info>%d</info>', $this->prepareOutput($trace['file']), $trace['line']));
|
||||
}
|
||||
}
|
||||
|
||||
private function prepareOutput(string $string): string
|
||||
{
|
||||
return $this->isDecorated
|
||||
? OutputFormatter::escape($string)
|
||||
: $string
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Console\Output;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class NullOutput implements ProcessOutputInterface
|
||||
{
|
||||
public function printLegend(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
<?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\Console\Output;
|
||||
|
||||
use PhpCsFixer\FixerFileProcessedEvent;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Output writer to show the process of a FixCommand.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ProcessOutput implements ProcessOutputInterface
|
||||
{
|
||||
/**
|
||||
* File statuses map.
|
||||
*
|
||||
* @var array<FixerFileProcessedEvent::STATUS_*, array{symbol: string, format: string, description: string}>
|
||||
*/
|
||||
private static array $eventStatusMap = [
|
||||
FixerFileProcessedEvent::STATUS_NO_CHANGES => ['symbol' => '.', 'format' => '%s', 'description' => 'no changes'],
|
||||
FixerFileProcessedEvent::STATUS_FIXED => ['symbol' => 'F', 'format' => '<fg=green>%s</fg=green>', 'description' => 'fixed'],
|
||||
FixerFileProcessedEvent::STATUS_SKIPPED => ['symbol' => 'S', 'format' => '<fg=cyan>%s</fg=cyan>', 'description' => 'skipped (cached or empty file)'],
|
||||
FixerFileProcessedEvent::STATUS_INVALID => ['symbol' => 'I', 'format' => '<bg=red>%s</bg=red>', 'description' => 'invalid file syntax (file ignored)'],
|
||||
FixerFileProcessedEvent::STATUS_EXCEPTION => ['symbol' => 'E', 'format' => '<bg=red>%s</bg=red>', 'description' => 'error'],
|
||||
FixerFileProcessedEvent::STATUS_LINT => ['symbol' => 'E', 'format' => '<bg=red>%s</bg=red>', 'description' => 'error'],
|
||||
];
|
||||
|
||||
private OutputInterface $output;
|
||||
|
||||
private EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
private int $files;
|
||||
|
||||
private int $processedFiles = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $symbolsPerLine;
|
||||
|
||||
public function __construct(OutputInterface $output, EventDispatcherInterface $dispatcher, int $width, int $nbFiles)
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->eventDispatcher = $dispatcher;
|
||||
$this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']);
|
||||
$this->files = $nbFiles;
|
||||
|
||||
// max number of characters per line
|
||||
// - total length x 2 (e.g. " 1 / 123" => 6 digits and padding spaces)
|
||||
// - 11 (extra spaces, parentheses and percentage characters, e.g. " x / x (100%)")
|
||||
$this->symbolsPerLine = max(1, $width - \strlen((string) $this->files) * 2 - 11);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is not intended to be serialized,
|
||||
* and cannot be deserialized (see __wakeup method).
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the deserialization of the class to prevent attacker executing
|
||||
* code by leveraging the __destruct method.
|
||||
*
|
||||
* @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection
|
||||
*/
|
||||
public function __wakeup(): void
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function onFixerFileProcessed(FixerFileProcessedEvent $event): void
|
||||
{
|
||||
$status = self::$eventStatusMap[$event->getStatus()];
|
||||
$this->output->write($this->output->isDecorated() ? sprintf($status['format'], $status['symbol']) : $status['symbol']);
|
||||
|
||||
++$this->processedFiles;
|
||||
|
||||
$symbolsOnCurrentLine = $this->processedFiles % $this->symbolsPerLine;
|
||||
$isLast = $this->processedFiles === $this->files;
|
||||
|
||||
if (0 === $symbolsOnCurrentLine || $isLast) {
|
||||
$this->output->write(sprintf(
|
||||
'%s %'.\strlen((string) $this->files).'d / %d (%3d%%)',
|
||||
$isLast && 0 !== $symbolsOnCurrentLine ? str_repeat(' ', $this->symbolsPerLine - $symbolsOnCurrentLine) : '',
|
||||
$this->processedFiles,
|
||||
$this->files,
|
||||
round($this->processedFiles / $this->files * 100)
|
||||
));
|
||||
|
||||
if (!$isLast) {
|
||||
$this->output->writeln('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function printLegend(): void
|
||||
{
|
||||
$symbols = [];
|
||||
|
||||
foreach (self::$eventStatusMap as $status) {
|
||||
$symbol = $status['symbol'];
|
||||
if ('' === $symbol || isset($symbols[$symbol])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$symbols[$symbol] = sprintf('%s-%s', $this->output->isDecorated() ? sprintf($status['format'], $symbol) : $symbol, $status['description']);
|
||||
}
|
||||
|
||||
$this->output->write(sprintf("\nLegend: %s\n", implode(', ', $symbols)));
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?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\Console\Output;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface ProcessOutputInterface
|
||||
{
|
||||
public function printLegend(): void;
|
||||
}
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
|
||||
/**
|
||||
* @author Kévin Gomez <contact@kevingomez.fr>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CheckstyleReporter implements ReporterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormat(): string
|
||||
{
|
||||
return 'checkstyle';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string
|
||||
{
|
||||
if (!\extension_loaded('dom')) {
|
||||
throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!');
|
||||
}
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$checkstyles = $dom->appendChild($dom->createElement('checkstyle'));
|
||||
|
||||
foreach ($reportSummary->getChanged() as $filePath => $fixResult) {
|
||||
/** @var \DOMElement $file */
|
||||
$file = $checkstyles->appendChild($dom->createElement('file'));
|
||||
$file->setAttribute('name', $filePath);
|
||||
|
||||
foreach ($fixResult['appliedFixers'] as $appliedFixer) {
|
||||
$error = $this->createError($dom, $appliedFixer);
|
||||
$file->appendChild($error);
|
||||
}
|
||||
}
|
||||
|
||||
$dom->formatOutput = true;
|
||||
|
||||
return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML();
|
||||
}
|
||||
|
||||
private function createError(\DOMDocument $dom, string $appliedFixer): \DOMElement
|
||||
{
|
||||
$error = $dom->createElement('error');
|
||||
$error->setAttribute('severity', 'warning');
|
||||
$error->setAttribute('source', 'PHP-CS-Fixer.'.$appliedFixer);
|
||||
$error->setAttribute('message', 'Found violation(s) of type: '.$appliedFixer);
|
||||
|
||||
return $error;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
|
||||
/**
|
||||
* Generates a report according to gitlabs subset of codeclimate json files.
|
||||
*
|
||||
* @see https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types
|
||||
*
|
||||
* @author Hans-Christian Otto <c.otto@suora.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class GitlabReporter implements ReporterInterface
|
||||
{
|
||||
public function getFormat(): string
|
||||
{
|
||||
return 'gitlab';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process changed files array. Returns generated report.
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string
|
||||
{
|
||||
$report = [];
|
||||
foreach ($reportSummary->getChanged() as $fileName => $change) {
|
||||
foreach ($change['appliedFixers'] as $fixerName) {
|
||||
$report[] = [
|
||||
'description' => $fixerName,
|
||||
'fingerprint' => md5($fileName.$fixerName),
|
||||
'severity' => 'minor',
|
||||
'location' => [
|
||||
'path' => $fileName,
|
||||
'lines' => [
|
||||
'begin' => 0, // line numbers are required in the format, but not available to reports
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$jsonString = json_encode($report);
|
||||
|
||||
return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($jsonString) : $jsonString;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
|
||||
/**
|
||||
* @author Boris Gorbylev <ekho@ekho.name>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class JsonReporter implements ReporterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormat(): string
|
||||
{
|
||||
return 'json';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string
|
||||
{
|
||||
$jsonFiles = [];
|
||||
|
||||
foreach ($reportSummary->getChanged() as $file => $fixResult) {
|
||||
$jsonFile = ['name' => $file];
|
||||
|
||||
if ($reportSummary->shouldAddAppliedFixers()) {
|
||||
$jsonFile['appliedFixers'] = $fixResult['appliedFixers'];
|
||||
}
|
||||
|
||||
if ('' !== $fixResult['diff']) {
|
||||
$jsonFile['diff'] = $fixResult['diff'];
|
||||
}
|
||||
|
||||
$jsonFiles[] = $jsonFile;
|
||||
}
|
||||
|
||||
$json = [
|
||||
'files' => $jsonFiles,
|
||||
'time' => [
|
||||
'total' => round($reportSummary->getTime() / 1000, 3),
|
||||
],
|
||||
'memory' => round($reportSummary->getMemory() / 1024 / 1024, 3),
|
||||
];
|
||||
|
||||
$json = json_encode($json);
|
||||
|
||||
return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($json) : $json;
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
use PhpCsFixer\Preg;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
|
||||
/**
|
||||
* @author Boris Gorbylev <ekho@ekho.name>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class JunitReporter implements ReporterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormat(): string
|
||||
{
|
||||
return 'junit';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string
|
||||
{
|
||||
if (!\extension_loaded('dom')) {
|
||||
throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!');
|
||||
}
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$testsuites = $dom->appendChild($dom->createElement('testsuites'));
|
||||
|
||||
/** @var \DomElement $testsuite */
|
||||
$testsuite = $testsuites->appendChild($dom->createElement('testsuite'));
|
||||
$testsuite->setAttribute('name', 'PHP CS Fixer');
|
||||
|
||||
if (\count($reportSummary->getChanged()) > 0) {
|
||||
$this->createFailedTestCases($dom, $testsuite, $reportSummary);
|
||||
} else {
|
||||
$this->createSuccessTestCase($dom, $testsuite);
|
||||
}
|
||||
|
||||
if ($reportSummary->getTime() > 0) {
|
||||
$testsuite->setAttribute(
|
||||
'time',
|
||||
sprintf(
|
||||
'%.3f',
|
||||
$reportSummary->getTime() / 1000
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$dom->formatOutput = true;
|
||||
|
||||
return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML();
|
||||
}
|
||||
|
||||
private function createSuccessTestCase(\DOMDocument $dom, \DOMElement $testsuite): void
|
||||
{
|
||||
$testcase = $dom->createElement('testcase');
|
||||
$testcase->setAttribute('name', 'All OK');
|
||||
$testcase->setAttribute('assertions', '1');
|
||||
|
||||
$testsuite->appendChild($testcase);
|
||||
$testsuite->setAttribute('tests', '1');
|
||||
$testsuite->setAttribute('assertions', '1');
|
||||
$testsuite->setAttribute('failures', '0');
|
||||
$testsuite->setAttribute('errors', '0');
|
||||
}
|
||||
|
||||
private function createFailedTestCases(\DOMDocument $dom, \DOMElement $testsuite, ReportSummary $reportSummary): void
|
||||
{
|
||||
$assertionsCount = 0;
|
||||
foreach ($reportSummary->getChanged() as $file => $fixResult) {
|
||||
$testcase = $this->createFailedTestCase(
|
||||
$dom,
|
||||
$file,
|
||||
$fixResult,
|
||||
$reportSummary->shouldAddAppliedFixers()
|
||||
);
|
||||
$testsuite->appendChild($testcase);
|
||||
$assertionsCount += (int) $testcase->getAttribute('assertions');
|
||||
}
|
||||
|
||||
$testsuite->setAttribute('tests', (string) \count($reportSummary->getChanged()));
|
||||
$testsuite->setAttribute('assertions', (string) $assertionsCount);
|
||||
$testsuite->setAttribute('failures', (string) $assertionsCount);
|
||||
$testsuite->setAttribute('errors', '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{appliedFixers: list<string>, diff: string} $fixResult
|
||||
*/
|
||||
private function createFailedTestCase(\DOMDocument $dom, string $file, array $fixResult, bool $shouldAddAppliedFixers): \DOMElement
|
||||
{
|
||||
$appliedFixersCount = \count($fixResult['appliedFixers']);
|
||||
|
||||
$testName = str_replace('.', '_DOT_', Preg::replace('@\.'.pathinfo($file, PATHINFO_EXTENSION).'$@', '', $file));
|
||||
|
||||
$testcase = $dom->createElement('testcase');
|
||||
$testcase->setAttribute('name', $testName);
|
||||
$testcase->setAttribute('file', $file);
|
||||
$testcase->setAttribute('assertions', (string) $appliedFixersCount);
|
||||
|
||||
$failure = $dom->createElement('failure');
|
||||
$failure->setAttribute('type', 'code_style');
|
||||
$testcase->appendChild($failure);
|
||||
|
||||
if ($shouldAddAppliedFixers) {
|
||||
$failureContent = "applied fixers:\n---------------\n";
|
||||
|
||||
foreach ($fixResult['appliedFixers'] as $appliedFixer) {
|
||||
$failureContent .= "* {$appliedFixer}\n";
|
||||
}
|
||||
} else {
|
||||
$failureContent = "Wrong code style\n";
|
||||
}
|
||||
|
||||
if ('' !== $fixResult['diff']) {
|
||||
$failureContent .= "\nDiff:\n---------------\n\n".$fixResult['diff'];
|
||||
}
|
||||
|
||||
$failure->appendChild($dom->createCDATASection(trim($failureContent)));
|
||||
|
||||
return $testcase;
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ReportSummary
|
||||
{
|
||||
/**
|
||||
* @var array<string, array{appliedFixers: list<string>, diff: string}>
|
||||
*/
|
||||
private array $changed;
|
||||
|
||||
private int $time;
|
||||
|
||||
private int $memory;
|
||||
|
||||
private bool $addAppliedFixers;
|
||||
|
||||
private bool $isDryRun;
|
||||
|
||||
private bool $isDecoratedOutput;
|
||||
|
||||
/**
|
||||
* @param array<string, array{appliedFixers: list<string>, diff: string}> $changed
|
||||
* @param int $time duration in milliseconds
|
||||
* @param int $memory memory usage in bytes
|
||||
*/
|
||||
public function __construct(
|
||||
array $changed,
|
||||
int $time,
|
||||
int $memory,
|
||||
bool $addAppliedFixers,
|
||||
bool $isDryRun,
|
||||
bool $isDecoratedOutput
|
||||
) {
|
||||
$this->changed = $changed;
|
||||
$this->time = $time;
|
||||
$this->memory = $memory;
|
||||
$this->addAppliedFixers = $addAppliedFixers;
|
||||
$this->isDryRun = $isDryRun;
|
||||
$this->isDecoratedOutput = $isDecoratedOutput;
|
||||
}
|
||||
|
||||
public function isDecoratedOutput(): bool
|
||||
{
|
||||
return $this->isDecoratedOutput;
|
||||
}
|
||||
|
||||
public function isDryRun(): bool
|
||||
{
|
||||
return $this->isDryRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{appliedFixers: list<string>, diff: string}>
|
||||
*/
|
||||
public function getChanged(): array
|
||||
{
|
||||
return $this->changed;
|
||||
}
|
||||
|
||||
public function getMemory(): int
|
||||
{
|
||||
return $this->memory;
|
||||
}
|
||||
|
||||
public function getTime(): int
|
||||
{
|
||||
return $this->time;
|
||||
}
|
||||
|
||||
public function shouldAddAppliedFixers(): bool
|
||||
{
|
||||
return $this->addAppliedFixers;
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
use Symfony\Component\Finder\Finder as SymfonyFinder;
|
||||
|
||||
/**
|
||||
* @author Boris Gorbylev <ekho@ekho.name>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ReporterFactory
|
||||
{
|
||||
/**
|
||||
* @var array<string, ReporterInterface>
|
||||
*/
|
||||
private array $reporters = [];
|
||||
|
||||
public function registerBuiltInReporters(): self
|
||||
{
|
||||
/** @var null|list<string> $builtInReporters */
|
||||
static $builtInReporters;
|
||||
|
||||
if (null === $builtInReporters) {
|
||||
$builtInReporters = [];
|
||||
|
||||
foreach (SymfonyFinder::create()->files()->name('*Reporter.php')->in(__DIR__) as $file) {
|
||||
$relativeNamespace = $file->getRelativePath();
|
||||
$builtInReporters[] = sprintf(
|
||||
'%s\\%s%s',
|
||||
__NAMESPACE__,
|
||||
$relativeNamespace ? $relativeNamespace.'\\' : '',
|
||||
$file->getBasename('.php')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($builtInReporters as $reporterClass) {
|
||||
$this->registerReporter(new $reporterClass());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function registerReporter(ReporterInterface $reporter): self
|
||||
{
|
||||
$format = $reporter->getFormat();
|
||||
|
||||
if (isset($this->reporters[$format])) {
|
||||
throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format));
|
||||
}
|
||||
|
||||
$this->reporters[$format] = $reporter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFormats(): array
|
||||
{
|
||||
$formats = array_keys($this->reporters);
|
||||
sort($formats);
|
||||
|
||||
return $formats;
|
||||
}
|
||||
|
||||
public function getReporter(string $format): ReporterInterface
|
||||
{
|
||||
if (!isset($this->reporters[$format])) {
|
||||
throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format));
|
||||
}
|
||||
|
||||
return $this->reporters[$format];
|
||||
}
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
/**
|
||||
* @author Boris Gorbylev <ekho@ekho.name>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface ReporterInterface
|
||||
{
|
||||
public function getFormat(): string;
|
||||
|
||||
/**
|
||||
* Process changed files array. Returns generated report.
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string;
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
use PhpCsFixer\Differ\DiffConsoleFormatter;
|
||||
|
||||
/**
|
||||
* @author Boris Gorbylev <ekho@ekho.name>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TextReporter implements ReporterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormat(): string
|
||||
{
|
||||
return 'txt';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string
|
||||
{
|
||||
$output = '';
|
||||
|
||||
$i = 0;
|
||||
foreach ($reportSummary->getChanged() as $file => $fixResult) {
|
||||
++$i;
|
||||
$output .= sprintf('%4d) %s', $i, $file);
|
||||
|
||||
if ($reportSummary->shouldAddAppliedFixers()) {
|
||||
$output .= $this->getAppliedFixers(
|
||||
$reportSummary->isDecoratedOutput(),
|
||||
$fixResult['appliedFixers'],
|
||||
);
|
||||
}
|
||||
|
||||
$output .= $this->getDiff($reportSummary->isDecoratedOutput(), $fixResult['diff']);
|
||||
$output .= PHP_EOL;
|
||||
}
|
||||
|
||||
return $output.$this->getFooter($reportSummary->getTime(), $reportSummary->getMemory(), $reportSummary->isDryRun());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $appliedFixers
|
||||
*/
|
||||
private function getAppliedFixers(bool $isDecoratedOutput, array $appliedFixers): string
|
||||
{
|
||||
return sprintf(
|
||||
$isDecoratedOutput ? ' (<comment>%s</comment>)' : ' (%s)',
|
||||
implode(', ', $appliedFixers)
|
||||
);
|
||||
}
|
||||
|
||||
private function getDiff(bool $isDecoratedOutput, string $diff): string
|
||||
{
|
||||
if ('' === $diff) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$diffFormatter = new DiffConsoleFormatter($isDecoratedOutput, sprintf(
|
||||
'<comment> ---------- begin diff ----------</comment>%s%%s%s<comment> ----------- end diff -----------</comment>',
|
||||
PHP_EOL,
|
||||
PHP_EOL
|
||||
));
|
||||
|
||||
return PHP_EOL.$diffFormatter->format($diff).PHP_EOL;
|
||||
}
|
||||
|
||||
private function getFooter(int $time, int $memory, bool $isDryRun): string
|
||||
{
|
||||
if (0 === $time || 0 === $memory) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return PHP_EOL.sprintf(
|
||||
'%s all files in %.3f seconds, %.3f MB memory used'.PHP_EOL,
|
||||
$isDryRun ? 'Checked' : 'Fixed',
|
||||
$time / 1000,
|
||||
$memory / 1024 / 1024
|
||||
);
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?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\Console\Report\FixReport;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
|
||||
/**
|
||||
* @author Boris Gorbylev <ekho@ekho.name>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class XmlReporter implements ReporterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormat(): string
|
||||
{
|
||||
return 'xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string
|
||||
{
|
||||
if (!\extension_loaded('dom')) {
|
||||
throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!');
|
||||
}
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
// new nodes should be added to this or existing children
|
||||
$root = $dom->createElement('report');
|
||||
$dom->appendChild($root);
|
||||
|
||||
$filesXML = $dom->createElement('files');
|
||||
$root->appendChild($filesXML);
|
||||
|
||||
$i = 1;
|
||||
foreach ($reportSummary->getChanged() as $file => $fixResult) {
|
||||
$fileXML = $dom->createElement('file');
|
||||
$fileXML->setAttribute('id', (string) $i++);
|
||||
$fileXML->setAttribute('name', $file);
|
||||
$filesXML->appendChild($fileXML);
|
||||
|
||||
if ($reportSummary->shouldAddAppliedFixers()) {
|
||||
$fileXML->appendChild(
|
||||
$this->createAppliedFixersElement($dom, $fixResult['appliedFixers']),
|
||||
);
|
||||
}
|
||||
|
||||
if ('' !== $fixResult['diff']) {
|
||||
$fileXML->appendChild($this->createDiffElement($dom, $fixResult['diff']));
|
||||
}
|
||||
}
|
||||
|
||||
if (0 !== $reportSummary->getTime()) {
|
||||
$root->appendChild($this->createTimeElement($reportSummary->getTime(), $dom));
|
||||
}
|
||||
|
||||
if (0 !== $reportSummary->getMemory()) {
|
||||
$root->appendChild($this->createMemoryElement($reportSummary->getMemory(), $dom));
|
||||
}
|
||||
|
||||
$dom->formatOutput = true;
|
||||
|
||||
return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $appliedFixers
|
||||
*/
|
||||
private function createAppliedFixersElement(\DOMDocument $dom, array $appliedFixers): \DOMElement
|
||||
{
|
||||
$appliedFixersXML = $dom->createElement('applied_fixers');
|
||||
|
||||
foreach ($appliedFixers as $appliedFixer) {
|
||||
$appliedFixerXML = $dom->createElement('applied_fixer');
|
||||
$appliedFixerXML->setAttribute('name', $appliedFixer);
|
||||
$appliedFixersXML->appendChild($appliedFixerXML);
|
||||
}
|
||||
|
||||
return $appliedFixersXML;
|
||||
}
|
||||
|
||||
private function createDiffElement(\DOMDocument $dom, string $diff): \DOMElement
|
||||
{
|
||||
$diffXML = $dom->createElement('diff');
|
||||
$diffXML->appendChild($dom->createCDATASection($diff));
|
||||
|
||||
return $diffXML;
|
||||
}
|
||||
|
||||
private function createTimeElement(float $time, \DOMDocument $dom): \DOMElement
|
||||
{
|
||||
$time = round($time / 1000, 3);
|
||||
|
||||
$timeXML = $dom->createElement('time');
|
||||
$timeXML->setAttribute('unit', 's');
|
||||
$timeTotalXML = $dom->createElement('total');
|
||||
$timeTotalXML->setAttribute('value', (string) $time);
|
||||
$timeXML->appendChild($timeTotalXML);
|
||||
|
||||
return $timeXML;
|
||||
}
|
||||
|
||||
private function createMemoryElement(float $memory, \DOMDocument $dom): \DOMElement
|
||||
{
|
||||
$memory = round($memory / 1024 / 1024, 3);
|
||||
|
||||
$memoryXML = $dom->createElement('memory');
|
||||
$memoryXML->setAttribute('value', (string) $memory);
|
||||
$memoryXML->setAttribute('unit', 'MB');
|
||||
|
||||
return $memoryXML;
|
||||
}
|
||||
}
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
<?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\Console\Report\ListSetsReport;
|
||||
|
||||
use PhpCsFixer\RuleSet\RuleSetDescriptionInterface;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class JsonReporter implements ReporterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormat(): string
|
||||
{
|
||||
return 'json';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string
|
||||
{
|
||||
$sets = $reportSummary->getSets();
|
||||
|
||||
usort($sets, static function (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b): int {
|
||||
return strcmp($a->getName(), $b->getName());
|
||||
});
|
||||
|
||||
$json = ['sets' => []];
|
||||
|
||||
foreach ($sets as $set) {
|
||||
$setName = $set->getName();
|
||||
$json['sets'][$setName] = [
|
||||
'description' => $set->getDescription(),
|
||||
'isRisky' => $set->isRisky(),
|
||||
'name' => $setName,
|
||||
];
|
||||
}
|
||||
|
||||
return json_encode($json, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
<?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\Console\Report\ListSetsReport;
|
||||
|
||||
use PhpCsFixer\RuleSet\RuleSetDescriptionInterface;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ReportSummary
|
||||
{
|
||||
/**
|
||||
* @var list<RuleSetDescriptionInterface>
|
||||
*/
|
||||
private array $sets;
|
||||
|
||||
/**
|
||||
* @param list<RuleSetDescriptionInterface> $sets
|
||||
*/
|
||||
public function __construct(array $sets)
|
||||
{
|
||||
$this->sets = $sets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<RuleSetDescriptionInterface>
|
||||
*/
|
||||
public function getSets(): array
|
||||
{
|
||||
return $this->sets;
|
||||
}
|
||||
}
|
||||
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
<?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\Console\Report\ListSetsReport;
|
||||
|
||||
use Symfony\Component\Finder\Finder as SymfonyFinder;
|
||||
|
||||
/**
|
||||
* @author Boris Gorbylev <ekho@ekho.name>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ReporterFactory
|
||||
{
|
||||
/**
|
||||
* @var array<string, ReporterInterface>
|
||||
*/
|
||||
private array $reporters = [];
|
||||
|
||||
public function registerBuiltInReporters(): self
|
||||
{
|
||||
/** @var null|list<string> $builtInReporters */
|
||||
static $builtInReporters;
|
||||
|
||||
if (null === $builtInReporters) {
|
||||
$builtInReporters = [];
|
||||
|
||||
foreach (SymfonyFinder::create()->files()->name('*Reporter.php')->in(__DIR__) as $file) {
|
||||
$relativeNamespace = $file->getRelativePath();
|
||||
$builtInReporters[] = sprintf(
|
||||
'%s\\%s%s',
|
||||
__NAMESPACE__,
|
||||
$relativeNamespace ? $relativeNamespace.'\\' : '',
|
||||
$file->getBasename('.php')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($builtInReporters as $reporterClass) {
|
||||
$this->registerReporter(new $reporterClass());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function registerReporter(ReporterInterface $reporter): self
|
||||
{
|
||||
$format = $reporter->getFormat();
|
||||
|
||||
if (isset($this->reporters[$format])) {
|
||||
throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format));
|
||||
}
|
||||
|
||||
$this->reporters[$format] = $reporter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFormats(): array
|
||||
{
|
||||
$formats = array_keys($this->reporters);
|
||||
sort($formats);
|
||||
|
||||
return $formats;
|
||||
}
|
||||
|
||||
public function getReporter(string $format): ReporterInterface
|
||||
{
|
||||
if (!isset($this->reporters[$format])) {
|
||||
throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format));
|
||||
}
|
||||
|
||||
return $this->reporters[$format];
|
||||
}
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
<?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\Console\Report\ListSetsReport;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface ReporterInterface
|
||||
{
|
||||
public function getFormat(): string;
|
||||
|
||||
/**
|
||||
* Process changed files array. Returns generated report.
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string;
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
<?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\Console\Report\ListSetsReport;
|
||||
|
||||
use PhpCsFixer\RuleSet\RuleSetDescriptionInterface;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TextReporter implements ReporterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormat(): string
|
||||
{
|
||||
return 'txt';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(ReportSummary $reportSummary): string
|
||||
{
|
||||
$sets = $reportSummary->getSets();
|
||||
|
||||
usort($sets, static function (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b): int {
|
||||
return strcmp($a->getName(), $b->getName());
|
||||
});
|
||||
|
||||
$output = '';
|
||||
|
||||
foreach ($sets as $i => $set) {
|
||||
$output .= sprintf('%2d) %s', $i + 1, $set->getName()).PHP_EOL.' '.$set->getDescription().PHP_EOL;
|
||||
|
||||
if ($set->isRisky()) {
|
||||
$output .= ' Set contains risky rules.'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?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\Console\SelfUpdate;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class GithubClient implements GithubClientInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTags(): array
|
||||
{
|
||||
$url = 'https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/tags';
|
||||
|
||||
$result = @file_get_contents(
|
||||
$url,
|
||||
false,
|
||||
stream_context_create([
|
||||
'http' => [
|
||||
'header' => 'User-Agent: PHP-CS-Fixer/PHP-CS-Fixer',
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
if (false === $result) {
|
||||
throw new \RuntimeException(sprintf('Failed to load tags at "%s".', $url));
|
||||
}
|
||||
|
||||
$result = json_decode($result, true);
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Failed to read response from "%s" as JSON: %s.',
|
||||
$url,
|
||||
json_last_error_msg()
|
||||
));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?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\Console\SelfUpdate;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface GithubClientInterface
|
||||
{
|
||||
/**
|
||||
* @return list<array{
|
||||
* name: string,
|
||||
* zipball_url: string,
|
||||
* tarball_url: string,
|
||||
* commit: array{sha: string, url: string},
|
||||
* }>
|
||||
*/
|
||||
public function getTags(): array;
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<?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\Console\SelfUpdate;
|
||||
|
||||
use Composer\Semver\Comparator;
|
||||
use Composer\Semver\Semver;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class NewVersionChecker implements NewVersionCheckerInterface
|
||||
{
|
||||
private GithubClientInterface $githubClient;
|
||||
|
||||
private VersionParser $versionParser;
|
||||
|
||||
/**
|
||||
* @var null|string[]
|
||||
*/
|
||||
private $availableVersions;
|
||||
|
||||
public function __construct(GithubClientInterface $githubClient)
|
||||
{
|
||||
$this->githubClient = $githubClient;
|
||||
$this->versionParser = new VersionParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLatestVersion(): string
|
||||
{
|
||||
$this->retrieveAvailableVersions();
|
||||
|
||||
return $this->availableVersions[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLatestVersionOfMajor(int $majorVersion): ?string
|
||||
{
|
||||
$this->retrieveAvailableVersions();
|
||||
|
||||
$semverConstraint = '^'.$majorVersion;
|
||||
|
||||
foreach ($this->availableVersions as $availableVersion) {
|
||||
if (Semver::satisfies($availableVersion, $semverConstraint)) {
|
||||
return $availableVersion;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compareVersions(string $versionA, string $versionB): int
|
||||
{
|
||||
$versionA = $this->versionParser->normalize($versionA);
|
||||
$versionB = $this->versionParser->normalize($versionB);
|
||||
|
||||
if (Comparator::lessThan($versionA, $versionB)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Comparator::greaterThan($versionA, $versionB)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function retrieveAvailableVersions(): void
|
||||
{
|
||||
if (null !== $this->availableVersions) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->githubClient->getTags() as $tag) {
|
||||
$version = $tag['name'];
|
||||
|
||||
try {
|
||||
$this->versionParser->normalize($version);
|
||||
|
||||
if ('stable' === VersionParser::parseStability($version)) {
|
||||
$this->availableVersions[] = $version;
|
||||
}
|
||||
} catch (\UnexpectedValueException $exception) {
|
||||
// not a valid version tag
|
||||
}
|
||||
}
|
||||
|
||||
$this->availableVersions = Semver::rsort($this->availableVersions);
|
||||
}
|
||||
}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
<?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\Console\SelfUpdate;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface NewVersionCheckerInterface
|
||||
{
|
||||
/**
|
||||
* Returns the tag of the latest version.
|
||||
*/
|
||||
public function getLatestVersion(): string;
|
||||
|
||||
/**
|
||||
* Returns the tag of the latest minor/patch version of the given major version.
|
||||
*/
|
||||
public function getLatestVersionOfMajor(int $majorVersion): ?string;
|
||||
|
||||
/**
|
||||
* Returns -1, 0, or 1 if the first version is respectively less than,
|
||||
* equal to, or greater than the second.
|
||||
*/
|
||||
public function compareVersions(string $versionA, string $versionB): int;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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\Console;
|
||||
|
||||
use PhpCsFixer\ToolInfo;
|
||||
use PhpCsFixer\ToolInfoInterface;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class WarningsDetector
|
||||
{
|
||||
private ToolInfoInterface $toolInfo;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $warnings = [];
|
||||
|
||||
public function __construct(ToolInfoInterface $toolInfo)
|
||||
{
|
||||
$this->toolInfo = $toolInfo;
|
||||
}
|
||||
|
||||
public function detectOldMajor(): void
|
||||
{
|
||||
// @TODO 3.99 to be activated with new MAJOR release 4.0
|
||||
// $currentMajorVersion = \intval(explode('.', Application::VERSION)[0], 10);
|
||||
// $nextMajorVersion = $currentMajorVersion + 1;
|
||||
// $this->warnings[] = "You are running PHP CS Fixer v{$currentMajorVersion}, which is not maintained anymore. Please update to v{$nextMajorVersion}.";
|
||||
// $this->warnings[] = "You may find an UPGRADE guide at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v{$nextMajorVersion}.0.0/UPGRADE-v{$nextMajorVersion}.md .";
|
||||
}
|
||||
|
||||
public function detectOldVendor(): void
|
||||
{
|
||||
if ($this->toolInfo->isInstalledByComposer()) {
|
||||
$details = $this->toolInfo->getComposerInstallationDetails();
|
||||
if (ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME === $details['name']) {
|
||||
$this->warnings[] = sprintf(
|
||||
'You are running PHP CS Fixer installed with old vendor `%s`. Please update to `%s`.',
|
||||
ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME,
|
||||
ToolInfo::COMPOSER_PACKAGE_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getWarnings(): array
|
||||
{
|
||||
if (0 === \count($this->warnings)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_unique(array_merge(
|
||||
$this->warnings,
|
||||
['If you need help while solving warnings, ask at https://gitter.im/PHP-CS-Fixer, we will help you!']
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user