Skip to content

Instantly share code, notes, and snippets.

@NickSdot
Created August 31, 2025 04:57
Show Gist options
  • Save NickSdot/58d27003b5c76c662bb1bfac550f3517 to your computer and use it in GitHub Desktop.
Save NickSdot/58d27003b5c76c662bb1bfac550f3517 to your computer and use it in GitHub Desktop.
Show the PHPStan error identifier in PHPStorm inspections
image

Steps

  1. Copy the class somewhere in your project
  2. Change the namespace
  3. Add the following to your phpstan.neon
services:
	errorFormatter.checkstyle:
		class: Local\PhpStan\CheckstyleErrorFormatter # change to where you pasted it!

That's it. No other changes compared to the original formatter where made.

This is a PHPStorm bug, a PR to PHPStan doesn't make sense.

<?php
declare(strict_types=1);
namespace Local\PhpStan; // <----------------------------------- change to where you paste it!
use PHPStan\Analyser\Error;
use PHPStan\Command\AnalysisResult;
use PHPStan\Command\ErrorFormatter\ErrorFormatter;
use PHPStan\Command\Output;
use PHPStan\File\RelativePathHelper;
use function count;
use function htmlspecialchars;
use function sprintf;
final readonly class CheckstyleErrorFormatter implements ErrorFormatter
{
public function __construct(private RelativePathHelper $relativePathHelper) {}
public function formatErrors(
AnalysisResult $analysisResult,
Output $output,
): int {
$output->writeRaw('<?xml version="1.0" encoding="UTF-8"?>');
$output->writeLineFormatted('');
$output->writeRaw('<checkstyle>');
$output->writeLineFormatted('');
foreach ($this->groupByFile($analysisResult) as $relativeFilePath => $errors) {
$output->writeRaw(sprintf(
'<file name="%s">',
$this->escape($relativeFilePath),
));
$output->writeLineFormatted('');
foreach ($errors as $error) {
// Note:
// PHPStorm ignores the "source" attribute; also we need better formatting options.
// Go and smash the upvote button here:
// https://youtrack.jetbrains.com/issue/WI-81974/PHPStan-Show-source-of-the-message-too
// https://youtrack.jetbrains.com/issue/WI-78524/PHPStan-Make-it-possible-to-use-more-detailed-error-reporting-formats
$identifier = null !== $error->getIdentifier()
? " // @phpstan-ignore {$error->getIdentifier()}" :
'';
$output->writeRaw(sprintf(
'<error line="%d" column="1" severity="error" message="%s" />',
$this->escape((string) $error->getLine()),
$this->escape($error->getMessage() . $identifier),
));
$output->writeLineFormatted('');
}
$output->writeRaw('</file>');
$output->writeLineFormatted('');
}
$notFileSpecificErrors = $analysisResult->getNotFileSpecificErrors();
if (count($notFileSpecificErrors) > 0) {
$output->writeRaw('<file>');
$output->writeLineFormatted('');
foreach ($notFileSpecificErrors as $error) {
$output->writeRaw(sprintf(' <error severity="error" message="%s" />', $this->escape($error)));
$output->writeLineFormatted('');
}
$output->writeRaw('</file>');
$output->writeLineFormatted('');
}
if ($analysisResult->hasWarnings()) {
$output->writeRaw('<file>');
$output->writeLineFormatted('');
foreach ($analysisResult->getWarnings() as $warning) {
$output->writeRaw(sprintf(' <error severity="warning" message="%s" />', $this->escape($warning)));
$output->writeLineFormatted('');
}
$output->writeRaw('</file>');
$output->writeLineFormatted('');
}
$output->writeRaw('</checkstyle>');
$output->writeLineFormatted('');
return $analysisResult->hasErrors() ? 1 : 0;
}
/** Escapes values for using in XML */
private function escape(string $string): string
{
return htmlspecialchars($string, \ENT_XML1 | \ENT_COMPAT, 'UTF-8');
}
/**
* Group errors by file
*
* @return Error Array that have as key the relative path of file
* and as value an array with occurred errors.
*/
private function groupByFile(AnalysisResult $analysisResult): array
{
$files = [];
/** @var Error $fileSpecificError */
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
$absolutePath = $fileSpecificError->getFilePath();
if (null !== $fileSpecificError->getTraitFilePath()) {
$absolutePath = $fileSpecificError->getTraitFilePath();
}
$relativeFilePath = $this->relativePathHelper->getRelativePath(
$absolutePath,
);
$files[$relativeFilePath][] = $fileSpecificError;
}
return $files;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment