mirror of
https://github.com/danog/psalm.git
synced 2024-12-02 09:37:59 +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.
|
- `AfterFunctionLikeAnalysisInterface` - called after Psalm has completed its analysis of a given function-like.
|
||||||
- `AfterMethodCallAnalysisInterface` - called after Psalm analyzes a method call.
|
- `AfterMethodCallAnalysisInterface` - called after Psalm analyzes a method call.
|
||||||
- `AfterStatementAnalysisInterface` - called after Psalm evaluates an statement.
|
- `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.
|
- `BeforeFileAnalysisInterface` - called before Psalm analyzes a file.
|
||||||
- `FunctionExistenceProviderInterface` - can be used to override Psalm's builtin function existence checks for one or more functions.
|
- `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.
|
- `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\AfterFunctionLikeAnalysisInterface;
|
||||||
use Psalm\Plugin\EventHandler\AfterMethodCallAnalysisInterface;
|
use Psalm\Plugin\EventHandler\AfterMethodCallAnalysisInterface;
|
||||||
use Psalm\Plugin\EventHandler\AfterStatementAnalysisInterface;
|
use Psalm\Plugin\EventHandler\AfterStatementAnalysisInterface;
|
||||||
|
use Psalm\Plugin\EventHandler\BeforeAddIssueInterface;
|
||||||
use Psalm\Plugin\EventHandler\BeforeFileAnalysisInterface;
|
use Psalm\Plugin\EventHandler\BeforeFileAnalysisInterface;
|
||||||
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
|
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
|
||||||
use Psalm\Plugin\EventHandler\Event\AfterAnalysisEvent;
|
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\AfterFunctionLikeAnalysisEvent;
|
||||||
use Psalm\Plugin\EventHandler\Event\AfterMethodCallAnalysisEvent;
|
use Psalm\Plugin\EventHandler\Event\AfterMethodCallAnalysisEvent;
|
||||||
use Psalm\Plugin\EventHandler\Event\AfterStatementAnalysisEvent;
|
use Psalm\Plugin\EventHandler\Event\AfterStatementAnalysisEvent;
|
||||||
|
use Psalm\Plugin\EventHandler\Event\BeforeAddIssueEvent;
|
||||||
use Psalm\Plugin\EventHandler\Event\BeforeFileAnalysisEvent;
|
use Psalm\Plugin\EventHandler\Event\BeforeFileAnalysisEvent;
|
||||||
use Psalm\Plugin\EventHandler\Event\StringInterpreterEvent;
|
use Psalm\Plugin\EventHandler\Event\StringInterpreterEvent;
|
||||||
use Psalm\Plugin\EventHandler\RemoveTaintsInterface;
|
use Psalm\Plugin\EventHandler\RemoveTaintsInterface;
|
||||||
@ -37,6 +39,7 @@ use Psalm\Type\Atomic\TLiteralString;
|
|||||||
|
|
||||||
use function array_merge;
|
use function array_merge;
|
||||||
use function count;
|
use function count;
|
||||||
|
use function is_bool;
|
||||||
use function is_subclass_of;
|
use function is_subclass_of;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,6 +125,11 @@ class EventDispatcher
|
|||||||
*/
|
*/
|
||||||
public $after_codebase_populated = [];
|
public $after_codebase_populated = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var list<class-string<BeforeAddIssueInterface>>
|
||||||
|
*/
|
||||||
|
private array $before_add_issue = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static methods to be called after codebase has been populated
|
* Static methods to be called after codebase has been populated
|
||||||
*
|
*
|
||||||
@ -209,6 +217,10 @@ class EventDispatcher
|
|||||||
$this->after_codebase_populated[] = $class;
|
$this->after_codebase_populated[] = $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_subclass_of($class, BeforeAddIssueInterface::class)) {
|
||||||
|
$this->before_add_issue[] = $class;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_subclass_of($class, AfterAnalysisInterface::class)) {
|
if (is_subclass_of($class, AfterAnalysisInterface::class)) {
|
||||||
$this->after_analysis[] = $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
|
public function dispatchAfterAnalysis(AfterAnalysisEvent $event): void
|
||||||
{
|
{
|
||||||
foreach ($this->after_analysis as $handler) {
|
foreach ($this->after_analysis as $handler) {
|
||||||
|
@ -16,6 +16,7 @@ use Psalm\Issue\MixedIssue;
|
|||||||
use Psalm\Issue\TaintedInput;
|
use Psalm\Issue\TaintedInput;
|
||||||
use Psalm\Issue\UnusedPsalmSuppress;
|
use Psalm\Issue\UnusedPsalmSuppress;
|
||||||
use Psalm\Plugin\EventHandler\Event\AfterAnalysisEvent;
|
use Psalm\Plugin\EventHandler\Event\AfterAnalysisEvent;
|
||||||
|
use Psalm\Plugin\EventHandler\Event\BeforeAddIssueEvent;
|
||||||
use Psalm\Report\CheckstyleReport;
|
use Psalm\Report\CheckstyleReport;
|
||||||
use Psalm\Report\CodeClimateReport;
|
use Psalm\Report\CodeClimateReport;
|
||||||
use Psalm\Report\CompactReport;
|
use Psalm\Report\CompactReport;
|
||||||
@ -250,6 +251,11 @@ class IssueBuffer
|
|||||||
{
|
{
|
||||||
$config = Config::getInstance();
|
$config = Config::getInstance();
|
||||||
|
|
||||||
|
$event = new BeforeAddIssueEvent($e, $is_fixable);
|
||||||
|
if ($config->eventDispatcher->dispatchBeforeAddIssue($event) === false) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
$fqcn_parts = explode('\\', get_class($e));
|
$fqcn_parts = explode('\\', get_class($e));
|
||||||
$issue_type = array_pop($fqcn_parts);
|
$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\Codebase;
|
||||||
use Psalm\Context;
|
use Psalm\Context;
|
||||||
use Psalm\Exception\UnpopulatedClasslikeException;
|
use Psalm\Exception\UnpopulatedClasslikeException;
|
||||||
|
use Psalm\Issue\InvalidReturnStatement;
|
||||||
|
use Psalm\Issue\InvalidReturnType;
|
||||||
|
use Psalm\IssueBuffer;
|
||||||
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
|
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
|
||||||
|
use Psalm\Plugin\EventHandler\BeforeAddIssueInterface;
|
||||||
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
|
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
|
||||||
|
use Psalm\Plugin\EventHandler\Event\BeforeAddIssueEvent;
|
||||||
use Psalm\PluginRegistrationSocket;
|
use Psalm\PluginRegistrationSocket;
|
||||||
use Psalm\Tests\Internal\Provider\ClassLikeStorageInstanceCacheProvider;
|
use Psalm\Tests\Internal\Provider\ClassLikeStorageInstanceCacheProvider;
|
||||||
use Psalm\Type;
|
use Psalm\Type;
|
||||||
@ -207,4 +212,45 @@ class CodebaseTest extends TestCase
|
|||||||
|
|
||||||
$this->codebase->classExtends('A', 'B');
|
$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