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