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