• 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

97.71
/src/Validator/MismatchValidator.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace MoveElevator\ComposerTranslationValidator\Validator;
6

7
use MoveElevator\ComposerTranslationValidator\FileDetector\FileSet;
8
use MoveElevator\ComposerTranslationValidator\Parser\ParserInterface;
9
use MoveElevator\ComposerTranslationValidator\Parser\XliffParser;
10
use MoveElevator\ComposerTranslationValidator\Parser\YamlParser;
11
use MoveElevator\ComposerTranslationValidator\Result\Issue;
12
use Symfony\Component\Console\Helper\Table;
13
use Symfony\Component\Console\Helper\TableStyle;
14
use Symfony\Component\Console\Output\OutputInterface;
15

16
class MismatchValidator extends AbstractValidator implements ValidatorInterface
17
{
18
    /** @var array<string, array<string>> */
19
    protected array $keyArray = [];
20

21
    public function processFile(ParserInterface $file): array
4✔
22
    {
23
        $keys = $file->extractKeys();
4✔
24

25
        if (!$keys) {
4✔
26
            $this->logger?->error('The source file '.$file->getFileName().' is not valid.');
1✔
27

28
            return [];
1✔
29
        }
30
        foreach ($keys as $key) {
3✔
31
            $value = $file->getContentByKey($key);
3✔
32
            $this->keyArray[$file->getFileName()][$key] = $value ?? null;
3✔
33
        }
34

35
        return [];
3✔
36
    }
37

38
    public function postProcess(): void
2✔
39
    {
40
        $allKeys = [];
2✔
41
        foreach ($this->keyArray as $values) {
2✔
42
            $allKeys[] = array_keys($values);
2✔
43
        }
44
        $allKeys = array_unique(array_merge(...$allKeys));
2✔
45

46
        foreach ($allKeys as $key) {
2✔
47
            $missingInSome = false;
2✔
48
            foreach ($this->keyArray as $keys) {
2✔
49
                if (!array_key_exists($key, $keys)) {
2✔
50
                    $missingInSome = true;
1✔
51
                    break;
1✔
52
                }
53
            }
54
            if ($missingInSome) {
2✔
55
                $result = [
1✔
56
                    'key' => $key,
1✔
57
                    'files' => [],
1✔
58
                ];
1✔
59
                foreach ($this->keyArray as $file => $keys) {
1✔
60
                    $result['files'][] = [
1✔
61
                        'file' => $file,
1✔
62
                        'value' => $keys[$key] ?? null,
1✔
63
                    ];
1✔
64
                }
65
                $this->addIssue(new Issue(
1✔
66
                    '',
1✔
67
                    $result,
1✔
68
                    '',
1✔
69
                    'MismatchValidator'
1✔
70
                ));
1✔
71
            }
72
        }
73
    }
74

75
    public function formatIssueMessage(Issue $issue, string $prefix = ''): string
1✔
76
    {
77
        $details = $issue->getDetails();
1✔
78
        $resultType = $this->resultTypeOnValidationFailure();
1✔
79

80
        $level = $resultType->toString();
1✔
81
        $color = $resultType->toColorString();
1✔
82

83
        $key = $details['key'] ?? 'unknown';
1✔
84
        $files = $details['files'] ?? [];
1✔
85
        $currentFile = basename($issue->getFile());
1✔
86
        $otherFiles = [];
1✔
87
        $currentFileHasValue = false;
1✔
88

89
        foreach ($files as $fileInfo) {
1✔
90
            $fileName = $fileInfo['file'] ?? 'unknown';
1✔
91
            if ($fileName === $currentFile) {
1✔
NEW
92
                $currentFileHasValue = null !== $fileInfo['value'];
×
93
            } else {
94
                $otherFiles[] = $fileName;
1✔
95
            }
96
        }
97

98
        if ($currentFileHasValue) {
1✔
NEW
99
            $action = 'missing from';
×
100
        } else {
101
            $action = 'missing but present in';
1✔
102
        }
103

104
        $otherFilesList = !empty($otherFiles) ? implode('`, `', $otherFiles) : 'other files';
1✔
105

106
        return "- <fg=$color>$level</> {$prefix}translation key `$key` is $action other translation files (`$otherFilesList`)";
1✔
107
    }
108

109
    public function distributeIssuesForDisplay(FileSet $fileSet): array
1✔
110
    {
111
        $distribution = [];
1✔
112

113
        foreach ($this->issues as $issue) {
1✔
114
            $details = $issue->getDetails();
1✔
115
            $files = $details['files'] ?? [];
1✔
116

117
            foreach ($files as $fileInfo) {
1✔
118
                $fileName = $fileInfo['file'] ?? '';
1✔
119
                if (!empty($fileName)) {
1✔
120
                    $basePath = rtrim($fileSet->getPath(), '/');
1✔
121
                    $filePath = $basePath.'/'.$fileName;
1✔
122

123
                    $fileSpecificIssue = new Issue(
1✔
124
                        $filePath,
1✔
125
                        $details,
1✔
126
                        $issue->getParser(),
1✔
127
                        $issue->getValidatorType()
1✔
128
                    );
1✔
129

130
                    if (!isset($distribution[$filePath])) {
1✔
131
                        $distribution[$filePath] = [];
1✔
132
                    }
133

134
                    $distribution[$filePath][] = $fileSpecificIssue;
1✔
135
                }
136
            }
137
        }
138

139
        return $distribution;
1✔
140
    }
141

142
    public function renderDetailedOutput(OutputInterface $output, array $issues): void
1✔
143
    {
144
        if (empty($issues)) {
1✔
NEW
145
            return;
×
146
        }
147

148
        $rows = [];
1✔
149
        $allKeys = [];
1✔
150
        $allFilesData = [];
1✔
151

152
        foreach ($issues as $issue) {
1✔
153
            $details = $issue->getDetails();
1✔
154
            $key = $details['key'] ?? 'unknown';
1✔
155
            $files = $details['files'] ?? [];
1✔
156
            $currentFile = basename($issue->getFile());
1✔
157

158
            if (!in_array($key, $allKeys)) {
1✔
159
                $allKeys[] = $key;
1✔
160
            }
161

162
            foreach ($files as $fileInfo) {
1✔
163
                $fileName = $fileInfo['file'] ?? '';
1✔
164
                $value = $fileInfo['value'];
1✔
165

166
                if (!isset($allFilesData[$key])) {
1✔
167
                    $allFilesData[$key] = [];
1✔
168
                }
169
                $allFilesData[$key][$fileName] = $value;
1✔
170
            }
171
        }
172

173
        $firstIssue = $issues[0];
1✔
174
        $currentFile = basename($firstIssue->getFile());
1✔
175
        $firstDetails = $firstIssue->getDetails();
1✔
176
        $firstFiles = $firstDetails['files'] ?? [];
1✔
177

178
        $fileOrder = [$currentFile];
1✔
179
        foreach ($firstFiles as $fileInfo) {
1✔
180
            $fileName = $fileInfo['file'] ?? '';
1✔
181
            if ($fileName !== $currentFile && !in_array($fileName, $fileOrder, true)) {
1✔
182
                $fileOrder[] = $fileName;
1✔
183
            }
184
        }
185

186
        $header = ['Translation Key', $currentFile];
1✔
187
        foreach ($fileOrder as $fileName) {
1✔
188
            if ($fileName !== $currentFile) {
1✔
189
                $header[] = $fileName;
1✔
190
            }
191
        }
192

193
        foreach ($allKeys as $key) {
1✔
194
            $row = [$key];
1✔
195
            foreach ($fileOrder as $fileName) {
1✔
196
                $value = $allFilesData[$key][$fileName] ?? null;
1✔
197
                $row[] = $value ?? '';
1✔
198
            }
199
            $rows[] = $row;
1✔
200
        }
201

202
        $table = new Table($output);
1✔
203
        $table->setHeaders($header)
1✔
204
            ->setRows($rows)
1✔
205
            ->setStyle(
1✔
206
                (new TableStyle())
1✔
207
                    ->setCellHeaderFormat('%s')
1✔
208
            )
1✔
209
            ->render();
1✔
210
    }
211

212
    /**
213
     * @return class-string<ParserInterface>[]
214
     */
215
    public function supportsParser(): array
1✔
216
    {
217
        return [XliffParser::class, YamlParser::class];
1✔
218
    }
219

220
    protected function resetState(): void
1✔
221
    {
222
        parent::resetState();
1✔
223
        $this->keyArray = [];
1✔
224
    }
225

226
    public function resultTypeOnValidationFailure(): ResultType
1✔
227
    {
228
        return ResultType::WARNING;
1✔
229
    }
230

231
    public function shouldShowDetailedOutput(): bool
1✔
232
    {
233
        return true;
1✔
234
    }
235
}
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