123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- <?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\Tests\Console\Command;
- use PhpCsFixer\AbstractFixer;
- use PhpCsFixer\Console\Application;
- use PhpCsFixer\Console\Command\DescribeCommand;
- use PhpCsFixer\Fixer\ConfigurableFixerInterface;
- use PhpCsFixer\Fixer\DeprecatedFixerInterface;
- use PhpCsFixer\Fixer\FixerInterface;
- use PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer;
- use PhpCsFixer\FixerConfiguration\AliasedFixerOptionBuilder;
- use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
- use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
- use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
- use PhpCsFixer\FixerDefinition\CodeSample;
- use PhpCsFixer\FixerDefinition\CodeSampleInterface;
- use PhpCsFixer\FixerDefinition\FixerDefinition;
- use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
- use PhpCsFixer\FixerDefinition\VersionSpecification;
- use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
- use PhpCsFixer\FixerFactory;
- use PhpCsFixer\Tests\Fixtures\DescribeCommand\DescribeFixtureFixer;
- use PhpCsFixer\Tests\TestCase;
- use PhpCsFixer\Tokenizer\Token;
- use PhpCsFixer\Tokenizer\Tokens;
- use Symfony\Component\Console\Output\OutputInterface;
- use Symfony\Component\Console\Tester\CommandTester;
- /**
- * @internal
- *
- * @group legacy
- *
- * @covers \PhpCsFixer\Console\Command\DescribeCommand
- */
- final class DescribeCommandTest extends TestCase
- {
- /**
- * @dataProvider provideExecuteOutputCases
- */
- public function testExecuteOutput(string $expected, bool $expectedIsRegEx, bool $decorated, FixerInterface $fixer): void
- {
- if ($fixer instanceof DeprecatedFixerInterface) {
- $this->expectDeprecation(\sprintf('Rule "%s" is deprecated. Use "%s" instead.', $fixer->getName(), implode('", "', $fixer->getSuccessorsNames())));
- }
- // @TODO 4.0 Remove these expectations:
- $this->expectDeprecation('Rule set "@PER" is deprecated. Use "@PER-CS" instead.');
- $this->expectDeprecation('Rule set "@PER:risky" is deprecated. Use "@PER-CS:risky" instead.');
- $actual = $this->execute($fixer->getName(), $decorated, $fixer)->getDisplay(true);
- if (true === $expectedIsRegEx) {
- self::assertMatchesRegularExpression($expected, $actual);
- } else {
- self::assertSame($expected, $actual);
- }
- }
- /**
- * @return iterable<string, array{string, bool, bool, FixerInterface}>
- */
- public static function provideExecuteOutputCases(): iterable
- {
- yield 'rule is configurable, risky and deprecated' => [
- "Description of the `Foo/bar` rule.
- DEPRECATED: use `Foo/baz` instead.
- Fixes stuff.
- Replaces bad stuff with good stuff.
- Fixer applying this rule is RISKY.
- Can break stuff.
- Fixer is configurable using following options:
- * deprecated_option (bool): a deprecated option; defaults to false. DEPRECATED: use option `functions` instead.
- * functions (a subset of ['foo', 'test']): list of `function` names to fix; defaults to ['foo', 'test']; DEPRECATED alias: funcs
- Fixing examples:
- * Example #1. Fixing with the default configuration.
- ---------- begin diff ----------
- --- Original
- +++ New
- @@ -1,1 +1,1 @@
- -<?php echo 'bad stuff and bad thing';
- +<?php echo 'good stuff and bad thing';
- "."
- ----------- end diff -----------
- * Example #2. Fixing with configuration: ['functions' => ['foo', 'bar']].
- ---------- begin diff ----------
- --- Original
- +++ New
- @@ -1,1 +1,1 @@
- -<?php echo 'bad stuff and bad thing';
- +<?php echo 'good stuff and good thing';
- ".'
- ----------- end diff -----------
- ',
- false,
- false,
- self::createConfigurableDeprecatedFixerDouble(),
- ];
- yield 'rule is configurable, risky and deprecated [with decoration]' => [
- "\033[34mDescription of the \033[39m\033[32m`Foo/bar`\033[39m\033[34m rule.\033[39m
- \033[37;41mDEPRECATED\033[39;49m: use \033[32m`Foo/baz`\033[39m instead.
- Fixes stuff.
- Replaces bad stuff with good stuff.
- \033[37;41mFixer applying this rule is RISKY.\033[39;49m
- Can break stuff.
- Fixer is configurable using following options:
- * \033[32mdeprecated_option\033[39m (\033[33mbool\033[39m): a deprecated option; defaults to \e[33mfalse\e[39m. \033[37;41mDEPRECATED\033[39;49m: use option \e[32m`functions`\e[39m instead.
- * \033[32mfunctions\033[39m (a subset of \e[33m['foo', 'test']\e[39m): list of \033[32m`function`\033[39m names to fix; defaults to \033[33m['foo', 'test']\033[39m; \e[37;41mDEPRECATED\e[39;49m alias: \033[33mfuncs\033[39m
- Fixing examples:
- * Example #1. Fixing with the \033[33mdefault\033[39m configuration.
- \033[33m ---------- begin diff ----------\033[39m
- \033[31m--- Original\033[39m
- \033[32m+++ New\033[39m
- \033[36m@@ -1,1 +1,1 @@\033[39m
- \033[31m-<?php echo 'bad stuff and bad thing';\033[39m
- \033[32m+<?php echo 'good stuff and bad thing';\033[39m
- "."
- \033[33m ----------- end diff -----------\033[39m
- * Example #2. Fixing with configuration: \033[33m['functions' => ['foo', 'bar']]\033[39m.
- \033[33m ---------- begin diff ----------\033[39m
- \033[31m--- Original\033[39m
- \033[32m+++ New\033[39m
- \033[36m@@ -1,1 +1,1 @@\033[39m
- \033[31m-<?php echo 'bad stuff and bad thing';\033[39m
- \033[32m+<?php echo 'good stuff and good thing';\033[39m
- "."
- \033[33m ----------- end diff -----------\033[39m
- ",
- false,
- true,
- self::createConfigurableDeprecatedFixerDouble(),
- ];
- yield 'rule without code samples' => [
- 'Description of the `Foo/samples` rule.
- Summary of the rule.
- Description of the rule.
- Fixing examples are not available for this rule.
- ',
- false,
- false,
- self::createFixerWithSamplesDouble([]),
- ];
- yield 'rule with code samples' => [
- "Description of the `Foo/samples` rule.
- Summary of the rule.
- Description of the rule.
- Fixing examples:
- * Example #1.
- ---------- begin diff ----------
- --- Original
- +++ New
- @@ -1,1 +1,1 @@
- -<?php echo 'BEFORE';
- +<?php echo 'AFTER';
- "."
- ----------- end diff -----------
- * Example #2.
- ---------- begin diff ----------
- --- Original
- +++ New
- @@ -1,1 +1,1 @@
- -<?php echo 'BEFORE'.'-B';
- +<?php echo 'AFTER'.'-B';
- ".'
- ----------- end diff -----------
- ',
- false,
- false,
- self::createFixerWithSamplesDouble([
- new CodeSample(
- "<?php echo 'BEFORE';".PHP_EOL,
- ),
- new CodeSample(
- "<?php echo 'BEFORE'.'-B';".PHP_EOL,
- ),
- ]),
- ];
- yield 'rule with code samples (one with matching PHP version, one NOT)' => [
- "Description of the `Foo/samples` rule.
- Summary of the rule.
- Description of the rule.
- Fixing examples:
- * Example #1.
- ---------- begin diff ----------
- --- Original
- +++ New
- @@ -1,1 +1,1 @@
- -<?php echo 'BEFORE';
- +<?php echo 'AFTER';
- ".'
- ----------- end diff -----------
- ',
- false,
- false,
- self::createFixerWithSamplesDouble([
- new CodeSample(
- "<?php echo 'BEFORE';".PHP_EOL,
- ),
- new VersionSpecificCodeSample(
- "<?php echo 'BEFORE'.'-B';".PHP_EOL,
- new VersionSpecification(20_00_00)
- ),
- ]),
- ];
- yield 'rule with code samples (all with NOT matching PHP version)' => [
- 'Description of the `Foo/samples` rule.
- Summary of the rule.
- Description of the rule.
- Fixing examples cannot be demonstrated on the current PHP version.
- ',
- false,
- false,
- self::createFixerWithSamplesDouble([
- new VersionSpecificCodeSample(
- "<?php echo 'BEFORE';".PHP_EOL,
- new VersionSpecification(20_00_00)
- ),
- new VersionSpecificCodeSample(
- "<?php echo 'BEFORE'.'-B';".PHP_EOL,
- new VersionSpecification(20_00_00)
- ),
- ]),
- ];
- yield 'rule that is part of ruleset' => [
- '/^Description of the `binary_operator_spaces` rule.
- .*
- ----------- end diff -----------
- '.preg_quote("Fixer is part of the following rule sets:
- * @PER with config: ['default' => 'at_least_single_space']
- * @PER-CS with config: ['default' => 'at_least_single_space']
- * @PER-CS1.0 with config: ['default' => 'at_least_single_space']
- * @PER-CS2.0 with config: ['default' => 'at_least_single_space']
- * @PSR12 with config: ['default' => 'at_least_single_space']
- * @PhpCsFixer with default config
- * @Symfony with default config").'
- $/s',
- true,
- false,
- new BinaryOperatorSpacesFixer(),
- ];
- }
- public function testExecuteStatusCode(): void
- {
- $this->expectDeprecation('Rule "Foo/bar" is deprecated. Use "Foo/baz" instead.');
- // @TODO 4.0 Remove these expectations:
- $this->expectDeprecation('Rule set "@PER" is deprecated. Use "@PER-CS" instead.');
- $this->expectDeprecation('Rule set "@PER:risky" is deprecated. Use "@PER-CS:risky" instead.');
- self::assertSame(0, $this->execute('Foo/bar', false)->getStatusCode());
- }
- public function testExecuteWithUnknownRuleName(): void
- {
- $application = new Application();
- $application->add(new DescribeCommand(new FixerFactory()));
- $command = $application->find('describe');
- $commandTester = new CommandTester($command);
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessageMatches('#^Rule "Foo/bar" not found\.$#');
- $commandTester->execute([
- 'command' => $command->getName(),
- 'name' => 'Foo/bar',
- ]);
- }
- public function testExecuteWithUnknownSetName(): void
- {
- $application = new Application();
- $application->add(new DescribeCommand(new FixerFactory()));
- $command = $application->find('describe');
- $commandTester = new CommandTester($command);
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessageMatches('#^Set "@NoSuchSet" not found\.$#');
- $commandTester->execute([
- 'command' => $command->getName(),
- 'name' => '@NoSuchSet',
- ]);
- }
- public function testExecuteWithoutName(): void
- {
- $application = new Application();
- $application->add(new DescribeCommand(new FixerFactory()));
- $command = $application->find('describe');
- $commandTester = new CommandTester($command);
- $this->expectException(\RuntimeException::class);
- $this->expectExceptionMessageMatches('/^Not enough arguments( \(missing: "name"\))?\.$/');
- $commandTester->execute([
- 'command' => $command->getName(),
- ]);
- }
- public function testGetAlternativeSuggestion(): void
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessageMatches('#^Rule "Foo2/bar" not found\. Did you mean "Foo/bar"\?$#');
- $this->execute('Foo2/bar', false);
- }
- public function testFixerClassNameIsExposedWhenVerbose(): void
- {
- // @TODO 4.0 Remove these expectations:
- $this->expectDeprecation('Rule set "@PER" is deprecated. Use "@PER-CS" instead.');
- $this->expectDeprecation('Rule set "@PER:risky" is deprecated. Use "@PER-CS:risky" instead.');
- $fixer = new class implements FixerInterface {
- public function isCandidate(Tokens $tokens): bool
- {
- throw new \LogicException('Not implemented.');
- }
- public function isRisky(): bool
- {
- return true;
- }
- public function fix(\SplFileInfo $file, Tokens $tokens): void
- {
- throw new \LogicException('Not implemented.');
- }
- public function getDefinition(): FixerDefinition
- {
- return new FixerDefinition('Fixes stuff.', []);
- }
- public function getName(): string
- {
- return 'Foo/bar_baz';
- }
- public function getPriority(): int
- {
- return 0;
- }
- public function supports(\SplFileInfo $file): bool
- {
- throw new \LogicException('Not implemented.');
- }
- };
- $fixerFactory = new FixerFactory();
- $fixerFactory->registerFixer($fixer, true);
- $application = new Application();
- $application->add(new DescribeCommand($fixerFactory));
- $command = $application->find('describe');
- $commandTester = new CommandTester($command);
- $commandTester->execute(
- [
- 'command' => $command->getName(),
- 'name' => 'Foo/bar_baz',
- ],
- [
- 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
- ]
- );
- self::assertStringContainsString(str_replace("\0", '\\', \get_class($fixer)), $commandTester->getDisplay(true));
- }
- public function testCommandDescribesCustomFixer(): void
- {
- // @TODO 4.0 Remove these expectations:
- $this->expectDeprecation('Rule set "@PER" is deprecated. Use "@PER-CS" instead.');
- $this->expectDeprecation('Rule set "@PER:risky" is deprecated. Use "@PER-CS:risky" instead.');
- $application = new Application();
- $application->add(new DescribeCommand());
- $command = $application->find('describe');
- $commandTester = new CommandTester($command);
- $commandTester->execute([
- 'command' => $command->getName(),
- 'name' => (new DescribeFixtureFixer())->getName(),
- '--config' => __DIR__.'/../../Fixtures/DescribeCommand/.php-cs-fixer.fixture.php',
- ]);
- $expected =
- "Description of the `Vendor/describe_fixture` rule.
- Fixture for describe command.
- Fixing examples:
- * Example #1.
- ---------- begin diff ----------
- --- Original
- +++ New
- @@ -1,2 +1,2 @@
- <?php
- -echo 'describe fixture';
- +echo 'fixture for describe';
- ".'
- ----------- end diff -----------
- ';
- self::assertSame($expected, $commandTester->getDisplay(true));
- self::assertSame(0, $commandTester->getStatusCode());
- }
- /**
- * @param list<CodeSampleInterface> $samples
- */
- private static function createFixerWithSamplesDouble(array $samples): FixerInterface
- {
- return new class($samples) extends AbstractFixer {
- /**
- * @var list<CodeSampleInterface>
- */
- private $samples;
- /**
- * @param list<CodeSampleInterface> $samples
- */
- public function __construct(
- array $samples
- ) {
- parent::__construct();
- $this->samples = $samples;
- }
- public function getName(): string
- {
- return 'Foo/samples';
- }
- public function getDefinition(): FixerDefinitionInterface
- {
- return new FixerDefinition(
- 'Summary of the rule.',
- $this->samples,
- 'Description of the rule.',
- null,
- );
- }
- public function isCandidate(Tokens $tokens): bool
- {
- return true;
- }
- public function applyFix(\SplFileInfo $file, Tokens $tokens): void
- {
- $tokens[3] = new Token([
- $tokens[3]->getId(),
- "'AFTER'",
- ]);
- }
- };
- }
- private static function createConfigurableDeprecatedFixerDouble(): FixerInterface
- {
- return new class implements ConfigurableFixerInterface, DeprecatedFixerInterface {
- /** @var array<string, mixed> */
- private array $configuration;
- public function configure(array $configuration): void
- {
- $this->configuration = $configuration;
- }
- public function getConfigurationDefinition(): FixerConfigurationResolver
- {
- $functionNames = ['foo', 'test'];
- return new FixerConfigurationResolver([
- (new AliasedFixerOptionBuilder(new FixerOptionBuilder('functions', 'List of `function` names to fix.'), 'funcs'))
- ->setAllowedTypes(['string[]'])
- ->setAllowedValues([new AllowedValueSubset($functionNames)])
- ->setDefault($functionNames)
- ->getOption(),
- (new FixerOptionBuilder('deprecated_option', 'A deprecated option.'))
- ->setAllowedTypes(['bool'])
- ->setDefault(false)
- ->setDeprecationMessage('Use option `functions` instead.')
- ->getOption(),
- ]);
- }
- public function getSuccessorsNames(): array
- {
- return ['Foo/baz'];
- }
- public function isCandidate(Tokens $tokens): bool
- {
- throw new \LogicException('Not implemented.');
- }
- public function isRisky(): bool
- {
- return true;
- }
- public function fix(\SplFileInfo $file, Tokens $tokens): void
- {
- $tokens[3] = new Token([
- $tokens[3]->getId(),
- [] !== $this->configuration ? '\'good stuff and good thing\'' : '\'good stuff and bad thing\'',
- ]);
- }
- public function getDefinition(): FixerDefinition
- {
- return new FixerDefinition(
- 'Fixes stuff.',
- [
- new CodeSample(
- "<?php echo 'bad stuff and bad thing';\n"
- ),
- new CodeSample(
- "<?php echo 'bad stuff and bad thing';\n",
- ['functions' => ['foo', 'bar']]
- ),
- ],
- 'Replaces bad stuff with good stuff.',
- 'Can break stuff.'
- );
- }
- public function getName(): string
- {
- return 'Foo/bar';
- }
- public function getPriority(): int
- {
- return 0;
- }
- public function supports(\SplFileInfo $file): bool
- {
- throw new \LogicException('Not implemented.');
- }
- };
- }
- private function execute(string $name, bool $decorated, ?FixerInterface $fixer = null): CommandTester
- {
- $fixer ??= self::createConfigurableDeprecatedFixerDouble();
- $fixerClassName = \get_class($fixer);
- $isBuiltIn = str_starts_with($fixerClassName, 'PhpCsFixer') && !str_contains($fixerClassName, '@anon');
- $fixerFactory = new FixerFactory();
- $fixerFactory->registerFixer($fixer, !$isBuiltIn);
- $application = new Application();
- $application->add(new DescribeCommand($fixerFactory));
- $command = $application->find('describe');
- $commandTester = new CommandTester($command);
- $commandTester->execute(
- [
- 'command' => $command->getName(),
- 'name' => $name,
- ],
- [
- 'decorated' => $decorated,
- ]
- );
- return $commandTester;
- }
- }
|