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

move-elevator / composer-translation-validator / 16189647882

10 Jul 2025 08:07AM UTC coverage: 94.694% (+0.2%) from 94.509%
16189647882

Pull #22

github

jackd248
feat: implement ParserCache for caching parser instances and add cache statistics
Pull Request #22: feat: implement ParserCache for caching parser instances and add cache statistics

37 of 45 new or added lines in 6 files covered. (82.22%)

4 existing lines in 1 file now uncovered.

928 of 980 relevant lines covered (94.69%)

5.69 hits per line

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

83.1
/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
     * @throws \ReflectionException
31
     */
32
    public function validate(array $files, ?string $parserClass): array
5✔
33
    {
34
        // Reset state for fresh validation run
35
        $this->resetState();
5✔
36

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

45
        foreach ($files as $filePath) {
5✔
46
            $file = ParserCache::get($filePath, $parserClass ?: ParserRegistry::resolveParserClass($filePath));
5✔
47
            /* @var ParserInterface $file */
48

49
            if (!in_array($file::class, $this->supportsParser(), true)) {
5✔
NEW
50
                $this->logger?->debug(
×
NEW
51
                    sprintf(
×
NEW
52
                        'The file <fg=cyan>%s</> is not supported by the validator <fg=red>%s</>.',
×
NEW
53
                        $file->getFileName(),
×
NEW
54
                        static::class
×
NEW
55
                    )
×
NEW
56
                );
×
NEW
57
                continue;
×
58
            }
59

60
            $this->logger->debug('> Checking language file: <fg=gray>'.$file->getFileDirectory().'</><fg=cyan>'.$file->getFileName().'</> ...');
5✔
61

62
            $validationResult = $this->processFile($file);
5✔
63
            if (!empty($validationResult)) {
5✔
64
                $this->addIssue(new Issue(
4✔
65
                    $file->getFileName(),
4✔
66
                    $validationResult,
4✔
67
                    $file::class,
4✔
68
                    $name
4✔
69
                ));
4✔
70
            }
71
        }
72

73
        $this->postProcess();
5✔
74

75
        return array_map(fn ($issue) => $issue->toArray(), $this->issues);
5✔
76
    }
77

78
    /**
79
     * @return array<mixed>
80
     */
81
    abstract public function processFile(ParserInterface $file): array;
82

83
    /**
84
     * @return class-string<ParserInterface>[]
85
     */
86
    abstract public function supportsParser(): array;
87

UNCOV
88
    public function postProcess(): void
×
89
    {
90
        // This method can be overridden by subclasses to perform additional processing after validation.
UNCOV
91
    }
×
92

93
    public function resultTypeOnValidationFailure(): ResultType
2✔
94
    {
95
        return ResultType::ERROR;
2✔
96
    }
97

98
    public function hasIssues(): bool
6✔
99
    {
100
        return !empty($this->issues);
6✔
101
    }
102

103
    /**
104
     * @return array<Issue>
105
     */
106
    public function getIssues(): array
5✔
107
    {
108
        return $this->issues;
5✔
109
    }
110

111
    public function addIssue(Issue $issue): void
13✔
112
    {
113
        $this->issues[] = $issue;
13✔
114
    }
115

116
    /**
117
     * Reset validator state for fresh validation run.
118
     * Override in subclasses if they have additional state to reset.
119
     */
120
    protected function resetState(): void
8✔
121
    {
122
        $this->issues = [];
8✔
123
    }
124

125
    public function formatIssueMessage(Issue $issue, string $prefix = ''): string
3✔
126
    {
127
        $details = $issue->getDetails();
3✔
128
        $resultType = $this->resultTypeOnValidationFailure();
3✔
129

130
        $level = $resultType->toString();
3✔
131
        $color = $resultType->toColorString();
3✔
132

133
        $message = $details['message'] ?? 'Validation error';
3✔
134

135
        return "- <fg=$color>$level</> {$prefix}$message";
3✔
136
    }
137

138
    /**
139
     * @return array<string, array<Issue>>
140
     */
141
    public function distributeIssuesForDisplay(FileSet $fileSet): array
2✔
142
    {
143
        $distribution = [];
2✔
144

145
        foreach ($this->issues as $issue) {
2✔
146
            $fileName = $issue->getFile();
2✔
147
            if (empty($fileName)) {
2✔
148
                continue;
1✔
149
            }
150

151
            // Build full path from fileSet and filename for consistency
152
            $basePath = rtrim($fileSet->getPath(), '/');
1✔
153
            $filePath = $basePath.'/'.$fileName;
1✔
154

155
            if (!isset($distribution[$filePath])) {
1✔
156
                $distribution[$filePath] = [];
1✔
157
            }
158
            $distribution[$filePath][] = $issue;
1✔
159
        }
160

161
        return $distribution;
2✔
162
    }
163

164
    public function shouldShowDetailedOutput(): bool
1✔
165
    {
166
        return false;
1✔
167
    }
168

169
    /**
170
     * @param array<Issue> $issues
171
     */
UNCOV
172
    public function renderDetailedOutput(OutputInterface $output, array $issues): void
×
173
    {
174
        // Default implementation: no detailed output
UNCOV
175
    }
×
176

177
    public function getShortName(): string
7✔
178
    {
179
        $classPart = strrchr(static::class, '\\');
7✔
180

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