Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
RefactorCommand
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
3 / 3
9
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 configure
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Console\Command;
21
22use FastForward\DevTools\Console\Command\Traits\LogsCommandResults;
23use Composer\Command\BaseCommand;
24use FastForward\DevTools\Console\Input\HasJsonOption;
25use FastForward\DevTools\Process\ProcessBuilderInterface;
26use FastForward\DevTools\Process\ProcessQueueInterface;
27use Psr\Log\LoggerInterface;
28use Symfony\Component\Config\FileLocatorInterface;
29use Symfony\Component\Console\Attribute\AsCommand;
30use Symfony\Component\Console\Input\InputInterface;
31use Symfony\Component\Console\Input\InputOption;
32use Symfony\Component\Console\Output\BufferedOutput;
33use Symfony\Component\Console\Output\OutputInterface;
34
35/**
36 * Provides functionality to execute automated code refactoring using Rector.
37 * This class MUST NOT be extended and SHALL encapsulate the logic for Rector invocation.
38 */
39#[AsCommand(
40    name: 'refactor',
41    description: 'Runs Rector for code refactoring.',
42    aliases: ['rector'],
43    help: 'This command runs Rector to refactor your code.'
44)]
45 class RefactorCommand extends BaseCommand implements LoggerAwareCommandInterface
46{
47    use HasJsonOption;
48    use LogsCommandResults;
49
50    /**
51     * @var string the default Rector configuration file
52     */
53    public const string CONFIG = 'rector.php';
54
55    /**
56     * Creates a new RefactorCommand instance.
57     *
58     * @param FileLocatorInterface $fileLocator the file locator
59     * @param ProcessBuilderInterface $processBuilder the process builder
60     * @param ProcessQueueInterface $processQueue the process queue
61     * @param LoggerInterface $logger the output-aware logger
62     */
63    public function __construct(
64        private  FileLocatorInterface $fileLocator,
65        private  ProcessBuilderInterface $processBuilder,
66        private  ProcessQueueInterface $processQueue,
67        private  LoggerInterface $logger,
68    ) {
69        parent::__construct();
70    }
71
72    /**
73     * Configures the refactor command options and description.
74     *
75     * This method MUST define the expected `--fix` option. It SHALL configure the command name
76     * and descriptions accurately.
77     *
78     * @return void
79     */
80    protected function configure(): void
81    {
82        $this->addJsonOption()
83            ->addOption(
84                name: 'progress',
85                mode: InputOption::VALUE_NONE,
86                description: 'Whether to enable progress output from Rector.',
87            )
88            ->addOption(
89                name: 'fix',
90                shortcut: 'f',
91                mode: InputOption::VALUE_NONE,
92                description: 'Automatically fix code refactoring issues.'
93            )
94            ->addOption(
95                name: 'config',
96                shortcut: 'c',
97                mode: InputOption::VALUE_OPTIONAL,
98                description: 'The path to the Rector configuration file.',
99                default: self::CONFIG
100            );
101    }
102
103    /**
104     * Executes the refactoring process securely.
105     *
106     * The method MUST execute Rector securely via `Process`. It SHALL use dry-run mode
107     * unless the `--fix` option is specified. It MUST return `self::SUCCESS` or `self::FAILURE`.
108     *
109     * @param InputInterface $input the input interface to retrieve arguments properly
110     * @param OutputInterface $output the output interface to log outputs
111     *
112     * @return int the status code denoting success or failure
113     */
114    protected function execute(InputInterface $input, OutputInterface $output): int
115    {
116        $jsonOutput = $this->isJsonOutput($input);
117        $processOutput = $jsonOutput ? new BufferedOutput() : $output;
118        $fix = (bool) $input->getOption('fix');
119        $progress = ! $jsonOutput && (bool) $input->getOption('progress');
120
121        $this->logger->info('Running Rector for code refactoring...', [
122            'input' => $input,
123        ]);
124
125        $processBuilder = $this->processBuilder
126            ->withArgument('process')
127            ->withArgument('--config')
128            ->withArgument($this->fileLocator->locate(self::CONFIG));
129
130        if (! $progress) {
131            $processBuilder = $processBuilder->withArgument('--no-progress-bar');
132        }
133
134        if ($jsonOutput) {
135            $processBuilder = $processBuilder
136                ->withArgument('--output-format', 'json');
137        }
138
139        if (! $fix) {
140            $processBuilder = $processBuilder->withArgument('--dry-run');
141        }
142
143        $this->processQueue->add($processBuilder->build('vendor/bin/rector'));
144
145        $result = $this->processQueue->run($processOutput);
146
147        if (self::SUCCESS === $result) {
148            return $this->success('Code refactoring checks completed successfully.', $input, [
149                'output' => $processOutput,
150            ]);
151        }
152
153        return $this->failure('Code refactoring checks failed.', $input, [
154            'output' => $processOutput,
155        ]);
156    }
157}