vimeo /
psalm
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | namespace Psalm; |
||
| 3 | |||
| 4 | use function array_filter; |
||
| 5 | use function array_intersect; |
||
| 6 | use function array_map; |
||
| 7 | use function array_merge; |
||
| 8 | use function array_reduce; |
||
| 9 | use function explode; |
||
| 10 | use function get_loaded_extensions; |
||
| 11 | use function implode; |
||
| 12 | use function ksort; |
||
| 13 | use const LIBXML_NOBLANKS; |
||
| 14 | use function min; |
||
| 15 | use const PHP_VERSION; |
||
| 16 | use function phpversion; |
||
| 17 | use function preg_replace_callback; |
||
| 18 | use Psalm\Internal\Analyzer\IssueData; |
||
| 19 | use Psalm\Internal\Provider\FileProvider; |
||
| 20 | use RuntimeException; |
||
| 21 | use function str_replace; |
||
| 22 | use function strpos; |
||
| 23 | use function usort; |
||
| 24 | use function count; |
||
| 25 | use function array_values; |
||
| 26 | |||
| 27 | class ErrorBaseline |
||
| 28 | { |
||
| 29 | /** |
||
| 30 | * @param array<string,array<string,array{o:int, s:array<int, string>}>> $existingIssues |
||
| 31 | * |
||
| 32 | * @return int |
||
| 33 | */ |
||
| 34 | public static function countTotalIssues(array $existingIssues) |
||
| 35 | { |
||
| 36 | $totalIssues = 0; |
||
| 37 | |||
| 38 | foreach ($existingIssues as $existingIssue) { |
||
| 39 | $totalIssues += array_reduce( |
||
| 40 | $existingIssue, |
||
| 41 | /** |
||
| 42 | * @param array{o:int, s:array<int, string>} $existingIssue |
||
| 43 | */ |
||
| 44 | function (int $carry, array $existingIssue): int { |
||
| 45 | return $carry + $existingIssue['o']; |
||
| 46 | }, |
||
| 47 | 0 |
||
| 48 | ); |
||
| 49 | } |
||
| 50 | |||
| 51 | return $totalIssues; |
||
| 52 | } |
||
| 53 | |||
| 54 | /** |
||
| 55 | * @param FileProvider $fileProvider |
||
| 56 | * @param string $baselineFile |
||
| 57 | * @param array<string, list<IssueData>> $issues |
||
| 58 | * |
||
| 59 | * @return void |
||
| 60 | */ |
||
| 61 | public static function create( |
||
| 62 | FileProvider $fileProvider, |
||
| 63 | string $baselineFile, |
||
| 64 | array $issues, |
||
| 65 | bool $include_php_versions |
||
| 66 | ) { |
||
| 67 | $groupedIssues = self::countIssueTypesByFile($issues); |
||
| 68 | |||
| 69 | self::writeToFile($fileProvider, $baselineFile, $groupedIssues, $include_php_versions); |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * @param FileProvider $fileProvider |
||
| 74 | * @param string $baselineFile |
||
| 75 | * |
||
| 76 | * @throws Exception\ConfigException |
||
| 77 | * |
||
| 78 | * @return array<string,array<string,array{o:int, s:array<int, string>}>> |
||
|
0 ignored issues
–
show
|
|||
| 79 | */ |
||
| 80 | public static function read(FileProvider $fileProvider, string $baselineFile): array |
||
| 81 | { |
||
| 82 | if (!$fileProvider->fileExists($baselineFile)) { |
||
| 83 | throw new Exception\ConfigException("{$baselineFile} does not exist or is not readable"); |
||
| 84 | } |
||
| 85 | |||
| 86 | $xmlSource = $fileProvider->getContents($baselineFile); |
||
| 87 | |||
| 88 | $baselineDoc = new \DOMDocument(); |
||
| 89 | $baselineDoc->loadXML($xmlSource, LIBXML_NOBLANKS); |
||
| 90 | |||
| 91 | /** @var \DOMNodeList $filesElement */ |
||
| 92 | $filesElement = $baselineDoc->getElementsByTagName('files'); |
||
| 93 | |||
| 94 | if ($filesElement->length === 0) { |
||
| 95 | throw new Exception\ConfigException('Baseline file does not contain <files>'); |
||
| 96 | } |
||
| 97 | |||
| 98 | $files = []; |
||
| 99 | |||
| 100 | /** @var \DOMElement $filesElement */ |
||
| 101 | $filesElement = $filesElement[0]; |
||
| 102 | |||
| 103 | foreach ($filesElement->getElementsByTagName('file') as $file) { |
||
| 104 | $fileName = $file->getAttribute('src'); |
||
| 105 | |||
| 106 | $fileName = str_replace('\\', '/', $fileName); |
||
| 107 | |||
| 108 | $files[$fileName] = []; |
||
| 109 | |||
| 110 | foreach ($file->childNodes as $issue) { |
||
| 111 | if (!$issue instanceof \DOMElement) { |
||
| 112 | continue; |
||
| 113 | } |
||
| 114 | |||
| 115 | $issueType = $issue->tagName; |
||
| 116 | |||
| 117 | $files[$fileName][$issueType] = [ |
||
| 118 | 'o' => (int)$issue->getAttribute('occurrences'), |
||
| 119 | 's' => [], |
||
| 120 | ]; |
||
| 121 | $codeSamples = $issue->getElementsByTagName('code'); |
||
| 122 | |||
| 123 | foreach ($codeSamples as $codeSample) { |
||
| 124 | $files[$fileName][$issueType]['s'][] = $codeSample->textContent; |
||
| 125 | } |
||
| 126 | } |
||
| 127 | } |
||
| 128 | |||
| 129 | return $files; |
||
| 130 | } |
||
| 131 | |||
| 132 | /** |
||
| 133 | * @param FileProvider $fileProvider |
||
| 134 | * @param string $baselineFile |
||
| 135 | * @param array<string, list<IssueData>> $issues |
||
| 136 | * |
||
| 137 | * @throws Exception\ConfigException |
||
| 138 | * |
||
| 139 | * @return array<string,array<string,array{o:int, s:array<int, string>}>> |
||
|
0 ignored issues
–
show
The doc-type
array<string,array<string,array{o:int, could not be parsed: Unknown type name "array{o:int" at position 26. (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types. Loading history...
|
|||
| 140 | */ |
||
| 141 | public static function update( |
||
| 142 | FileProvider $fileProvider, |
||
| 143 | string $baselineFile, |
||
| 144 | array $issues, |
||
| 145 | bool $include_php_versions |
||
| 146 | ) { |
||
| 147 | $existingIssues = self::read($fileProvider, $baselineFile); |
||
| 148 | $newIssues = self::countIssueTypesByFile($issues); |
||
| 149 | |||
| 150 | foreach ($existingIssues as $file => &$existingIssuesCount) { |
||
| 151 | if (!isset($newIssues[$file])) { |
||
| 152 | unset($existingIssues[$file]); |
||
| 153 | |||
| 154 | continue; |
||
| 155 | } |
||
| 156 | |||
| 157 | foreach ($existingIssuesCount as $issueType => $existingIssueType) { |
||
| 158 | if (!isset($newIssues[$file][$issueType])) { |
||
| 159 | unset($existingIssuesCount[$issueType]); |
||
| 160 | |||
| 161 | continue; |
||
| 162 | } |
||
| 163 | |||
| 164 | $existingIssuesCount[$issueType]['o'] = min( |
||
| 165 | $existingIssueType['o'], |
||
| 166 | $newIssues[$file][$issueType]['o'] |
||
| 167 | ); |
||
| 168 | $existingIssuesCount[$issueType]['s'] = array_intersect( |
||
| 169 | $existingIssueType['s'], |
||
| 170 | $newIssues[$file][$issueType]['s'] |
||
| 171 | ); |
||
| 172 | } |
||
| 173 | } |
||
| 174 | |||
| 175 | $groupedIssues = array_filter($existingIssues); |
||
| 176 | |||
| 177 | self::writeToFile($fileProvider, $baselineFile, $groupedIssues, $include_php_versions); |
||
| 178 | |||
| 179 | return $groupedIssues; |
||
| 180 | } |
||
| 181 | |||
| 182 | /** |
||
| 183 | * @param array<string, list<IssueData>> $issues |
||
| 184 | * |
||
| 185 | * @return array<string,array<string,array{o:int, s:array<int, string>}>> |
||
|
0 ignored issues
–
show
The doc-type
array<string,array<string,array{o:int, could not be parsed: Unknown type name "array{o:int" at position 26. (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types. Loading history...
|
|||
| 186 | */ |
||
| 187 | private static function countIssueTypesByFile(array $issues): array |
||
| 188 | { |
||
| 189 | if ($issues === []) { |
||
| 190 | return []; |
||
| 191 | } |
||
| 192 | $groupedIssues = array_reduce( |
||
| 193 | array_merge(...array_values($issues)), |
||
| 194 | /** |
||
| 195 | * @param array<string,array<string,array{o:int, s:array<int, string>}>> $carry |
||
| 196 | * |
||
| 197 | * @return array<string,array<string,array{o:int, s:array<int, string>}>> |
||
|
0 ignored issues
–
show
The doc-type
array<string,array<string,array{o:int, could not be parsed: Unknown type name "array{o:int" at position 26. (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types. Loading history...
|
|||
| 198 | */ |
||
| 199 | function (array $carry, IssueData $issue): array { |
||
| 200 | if ($issue->severity !== Config::REPORT_ERROR) { |
||
| 201 | return $carry; |
||
| 202 | } |
||
| 203 | |||
| 204 | $fileName = $issue->file_name; |
||
| 205 | $fileName = str_replace('\\', '/', $fileName); |
||
| 206 | $issueType = $issue->type; |
||
| 207 | |||
| 208 | if (!isset($carry[$fileName])) { |
||
| 209 | $carry[$fileName] = []; |
||
| 210 | } |
||
| 211 | |||
| 212 | if (!isset($carry[$fileName][$issueType])) { |
||
| 213 | $carry[$fileName][$issueType] = ['o' => 0, 's' => []]; |
||
| 214 | } |
||
| 215 | |||
| 216 | ++$carry[$fileName][$issueType]['o']; |
||
| 217 | |||
| 218 | if (!strpos($issue->selected_text, "\n")) { |
||
| 219 | $carry[$fileName][$issueType]['s'][] = $issue->selected_text; |
||
| 220 | } |
||
| 221 | |||
| 222 | return $carry; |
||
| 223 | }, |
||
| 224 | [] |
||
| 225 | ); |
||
| 226 | |||
| 227 | // Sort files first |
||
| 228 | ksort($groupedIssues); |
||
| 229 | |||
| 230 | foreach ($groupedIssues as &$issues) { |
||
| 231 | ksort($issues); |
||
| 232 | } |
||
| 233 | |||
| 234 | return $groupedIssues; |
||
| 235 | } |
||
| 236 | |||
| 237 | /** |
||
| 238 | * @param FileProvider $fileProvider |
||
| 239 | * @param string $baselineFile |
||
| 240 | * @param array<string,array<string,array{o:int, s:array<int, string>}>> $groupedIssues |
||
| 241 | * |
||
| 242 | * @return void |
||
| 243 | */ |
||
| 244 | private static function writeToFile( |
||
| 245 | FileProvider $fileProvider, |
||
| 246 | string $baselineFile, |
||
| 247 | array $groupedIssues, |
||
| 248 | bool $include_php_versions |
||
| 249 | ) { |
||
| 250 | $baselineDoc = new \DOMDocument('1.0', 'UTF-8'); |
||
| 251 | $filesNode = $baselineDoc->createElement('files'); |
||
| 252 | $filesNode->setAttribute('psalm-version', PSALM_VERSION); |
||
| 253 | |||
| 254 | if ($include_php_versions) { |
||
| 255 | $extensions = array_merge(get_loaded_extensions(), get_loaded_extensions(true)); |
||
| 256 | |||
| 257 | usort($extensions, 'strnatcasecmp'); |
||
| 258 | |||
| 259 | $filesNode->setAttribute('php-version', implode(';' . "\n\t", array_merge( |
||
| 260 | [ |
||
| 261 | ('php:' . PHP_VERSION), |
||
| 262 | ], |
||
| 263 | array_map( |
||
| 264 | function (string $extension) : string { |
||
| 265 | return $extension . ':' . phpversion($extension); |
||
| 266 | }, |
||
| 267 | $extensions |
||
| 268 | ) |
||
| 269 | ))); |
||
| 270 | } |
||
| 271 | |||
| 272 | foreach ($groupedIssues as $file => $issueTypes) { |
||
| 273 | $fileNode = $baselineDoc->createElement('file'); |
||
| 274 | |||
| 275 | $fileNode->setAttribute('src', $file); |
||
| 276 | |||
| 277 | foreach ($issueTypes as $issueType => $existingIssueType) { |
||
| 278 | $issueNode = $baselineDoc->createElement($issueType); |
||
| 279 | |||
| 280 | $issueNode->setAttribute('occurrences', (string)$existingIssueType['o']); |
||
| 281 | foreach ($existingIssueType['s'] as $selection) { |
||
| 282 | $codeNode = $baselineDoc->createElement('code'); |
||
| 283 | |||
| 284 | $codeNode->textContent = $selection; |
||
| 285 | $issueNode->appendChild($codeNode); |
||
| 286 | } |
||
| 287 | $fileNode->appendChild($issueNode); |
||
| 288 | } |
||
| 289 | |||
| 290 | $filesNode->appendChild($fileNode); |
||
| 291 | } |
||
| 292 | |||
| 293 | $baselineDoc->appendChild($filesNode); |
||
| 294 | $baselineDoc->formatOutput = true; |
||
| 295 | |||
| 296 | $xml = preg_replace_callback( |
||
| 297 | '/<files (psalm-version="[^"]+") (?:php-version="(.+)"(\/?>)\n)/', |
||
| 298 | /** |
||
| 299 | * @param array<int, string> $matches |
||
| 300 | */ |
||
| 301 | function (array $matches) : string { |
||
| 302 | return |
||
| 303 | '<files' . |
||
| 304 | "\n " . |
||
| 305 | $matches[1] . |
||
| 306 | "\n" . |
||
| 307 | ' php-version="' . |
||
| 308 | "\n " . |
||
| 309 | implode("\n ", explode(' 	', $matches[2])) . |
||
| 310 | "\n" . |
||
| 311 | ' "' . |
||
| 312 | "\n" . |
||
| 313 | $matches[3] . |
||
| 314 | "\n"; |
||
| 315 | }, |
||
| 316 | $baselineDoc->saveXML() |
||
| 317 | ); |
||
| 318 | |||
| 319 | if ($xml === null) { |
||
| 320 | throw new RuntimeException('Failed to reformat opening attributes!'); |
||
| 321 | } |
||
| 322 | |||
| 323 | $fileProvider->setContents($baselineFile, $xml); |
||
| 324 | } |
||
| 325 | } |
||
| 326 |
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.