diff --git a/docs/running_psalm/plugins/authoring_plugins.md b/docs/running_psalm/plugins/authoring_plugins.md index fc5df4bfa..e1e03047d 100644 --- a/docs/running_psalm/plugins/authoring_plugins.md +++ b/docs/running_psalm/plugins/authoring_plugins.md @@ -19,6 +19,7 @@ class SomePlugin implements \Psalm\Plugin\Hook\AfterStatementAnalysisInterface - `AfterEveryFunctionCallAnalysisInterface` - called after Psalm evaluates any function call. Cannot influence the call further. - `AfterExpressionAnalysisInterface` - called after Psalm evaluates an expression. - `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. - `AfterStatementAnalysisInterface` - called after Psalm evaluates an statement. - `FunctionExistenceProviderInterface` - can be used to override Psalm's builtin function existence checks for one or more functions. diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 06b43177a..c26ed6421 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -496,6 +496,13 @@ class Config */ public $before_analyze_file = []; + /** + * Static methods to be called after functionlike checks have completed + * + * @var class-string[] + */ + public $after_functionlike_checks = []; + /** @var array */ private $predefined_constants; diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index e1c3daad8..20f44752d 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -766,6 +766,31 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer } } + $plugin_classes = $codebase->config->after_functionlike_checks; + + if ($plugin_classes) { + $file_manipulations = []; + + foreach ($plugin_classes as $plugin_fq_class_name) { + if ($plugin_fq_class_name::afterStatementAnalysis( + $this->function, + $storage, + $this, + $codebase, + $file_manipulations + ) === false) { + return false; + } + } + + if ($file_manipulations) { + \Psalm\Internal\FileManipulation\FileManipulationBuffer::add( + $this->getFilePath(), + $file_manipulations + ); + } + } + return null; } diff --git a/src/Psalm/Plugin/Hook/AfterFunctionLikeAnalysisInterface.php b/src/Psalm/Plugin/Hook/AfterFunctionLikeAnalysisInterface.php new file mode 100644 index 000000000..801bd338e --- /dev/null +++ b/src/Psalm/Plugin/Hook/AfterFunctionLikeAnalysisInterface.php @@ -0,0 +1,26 @@ +config->string_interpreters[$handler] = $handler; } + + if (is_subclass_of($handler, Hook\AfterFunctionLikeAnalysisInterface::class)) { + $this->config->after_functionlike_checks[$handler] = $handler; + } } }