-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathPhpstanJsonResultsParser.php
152 lines (128 loc) · 5.79 KB
/
PhpstanJsonResultsParser.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<?php
/**
* Static Analysis Results Baseliner (sarb).
*
* (c) Dave Liddament
*
* For the full copyright and licence information please view the LICENSE file distributed with this source code.
*/
declare(strict_types=1);
namespace DaveLiddament\StaticAnalysisResultsBaseliner\Plugins\ResultsParsers\PhpstanJsonResultsParser;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\AbsoluteFileName;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\InvalidPathException;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\LineNumber;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\Location;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\ProjectRoot;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\Severity;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Common\Type;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\AnalysisResult;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\AnalysisResults;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\AnalysisResultsBuilder;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\ErrorReportedByStaticAnalysisTool;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\Identifier;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\ResultsParser\ResultsParser;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\ArrayParseException;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\ArrayUtils;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\FqcnRemover;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\JsonUtils;
use DaveLiddament\StaticAnalysisResultsBaseliner\Domain\Utils\ParseAtLocationException;
/**
* Handles PHPStan's JSON output.
*
* NOTE: SARB only deals with errors that are attached to a particular line in a file.
* PHPStan can report general errors (not specific to file). These are ignored by SARB.
*/
final class PhpstanJsonResultsParser implements ResultsParser
{
private const LINE = 'line';
private const TYPE = 'message';
private const FILES = 'files';
private const MESSAGES = 'messages';
private const ERRORS = 'errors';
public function __construct(
private FqcnRemover $fqcnRemover,
) {
}
public function convertFromString(string $resultsAsString, ProjectRoot $projectRoot): AnalysisResults
{
$analysisResultsAsArray = JsonUtils::toArray($resultsAsString);
$analysisResultsBuilder = new AnalysisResultsBuilder();
try {
$errors = ArrayUtils::getArrayValue($analysisResultsAsArray, self::ERRORS);
} catch (ArrayParseException $e) {
throw ParseAtLocationException::issueParsing($e, 'Root node');
}
if (count($errors) > 0) {
try {
ArrayUtils::assertArrayOfStrings($errors);
} catch (ArrayParseException $e) {
throw ParseAtLocationException::issueParsing($e, self::ERRORS);
}
$errorMessage = 'PHPStan failed with errors:'.\PHP_EOL.implode("\n", $errors);
throw new ErrorReportedByStaticAnalysisTool($errorMessage);
}
try {
$filesErrors = ArrayUtils::getArrayValue($analysisResultsAsArray, self::FILES);
} catch (ArrayParseException $e) {
throw ParseAtLocationException::issueParsing($e, 'Root node');
}
/** @psalm-suppress MixedAssignment */
foreach ($filesErrors as $absoluteFileNameAsString => $fileErrors) {
try {
if (!is_string($absoluteFileNameAsString)) {
throw new ArrayParseException('Expected filename to be of type string');
}
ArrayUtils::assertArray($fileErrors);
$absoluteFileName = new AbsoluteFileName($absoluteFileNameAsString);
$messages = ArrayUtils::getArrayValue($fileErrors, self::MESSAGES);
foreach ($messages as $message) {
ArrayUtils::assertArray($message);
$analysisResult = $this->convertAnalysisResultFromArray($message, $absoluteFileName, $projectRoot);
$analysisResultsBuilder->addAnalysisResult($analysisResult);
}
} catch (ArrayParseException|InvalidPathException $e) {
throw ParseAtLocationException::issueParsing($e, "Result [$absoluteFileNameAsString]");
}
}
return $analysisResultsBuilder->build();
}
/**
* @psalm-param array<mixed> $analysisResultAsArray
*
* @throws ArrayParseException
* @throws InvalidPathException
*/
private function convertAnalysisResultFromArray(
array $analysisResultAsArray,
AbsoluteFileName $absoluteFileName,
ProjectRoot $projectRoot,
): AnalysisResult {
$lineAsInt = ArrayUtils::getIntOrNullValue($analysisResultAsArray, self::LINE);
// PHPStan sometimes reports errors not assigned to any line number. In this case give the line number as 0
if (null === $lineAsInt) {
$lineAsInt = 0;
}
$rawType = ArrayUtils::getStringValue($analysisResultAsArray, self::TYPE);
$type = $this->fqcnRemover->removeRqcn($rawType);
$location = Location::fromAbsoluteFileName(
$absoluteFileName,
$projectRoot,
new LineNumber($lineAsInt),
);
return new AnalysisResult(
$location,
new Type($type),
$rawType,
$analysisResultAsArray,
Severity::error(),
);
}
public function getIdentifier(): Identifier
{
return new PhpstanJsonIdentifier();
}
public function showTypeGuessingWarning(): bool
{
return true;
}
}