Skip to content

Instantly share code, notes, and snippets.

@paranoiq
Last active April 5, 2026 12:41
Show Gist options
  • Select an option

  • Save paranoiq/d4a5c5396af2019cb367100e7b3dffbb to your computer and use it in GitHub Desktop.

Select an option

Save paranoiq/d4a5c5396af2019cb367100e7b3dffbb to your computer and use it in GitHub Desktop.
PHPStan output tweaker - version, colors, better dividers
<?php
// Ensure output is treated as a TTY/ANSI capable stream
// Usage: phpstan analyze --ansi | php phpstan-tweak.php
$width = static function (): int
{
static $col;
if ($col !== null) {
return $col;
}
$col = (int) @exec('tput cols');
if ($col !== 0) {
return $col;
}
if (PHP_OS_FAMILY === 'Windows') {
exec('mode CON', $output);
[, $col] = explode(':', $output[4]);
$col = (int) trim($col);
}
return $col = $col ?: 120;
};
// PHPStan version
$version = null;
$lockFile = __DIR__ . '/../composer.lock';
if (file_exists($lockFile)) {
$data = json_decode(file_get_contents($lockFile), true, 512, JSON_THROW_ON_ERROR);
$packages = array_merge($data['packages'] ?? [], $data['packages-dev'] ?? []);
foreach ($packages as $package) {
if ($package['name'] === 'phpstan/phpstan') {
$version = $package['version'];
break;
}
}
}
echo " \e[32mPHPStan {$version}\e[39m on PHP " . PHP_VERSION . "\n\n";
while ($line = fgets(STDIN)) {
// drop less useful tips
if (str_contains($line, 'user-guide/discovering-symbols')) {
continue;
}
// remove original separators
if (preg_match('~^ ------ -+\s+$~', $line)) {
continue;
}
// add better
if (preg_match("~\e\[32mLine\e\[39m~", $line)) {
$line .= " ------ " . str_repeat('-', $width() - 9) . "\n";
}
// highlight rule identifiers (e.g., ? class.notFound)
$line = preg_replace('/?\s*([a-zA-Z0-9._-]+)/u', "\e[30m$0\e[0m", $line);
// highlight variables
$line = preg_replace('/(\\$\w+)/u', "\e[33m$0\e[0m", $line);
// highlight types
$types = 'bool|true|false|null|scalar|empty-scalar|non-empty-scalar'
. '|(?:non-)?(?:negative-|positive-|zero-)?int(?:<(?:\d+|min|max)>)?|float|number|numeric'
. '|(?:non-empty-|non-falsy-|numeric-|class-|callable-|)string|array-key'
. '|(?:non-empty-)?(?:array|list|iterable|key-of|value-of)(?:<[^>]>|\\[\\]|{[^}]})?' // todo: nested <>, collections
. '|object|resource|callable|mixed|never|void|self|static|parent';
$codePattern = '/(::[a-zA-Z0-9_]+)|([a-zA-Z0-9_]+\(\))|([a-zA-Z0-9_\\\|]*[\\\|][a-zA-Z0-9_\\\|]+)|(\b(' . $types . ')\b)/';
$line = preg_replace_callback($codePattern, static function ($m): string {
$str = $m[0];
$str = preg_replace('~[|&<>[\\]]~', "\e[95m$0\e[34m", $str);
return "\e[34m{$str}\e[0m";
}, $line);
echo $line;
}
@paranoiq
Copy link
Copy Markdown
Author

paranoiq commented Apr 5, 2026

image

@paranoiq
Copy link
Copy Markdown
Author

paranoiq commented Apr 5, 2026

in ide with light background:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment