Passed
Pull Request — master (#60)
by Stephan
09:11
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\FqcnRemover;
29
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\JsonParseException;
30
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\JsonUtils;
31
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\ParseAtLocationException;
32
use Webmozart\Assert\Assert;
33
34
/**
35
 * Handles PHPStan's JSON output.
36
 *
37
 * NOTE: SARB only deals with errors that are attached to a particular line in a file.
38
 * PHPStan can report general errors (not specific to file). These are ignored by SARB.
39
 */
40
class PhpCodeSnifferJsonResultsParser implements ResultsParser
41
{
42
    private const LINE = 'line';
43
    private const TYPE = 'type';
44
    private const SOURCE = 'source';
45
    private const FILES = 'files';
46
    private const MESSAGES = 'messages';
47
    private const MESSAGE = 'message';
48
    private const ABSOLUTE_FILE_PATH = 'absoluteFilePath';
49
    private const ERRORS = 'errors';
50
    private const WARNINGS = 'warnings';
51
52
    /**
53
     * @var FqcnRemover
54
     */
55
    private $fqcnRemover;
56
57
    /**
58
     * PhpstanJsonResultsParser constructor.
59
     */
60
    public function __construct(FqcnRemover $fqcnRemover)
61
    {
62
        $this->fqcnRemover = $fqcnRemover;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function convertFromString(string $resultsAsString, ProjectRoot $projectRoot): AnalysisResults
69
    {
70
        try {
71
            $asArray = JsonUtils::toArray($resultsAsString);
72
        } catch (JsonParseException $e) {
73
            throw new InvalidFileFormatException('Not a valid JSON format');
74
        }
75
76
        return $this->convertFromArray($asArray, $projectRoot);
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function convertToString(AnalysisResults $analysisResults): string
83
    {
84
        $files = [];
85
        foreach ($analysisResults->getAnalysisResults() as $analysisResult) {
86
            $fullDetails = JsonUtils::toArray($analysisResult->getFullDetails());
87
            $message = ArrayUtils::getArrayValue($fullDetails, self::MESSAGE);
88
            $absoluteFilePath = ArrayUtils::getStringValue($fullDetails, self::ABSOLUTE_FILE_PATH);
89
90
            if (!array_key_exists($absoluteFilePath, $files)) {
91
                $files[$absoluteFilePath] = [];
92
            }
93
94
            $files[$absoluteFilePath][] = $message;
95
        }
96
97
        $asArray = [
98
            'totals' => [
99
                'errors' => count(
100
                    array_filter(
101
                        $analysisResults->getAnalysisResults(),
102
                        static function (AnalysisResult $result): bool {
103
                            $details = JsonUtils::toArray($result->getFullDetails());
104
                            ArrayUtils::assertArray($details['message']);
105
106
                            return $details['message']['type'] === 'ERROR';
107
                        }
108
                    )
109
                ),
110
                'warnings' => count(
111
                    array_filter(
112
                        $analysisResults->getAnalysisResults(),
113
                        static function (AnalysisResult $result): bool {
114
                            $details = JsonUtils::toArray($result->getFullDetails());
115
                            ArrayUtils::assertArray($details['message']);
116
117
                            return $details['message']['type'] === 'WARNING';
118
                        }
119
                    )
120
                ),
121
                'fixable' => count(
122
                    array_filter(
123
                        $analysisResults->getAnalysisResults(),
124
                        static function (AnalysisResult $result): bool {
125
                            $details = JsonUtils::toArray($result->getFullDetails());
126
                            ArrayUtils::assertArray($details['message']);
127
128
                            return (bool) $details['message']['fixable'];
129
                        }
130
                    )
131
                ),
132
            ],
133
            self::FILES => []
134
        ];
135
136
        foreach ($files as $fileName => $messages) {
137
            $asArray[self::FILES][$fileName] = [
138
                'errors' => count(
139
                    array_filter(
140
                        $messages,
141
                        static function (array $message): bool {
142
                            return $message['type'] === 'ERROR';
143
                        }
144
                    )
145
                ),
146
                'warnings' => count(
147
                    array_filter(
148
                        $messages,
149
                        static function (array $message): bool {
150
                            return $message['type'] === 'WARNING';
151
                        }
152
                    )
153
                ),
154
                self::MESSAGES => $messages,
155
            ];
156
        }
157
158
        return JsonUtils::toString($asArray);
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function getIdentifier(): Identifier
165
    {
166
        return new PhpCodeSnifferJsonIdentifier();
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function showTypeGuessingWarning(): bool
173
    {
174
        return false;
175
    }
176
177
    /**
178
     * Converts from an array.
179
     *
180
     * @phpstan-param array<mixed> $analysisResultsAsArray
181
     *
182
     * @throws ParseAtLocationException
183
     */
184
    private function convertFromArray(array $analysisResultsAsArray, ProjectRoot $projectRoot): AnalysisResults
185
    {
186
        $analysisResults = new AnalysisResults();
187
188
        try {
189
            $filesErrors = ArrayUtils::getArrayValue($analysisResultsAsArray, self::FILES);
190
        } catch (ArrayParseException $e) {
191
            throw new ParseAtLocationException('Root node', $e);
192
        }
193
194
        /** @psalm-suppress MixedAssignment */
195
        foreach ($filesErrors as $absoluteFilePath => $fileErrors) {
196
            try {
197
                if (!is_string($absoluteFilePath)) {
198
                    throw new ArrayParseException('Expected filename to be of type string');
199
                }
200
201
                ArrayUtils::assertArray($fileErrors);
202
203
                $errorsCount = ArrayUtils::getIntValue($fileErrors, self::ERRORS);
204
                $warningsCount = ArrayUtils::getIntValue($fileErrors, self::WARNINGS);
205
                if ($errorsCount === 0 && $warningsCount === 0) {
206
                    continue;
207
                }
208
209
                $fileNameAsString = $projectRoot->getPathRelativeToRootDirectory($absoluteFilePath);
210
                $fileName = new FileName($fileNameAsString);
211
212
                $messages = ArrayUtils::getArrayValue($fileErrors, self::MESSAGES);
213
214
                foreach ($messages as $message) {
215
                    ArrayUtils::assertArray($message);
216
                    $analysisResult = $this->convertAnalysisResultFromArray($message, $fileName, $absoluteFilePath);
217
                    $analysisResults->addAnalysisResult($analysisResult);
218
                }
219
            } catch (ArrayParseException | JsonParseException | InvalidPathException $e) {
220
                throw new ParseAtLocationException("Result [$absoluteFilePath]", $e);
221
            }
222
        }
223
224
        return $analysisResults;
225
    }
226
227
    /**
228
     * @phpstan-param array<mixed> $analysisResultAsArray
229
     *
230
     * @throws ArrayParseException
231
     * @throws JsonParseException
232
     */
233
    private function convertAnalysisResultFromArray(
234
        array $analysisResultAsArray,
235
        FileName $fileName,
236
        string $absoluteFilePath
237
    ): AnalysisResult {
238
        $lineAsInt = ArrayUtils::getIntValue($analysisResultAsArray, self::LINE);
239
        $rawMessage = ArrayUtils::getStringValue($analysisResultAsArray, self::MESSAGE);
240
        $rawType = ArrayUtils::getStringValue($analysisResultAsArray, self::TYPE);
0 ignored issues
show
Unused Code introduced by
The assignment to $rawType is dead and can be removed.
Loading history...
241
        $rawSource = ArrayUtils::getStringValue($analysisResultAsArray, self::SOURCE);
242
243
        $location = new Location(
244
            $fileName,
245
            new LineNumber($lineAsInt)
246
        );
247
248
        return new AnalysisResult(
249
            $location,
250
            new Type($rawSource),
251
            $rawMessage,
252
            JsonUtils::toString([
253
                self::ABSOLUTE_FILE_PATH => $absoluteFilePath,
254
                self::MESSAGE => $analysisResultAsArray,
255
            ])
256
        );
257
    }
258
}
259