Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.25% covered (success)
98.25%
56 / 57
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
CodeStyleCommand
98.25% covered (success)
98.25%
56 / 57
66.67% covered (warning)
66.67%
2 / 3
10
0.00% covered (danger)
0.00%
0 / 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%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 execute
97.56% covered (success)
97.56%
40 / 41
0.00% covered (danger)
0.00%
0 / 1
8
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\Output\BufferedOutput;
31use Symfony\Component\Console\Input\InputInterface;
32use Symfony\Component\Console\Input\InputOption;
33use Symfony\Component\Console\Output\OutputInterface;
34
35/**
36 * Represents the command responsible for checking and fixing code style issues.
37 * This class MUST NOT be overridden and SHALL rely on external tools like ECS and Composer Normalize.
38 */
39#[AsCommand(
40    name: 'standards:code-style',
41    description: 'Checks and fixes code style issues using EasyCodingStandard and Composer Normalize.',
42    aliases: ['code-style']
43)]
44 class CodeStyleCommand extends Command
45{
46    use HasJsonOption;
47    use LogsCommandResults;
48
49    /**
50     * @var string the default configuration file used for EasyCodingStandard
51     */
52    public const string CONFIG = 'ecs.php';
53
54    /**
55     * Constructs a new command instance responsible for orchestrating code style checks.
56     *
57     * The provided collaborators SHALL be used to locate the ECS configuration,
58     * build process definitions, and execute the resulting process queue. These
59     * dependencies MUST be valid service instances capable of supporting the
60     * command lifecycle expected by this class.
61     *
62     * @param FileLocatorInterface $fileLocator locates the configuration file required by EasyCodingStandard
63     * @param ProcessBuilderInterface $processBuilder builds the process instances used to execute Composer and ECS commands
64     * @param ProcessQueueInterface $processQueue queues and executes the generated processes in the required order
65     */
66    public function __construct(
67        private  FileLocatorInterface $fileLocator,
68        private  ProcessBuilderInterface $processBuilder,
69        private  ProcessQueueInterface $processQueue,
70    ) {
71        parent::__construct();
72    }
73
74    /**
75     * Configures the current command.
76     *
77     * This method MUST define the name, description, help text, and options for the command.
78     * It SHALL register the `--fix` option to allow automatic resolutions of style issues.
79     *
80     * @return void
81     */
82    protected function configure(): void
83    {
84        $this->setHelp(
85            'This command runs EasyCodingStandard and Composer Normalize to check and fix code style issues.'
86        );
87
88        $this->addJsonOption()
89            ->addOption(
90                name: 'progress',
91                mode: InputOption::VALUE_NONE,
92                description: 'Whether to enable progress output from code style tools.',
93            )
94            ->addOption(
95                name: 'fix',
96                shortcut: 'f',
97                mode: InputOption::VALUE_NONE,
98                description: 'Automatically fix code style issues.'
99            );
100    }
101
102    /**
103     * Executes the code style checks and fixes block.
104     *
105     * The method MUST execute `composer update --lock`, `composer normalize`, and ECS using secure processes.
106     * It SHALL return `self::SUCCESS` if all commands succeed, or `self::FAILURE` otherwise.
107     *
108     * @param InputInterface $input the input interface to retrieve options
109     * @param OutputInterface $output the output interface to log messages
110     *
111     * @return int the status code of the command
112     */
113    protected function execute(InputInterface $input, OutputInterface $output): int
114    {
115        $jsonOutput = $this->isJsonOutput($input);
116        $processOutput = $jsonOutput ? new BufferedOutput() : $output;
117
118        $fix = (bool) $input->getOption('fix');
119        $progress = ! $jsonOutput && (bool) $input->getOption('progress');
120
121        $this->log('Running code style checks and fixes...', $input);
122
123        $composerUpdate = $this->processBuilder
124            ->withArgument('--lock')
125            ->withArgument('--quiet')
126            ->build('composer update');
127
128        $composerNormalize = $this->processBuilder
129            ->withArgument('--ansi')
130            ->withArgument($fix ? '--quiet' : '--dry-run')
131            ->build('composer normalize');
132
133        $processBuilder = $this->processBuilder
134            ->withArgument('--ansi')
135            ->withArgument('--config', $this->fileLocator->locate(self::CONFIG));
136
137        if (! $progress) {
138            $processBuilder = $processBuilder->withArgument('--no-progress-bar');
139        }
140
141        if ($jsonOutput) {
142            $processBuilder = $processBuilder->withArgument('--output-format', 'json');
143        }
144
145        if ($fix) {
146            $processBuilder = $processBuilder->withArgument('--fix');
147        }
148
149        $ecs = $processBuilder->build([DevToolsPathResolver::getPreferredToolBinaryPath('ecs')]);
150
151        $this->processQueue->add(process: $composerUpdate, label: 'Refreshing Composer Lock');
152        $this->processQueue->add(
153            process: $composerNormalize,
154            label: 'Normalizing composer.json with Composer Normalize'
155        );
156        $this->processQueue->add(process: $ecs, label: 'Checking Code Style with Easy Coding Standard');
157
158        $result = $this->processQueue->run($processOutput);
159
160        if (self::SUCCESS === $result) {
161            return $this->success('Code style checks completed successfully.', $input, [
162                'fix' => $fix,
163                'config' => self::CONFIG,
164                'output' => $processOutput,
165            ]);
166        }
167
168        return $this->failure('Code style checks failed.', $input, [
169            'fix' => $fix,
170            'config' => self::CONFIG,
171            'output' => $processOutput,
172        ]);
173    }
174}