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

move-elevator / composer-translation-validator / 18560103885

16 Oct 2025 11:42AM UTC coverage: 95.519%. Remained the same
18560103885

Pull #73

github

jackd248
refactor: remove unnecessary type hint from MismatchValidator
Pull Request #73: build: add php-cs-fixer-preset

206 of 210 new or added lines in 16 files covered. (98.1%)

91 existing lines in 20 files now uncovered.

2345 of 2455 relevant lines covered (95.52%)

7.73 hits per line

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

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

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the "composer-translation-validator" Composer plugin.
7
 *
8
 * (c) 2025 Konrad Michalik <km@move-elevator.de>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13

14
namespace MoveElevator\ComposerTranslationValidator\Validator;
15

16
use MoveElevator\ComposerTranslationValidator\FileDetector\FileSet;
17
use MoveElevator\ComposerTranslationValidator\Parser\{JsonParser, ParserInterface, PhpParser, XliffParser, YamlParser};
18
use MoveElevator\ComposerTranslationValidator\Result\Issue;
19
use Symfony\Component\Console\Helper\{Table, TableStyle};
20
use Symfony\Component\Console\Output\OutputInterface;
21

22
use function array_key_exists;
23
use function in_array;
24

25
/**
26
 * MismatchValidator.
27
 *
28
 * @author Konrad Michalik <km@move-elevator.de>
29
 * @license GPL-3.0-or-later
30
 */
31
class MismatchValidator extends AbstractValidator implements ValidatorInterface
32
{
33
    /**
34
     * @var array<string, array<string, string|null>>
35
     */
36
    protected array $keyArray = [];
37

38
    public function processFile(ParserInterface $file): array
4✔
39
    {
40
        $keys = $file->extractKeys();
4✔
41

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

45
            return [];
1✔
46
        }
47
        foreach ($keys as $key) {
3✔
48
            $value = $file->getContentByKey($key);
3✔
49
            $fileKey = !empty($this->currentFilePath) ? $this->currentFilePath : $file->getFileName();
3✔
50
            $this->keyArray[$fileKey][$key] = $value ?? null;
3✔
51
        }
52

53
        return [];
3✔
54
    }
55

56
    public function postProcess(): void
2✔
57
    {
58
        $allKeys = [];
2✔
59
        foreach ($this->keyArray as $values) {
2✔
60
            $allKeys[] = array_keys($values);
2✔
61
        }
62
        $allKeys = array_unique(array_merge(...$allKeys));
2✔
63

64
        foreach ($allKeys as $key) {
2✔
65
            $missingInSome = false;
2✔
66
            foreach ($this->keyArray as $keys) {
2✔
67
                if (!array_key_exists($key, $keys)) {
2✔
68
                    $missingInSome = true;
1✔
69
                    break;
1✔
70
                }
71
            }
72
            if ($missingInSome) {
2✔
73
                $result = [
1✔
74
                    'key' => $key,
1✔
75
                    'files' => [],
1✔
76
                ];
1✔
77
                foreach ($this->keyArray as $file => $keys) {
1✔
78
                    $result['files'][] = [
1✔
79
                        'file' => $file,
1✔
80
                        'value' => $keys[$key] ?? null,
1✔
81
                    ];
1✔
82
                }
83
                $this->addIssue(new Issue(
1✔
84
                    '',
1✔
85
                    $result,
1✔
86
                    '',
1✔
87
                    $this->getShortName(),
1✔
88
                ));
1✔
89
            }
90
        }
91
    }
92

93
    public function formatIssueMessage(Issue $issue, string $prefix = ''): string
1✔
94
    {
95
        $details = $issue->getDetails();
1✔
96
        $resultType = $this->resultTypeOnValidationFailure();
1✔
97

98
        $level = $resultType->toString();
1✔
99
        $color = $resultType->toColorString();
1✔
100

101
        $key = $details['key'] ?? 'unknown';
1✔
102
        $files = $details['files'] ?? [];
1✔
103
        $currentFile = basename($issue->getFile());
1✔
104
        $otherFiles = [];
1✔
105
        $currentFileHasValue = false;
1✔
106

107
        foreach ($files as $fileInfo) {
1✔
108
            $fileName = basename($fileInfo['file'] ?? 'unknown');
1✔
109
            if ($fileName === $currentFile) {
1✔
UNCOV
110
                $currentFileHasValue = null !== $fileInfo['value'];
×
111
            } else {
112
                $otherFiles[] = $fileName;
1✔
113
            }
114
        }
115

116
        if ($currentFileHasValue) {
1✔
UNCOV
117
            $action = 'missing from';
×
118
        } else {
119
            $action = 'missing but present in';
1✔
120
        }
121

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

124
        return "- <fg=$color>$level</> {$prefix} the translation key `$key` is $action other translation files (`$otherFilesList`)";
1✔
125
    }
126

127
    public function distributeIssuesForDisplay(FileSet $fileSet): array
1✔
128
    {
129
        $distribution = [];
1✔
130

131
        foreach ($this->issues as $issue) {
1✔
132
            $details = $issue->getDetails();
1✔
133
            $files = $details['files'] ?? [];
1✔
134

135
            foreach ($files as $fileInfo) {
1✔
136
                $filePath = $fileInfo['file'] ?? '';
1✔
137
                if (!empty($filePath)) {
1✔
138
                    $fileSpecificIssue = new Issue(
1✔
139
                        $filePath,
1✔
140
                        $details,
1✔
141
                        $issue->getParser(),
1✔
142
                        $issue->getValidatorType(),
1✔
143
                    );
1✔
144

145
                    if (!isset($distribution[$filePath])) {
1✔
146
                        $distribution[$filePath] = [];
1✔
147
                    }
148

149
                    $distribution[$filePath][] = $fileSpecificIssue;
1✔
150
                }
151
            }
152
        }
153

154
        return $distribution;
1✔
155
    }
156

157
    public function renderDetailedOutput(OutputInterface $output, array $issues): void
1✔
158
    {
159
        if (empty($issues)) {
1✔
UNCOV
160
            return;
×
161
        }
162

163
        $rows = [];
1✔
164
        $allKeys = [];
1✔
165
        $allFilesData = [];
1✔
166

167
        foreach ($issues as $issue) {
1✔
168
            $details = $issue->getDetails();
1✔
169
            $key = $details['key'] ?? 'unknown';
1✔
170
            $files = $details['files'] ?? [];
1✔
171
            $currentFile = basename($issue->getFile());
1✔
172

173
            if (!in_array($key, $allKeys)) {
1✔
174
                $allKeys[] = $key;
1✔
175
            }
176

177
            foreach ($files as $fileInfo) {
1✔
178
                $fileName = basename($fileInfo['file'] ?? '');
1✔
179
                $value = $fileInfo['value'];
1✔
180

181
                if (!isset($allFilesData[$key])) {
1✔
182
                    $allFilesData[$key] = [];
1✔
183
                }
184
                $allFilesData[$key][$fileName] = $value;
1✔
185
            }
186
        }
187

188
        $firstIssue = $issues[0];
1✔
189
        $currentFile = basename($firstIssue->getFile());
1✔
190
        $firstDetails = $firstIssue->getDetails();
1✔
191
        $firstFiles = $firstDetails['files'] ?? [];
1✔
192

193
        $fileOrder = [$currentFile];
1✔
194
        foreach ($firstFiles as $fileInfo) {
1✔
195
            $fileName = basename($fileInfo['file'] ?? '');
1✔
196
            if ($fileName !== $currentFile && !in_array($fileName, $fileOrder, true)) {
1✔
197
                $fileOrder[] = $fileName;
1✔
198
            }
199
        }
200

201
        $header = ['Translation Key', $currentFile];
1✔
202
        foreach ($fileOrder as $fileName) {
1✔
203
            if ($fileName !== $currentFile) {
1✔
204
                $header[] = $fileName;
1✔
205
            }
206
        }
207

208
        foreach ($allKeys as $key) {
1✔
209
            $row = [$key];
1✔
210
            foreach ($fileOrder as $fileName) {
1✔
211
                $value = $allFilesData[$key][$fileName] ?? null;
1✔
212
                $row[] = $value ?? '';
1✔
213
            }
214
            $rows[] = $row;
1✔
215
        }
216

217
        $table = new Table($output);
1✔
218
        $table->setHeaders($header)
1✔
219
            ->setRows($rows)
1✔
220
            ->setStyle(
1✔
221
                (new TableStyle())
1✔
222
                    ->setCellHeaderFormat('%s'),
1✔
223
            )
1✔
224
            ->render();
1✔
225
    }
226

227
    /**
228
     * @return class-string<ParserInterface>[]
229
     */
230
    public function supportsParser(): array
1✔
231
    {
232
        return [XliffParser::class, YamlParser::class, JsonParser::class, PhpParser::class];
1✔
233
    }
234

235
    public function shouldShowDetailedOutput(): bool
1✔
236
    {
237
        return true;
1✔
238
    }
239

240
    protected function resetState(): void
1✔
241
    {
242
        parent::resetState();
1✔
243
        $this->keyArray = [];
1✔
244
    }
245
}
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

© 2025 Coveralls, Inc