|
19 | 19 | use function arsort; |
20 | 20 | use function count; |
21 | 21 | use function file_put_contents; |
22 | | -use function implode; |
23 | 22 | use function is_file; |
24 | 23 | use function max; |
25 | 24 | use function mb_strwidth; |
@@ -315,18 +314,27 @@ private function renderFindings(SymfonyStyle $io, array $errors): void |
315 | 314 | } |
316 | 315 | } |
317 | 316 |
|
318 | | - // PHPStan-style horizontal rules (no vertical borders): rule, header, rule, body, rule. |
319 | | - $rule = $this->rule(array_merge([$header], $body)); |
320 | | - $io->writeln($rule); |
321 | | - $io->writeln($header); |
322 | | - $io->writeln($rule); |
323 | | - foreach ($body as $line) { |
324 | | - $io->writeln($line); |
325 | | - } |
| 317 | + $this->renderBlock($io, $header, $body); |
| 318 | + } |
| 319 | + } |
326 | 320 |
|
327 | | - $io->writeln($rule); |
328 | | - $io->newLine(); |
| 321 | + /** |
| 322 | + * PHPStan-style horizontal rules (no vertical borders): rule, header, rule, body, rule. |
| 323 | + * |
| 324 | + * @param list<string> $body |
| 325 | + */ |
| 326 | + private function renderBlock(SymfonyStyle $io, string $header, array $body): void |
| 327 | + { |
| 328 | + $rule = $this->rule(array_merge([$header], $body)); |
| 329 | + $io->writeln($rule); |
| 330 | + $io->writeln($header); |
| 331 | + $io->writeln($rule); |
| 332 | + foreach ($body as $line) { |
| 333 | + $io->writeln($line); |
329 | 334 | } |
| 335 | + |
| 336 | + $io->writeln($rule); |
| 337 | + $io->newLine(); |
330 | 338 | } |
331 | 339 |
|
332 | 340 | /** |
@@ -458,44 +466,64 @@ private function renderWarningsAndUnmatched(SymfonyStyle $io, array $warnings, a |
458 | 466 | $io->warning($warning->getMessage()); |
459 | 467 | } |
460 | 468 |
|
461 | | - foreach ($unmatched as $ignore) { |
462 | | - $io->error('Ignored error never matched: ' . $this->describeIgnore($ignore)); |
| 469 | + if ($unmatched === []) { |
| 470 | + return; |
463 | 471 | } |
464 | | - } |
465 | 472 |
|
466 | | - private function renderFooter(SymfonyStyle $io, float $elapsed, int $peakBytes): void |
467 | | - { |
468 | | - $io->writeln(sprintf('⏱ Time: %.2fs 💾 Memory: %.1f MB', $elapsed, $peakBytes / 1_048_576)); |
| 473 | + $io->writeln(' <fg=red>These ignored errors never matched — remove them from the baseline:</>'); |
| 474 | + $io->newLine(); |
| 475 | + |
| 476 | + foreach ($unmatched as $ignore) { |
| 477 | + $this->renderBlock($io, $this->ignoreHeader($ignore), $this->ignoreBody($ignore)); |
| 478 | + } |
469 | 479 | } |
470 | 480 |
|
471 | | - private function describeIgnore(IgnoredError $ignore): string |
| 481 | + private function ignoreHeader(IgnoredError $ignore): string |
472 | 482 | { |
473 | | - $parts = []; |
474 | | - if ($ignore->getRawMessage() !== null) { |
475 | | - $parts[] = 'rawMessage=' . $ignore->getRawMessage(); |
476 | | - } |
| 483 | + $table = $ignore->getTable(); |
| 484 | + $column = $ignore->getColumn(); |
477 | 485 |
|
478 | | - if ($ignore->getMessage() !== null) { |
479 | | - $parts[] = 'message=' . $ignore->getMessage(); |
| 486 | + if ($table !== null) { |
| 487 | + $ref = '[' . $table . ']' . ($column !== null ? '[' . $column . ']' : ''); |
| 488 | + } elseif ($column !== null) { |
| 489 | + $ref = '[' . $column . ']'; |
| 490 | + } else { |
| 491 | + $ref = '[any source]'; |
480 | 492 | } |
481 | 493 |
|
482 | | - if ($ignore->getKey() !== null) { |
483 | | - $parts[] = 'key=' . $ignore->getKey(); |
484 | | - } |
| 494 | + return ' ' . $this->bracketize($ref, 'red'); |
| 495 | + } |
485 | 496 |
|
486 | | - if ($ignore->getTable() !== null) { |
487 | | - $parts[] = 'table=' . $ignore->getTable(); |
| 497 | + /** |
| 498 | + * @return list<string> |
| 499 | + */ |
| 500 | + private function ignoreBody(IgnoredError $ignore): array |
| 501 | + { |
| 502 | + $rawMessage = $ignore->getRawMessage(); |
| 503 | + $message = $ignore->getMessage(); |
| 504 | + if ($rawMessage !== null) { |
| 505 | + $body = [' 🚫 ' . $this->highlight($rawMessage)]; |
| 506 | + } elseif ($message !== null) { |
| 507 | + // A regex pattern, not a literal message — shown verbatim, without token highlighting. |
| 508 | + $body = [' 🚫 ' . $message]; |
| 509 | + } else { |
| 510 | + $body = [' 🚫 <fg=gray>(matches any message)</>']; |
488 | 511 | } |
489 | 512 |
|
490 | | - if ($ignore->getColumn() !== null) { |
491 | | - $parts[] = 'column=' . $ignore->getColumn(); |
| 513 | + if ($ignore->getKey() !== null) { |
| 514 | + $body[] = ' <fg=white>🪪 ' . $ignore->getKey() . '</>'; |
492 | 515 | } |
493 | 516 |
|
494 | 517 | if ($ignore->getCount() !== null) { |
495 | | - $parts[] = 'count=' . $ignore->getCount(); |
| 518 | + $body[] = ' <fg=gray>count: ' . $ignore->getCount() . '</>'; |
496 | 519 | } |
497 | 520 |
|
498 | | - return implode(', ', $parts); |
| 521 | + return $body; |
| 522 | + } |
| 523 | + |
| 524 | + private function renderFooter(SymfonyStyle $io, float $elapsed, int $peakBytes): void |
| 525 | + { |
| 526 | + $io->writeln(sprintf('⏱ Time: %.2fs 💾 Memory: %.1f MB', $elapsed, $peakBytes / 1_048_576)); |
499 | 527 | } |
500 | 528 |
|
501 | 529 | } |
0 commit comments