mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
add plugin hook to be called after every function call
compared to AfterFunctionCallAnalysisInterface which gets only called after a call to a function declared within the project, a plugin implementing AfterEveryFunctionCallAnalysisInterface will get called for every function call, including calls of PHP builtins. On the other hand, this interface doesn't allow modification of the code nor tweaking the return type, but it's still useful for accounting purposes and for depreacting calls to PHP builtins this fixes #2804
This commit is contained in:
parent
ae0b1a6acb
commit
395cf587d3
@ -16,8 +16,9 @@ class SomePlugin implements \Psalm\Plugin\Hook\AfterStatementAnalysisInterface
|
||||
- `AfterClassLikeExistenceCheckInterface` - called after Psalm analyzes a reference to a class, interface or trait.
|
||||
- `AfterClassLikeVisitInterface` - called after Psalm crawls the parsed Abstract Syntax Tree for a class-like (class, interface, trait). Due to caching the AST is crawled the first time Psalm sees the file, and is only re-crawled if the file changes, the cache is cleared, or you're disabling cache with `--no-cache`/`--no-reflection-cache`. Use this if you want to collect or modify information about a class before Psalm begins its analysis.
|
||||
- `AfterCodebasePopulatedInterface` - called after Psalm has scanned necessary files and populated codebase data.
|
||||
- `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 an function call.
|
||||
- `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.
|
||||
- `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.
|
||||
|
@ -398,12 +398,28 @@ class Config
|
||||
public $after_method_checks = [];
|
||||
|
||||
/**
|
||||
* Static methods to be called after function checks have completed
|
||||
* Static methods to be called after project function checks have completed
|
||||
*
|
||||
* Called after function calls to functions defined in the project.
|
||||
*
|
||||
* Allows influencing the return type and adding of modifications.
|
||||
*
|
||||
* @var class-string<Hook\AfterFunctionCallAnalysisInterface>[]
|
||||
*/
|
||||
public $after_function_checks = [];
|
||||
|
||||
/**
|
||||
* Static methods to be called after every function call
|
||||
*
|
||||
* Called after each function call, including php internal functions.
|
||||
*
|
||||
* Cannot change the call or influence its return type
|
||||
*
|
||||
* @var class-string<Hook\AfterEveryFunctionCallAnalysisInterface>[]
|
||||
*/
|
||||
public $after_every_function_checks = [];
|
||||
|
||||
|
||||
/**
|
||||
* Static methods to be called after expression checks have completed
|
||||
*
|
||||
|
@ -635,6 +635,18 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
|
||||
if ($stmt_type) {
|
||||
$statements_analyzer->node_data->setType($real_stmt, $stmt_type);
|
||||
}
|
||||
|
||||
if ($config->after_every_function_checks) {
|
||||
foreach ($config->after_every_function_checks as $plugin_fq_class_name) {
|
||||
$plugin_fq_class_name::afterEveryFunctionCallAnalysis(
|
||||
$stmt,
|
||||
$function_id,
|
||||
$context,
|
||||
$statements_analyzer->getSource(),
|
||||
$codebase
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($defined_constants as $const_name => $const_type) {
|
||||
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Plugin\Hook;
|
||||
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Context;
|
||||
use Psalm\StatementsSource;
|
||||
|
||||
interface AfterEveryFunctionCallAnalysisInterface
|
||||
{
|
||||
public static function afterEveryFunctionCallAnalysis(
|
||||
FuncCall $expr,
|
||||
string $function_id,
|
||||
Context $context,
|
||||
StatementsSource $statements_source,
|
||||
Codebase $codebase
|
||||
): void;
|
||||
}
|
@ -46,6 +46,10 @@ class PluginRegistrationSocket implements RegistrationInterface
|
||||
$this->config->after_function_checks[$handler] = $handler;
|
||||
}
|
||||
|
||||
if (is_subclass_of($handler, Hook\AfterEveryFunctionCallAnalysisInterface::class)) {
|
||||
$this->config->after_every_function_checks[$handler] = $handler;
|
||||
}
|
||||
|
||||
if (is_subclass_of($handler, Hook\AfterExpressionAnalysisInterface::class)) {
|
||||
$this->config->after_expression_checks[$handler] = $handler;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
<?php
|
||||
namespace Psalm\Tests\Config;
|
||||
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psalm\Plugin\Hook\AfterEveryFunctionCallAnalysisInterface;
|
||||
use Psalm\StatementsSource;
|
||||
use function define;
|
||||
use function defined;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
@ -865,4 +869,64 @@ class PluginTest extends \Psalm\Tests\TestCase
|
||||
|
||||
$this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer);
|
||||
}
|
||||
|
||||
public function testAfterEveryFunctionPluginIsCalledInAllCases(): void
|
||||
{
|
||||
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
|
||||
TestConfig::loadFromXML(
|
||||
dirname(__DIR__, 2) . DIRECTORY_SEPARATOR,
|
||||
'<?xml version="1.0"?>
|
||||
<psalm></psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$mock = $this->getMockBuilder(\stdClass::class)->setMethods(['check'])->getMock();
|
||||
$mock->expects($this->exactly(3))
|
||||
->method('check')
|
||||
->withConsecutive(
|
||||
[$this->equalTo('array_map')],
|
||||
[$this->equalTo('fopen')],
|
||||
[$this->equalTo('a')]
|
||||
);
|
||||
$plugin = new class($mock) implements AfterEveryFunctionCallAnalysisInterface {
|
||||
/** @var MockObject */
|
||||
private static $m;
|
||||
|
||||
public function __construct(MockObject $m)
|
||||
{
|
||||
self::$m = $m;
|
||||
}
|
||||
|
||||
public static function afterEveryFunctionCallAnalysis(
|
||||
FuncCall $expr,
|
||||
string $function_id,
|
||||
Context $context,
|
||||
StatementsSource $statements_source,
|
||||
Codebase $codebase
|
||||
): void {
|
||||
/** @psalm-suppress UndefinedInterfaceMethod */
|
||||
self::$m->check($function_id);
|
||||
}
|
||||
};
|
||||
|
||||
$this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer);
|
||||
$this->project_analyzer->getCodebase()->config->after_every_function_checks[] = get_class($plugin);
|
||||
|
||||
$file_path = getcwd() . '/src/somefile.php';
|
||||
|
||||
$this->addFile(
|
||||
$file_path,
|
||||
'<?php
|
||||
|
||||
function a(): void {}
|
||||
function b(int $e): int { return $e; }
|
||||
|
||||
array_map("b", [1,3,3]);
|
||||
fopen("/tmp/foo.dat", "r");
|
||||
a();
|
||||
'
|
||||
);
|
||||
|
||||
$this->analyzeFile($file_path, new Context());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user