Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
RefactorCommand
100.00% covered (success)
100.00%
52 / 52
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%
31 / 31
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\Process\ProcessBuilderInterface;
25use FastForward\DevTools\Process\ProcessQueueInterface;
26use Psr\Log\LoggerInterface;
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     * @param LoggerInterface $logger the output-aware logger
61     */
62    public function __construct(
63        private  FileLocatorInterface $fileLocator,
64        private  ProcessBuilderInterface $processBuilder,
65        private  ProcessQueueInterface $processQueue,
66        private  LoggerInterface $logger,
67    ) {
68        parent::__construct();
69    }
70
71    /**
72     * Configures the refactor command options and description.
73     *
74     * This method MUST define the expected `--fix` option. It SHALL configure the command name
75     * and descriptions accurately.
76     *
77     * @return void
78     */
79    protected function configure(): void
80    {
81        $this->setHelp('This command runs Rector to refactor your code.');
82
83        $this->addJsonOption()
84            ->addOption(
85                name: 'progress',
86                mode: InputOption::VALUE_NONE,
87                description: 'Whether to enable progress output from Rector.',
88            )
89            ->addOption(
90                name: 'fix',
91                shortcut: 'f',
92                mode: InputOption::VALUE_NONE,
93                description: 'Automatically fix code refactoring issues.'
94            )
95            ->addOption(
96                name: 'config',
97                shortcut: 'c',
98                mode: InputOption::VALUE_OPTIONAL,
99                description: 'The path to the Rector configuration file.',
100                default: self::CONFIG
101            );
102    }
103
104    /**
105     * Executes the refactoring process securely.
106     *
107     * The method MUST execute Rector securely via `Process`. It SHALL use dry-run mode
108     * unless the `--fix` option is specified. It MUST return `self::SUCCESS` or `self::FAILURE`.
109     *
110     * @param InputInterface $input the input interface to retrieve arguments properly
111     * @param OutputInterface $output the output interface to log outputs
112     *
113     * @return int the status code denoting success or failure
114     */
115    protected function execute(InputInterface $input, OutputInterface $output): int
116    {
117        $jsonOutput = $this->isJsonOutput($input);
118        $processOutput = $jsonOutput ? new BufferedOutput() : $output;
119        $fix = (bool) $input->getOption('fix');
120        $progress = ! $jsonOutput && (bool) $input->getOption('progress');
121
122        $this->logger->info('Running Rector for code refactoring...', [
123            'input' => $input,
124        ]);
125
126        $processBuilder = $this->processBuilder
127            ->withArgument('process')
128            ->withArgument('--ansi')
129            ->withArgument('--config')
130            ->withArgument($this->fileLocator->locate(self::CONFIG));
131
132        if (! $progress) {
133            $processBuilder = $processBuilder->withArgument('--no-progress-bar');
134        }
135
136        if ($jsonOutput) {
137            $processBuilder = $processBuilder
138                ->withArgument('--output-format', 'json');
139        }
140
141        if (! $fix) {
142            $processBuilder = $processBuilder->withArgument('--dry-run');
143        }
144
145        $this->processQueue->add(
146            process: $processBuilder->build('vendor/bin/rector'),
147            label: 'Refactoring Code with Rector',
148        );
149
150        $result = $this->processQueue->run($processOutput);
151
152        if (self::SUCCESS === $result) {
153            return $this->success('Code refactoring checks completed successfully.', $input, [
154                'output' => $processOutput,
155            ]);
156        }
157
158        return $this->failure('Code refactoring checks failed.', $input, [
159            'output' => $processOutput,
160        ]);
161    }
162}