From f5986950a2e90a3547db11bdd02d3c18febd5dc3 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Sun, 30 Jan 2022 19:11:58 +0100 Subject: [PATCH 1/2] [FEATURE] Introduce BeforeStatementAnalysisEvent As counterpart to existing `AfterStatementAnalysisEvent` - invoked in `\Psalm\Internal\Analyzer\StatementsAnalyzer` - this changed introcued a corresponding `BeforeStatementAnalysisEvent`. Resolves: #7534 --- .../plugins/authoring_plugins.md | 1 + .../Internal/Analyzer/StatementsAnalyzer.php | 74 +++++++++++++---- src/Psalm/Internal/EventDispatcher.php | 23 +++++ .../BeforeStatementAnalysisInterface.php | 19 +++++ .../Event/BeforeStatementAnalysisEvent.php | 83 +++++++++++++++++++ 5 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 src/Psalm/Plugin/EventHandler/BeforeStatementAnalysisInterface.php create mode 100644 src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php diff --git a/docs/running_psalm/plugins/authoring_plugins.md b/docs/running_psalm/plugins/authoring_plugins.md index d08197c4b7b..9a54ff91d1a 100644 --- a/docs/running_psalm/plugins/authoring_plugins.md +++ b/docs/running_psalm/plugins/authoring_plugins.md @@ -80,6 +80,7 @@ class SomePlugin implements \Psalm\Plugin\EventHandler\AfterStatementAnalysisInt - `AfterFunctionCallAnalysisInterface` - called after Psalm evaluates a function call to any function defined within the project itself. Can alter the return type or perform modifications of the call. - `AfterFunctionLikeAnalysisInterface` - called after Psalm has completed its analysis of a given function-like. - `AfterMethodCallAnalysisInterface` - called after Psalm analyzes a method call. +- `BeforeStatementAnalysisInterface` - called before Psalm evaluates an statement. - `AfterStatementAnalysisInterface` - called after Psalm evaluates an statement. - `BeforeFileAnalysisInterface` - called before Psalm analyzes a file. - `FunctionExistenceProviderInterface` - can be used to override Psalm's builtin function existence checks for one or more functions. diff --git a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php index 2881e5273bf..c2ba959b28a 100644 --- a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php @@ -56,6 +56,7 @@ use Psalm\IssueBuffer; use Psalm\NodeTypeProvider; use Psalm\Plugin\EventHandler\Event\AfterStatementAnalysisEvent; +use Psalm\Plugin\EventHandler\Event\BeforeStatementAnalysisEvent; use Psalm\Type; use UnexpectedValueException; @@ -353,6 +354,10 @@ private static function analyzeStatement( Context $context, ?Context $global_context ): ?bool { + if (self::dispatchBeforeStatementAnalysis($stmt, $context, $statements_analyzer) === false) { + return false; + } + $ignore_variable_property = false; $ignore_variable_method = false; @@ -619,25 +624,10 @@ private static function analyzeStatement( } } - $codebase = $statements_analyzer->getCodebase(); - - $event = new AfterStatementAnalysisEvent( - $stmt, - $context, - $statements_analyzer, - $codebase, - [] - ); - - if ($codebase->config->eventDispatcher->dispatchAfterStatementAnalysis($event) === false) { + if (self::dispatchAfterStatementAnalysis($stmt, $context, $statements_analyzer) === false) { return false; } - $file_manipulations = $event->getFileReplacements(); - if ($file_manipulations) { - FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); - } - if ($new_issues) { $statements_analyzer->removeSuppressedIssues($new_issues); } @@ -673,6 +663,58 @@ private static function analyzeStatement( return null; } + private static function dispatchAfterStatementAnalysis( + PhpParser\Node\Stmt $stmt, + Context $context, + StatementsAnalyzer $statements_analyzer + ): ?bool { + $codebase = $statements_analyzer->getCodebase(); + + $event = new AfterStatementAnalysisEvent( + $stmt, + $context, + $statements_analyzer, + $codebase, + [] + ); + + if ($codebase->config->eventDispatcher->dispatchAfterStatementAnalysis($event) === false) { + return false; + } + + $file_manipulations = $event->getFileReplacements(); + if ($file_manipulations) { + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + return null; + } + + private static function dispatchBeforeStatementAnalysis( + PhpParser\Node\Stmt $stmt, + Context $context, + StatementsAnalyzer $statements_analyzer + ): ?bool { + $codebase = $statements_analyzer->getCodebase(); + + $event = new BeforeStatementAnalysisEvent( + $stmt, + $context, + $statements_analyzer, + $codebase, + [] + ); + + if ($codebase->config->eventDispatcher->dispatchBeforeStatementAnalysis($event) === false) { + return false; + } + + $file_manipulations = $event->getFileReplacements(); + if ($file_manipulations) { + FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations); + } + return null; + } + private function parseStatementDocblock( PhpParser\Comment\Doc $docblock, PhpParser\Node\Stmt $stmt, diff --git a/src/Psalm/Internal/EventDispatcher.php b/src/Psalm/Internal/EventDispatcher.php index 29e8067490d..d1845f45066 100644 --- a/src/Psalm/Internal/EventDispatcher.php +++ b/src/Psalm/Internal/EventDispatcher.php @@ -16,6 +16,7 @@ use Psalm\Plugin\EventHandler\AfterMethodCallAnalysisInterface; use Psalm\Plugin\EventHandler\AfterStatementAnalysisInterface; use Psalm\Plugin\EventHandler\BeforeFileAnalysisInterface; +use Psalm\Plugin\EventHandler\BeforeStatementAnalysisInterface; use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent; use Psalm\Plugin\EventHandler\Event\AfterAnalysisEvent; use Psalm\Plugin\EventHandler\Event\AfterClassLikeAnalysisEvent; @@ -30,6 +31,7 @@ use Psalm\Plugin\EventHandler\Event\AfterMethodCallAnalysisEvent; use Psalm\Plugin\EventHandler\Event\AfterStatementAnalysisEvent; use Psalm\Plugin\EventHandler\Event\BeforeFileAnalysisEvent; +use Psalm\Plugin\EventHandler\Event\BeforeStatementAnalysisEvent; use Psalm\Plugin\EventHandler\Event\StringInterpreterEvent; use Psalm\Plugin\EventHandler\RemoveTaintsInterface; use Psalm\Plugin\EventHandler\StringInterpreterInterface; @@ -80,6 +82,13 @@ class EventDispatcher */ public $after_expression_checks = []; + /** + * Static methods to be called before statement checks are processed + * + * @var list> + */ + public $before_statement_checks = []; + /** * Static methods to be called after statement checks have completed * @@ -185,6 +194,10 @@ public function registerClass(string $class): void $this->after_expression_checks[] = $class; } + if (is_subclass_of($class, BeforeStatementAnalysisInterface::class)) { + $this->before_statement_checks[] = $class; + } + if (is_subclass_of($class, AfterStatementAnalysisInterface::class)) { $this->after_statement_checks[] = $class; } @@ -271,6 +284,16 @@ public function dispatchAfterExpressionAnalysis(AfterExpressionAnalysisEvent $ev return null; } + public function dispatchBeforeStatementAnalysis(BeforeStatementAnalysisEvent $event): ?bool + { + foreach ($this->before_statement_checks as $handler) { + if ($handler::beforeStatementAnalysis($event) === false) { + return false; + } + } + return null; + } + public function dispatchAfterStatementAnalysis(AfterStatementAnalysisEvent $event): ?bool { foreach ($this->after_statement_checks as $handler) { diff --git a/src/Psalm/Plugin/EventHandler/BeforeStatementAnalysisInterface.php b/src/Psalm/Plugin/EventHandler/BeforeStatementAnalysisInterface.php new file mode 100644 index 00000000000..f5ed8bb24fa --- /dev/null +++ b/src/Psalm/Plugin/EventHandler/BeforeStatementAnalysisInterface.php @@ -0,0 +1,19 @@ + + */ + private array $file_replacements; + + /** + * Called after a statement has been checked + * + * @param list $file_replacements + */ + public function __construct( + Stmt $stmt, + Context $context, + StatementsSource $statements_source, + Codebase $codebase, + array $file_replacements = [] + ) { + $this->stmt = $stmt; + $this->context = $context; + $this->statements_source = $statements_source; + $this->codebase = $codebase; + $this->file_replacements = $file_replacements; + } + + public function getStmt(): Stmt + { + return $this->stmt; + } + + public function setStmt(Stmt $stmt): void + { + $this->stmt = $stmt; + } + + public function getContext(): Context + { + return $this->context; + } + + public function getStatementsSource(): StatementsSource + { + return $this->statements_source; + } + + public function getCodebase(): Codebase + { + return $this->codebase; + } + + /** + * @return list + */ + public function getFileReplacements(): array + { + return $this->file_replacements; + } + + /** + * @param list $file_replacements + */ + public function setFileReplacements(array $file_replacements): void + { + $this->file_replacements = $file_replacements; + } +} From c4c7138329189cc58291509a7b4122abcb22fc64 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Mon, 31 Jan 2022 07:53:44 +0100 Subject: [PATCH 2/2] Update src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php Co-authored-by: Bruce Weirdan --- .../Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php index 30b4b537718..ccd42151eb5 100644 --- a/src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php @@ -25,6 +25,7 @@ final class BeforeStatementAnalysisEvent * Called after a statement has been checked * * @param list $file_replacements + * @internal */ public function __construct( Stmt $stmt,