Completed
Push — master ( c38e17...9e23a4 )
by Dave
18s queued 13s
created

PhpCodeSnifferJsonResultsParser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * Static Analysis Results Baseliner (sarb).
5
 *
6
 * (c) Dave Liddament
7
 *
8
 * For the full copyright and licence information please view the LICENSE file distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace DaveLiddament\StaticAnalysisResultsBaseliner\Plugins\PhpCodeSnifferJsonResultsParser;
14
15
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\FileName;
16
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\InvalidPathException;
17
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\LineNumber;
18
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\Location;
19
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\ProjectRoot;
20
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\Type;
21
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\File\InvalidFileFormatException;
22
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\AnalysisResult;
23
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\AnalysisResults;
24
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\Identifier;
25
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\ResultsParser;
26
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\ArrayParseException;
27
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\ArrayUtils;
28
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\JsonParseException;
29
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\JsonUtils;
30
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\ParseAtLocationException;
31
32
/**
33
 * Handles PHP Code Sniffers's JSON output.
34
 */
35
class PhpCodeSnifferJsonResultsParser implements ResultsParser
36
{
37
    private const LINE = 'line';
38
    private const SOURCE = 'source';
39
    private const FILES = 'files';
40
    private const MESSAGES = 'messages';
41
    private const MESSAGE = 'message';
42
    private const ABSOLUTE_FILE_PATH = 'absoluteFilePath';
43
    private const ERRORS = 'errors';
44
    private const WARNINGS = 'warnings';
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function convertFromString(string $resultsAsString, ProjectRoot $projectRoot): AnalysisResults
50
    {
51
        try {
52
            $asArray = JsonUtils::toArray($resultsAsString);
53
        } catch (JsonParseException $e) {
54
            throw new InvalidFileFormatException('Not a valid JSON format');
55
        }
56
57
        return $this->convertFromArray($asArray, $projectRoot);
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function convertToString(AnalysisResults $analysisResults): string
64
    {
65
        $files = [];
66
        foreach ($analysisResults->getAnalysisResults() as $analysisResult) {
67
            $fullDetails = JsonUtils::toArray($analysisResult->getFullDetails());
68
            $message = ArrayUtils::getArrayValue($fullDetails, self::MESSAGE);
69
            $absoluteFilePath = ArrayUtils::getStringValue($fullDetails, self::ABSOLUTE_FILE_PATH);
70
71
            if (!array_key_exists($absoluteFilePath, $files)) {
72
                $files[$absoluteFilePath] = [];
73
            }
74
75
            $files[$absoluteFilePath][] = $message;
76
        }
77
78
        $asArray = [
79
            'totals' => [
80
                'errors' => count(
81
                    array_filter(
82
                        $analysisResults->getAnalysisResults(),
83
                        static function (AnalysisResult $result): bool {
84
                            $details = JsonUtils::toArray($result->getFullDetails());
85
                            ArrayUtils::assertArray($details['message']);
86
87
                            return 'ERROR' === $details['message']['type'];
88
                        }
89
                    )
90
                ),
91
                'warnings' => count(
92
                    array_filter(
93
                        $analysisResults->getAnalysisResults(),
94
                        static function (AnalysisResult $result): bool {
95
                            $details = JsonUtils::toArray($result->getFullDetails());
96
                            ArrayUtils::assertArray($details['message']);
97
98
                            return 'WARNING' === $details['message']['type'];
99
                        }
100
                    )
101
                ),
102
                'fixable' => count(
103
                    array_filter(
104
                        $analysisResults->getAnalysisResults(),
105
                        static function (AnalysisResult $result): bool {
106
                            $details = JsonUtils::toArray($result->getFullDetails());
107
                            ArrayUtils::assertArray($details['message']);
108
109
                            return (bool) $details['message']['fixable'];
110
                        }
111
                    )
112
                ),
113
            ],
114
            self::FILES => [],
115
        ];
116
117
        foreach ($files as $fileName => $messages) {
118
            $asArray[self::FILES][$fileName] = [
119
                'errors' => count(
120
                    array_filter(
121
                        $messages,
122
                        static function (array $message): bool {
123
                            return 'ERROR' === $message['type'];
124
                        }
125
                    )
126
                ),
127
                'warnings' => count(
128
                    array_filter(
129
                        $messages,
130
                        static function (array $message): bool {
131
                            return 'WARNING' === $message['type'];
132
                        }
133
                    )
134
                ),
135
                self::MESSAGES => $messages,
136
            ];
137
        }
138
139
        return JsonUtils::toString($asArray);
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function getIdentifier(): Identifier
146
    {
147
        return new PhpCodeSnifferJsonIdentifier();
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153
    public function showTypeGuessingWarning(): bool
154
    {
155
        return false;
156
    }
157
158
    /**
159
     * Converts from an array.
160
     *
161
     * @phpstan-param array<mixed> $analysisResultsAsArray
162
     *
163
     * @throws ParseAtLocationException
164
     */
165
    private function convertFromArray(array $analysisResultsAsArray, ProjectRoot $projectRoot): AnalysisResults
166
    {
167
        $analysisResults = new AnalysisResults();
168
169
        try {
170
            $filesErrors = ArrayUtils::getArrayValue($analysisResultsAsArray, self::FILES);
171
        } catch (ArrayParseException $e) {
172
            throw new ParseAtLocationException('Root node', $e);
173
        }
174
175
        /** @psalm-suppress MixedAssignment */
176
        foreach ($filesErrors as $absoluteFilePath => $fileErrors) {
177
            try {
178
                if (!is_string($absoluteFilePath)) {
179
                    throw new ArrayParseException('Expected filename to be of type string');
180
                }
181
182
                ArrayUtils::assertArray($fileErrors);
183
184
                $errorsCount = ArrayUtils::getIntValue($fileErrors, self::ERRORS);
185
                $warningsCount = ArrayUtils::getIntValue($fileErrors, self::WARNINGS);
186
                if (0 === $errorsCount && 0 === $warningsCount) {
187
                    continue;
188
                }
189
190
                $fileNameAsString = $projectRoot->getPathRelativeToRootDirectory($absoluteFilePath);
191
                $fileName = new FileName($fileNameAsString);
192
193
                $messages = ArrayUtils::getArrayValue($fileErrors, self::MESSAGES);
194
195
                foreach ($messages as $message) {
196
                    ArrayUtils::assertArray($message);
197
                    $analysisResult = $this->convertAnalysisResultFromArray($message, $fileName, $absoluteFilePath);
198
                    $analysisResults->addAnalysisResult($analysisResult);
199
                }
200
            } catch (ArrayParseException | JsonParseException | InvalidPathException $e) {
201
                throw new ParseAtLocationException("Result [$absoluteFilePath]", $e);
202
            }
203
        }
204
205
        return $analysisResults;
206
    }
207
208
    /**
209
     * @phpstan-param array<mixed> $analysisResultAsArray
210
     *
211
     * @throws ArrayParseException
212
     * @throws JsonParseException
213
     */
214
    private function convertAnalysisResultFromArray(
215
        array $analysisResultAsArray,
216
        FileName $fileName,
217
        string $absoluteFilePath
218
    ): AnalysisResult {
219
        $lineAsInt = ArrayUtils::getIntValue($analysisResultAsArray, self::LINE);
220
        $rawMessage = ArrayUtils::getStringValue($analysisResultAsArray, self::MESSAGE);
221
        $rawSource = ArrayUtils::getStringValue($analysisResultAsArray, self::SOURCE);
222
223
        $location = new Location(
224
            $fileName,
225
            new LineNumber($lineAsInt)
226
        );
227
228
        return new AnalysisResult(
229
            $location,
230
            new Type($rawSource),
231
            $rawMessage,
232
            JsonUtils::toString([
233
                self::ABSOLUTE_FILE_PATH => $absoluteFilePath,
234
                self::MESSAGE => $analysisResultAsArray,
235
            ])
236
        );
237
    }
238
}
239