Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.59% covered (success)
94.59%
70 / 74
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
StandardsCommand
94.59% covered (success)
94.59%
70 / 74
75.00% covered (warning)
75.00%
3 / 4
24.09
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%
22 / 22
100.00% covered (success)
100.00%
1 / 1
1
 execute
90.91% covered (success)
90.91%
40 / 44
0.00% covered (danger)
0.00%
0 / 1
16.19
 getProcessLabel
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
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\HasCacheOption;
24use FastForward\DevTools\Console\Input\HasJsonOption;
25use FastForward\DevTools\Path\DevToolsPathResolver;
26use FastForward\DevTools\Path\ManagedWorkspace;
27use FastForward\DevTools\Process\ProcessBuilderInterface;
28use FastForward\DevTools\Process\ProcessQueueInterface;
29use Psr\Log\LoggerInterface;
30use Symfony\Component\Console\Attribute\AsCommand;
31use Symfony\Component\Console\Command\Command;
32use Symfony\Component\Console\Input\InputInterface;
33use Symfony\Component\Console\Input\InputOption;
34use Symfony\Component\Console\Output\BufferedOutput;
35use Symfony\Component\Console\Output\OutputInterface;
36
37/**
38 * Executes the full suite of Fast Forward code standard checks.
39 * This class MUST NOT be modified through inheritance and SHALL streamline code validation workflows.
40 */
41#[AsCommand(
42    name: 'dev-tools:standards',
43    description: 'Runs Fast Forward code standards checks.',
44    aliases: ['standards'],
45)]
46 class StandardsCommand extends Command
47{
48    use HasCacheOption;
49    use HasJsonOption;
50    use LogsCommandResults;
51
52    /**
53     * @param ProcessBuilderInterface $processBuilder
54     * @param ProcessQueueInterface $processQueue
55     * @param LoggerInterface $logger
56     */
57    public function __construct(
58        private  ProcessBuilderInterface $processBuilder,
59        private  ProcessQueueInterface $processQueue,
60        private  LoggerInterface $logger,
61    ) {
62        parent::__construct();
63    }
64
65    /**
66     * Configures constraints and arguments for the collective standard runner.
67     *
68     * @return void
69     */
70    protected function configure(): void
71    {
72        $this->setHelp(
73            'This command runs all Fast Forward code standards checks, including code refactoring, PHPDoc'
74            . ' validation, code style checks, documentation generation, and tests execution.'
75        );
76        $this
77            ->addJsonOption()
78            ->addCacheOption('Whether to enable cache writes in nested cache-aware standards commands.')
79            ->addCacheDirOption(
80                description: 'Base cache directory used for nested cache-aware standards commands.',
81                default: ManagedWorkspace::getCacheDirectory('standards'),
82            )
83            ->addOption(
84                name: 'progress',
85                mode: InputOption::VALUE_NONE,
86                description: 'Whether to enable progress output from nested standards commands.'
87            )
88            ->addOption(
89                name: 'fix',
90                shortcut: 'f',
91                mode: InputOption::VALUE_NONE,
92                description: 'Automatically fix code standards issues.'
93            );
94    }
95
96    /**
97     * @param InputInterface $input
98     * @param OutputInterface $output
99     */
100    protected function execute(InputInterface $input, OutputInterface $output): int
101    {
102        $jsonOutput = $this->isJsonOutput($input);
103        $prettyJsonOutput = $this->isPrettyJsonOutput($input);
104        $progress = ! $jsonOutput && (bool) $input->getOption('progress');
105        $cacheArgument = $this->resolveCacheArgument($input);
106        $cacheDirEnabled = '--no-cache' !== $cacheArgument;
107
108        $commandOutput = $jsonOutput ? new BufferedOutput() : $output;
109        $commands = [];
110        $fix = (bool) $input->getOption('fix');
111
112        $this->logger->info('Running code standards checks...', [
113            'input' => $input,
114        ]);
115
116        foreach (['refactor', 'phpdoc', 'code-style', 'reports'] as $command) {
117            $commands[] = $command;
118            $processBuilder = $this->processBuilder;
119
120            if (! $jsonOutput) {
121                $processBuilder = $processBuilder->withArgument('--ansi');
122            }
123
124            if ($progress) {
125                $processBuilder = $processBuilder->withArgument('--progress');
126            }
127
128            if ('reports' !== $command && $fix) {
129                $processBuilder = $processBuilder->withArgument('--fix');
130            }
131
132            if (\in_array($command, ['phpdoc', 'reports'], true) && null !== $cacheArgument) {
133                $processBuilder = $processBuilder->withArgument($cacheArgument);
134            }
135
136            if (
137                $cacheDirEnabled
138                && \in_array($command, ['phpdoc', 'reports'], true)
139                && null !== $cacheDir = $this->resolveCacheDirArgument($input, $command)
140            ) {
141                $processBuilder = $processBuilder->withArgument('--cache-dir', $cacheDir);
142            }
143
144            if ($jsonOutput) {
145                $processBuilder = $processBuilder->withArgument('--json');
146            }
147
148            if ($prettyJsonOutput) {
149                $processBuilder = $processBuilder->withArgument('--pretty-json');
150            }
151
152            $this->processQueue->add(
153                process: $processBuilder->build(DevToolsPathResolver::getBinaryCommand($command)),
154                label: $this->getProcessLabel($command),
155            );
156        }
157
158        $result = $this->processQueue->run($commandOutput);
159
160        if (self::FAILURE === $result) {
161            return $this->failure('Code standards checks failed.', $input, [
162                'output' => $commandOutput,
163                'commands' => $commands,
164            ]);
165        }
166
167        return $this->success('Code standards checks completed successfully.', $input, [
168            'output' => $commandOutput,
169            'commands' => $commands,
170        ]);
171    }
172
173    /**
174     * Resolves a human-readable process label for a nested standards command.
175     *
176     * @param string $command the nested dev-tools command name
177     *
178     * @return string the process section label
179     */
180    private function getProcessLabel(string $command): string
181    {
182        return match ($command) {
183            'refactor' => 'Refactoring Code with DevTools',
184            'phpdoc' => 'Checking PHPDoc with DevTools',
185            'code-style' => 'Checking Code Style with DevTools',
186            'reports' => 'Generating Reports with DevTools',
187            default => 'Running DevTools Command',
188        };
189    }
190}