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

move-elevator / composer-translation-validator / 16142235573

08 Jul 2025 11:42AM UTC coverage: 94.927% (+0.4%) from 94.526%
16142235573

Pull #19

github

jackd248
refactor: remove readonly modifier from validation classes
Pull Request #19: refactor: improve output style

270 of 279 new or added lines in 10 files covered. (96.77%)

3 existing lines in 1 file now uncovered.

842 of 887 relevant lines covered (94.93%)

5.11 hits per line

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

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

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

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

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

36
        $classPart = strrchr(static::class, '\\');
5✔
37
        $name = false !== $classPart ? substr($classPart, 1) : static::class;
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 = new ($parserClass ?: ParserRegistry::resolveParserClass($filePath))($filePath);
5✔
47
            /* @var ParserInterface $file */
48

49
            if (!in_array($file::class, $this->supportsParser(), true)) {
5✔
50
                $this->logger?->debug(
×
51
                    sprintf(
×
52
                        'The file <fg=cyan>%s</> is not supported by the validator <fg=red>%s</>.',
×
53
                        $file->getFileName(),
×
54
                        static::class
×
55
                    )
×
56
                );
×
UNCOV
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 = '', bool $isVerbose = false): 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
    public function distributeIssuesForDisplay(FileSet $fileSet): array
2✔
139
    {
140
        $distribution = [];
2✔
141

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

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

152
            if (!isset($distribution[$filePath])) {
1✔
153
                $distribution[$filePath] = [];
1✔
154
            }
155
            $distribution[$filePath][] = $issue;
1✔
156
        }
157

158
        return $distribution;
2✔
159
    }
160

161
    public function shouldShowDetailedOutput(): bool
1✔
162
    {
163
        return false;
1✔
164
    }
165

NEW
166
    public function renderDetailedOutput(OutputInterface $output, array $issues): void
×
167
    {
168
        // Default implementation: no detailed output
NEW
169
    }
×
170

171
    public function getShortName(): string
6✔
172
    {
173
        $classPart = strrchr(static::class, '\\');
6✔
174

175
        return false !== $classPart ? substr($classPart, 1) : static::class;
6✔
176
    }
177
}
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