refactor: generalize SuppressHandler

This commit is contained in:
Claas Augner 2021-07-13 12:59:06 +02:00
parent fc80186694
commit aebbf15e48

View File

@ -2,73 +2,108 @@
namespace Psalm\LaravelPlugin\Handlers;
use Illuminate\Console\Command;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Notifications\Notification;
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\MethodStorage;
use function array_key_exists;
use Psalm\Storage\PropertyStorage;
use function array_intersect;
use function in_array;
class SuppressHandler implements AfterClassLikeVisitInterface
{
private const UNUSED_CLASSES = [
"App\Console\Kernel",
"App\Exceptions\Handler",
"App\Http\Controllers\Controller",
"App\Http\Kernel",
"App\Http\Middleware\Authenticate",
"App\Http\Middleware\TrustHosts",
"App\Providers\AppServiceProvider",
"App\Providers\AuthServiceProvider",
"App\Providers\BroadcastServiceProvider",
"App\Providers\EventServiceProvider",
/**
* @var array<string, list<class-string>>
*/
private const BY_CLASS = [
'UnusedClass' => [
'App\Console\Kernel',
'App\Exceptions\Handler',
'App\Http\Controllers\Controller',
'App\Http\Kernel',
'App\Http\Middleware\Authenticate',
'App\Http\Middleware\TrustHosts',
'App\Providers\AppServiceProvider',
'App\Providers\AuthServiceProvider',
'App\Providers\BroadcastServiceProvider',
'App\Providers\EventServiceProvider',
],
];
private const UNUSED_METHODS = [
"App\Http\Middleware\RedirectIfAuthenticated" => ["handle"],
/**
* @var array<string, array<class-string, list<string>>>
*/
private const BY_CLASS_METHOD = [
'PossiblyUnusedMethod' => [
'App\Http\Middleware\RedirectIfAuthenticated' => ['handle'],
],
];
/**
* @var array<string, list<class-string>>
*/
private const BY_PARENT_CLASS = [
'PropertyNotSetInConstructor' => [
'Illuminate\Console\Command',
'Illuminate\Foundation\Http\FormRequest',
'Illuminate\Notifications\Notification',
],
];
/**
* @var array<string, array<class-string, list<string>>>
*/
private const BY_PARENT_CLASS_PROPERTY = [
'NonInvariantDocblockPropertyType' => [
'Illuminate\Console\Command' => ['description'],
],
];
public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event)
{
$storage = $event->getStorage();
$class = $event->getStorage();
if (in_array(Command::class, $storage->parent_classes)) {
if (!in_array('PropertyNotSetInConstructor', $storage->suppressed_issues)) {
$storage->suppressed_issues[] = 'PropertyNotSetInConstructor';
foreach (self::BY_CLASS as $issue => $class_names) {
if (in_array($class->name, $class_names)) {
self::suppress($issue, $class);
}
if (isset($storage->properties['description'])) {
$property = $storage->properties['description'];
if (!in_array('NonInvariantDocblockPropertyType', $property->suppressed_issues)) {
$property->suppressed_issues[] = 'NonInvariantDocblockPropertyType';
}
foreach (self::BY_CLASS_METHOD as $issue => $method_by_class) {
foreach ($method_by_class[$class->name] ?? [] as $method_name) {
self::suppress($issue, $class->methods[$method_name] ?? null);
}
}
foreach (self::BY_PARENT_CLASS as $issue => $parent_classes) {
if (!array_intersect($class->parent_classes, $parent_classes)) {
continue;
}
self::suppress($issue, $class);
}
foreach (self::BY_PARENT_CLASS_PROPERTY as $issue => $methods_by_parent_class) {
foreach ($methods_by_parent_class as $parent_class => $property_names) {
if (!in_array($parent_class, $class->parent_classes)) {
continue;
}
}
}
// FormRequest: suppress PropertyNotSetInConstructor.
if (in_array(FormRequest::class, $storage->parent_classes) && !in_array('PropertyNotSetInConstructor', $storage->suppressed_issues)) {
$storage->suppressed_issues[] = 'PropertyNotSetInConstructor';
}
// Notification: suppress PropertyNotSetInConstructor.
if (in_array(Notification::class, $storage->parent_classes) && !in_array('PropertyNotSetInConstructor', $storage->suppressed_issues)) {
$storage->suppressed_issues[] = 'PropertyNotSetInConstructor';
}
// Suppress UnusedClass on well-known classes.
if (in_array($storage->name, self::UNUSED_CLASSES)) {
$storage->suppressed_issues[] = 'UnusedClass';
}
// Suppress PossiblyUnusedMethod on well-known methods.
if (array_key_exists($storage->name, self::UNUSED_METHODS)) {
foreach (self::UNUSED_METHODS[$storage->name] as $method_name) {
$method = $storage->methods[$method_name] ?? null;
if ($method instanceof MethodStorage) {
$method->suppressed_issues[] = 'PossiblyUnusedMethod';
foreach ($property_names as $property_name) {
self::suppress($issue, $class->properties[$property_name] ?? null);
}
}
}
}
/**
* @param string $issue
* @param ClassLikeStorage|PropertyStorage|MethodStorage|null $storage
*/
private static function suppress(string $issue, $storage): void
{
if ($storage && !in_array($issue, $storage->suppressed_issues)) {
$storage->suppressed_issues[] = $issue;
}
}
}