mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
[FEATURE] Allow to intercept adding issue in IssueBuffer
This change introduces new `BeforeAddIssueEvent` which is invoked from `IssueBuffer::add`, which allows to collect and intercept code issue in a generic way. Resolves: #7528
This commit is contained in:
parent
f93bd10c61
commit
e3602bbfe1
@ -81,6 +81,7 @@ class SomePlugin implements \Psalm\Plugin\EventHandler\AfterStatementAnalysisInt
|
||||
- `AfterFunctionLikeAnalysisInterface` - called after Psalm has completed its analysis of a given function-like.
|
||||
- `AfterMethodCallAnalysisInterface` - called after Psalm analyzes a method call.
|
||||
- `AfterStatementAnalysisInterface` - called after Psalm evaluates an statement.
|
||||
- `BeforeAddIssueInterface` - called before Psalm adds an item to it's internal `IssueBuffer`, allows handling code issues individually
|
||||
- `BeforeFileAnalysisInterface` - called before Psalm analyzes a file.
|
||||
- `FunctionExistenceProviderInterface` - can be used to override Psalm's builtin function existence checks for one or more functions.
|
||||
- `FunctionParamsProviderInterface.php` - can be used to override Psalm's builtin function parameter lookup for one or more functions.
|
||||
|
@ -15,6 +15,7 @@ use Psalm\Plugin\EventHandler\AfterFunctionCallAnalysisInterface;
|
||||
use Psalm\Plugin\EventHandler\AfterFunctionLikeAnalysisInterface;
|
||||
use Psalm\Plugin\EventHandler\AfterMethodCallAnalysisInterface;
|
||||
use Psalm\Plugin\EventHandler\AfterStatementAnalysisInterface;
|
||||
use Psalm\Plugin\EventHandler\BeforeAddIssueInterface;
|
||||
use Psalm\Plugin\EventHandler\BeforeFileAnalysisInterface;
|
||||
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterAnalysisEvent;
|
||||
@ -29,6 +30,7 @@ use Psalm\Plugin\EventHandler\Event\AfterFunctionCallAnalysisEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterFunctionLikeAnalysisEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterMethodCallAnalysisEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterStatementAnalysisEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\BeforeAddIssueEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\BeforeFileAnalysisEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\StringInterpreterEvent;
|
||||
use Psalm\Plugin\EventHandler\RemoveTaintsInterface;
|
||||
@ -37,6 +39,7 @@ use Psalm\Type\Atomic\TLiteralString;
|
||||
|
||||
use function array_merge;
|
||||
use function count;
|
||||
use function is_bool;
|
||||
use function is_subclass_of;
|
||||
|
||||
/**
|
||||
@ -122,6 +125,11 @@ class EventDispatcher
|
||||
*/
|
||||
public $after_codebase_populated = [];
|
||||
|
||||
/**
|
||||
* @var list<class-string<BeforeAddIssueInterface>>
|
||||
*/
|
||||
private array $before_add_issue = [];
|
||||
|
||||
/**
|
||||
* Static methods to be called after codebase has been populated
|
||||
*
|
||||
@ -209,6 +217,10 @@ class EventDispatcher
|
||||
$this->after_codebase_populated[] = $class;
|
||||
}
|
||||
|
||||
if (is_subclass_of($class, BeforeAddIssueInterface::class)) {
|
||||
$this->before_add_issue[] = $class;
|
||||
}
|
||||
|
||||
if (is_subclass_of($class, AfterAnalysisInterface::class)) {
|
||||
$this->after_analysis[] = $class;
|
||||
}
|
||||
@ -330,6 +342,17 @@ class EventDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
public function dispatchBeforeAddIssue(BeforeAddIssueEvent $event): ?bool
|
||||
{
|
||||
foreach ($this->before_add_issue as $handler) {
|
||||
$result = $handler::beforeAddIssue($event);
|
||||
if (is_bool($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function dispatchAfterAnalysis(AfterAnalysisEvent $event): void
|
||||
{
|
||||
foreach ($this->after_analysis as $handler) {
|
||||
|
@ -16,6 +16,7 @@ use Psalm\Issue\MixedIssue;
|
||||
use Psalm\Issue\TaintedInput;
|
||||
use Psalm\Issue\UnusedPsalmSuppress;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterAnalysisEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\BeforeAddIssueEvent;
|
||||
use Psalm\Report\CheckstyleReport;
|
||||
use Psalm\Report\CodeClimateReport;
|
||||
use Psalm\Report\CompactReport;
|
||||
@ -250,6 +251,11 @@ class IssueBuffer
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
|
||||
$event = new BeforeAddIssueEvent($e, $is_fixable);
|
||||
if ($config->eventDispatcher->dispatchBeforeAddIssue($event) === false) {
|
||||
return false;
|
||||
};
|
||||
|
||||
$fqcn_parts = explode('\\', get_class($e));
|
||||
$issue_type = array_pop($fqcn_parts);
|
||||
|
||||
|
21
src/Psalm/Plugin/EventHandler/BeforeAddIssueInterface.php
Normal file
21
src/Psalm/Plugin/EventHandler/BeforeAddIssueInterface.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psalm\Plugin\EventHandler;
|
||||
|
||||
use Psalm\Plugin\EventHandler\Event\BeforeAddIssueEvent;
|
||||
|
||||
interface BeforeAddIssueInterface
|
||||
{
|
||||
/**
|
||||
* Called before adding a code issue.
|
||||
*
|
||||
* @param BeforeAddIssueEvent $event
|
||||
* @return null|bool $event How and whether to continue:
|
||||
* + `null` continues with next event handler
|
||||
* + `true` stops event handling & keeps issue
|
||||
* + `false` stops event handling & ignores issue
|
||||
*/
|
||||
public static function beforeAddIssue(BeforeAddIssueEvent $event): ?bool;
|
||||
}
|
36
src/Psalm/Plugin/EventHandler/Event/BeforeAddIssueEvent.php
Normal file
36
src/Psalm/Plugin/EventHandler/Event/BeforeAddIssueEvent.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psalm\Plugin\EventHandler\Event;
|
||||
|
||||
use Psalm\Issue\CodeIssue;
|
||||
|
||||
class BeforeAddIssueEvent
|
||||
{
|
||||
/**
|
||||
* @var CodeIssue
|
||||
*/
|
||||
private CodeIssue $issue;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $fixable;
|
||||
|
||||
public function __construct(CodeIssue $issue, bool $fixable)
|
||||
{
|
||||
$this->issue = $issue;
|
||||
$this->fixable = $fixable;
|
||||
}
|
||||
|
||||
public function getIssue(): CodeIssue
|
||||
{
|
||||
return $this->issue;
|
||||
}
|
||||
|
||||
public function isFixable(): bool
|
||||
{
|
||||
return $this->fixable;
|
||||
}
|
||||
}
|
@ -7,8 +7,13 @@ use PhpParser\Node\Stmt\Class_;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Context;
|
||||
use Psalm\Exception\UnpopulatedClasslikeException;
|
||||
use Psalm\Issue\InvalidReturnStatement;
|
||||
use Psalm\Issue\InvalidReturnType;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
|
||||
use Psalm\Plugin\EventHandler\BeforeAddIssueInterface;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\BeforeAddIssueEvent;
|
||||
use Psalm\PluginRegistrationSocket;
|
||||
use Psalm\Tests\Internal\Provider\ClassLikeStorageInstanceCacheProvider;
|
||||
use Psalm\Type;
|
||||
@ -207,4 +212,45 @@ class CodebaseTest extends TestCase
|
||||
|
||||
$this->codebase->classExtends('A', 'B');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function addingCodeIssueIsIntercepted(): void
|
||||
{
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
namespace Psalm\CurrentTest;
|
||||
function invalidReturnType(int $value): string
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
echo invalidReturnType(123);
|
||||
'
|
||||
);
|
||||
|
||||
$eventHandler = new class implements BeforeAddIssueInterface
|
||||
{
|
||||
public static function beforeAddIssue(BeforeAddIssueEvent $event): ?bool
|
||||
{
|
||||
$issue = $event->getIssue();
|
||||
if ($issue->code_location->file_path !== 'somefile.php') {
|
||||
return null;
|
||||
}
|
||||
if ($issue instanceof InvalidReturnStatement && $event->isFixable() === false) {
|
||||
return false;
|
||||
} elseif ($issue instanceof InvalidReturnType && $event->isFixable() === true) {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
(new PluginRegistrationSocket($this->codebase->config, $this->codebase))
|
||||
->registerHooksFromClass(get_class($eventHandler));
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context);
|
||||
self::assertSame(0, IssueBuffer::getErrorCount());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user