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

move-elevator / composer-translation-validator / 16218658256

11 Jul 2025 11:13AM UTC coverage: 95.902% (+0.6%) from 95.347%
16218658256

push

github

web-flow
Merge pull request #28 from move-elevator/code-style

style: improve code formatting and readability across multiple files

153 of 155 new or added lines in 13 files covered. (98.71%)

1 existing line in 1 file now uncovered.

1287 of 1342 relevant lines covered (95.9%)

7.9 hits per line

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

77.42
/src/Validator/AbstractValidator.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\ParserCache;
9
use MoveElevator\ComposerTranslationValidator\Parser\ParserInterface;
10
use MoveElevator\ComposerTranslationValidator\Parser\ParserRegistry;
11
use MoveElevator\ComposerTranslationValidator\Result\Issue;
12
use Psr\Log\LoggerInterface;
13
use Symfony\Component\Console\Output\OutputInterface;
14

15
abstract class AbstractValidator
16
{
17
    /** @var array<Issue> */
18
    protected array $issues = [];
19

20
    public function __construct(protected ?LoggerInterface $logger = null)
56✔
21
    {
22
    }
56✔
23

24
    /**
25
     * @param string[]                           $files
26
     * @param class-string<ParserInterface>|null $parserClass
27
     *
28
     * @return array<string, array<mixed>>
29
     */
30
    public function validate(array $files, ?string $parserClass): array
5✔
31
    {
32
        // Reset state for fresh validation run
33
        $this->resetState();
5✔
34

35
        $name = $this->getShortName();
5✔
36
        $this->logger->debug(
5✔
37
            sprintf(
5✔
38
                '> Checking for <options=bold,underscore>%s</> ...',
5✔
39
                $name
5✔
40
            )
5✔
41
        );
5✔
42

43
        foreach ($files as $filePath) {
5✔
44
            $file = ParserCache::get(
5✔
45
                $filePath,
5✔
46
                $parserClass ?: ParserRegistry::resolveParserClass(
5✔
47
                    $filePath,
5✔
48
                    $this->logger
5✔
49
                )
5✔
50
            );
5✔
51
            /* @var ParserInterface $file */
52

53
            if (!$file instanceof ParserInterface) {
5✔
54
                $this->logger?->debug(
×
55
                    sprintf(
×
NEW
56
                        'The file <fg=cyan>%s</> could not be parsed by the '
×
NEW
57
                        .'validator <fg=red>%s</>.',
×
58
                        $filePath,
×
59
                        static::class
×
60
                    )
×
61
                );
×
62
                continue;
×
63
            }
64

65
            if (!in_array($file::class, $this->supportsParser(), true)) {
5✔
66
                $this->logger?->debug(
×
67
                    sprintf(
×
68
                        'The file <fg=cyan>%s</> is not supported by the validator <fg=red>%s</>.',
×
69
                        $file->getFileName(),
×
70
                        static::class
×
71
                    )
×
72
                );
×
73
                continue;
×
74
            }
75

76
            $this->logger->debug(
5✔
77
                '> Checking language file: <fg=gray>'
5✔
78
                .$file->getFileDirectory()
5✔
79
                .'</><fg=cyan>'
5✔
80
                .$file->getFileName()
5✔
81
                .'</> ...'
5✔
82
            );
5✔
83

84
            $validationResult = $this->processFile($file);
5✔
85
            if (empty($validationResult)) {
5✔
86
                continue;
1✔
87
            }
88

89
            $this->addIssue(new Issue(
4✔
90
                $file->getFileName(),
4✔
91
                $validationResult,
4✔
92
                $file::class,
4✔
93
                $name
4✔
94
            ));
4✔
95
        }
96

97
        $this->postProcess();
5✔
98

99
        return array_map(fn ($issue) => $issue->toArray(), $this->issues);
5✔
100
    }
101

102
    /**
103
     * @return array<mixed>
104
     */
105
    abstract public function processFile(ParserInterface $file): array;
106

107
    /**
108
     * @return class-string<ParserInterface>[]
109
     */
110
    abstract public function supportsParser(): array;
111

112
    public function postProcess(): void
×
113
    {
114
        // This method can be overridden by subclasses to perform
115
        // additional processing after validation.
UNCOV
116
    }
×
117

118
    public function resultTypeOnValidationFailure(): ResultType
2✔
119
    {
120
        return ResultType::ERROR;
2✔
121
    }
122

123
    public function hasIssues(): bool
6✔
124
    {
125
        return !empty($this->issues);
6✔
126
    }
127

128
    /**
129
     * @return array<Issue>
130
     */
131
    public function getIssues(): array
5✔
132
    {
133
        return $this->issues;
5✔
134
    }
135

136
    public function addIssue(Issue $issue): void
13✔
137
    {
138
        $this->issues[] = $issue;
13✔
139
    }
140

141
    /**
142
     * Reset validator state for fresh validation run.
143
     * Override in subclasses if they have additional state to reset.
144
     */
145
    protected function resetState(): void
8✔
146
    {
147
        $this->issues = [];
8✔
148
    }
149

150
    public function formatIssueMessage(Issue $issue, string $prefix = ''): string
3✔
151
    {
152
        $details = $issue->getDetails();
3✔
153
        $resultType = $this->resultTypeOnValidationFailure();
3✔
154

155
        $level = $resultType->toString();
3✔
156
        $color = $resultType->toColorString();
3✔
157

158
        $message = $details['message'] ?? 'Validation error';
3✔
159

160
        return "- <fg=$color>$level</> {$prefix}$message";
3✔
161
    }
162

163
    /**
164
     * @return array<string, array<Issue>>
165
     */
166
    public function distributeIssuesForDisplay(FileSet $fileSet): array
2✔
167
    {
168
        $distribution = [];
2✔
169

170
        foreach ($this->issues as $issue) {
2✔
171
            $fileName = $issue->getFile();
2✔
172
            if (empty($fileName)) {
2✔
173
                continue;
1✔
174
            }
175

176
            // Build full path from fileSet and filename for consistency
177
            $basePath = rtrim($fileSet->getPath(), '/');
1✔
178
            $filePath = $basePath.'/'.$fileName;
1✔
179

180
            $distribution[$filePath] ??= [];
1✔
181
            $distribution[$filePath][] = $issue;
1✔
182
        }
183

184
        return $distribution;
2✔
185
    }
186

187
    public function shouldShowDetailedOutput(): bool
1✔
188
    {
189
        return false;
1✔
190
    }
191

192
    /**
193
     * @param array<Issue> $issues
194
     */
195
    public function renderDetailedOutput(OutputInterface $output, array $issues): void
×
196
    {
197
        // Default implementation: no detailed output
198
    }
×
199

200
    public function getShortName(): string
7✔
201
    {
202
        $classPart = strrchr(static::class, '\\');
7✔
203

204
        return false !== $classPart ? substr($classPart, 1) : static::class;
7✔
205
    }
206
}
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