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

move-elevator / composer-translation-validator / 18559927341

16 Oct 2025 11:35AM UTC coverage: 95.519%. Remained the same
18559927341

Pull #73

github

jackd248
build: add php-cs-fixer-preset
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

79.8
/src/Validator/AbstractValidator.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\{ParserCache, ParserInterface, ParserRegistry};
18
use MoveElevator\ComposerTranslationValidator\Result\Issue;
19
use Psr\Log\LoggerInterface;
20
use Symfony\Component\Console\Output\OutputInterface;
21

22
use function in_array;
23
use function is_array;
24
use function sprintf;
25

26
/**
27
 * AbstractValidator.
28
 *
29
 * @author Konrad Michalik <km@move-elevator.de>
30
 * @license GPL-3.0-or-later
31
 */
32
abstract class AbstractValidator
33
{
34
    /** @var array<Issue> */
35
    protected array $issues = [];
36

37
    protected string $currentFilePath = '';
38

39
    public function __construct(protected ?LoggerInterface $logger = null) {}
249✔
40

41
    /**
42
     * @param string[]                           $files
43
     * @param class-string<ParserInterface>|null $parserClass
44
     *
45
     * @return array<string, array<mixed>>
46
     */
47
    public function validate(array $files, ?string $parserClass): array
6✔
48
    {
49
        // Reset state for fresh validation run
50
        $this->resetState();
6✔
51

52
        $name = $this->getShortName();
6✔
53
        $this->logger?->debug(
6✔
54
            sprintf(
6✔
55
                '> Checking for <options=bold,underscore>%s</> ...',
6✔
56
                $name,
6✔
57
            ),
6✔
58
        );
6✔
59

60
        foreach ($files as $filePath) {
6✔
61
            $this->currentFilePath = $filePath;
6✔
62

63
            $file = ParserCache::get(
6✔
64
                $filePath,
6✔
65
                $parserClass ?: ParserRegistry::resolveParserClass(
6✔
66
                    $filePath,
6✔
67
                    $this->logger,
6✔
68
                ),
6✔
69
            );
6✔
70
            /* @var ParserInterface $file */
71

72
            if (!$file instanceof ParserInterface) {
6✔
UNCOV
73
                $this->logger?->debug(
×
UNCOV
74
                    sprintf(
×
UNCOV
75
                        'The file <fg=cyan>%s</> could not be parsed by the '
×
UNCOV
76
                        .'validator <fg=red>%s</>.',
×
UNCOV
77
                        $filePath,
×
UNCOV
78
                        static::class,
×
79
                    ),
×
80
                );
×
81
                continue;
×
82
            }
83

84
            if (!in_array($file::class, $this->supportsParser(), true)) {
6✔
85
                $this->logger?->debug(
1✔
86
                    sprintf(
1✔
87
                        'The file <fg=cyan>%s</> is not supported by the validator <fg=red>%s</>.',
1✔
88
                        $file->getFileName(),
1✔
89
                        static::class,
1✔
90
                    ),
1✔
91
                );
1✔
92
                continue;
1✔
93
            }
94

95
            $this->logger?->debug(
5✔
96
                '> Checking language file: <fg=gray>'
5✔
97
                .$file->getFileDirectory()
5✔
98
                .'</><fg=cyan>'
5✔
99
                .$file->getFileName()
5✔
100
                .'</> ...',
5✔
101
            );
5✔
102

103
            $validationResult = $this->processFile($file);
5✔
104
            if (empty($validationResult)) {
5✔
105
                continue;
1✔
106
            }
107

108
            // Handle case where processFile returns multiple issues
109
            if (isset($validationResult[0]) && is_array($validationResult[0])) {
4✔
110
                // Multiple issues - create one Issue object per item
UNCOV
111
                foreach ($validationResult as $issueData) {
×
UNCOV
112
                    $this->addIssue(new Issue(
×
UNCOV
113
                        $filePath,
×
UNCOV
114
                        $issueData,
×
UNCOV
115
                        $file::class,
×
UNCOV
116
                        $name,
×
117
                    ));
×
118
                }
119
            } else {
120
                // Single issue data - create one Issue object
121
                $this->addIssue(new Issue(
4✔
122
                    $filePath,
4✔
123
                    $validationResult,
4✔
124
                    $file::class,
4✔
125
                    $name,
4✔
126
                ));
4✔
127
            }
128
        }
129

130
        $this->postProcess();
6✔
131

132
        return array_map(fn ($issue) => $issue->toArray(), $this->issues);
6✔
133
    }
134

135
    /**
136
     * @return array<mixed>
137
     */
138
    abstract public function processFile(ParserInterface $file): array;
139

140
    /**
141
     * @return class-string<ParserInterface>[]
142
     */
143
    abstract public function supportsParser(): array;
144

UNCOV
145
    public function postProcess(): void
×
146
    {
147
        // This method can be overridden by subclasses to perform
148
        // additional processing after validation.
UNCOV
149
    }
×
150

151
    public function resultTypeOnValidationFailure(): ResultType
3✔
152
    {
153
        return ResultType::ERROR;
3✔
154
    }
155

156
    public function hasIssues(): bool
13✔
157
    {
158
        return !empty($this->issues);
13✔
159
    }
160

161
    /**
162
     * @return array<Issue>
163
     */
164
    public function getIssues(): array
9✔
165
    {
166
        return $this->issues;
9✔
167
    }
168

169
    public function addIssue(Issue $issue): void
20✔
170
    {
171
        $this->issues[] = $issue;
20✔
172
    }
173

174
    public function formatIssueMessage(Issue $issue, string $prefix = ''): string
7✔
175
    {
176
        $details = $issue->getDetails();
7✔
177
        $resultType = $this->resultTypeOnValidationFailure();
7✔
178

179
        $level = $resultType->toString();
7✔
180
        $color = $resultType->toColorString();
7✔
181

182
        $message = $details['message'] ?? 'Validation error';
7✔
183

184
        return "- <fg=$color>$level</> {$prefix}$message";
7✔
185
    }
186

187
    /**
188
     * @return array<string, array<Issue>>
189
     */
190
    public function distributeIssuesForDisplay(FileSet $fileSet): array
2✔
191
    {
192
        $distribution = [];
2✔
193

194
        foreach ($this->issues as $issue) {
2✔
195
            $filePath = $issue->getFile();
2✔
196
            if (empty($filePath)) {
2✔
197
                continue;
1✔
198
            }
199

200
            // Use the full file path directly since it's now stored in Issue objects
201
            $distribution[$filePath] ??= [];
1✔
202
            $distribution[$filePath][] = $issue;
1✔
203
        }
204

205
        return $distribution;
2✔
206
    }
207

208
    public function shouldShowDetailedOutput(): bool
1✔
209
    {
210
        return false;
1✔
211
    }
212

213
    /**
214
     * @param array<Issue> $issues
215
     */
UNCOV
216
    public function renderDetailedOutput(OutputInterface $output, array $issues): void
×
217
    {
218
        // Default implementation: no detailed output
UNCOV
219
    }
×
220

221
    public function getShortName(): string
17✔
222
    {
223
        $classPart = strrchr(static::class, '\\');
17✔
224

225
        return false !== $classPart ? substr($classPart, 1) : static::class;
17✔
226
    }
227

228
    /**
229
     * Reset validator state for fresh validation run.
230
     * Override in subclasses if they have additional state to reset.
231
     */
232
    protected function resetState(): void
11✔
233
    {
234
        $this->issues = [];
11✔
235
    }
236
}
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