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

move-elevator / composer-translation-validator / 16120356120

07 Jul 2025 02:52PM UTC coverage: 94.526% (+1.3%) from 93.207%
16120356120

push

github

web-flow
Merge pull request #18 from move-elevator/validation-run

refactor: implement object-oriented validation architecture with unified rendering system

228 of 230 new or added lines in 11 files covered. (99.13%)

5 existing lines in 1 file now uncovered.

777 of 822 relevant lines covered (94.53%)

5.23 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace MoveElevator\ComposerTranslationValidator\Validator;
6

7
use MoveElevator\ComposerTranslationValidator\Parser\ParserInterface;
8
use MoveElevator\ComposerTranslationValidator\Parser\XliffParser;
9
use MoveElevator\ComposerTranslationValidator\Parser\YamlParser;
10
use MoveElevator\ComposerTranslationValidator\Result\Issue;
11
use Symfony\Component\Console\Helper\Table;
12
use Symfony\Component\Console\Helper\TableStyle;
13
use Symfony\Component\Console\Input\InputInterface;
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
    /**
76
     * @param array<string, array<int, array<mixed>>> $issueSets
77
     */
78
    public function renderIssueSets(InputInterface $input, OutputInterface $output, array $issueSets): void
1✔
79
    {
80
        $rows = [];
1✔
81
        $header = ['Key'];
1✔
82
        $allFiles = [];
1✔
83

84
        foreach ($issueSets as $issuesPerFile) {
1✔
85
            foreach ($issuesPerFile as $issues) {
1✔
86
                // Handle both new format (with 'issues' key) and old format (direct data)
87
                if (isset($issues['issues']) && is_array($issues['issues'])) {
1✔
NEW
88
                    $issueData = $issues['issues'];
×
89
                } else {
90
                    $issueData = $issues;
1✔
91
                }
92
                $key = $issueData['key'];
1✔
93
                $files = $issueData['files'];
1✔
94
                if (empty($allFiles)) {
1✔
95
                    $allFiles = array_column($files, 'file');
1✔
96
                    $header = array_merge(['Key'], array_map(static fn ($f) => "<fg=red>$f</>", $allFiles));
1✔
97
                }
98
                $row = [$key];
1✔
99
                foreach ($files as $fileInfo) {
1✔
100
                    $row[] = $fileInfo['value'] ?? '<fg=yellow><missing></>';
1✔
101
                }
102
                $rows[] = $row;
1✔
103
            }
104
        }
105

106
        (new Table($output))
1✔
107
            ->setHeaders($header)
1✔
108
            ->setRows($rows)
1✔
109
            ->setStyle(
1✔
110
                (new TableStyle())
1✔
111
                    ->setCellHeaderFormat('%s')
1✔
112
            )
1✔
113
            ->render();
1✔
114
    }
115

116
    public function explain(): string
1✔
117
    {
118
        return 'This validator checks for keys that are present in some files but not in others. '
1✔
119
            .'It helps to identify mismatches in translation keys across different translation files.';
1✔
120
    }
121

122
    /**
123
     * @return class-string<ParserInterface>[]
124
     */
125
    public function supportsParser(): array
1✔
126
    {
127
        return [XliffParser::class, YamlParser::class];
1✔
128
    }
129

130
    protected function resetState(): void
1✔
131
    {
132
        parent::resetState();
1✔
133
        $this->keyArray = [];
1✔
134
    }
135
}
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