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

move-elevator / composer-translation-validator / 16137064113

08 Jul 2025 07:41AM UTC coverage: 78.302% (-16.2%) from 94.526%
16137064113

Pull #19

github

web-flow
Merge 5f8b3255c into 3e904fb25
Pull Request #19: refactor: improve output style

93 of 278 new or added lines in 7 files covered. (33.45%)

103 existing lines in 7 files now uncovered.

830 of 1060 relevant lines covered (78.3%)

4.18 hits per line

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

49.28
/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)
46✔
20
    {
21
    }
46✔
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

UNCOV
93
    public function resultTypeOnValidationFailure(): ResultType
×
94
    {
UNCOV
95
        return ResultType::ERROR;
×
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
10✔
112
    {
113
        $this->issues[] = $issue;
10✔
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

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

NEW
130
        $level = $resultType->toString();
×
NEW
131
        $color = $resultType->toColorString();
×
132

NEW
133
        $message = $details['message'] ?? 'Validation error';
×
134

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

NEW
138
    public function distributeIssuesForDisplay(FileSet $fileSet): array
×
139
    {
NEW
140
        $distribution = [];
×
141

NEW
142
        foreach ($this->issues as $issue) {
×
NEW
143
            $fileName = $issue->getFile();
×
NEW
144
            if (empty($fileName)) {
×
NEW
145
                continue;
×
146
            }
147
            
148
            // Build full path from fileSet and filename for consistency
NEW
149
            $basePath = rtrim($fileSet->getPath(), '/');
×
NEW
150
            $filePath = $basePath.'/'.$fileName;
×
151

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

NEW
158
        return $distribution;
×
159
    }
160

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

NEW
166
    public function renderDetailedOutput(OutputInterface $output, array $issues): void
×
167
    {
168
        // Default implementation: no detailed output
NEW
169
    }
×
170
}
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