Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
RefactorCommand
100.00% covered (success)
100.00%
49 / 49
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%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
28 / 28
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 FastForward\DevTools\Console\Input\HasJsonOption;
24use FastForward\DevTools\Path\DevToolsPathResolver;
25use FastForward\DevTools\Process\ProcessBuilderInterface;
26use FastForward\DevTools\Process\ProcessQueueInterface;
27use Symfony\Component\Config\FileLocatorInterface;
28use Symfony\Component\Console\Attribute\AsCommand;
29use Symfony\Component\Console\Command\Command;
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: 'standards:rector',
41    description: 'Runs Rector for code refactoring.',
42    aliases: ['refactor', 'rector']
43)]
44 class RefactorCommand extends Command
45{
46    use HasJsonOption;
47    use LogsCommandResults;
48
49    /**
50     * @var string the default Rector configuration file
51     */
52    public const string CONFIG = 'rector.php';
53
54    /**
55     * Creates a new RefactorCommand instance.
56     *
57     * @param FileLocatorInterface $fileLocator the file locator
58     * @param ProcessBuilderInterface $processBuilder the process builder
59     * @param ProcessQueueInterface $processQueue the process queue
60     */
61    public function __construct(
62        private  FileLocatorInterface $fileLocator,
63        private  ProcessBuilderInterface $processBuilder,
64        private  ProcessQueueInterface $processQueue,
65    ) {
66        parent::__construct();
67    }
68
69    /**
70     * Configures the refactor command options and description.
71     *
72     * This method MUST define the expected `--fix` option. It SHALL configure the command name
73     * and descriptions accurately.
74     *
75     * @return void
76     */
77    protected function configure(): void
78    {
79        $this->setHelp('This command runs Rector to refactor your code.');
80
81        $this->addJsonOption()
82            ->addOption(
83                name: 'progress',
84                mode: InputOption::VALUE_NONE,
85                description: 'Whether to enable progress output from Rector.',
86            )
87            ->addOption(
88                name: 'fix',
89                shortcut: 'f',
90                mode: InputOption::VALUE_NONE,
91                description: 'Automatically fix code refactoring issues.'
92            )
93            ->addOption(
94                name: 'config',
95                shortcut: 'c',
96                mode: InputOption::VALUE_OPTIONAL,
97                description: 'The path to the Rector configuration file.',
98                default: self::CONFIG
99            );
100    }
101
102    /**
103     * Executes the refactoring process securely.
104     *
105     * The method MUST execute Rector securely via `Process`. It SHALL use dry-run mode
106     * unless the `--fix` option is specified. It MUST return `self::SUCCESS` or `self::FAILURE`.
107     *
108     * @param InputInterface $input the input interface to retrieve arguments properly
109     * @param OutputInterface $output the output interface to log outputs
110     *
111     * @return int the status code denoting success or failure
112     */
113    protected function execute(InputInterface $input, OutputInterface $output): int
114    {
115        $jsonOutput = $this->isJsonOutput($input);
116        $processOutput = $jsonOutput ? new BufferedOutput() : $output;
117        $fix = (bool) $input->getOption('fix');
118        $progress = ! $jsonOutput && (bool) $input->getOption('progress');
119
120        $this->log('Running Rector for code refactoring...', $input);
121
122        $processBuilder = $this->processBuilder
123            ->withArgument('--ansi')
124            ->withArgument('--config')
125            ->withArgument($this->fileLocator->locate(self::CONFIG));
126
127        if (! $progress) {
128            $processBuilder = $processBuilder->withArgument('--no-progress-bar');
129        }
130
131        if ($jsonOutput) {
132            $processBuilder = $processBuilder
133                ->withArgument('--output-format', 'json');
134        }
135
136        if (! $fix) {
137            $processBuilder = $processBuilder->withArgument('--dry-run');
138        }
139
140        $this->processQueue->add(
141            process: $processBuilder->build([DevToolsPathResolver::getPreferredToolBinaryPath('rector'), 'process']),
142            label: 'Refactoring Code with Rector',
143        );
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}