• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

move-elevator / composer-translation-validator / 16142710721

08 Jul 2025 12:05PM UTC coverage: 94.927% (+0.4%) from 94.526%
16142710721

Pull #19

github

jackd248
refactor: remove unused isVerbose parameter from formatIssueMessage method
Pull Request #19: refactor: improve output style

270 of 279 new or added lines in 10 files covered. (96.77%)

3 existing lines in 1 file now uncovered.

842 of 887 relevant lines covered (94.93%)

5.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.06
/src/Result/ValidationResultCliRenderer.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace MoveElevator\ComposerTranslationValidator\Result;
6

7
use MoveElevator\ComposerTranslationValidator\Utility\PathUtility;
8
use MoveElevator\ComposerTranslationValidator\Validator\ResultType;
9
use MoveElevator\ComposerTranslationValidator\Validator\ValidatorInterface;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use Symfony\Component\Console\Style\SymfonyStyle;
13

14
class ValidationResultCliRenderer implements ValidationResultRendererInterface
15
{
16
    private readonly SymfonyStyle $io;
17

18
    public function __construct(
20✔
19
        private readonly OutputInterface $output,
20
        private readonly InputInterface $input,
21
        private readonly bool $dryRun = false,
22
        private readonly bool $strict = false,
23
    ) {
24
        $this->io = new SymfonyStyle($this->input, $this->output);
20✔
25
    }
26

27
    public function render(ValidationResult $validationResult): int
13✔
28
    {
29
        if ($this->output->isVerbose()) {
13✔
30
            $this->renderVerboseOutput($validationResult);
3✔
31
        } else {
32
            $this->renderCompactOutput($validationResult);
11✔
33
        }
34

35
        $this->renderSummary($validationResult->getOverallResult());
13✔
36

37
        return $validationResult->getOverallResult()->resolveErrorToCommandExitCode($this->dryRun, $this->strict);
13✔
38
    }
39

40
    private function renderCompactOutput(ValidationResult $validationResult): void
11✔
41
    {
42
        $validatorPairs = $validationResult->getValidatorFileSetPairs();
11✔
43

44
        if (empty($validatorPairs)) {
11✔
45
            return;
3✔
46
        }
47

48
        $groupedByFile = [];
8✔
49
        foreach ($validatorPairs as $pair) {
8✔
50
            $validator = $pair['validator'];
8✔
51
            $fileSet = $pair['fileSet'];
8✔
52

53
            if (!$validator->hasIssues()) {
8✔
54
                continue;
×
55
            }
56

57
            $distributedIssues = $validator->distributeIssuesForDisplay($fileSet);
8✔
58

59
            foreach ($distributedIssues as $filePath => $issues) {
8✔
60
                if (!isset($groupedByFile[$filePath])) {
5✔
61
                    $groupedByFile[$filePath] = [];
5✔
62
                }
63

64
                foreach ($issues as $issue) {
5✔
65
                    $groupedByFile[$filePath][] = [
5✔
66
                        'validator' => $validator,
5✔
67
                        'issue' => $issue,
5✔
68
                    ];
5✔
69
                }
70
            }
71
        }
72

73
        foreach ($groupedByFile as $filePath => $fileIssues) {
8✔
74
            $relativePath = PathUtility::normalizeFolderPath($filePath);
5✔
75
            $this->io->writeln("<fg=cyan>$relativePath</>");
5✔
76
            $this->io->newLine();
5✔
77

78
            $sortedIssues = $this->sortIssuesBySeverity($fileIssues);
5✔
79

80
            foreach ($sortedIssues as $fileIssue) {
5✔
81
                $validator = $fileIssue['validator'];
5✔
82
                $issue = $fileIssue['issue'];
5✔
83
                $validatorName = $validator->getShortName();
5✔
84

85
                $message = $this->formatIssueMessage($validator, $issue, $validatorName);
5✔
86
                $lines = explode("\n", $message);
5✔
87
                foreach ($lines as $line) {
5✔
88
                    if (!empty(trim($line))) {
5✔
89
                        $this->io->writeln($line);
5✔
90
                    }
91
                }
92
            }
93

94
            $this->io->newLine();
5✔
95
        }
96
    }
97

98
    private function renderVerboseOutput(ValidationResult $validationResult): void
3✔
99
    {
100
        $validatorPairs = $validationResult->getValidatorFileSetPairs();
3✔
101

102
        if (empty($validatorPairs)) {
3✔
103
            return;
1✔
104
        }
105

106
        // Group by file path, then by validator
107
        $groupedByFile = [];
2✔
108
        foreach ($validatorPairs as $pair) {
2✔
109
            $validator = $pair['validator'];
2✔
110
            $fileSet = $pair['fileSet'];
2✔
111

112
            if (!$validator->hasIssues()) {
2✔
NEW
113
                continue;
×
114
            }
115

116
            $distributedIssues = $validator->distributeIssuesForDisplay($fileSet);
2✔
117
            $validatorClass = $validator::class;
2✔
118

119
            foreach ($distributedIssues as $filePath => $issues) {
2✔
120
                if (!isset($groupedByFile[$filePath])) {
2✔
121
                    $groupedByFile[$filePath] = [];
2✔
122
                }
123
                if (!isset($groupedByFile[$filePath][$validatorClass])) {
2✔
124
                    $groupedByFile[$filePath][$validatorClass] = [
2✔
125
                        'validator' => $validator,
2✔
126
                        'issues' => [],
2✔
127
                    ];
2✔
128
                }
129

130
                foreach ($issues as $issue) {
2✔
131
                    $groupedByFile[$filePath][$validatorClass]['issues'][] = $issue;
2✔
132
                }
133
            }
134
        }
135

136
        foreach ($groupedByFile as $filePath => $validatorGroups) {
2✔
137
            $relativePath = PathUtility::normalizeFolderPath($filePath);
2✔
138
            $this->io->writeln("<fg=cyan>$relativePath</>");
2✔
139
            $this->io->newLine();
2✔
140

141
            $sortedValidatorGroups = $this->sortValidatorGroupsBySeverity($validatorGroups);
2✔
142

143
            foreach ($sortedValidatorGroups as $validatorClass => $data) {
2✔
144
                $validator = $data['validator'];
2✔
145
                $issues = $data['issues'];
2✔
146
                $validatorName = $validator->getShortName();
2✔
147

148
                $this->io->writeln("  <options=bold>$validatorName</>");
2✔
149

150
                foreach ($issues as $issue) {
2✔
151
                    $message = $this->formatIssueMessage($validator, $issue, '', true);
2✔
152
                    $lines = explode("\n", $message);
2✔
153
                    foreach ($lines as $line) {
2✔
154
                        if (!empty(trim($line))) {
2✔
155
                            $this->io->writeln("    $line");
2✔
156
                        }
157
                    }
158
                }
159

160
                if ($validator->shouldShowDetailedOutput()) {
2✔
NEW
161
                    $this->io->newLine();
×
NEW
162
                    $validator->renderDetailedOutput($this->output, $issues);
×
163
                }
164

165
                $this->io->newLine();
2✔
166
            }
167
        }
168
    }
169

170
    /**
171
     * @param array<array{validator: ValidatorInterface, issue: Issue}> $fileIssues
172
     *
173
     * @return array<array{validator: ValidatorInterface, issue: Issue}>
174
     */
175
    private function sortIssuesBySeverity(array $fileIssues): array
6✔
176
    {
177
        usort($fileIssues, function ($a, $b) {
6✔
178
            $severityA = $this->getIssueSeverity($a['validator']);
1✔
179
            $severityB = $this->getIssueSeverity($b['validator']);
1✔
180

181
            return $severityA <=> $severityB;
1✔
182
        });
6✔
183

184
        return $fileIssues;
6✔
185
    }
186

187
    /**
188
     * @param array<string, array{validator: ValidatorInterface, issues: array<Issue>}> $validatorGroups
189
     *
190
     * @return array<string, array{validator: ValidatorInterface, issues: array<Issue>}>
191
     */
192
    private function sortValidatorGroupsBySeverity(array $validatorGroups): array
3✔
193
    {
194
        uksort($validatorGroups, function ($validatorClassA, $validatorClassB) {
3✔
195
            $severityA = $this->getValidatorSeverity($validatorClassA);
1✔
196
            $severityB = $this->getValidatorSeverity($validatorClassB);
1✔
197

198
            return $severityA <=> $severityB;
1✔
199
        });
3✔
200

201
        return $validatorGroups;
3✔
202
    }
203

204
    private function getIssueSeverity(ValidatorInterface $validator): int
1✔
205
    {
206
        return $this->getValidatorSeverity($validator::class);
1✔
207
    }
208

209
    private function getValidatorSeverity(string $validatorClass): int
3✔
210
    {
211
        if (str_contains($validatorClass, 'SchemaValidator')) {
3✔
212
            return 1;
1✔
213
        }
214

215
        try {
216
            $reflection = new \ReflectionClass($validatorClass);
3✔
217
            if ($reflection->isInstantiable()) {
1✔
218
                $validator = $reflection->newInstance();
1✔
219
                if ($validator instanceof ValidatorInterface) {
1✔
220
                    $resultType = $validator->resultTypeOnValidationFailure();
1✔
221

NEW
222
                    return ResultType::ERROR === $resultType ? 1 : 2;
×
223
                }
224
            }
225
        } catch (\Throwable) {
3✔
226
        }
227

228
        return 1;
3✔
229
    }
230

231
    private function formatIssueMessage(ValidatorInterface $validator, Issue $issue, string $validatorName = '', bool $isVerbose = false): string
7✔
232
    {
233
        $prefix = $isVerbose ? '' : "($validatorName) ";
7✔
234

235
        return $validator->formatIssueMessage($issue, $prefix);
7✔
236
    }
237

238
    private function renderSummary(ResultType $resultType): void
13✔
239
    {
240
        if ($resultType->notFullySuccessful()) {
13✔
241
            $this->io->newLine();
10✔
242
            $message = $this->dryRun
10✔
243
                ? 'Language validation failed and completed in dry-run mode.'
1✔
244
                : 'Language validation failed.';
9✔
245

246
            if (!$this->output->isVerbose()) {
10✔
247
                $message .= ' See more details with the `-v` verbose option.';
9✔
248
            }
249

250
            $this->io->{$this->dryRun || ResultType::WARNING === $resultType ? 'warning' : 'error'}($message);
10✔
251
        } else {
252
            $message = 'Language validation succeeded.';
3✔
253
            $this->output->isVerbose()
3✔
254
                ? $this->io->success($message)
1✔
255
                : $this->output->writeln('<fg=green>'.$message.'</>');
2✔
256
        }
257
    }
258
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc