1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Plugin interface segregation (#1076)

* Split Plugin into PluginApi\Hook\* interfaces

* dropped Psalm\Plugin

* updated docs

* s/PluginApi/Plugin/g
This commit is contained in:
Bruce Weirdan 2018-11-12 17:11:08 +02:00 committed by Matthew Brown
parent 8b0f2579a5
commit a338e76ef6
24 changed files with 209 additions and 214 deletions

View File

@ -23,12 +23,6 @@
<MissingConstructor errorLevel="suppress" /> <MissingConstructor errorLevel="suppress" />
<DeprecatedProperty errorLevel="suppress" /> <DeprecatedProperty errorLevel="suppress" />
<LessSpecificReturnType>
<errorLevel type="suppress">
<file name="src/Psalm/Plugin.php" />
</errorLevel>
</LessSpecificReturnType>
<UnusedProperty> <UnusedProperty>
<errorLevel type="info"> <errorLevel type="info">
<file name="src/Psalm/FileManipulation/FunctionDocblockManipulator.php" /> <file name="src/Psalm/FileManipulation/FunctionDocblockManipulator.php" />
@ -44,7 +38,6 @@
<PossiblyUnusedMethod> <PossiblyUnusedMethod>
<errorLevel type="suppress"> <errorLevel type="suppress">
<file name="src/Psalm/Type/Atomic/GenericTrait.php" /> <file name="src/Psalm/Type/Atomic/GenericTrait.php" />
<file name="src/Psalm/Plugin.php" />
</errorLevel> </errorLevel>
<errorLevel type="info"> <errorLevel type="info">
<file name="src/Psalm/Codebase.php" /> <file name="src/Psalm/Codebase.php" />

View File

@ -28,7 +28,7 @@
"bin": ["psalm", "psalter", "psalm-language-server"], "bin": ["psalm", "psalter", "psalm-language-server"],
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Psalm\\PluginApi\\": "src/Psalm/PluginApi", "Psalm\\Plugin\\": "src/Psalm/Plugin",
"Psalm\\": "src/Psalm" "Psalm\\": "src/Psalm"
} }
}, },

View File

@ -2,24 +2,25 @@
Psalm can be extended through plugins to find domain-specific issues. Psalm can be extended through plugins to find domain-specific issues.
All plugins must extend `Psalm\Plugin` Plugins may implement one of (or more than one of) `Psalm\Plugin\Hook\*` interface(s).
```php ```php
<?php <?php
class SomePlugin extends \Psalm\Plugin class SomePlugin implements \Psalm\Plugin\Hook\AfterStatementAnalysisInterface
{ {
} }
``` ```
`Psalm\Plugin` offers six methods that you can override: `Psalm\Plugin\Hook\*` offers six interfaces that you can implement:
- `afterStatementAnalysis` - called after Psalm evaluates each statement
- `afterExpressionAnalysis` - called after Psalm evaluates each expression
- `afterClassLikeVisit` - 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`
- `afterClassLikeExistenceCheck` - called after Psalm analyzes a reference to a class-like
- `afterMethodCallAnalysis` - called after Psalm analyzes a method call
- `afterFunctionCallAnalysis` - called after Psalm analyzes a function call
An example plugin that checks class references in strings is provided [here](https://github.com/vimeo/psalm/blob/master/examples/StringChecker.php). - `AfterStatementAnalysisInterface` - called after Psalm evaluates each statement
- `AfterExpressionAnalysisInterface` - called after Psalm evaluates each expression
- `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`
- `AfterClassLikeExistenceCheckInterface` - called after Psalm analyzes a reference to a class-like
- `AfterMethodCallAnalysisInterface` - called after Psalm analyzes a method call
- `AfterFunctionCallAnalysisInterface` - called after Psalm analyzes a function call
An example plugin that checks class references in strings is provided [here](https://github.com/vimeo/psalm/blob/master/examples/plugins/StringChecker.php).
To ensure your plugin runs when Psalm does, add it to your [config](Configuration): To ensure your plugin runs when Psalm does, add it to your [config](Configuration):
```php ```php
@ -55,7 +56,7 @@ Composer-based plugin is a composer package which conforms to these requirements
1. Its `type` field is set to `psalm-plugin` 1. Its `type` field is set to `psalm-plugin`
2. It has `extra.psalm.pluginClass` subkey in its `composer.json` that reference an entry-point class that will be invoked to register the plugin into Psalm runtime. 2. It has `extra.psalm.pluginClass` subkey in its `composer.json` that reference an entry-point class that will be invoked to register the plugin into Psalm runtime.
3. Entry-point class implements `Psalm\PluginApi\PluginEntryPointInterface` 3. Entry-point class implements `Psalm\Plugin\PluginEntryPointInterface`
### Using skeleton project ### Using skeleton project
@ -63,10 +64,10 @@ Run `composer create-project weirdan/psalm-plugin-skeleton:dev-master your-plugi
### Upgrading file-based plugin to composer-based version ### Upgrading file-based plugin to composer-based version
Create new plugin project using skeleton, then pass the class name of you file-based plugin to `registerHooksFromClass()` method of the `Psalm\PluginApi\RegistrationInterface` instance that was passed into your plugin entry point's `__invoke()` method. See the [conversion example](https://github.com/vimeo/psalm/examples/composer-based/echo-checker/). Create new plugin project using skeleton, then pass the class name of you file-based plugin to `registerHooksFromClass()` method of the `Psalm\Plugin\RegistrationInterface` instance that was passed into your plugin entry point's `__invoke()` method. See the [conversion example](https://github.com/vimeo/psalm/examples/plugins/composer-based/echo-checker/).
### Registering stub files ### Registering stub files
Use `Psalm\PluginApi\RegistrationInterface::addStubFile()`. See the [sample plugin] (https://github.com/weirdan/psalm-doctrine-collections/). Use `Psalm\Plugin\RegistrationInterface::addStubFile()`. See the [sample plugin] (https://github.com/weirdan/psalm-doctrine-collections/).
Stub files provide a way to override third-party type information when you cannot add Psalm's extended docblocks to the upstream source files directly. Stub files provide a way to override third-party type information when you cannot add Psalm's extended docblocks to the upstream source files directly.

View File

@ -5,10 +5,11 @@ use Psalm\Codebase;
use Psalm\CodeLocation; use Psalm\CodeLocation;
use Psalm\FileManipulation\FileManipulation; use Psalm\FileManipulation\FileManipulation;
use Psalm\FileSource; use Psalm\FileSource;
use Psalm\Plugin\Hook\AfterClassLikeExistenceCheckInterface;
use Psalm\StatementsSource; use Psalm\StatementsSource;
use Psalm\Type; use Psalm\Type;
class ClassUnqualifier extends \Psalm\Plugin class ClassUnqualifier implements AfterClassLikeExistenceCheckInterface
{ {
/** /**
* @param string $fq_class_name * @param string $fq_class_name

View File

@ -10,9 +10,10 @@ use Psalm\Context;
use Psalm\FileManipulation\FileManipulation; use Psalm\FileManipulation\FileManipulation;
use Psalm\IssueBuffer; use Psalm\IssueBuffer;
use Psalm\Issue\TypeCoercion; use Psalm\Issue\TypeCoercion;
use Psalm\Plugin\Hook\AfterExpressionAnalysisInterface;
use Psalm\StatementsSource; use Psalm\StatementsSource;
class StringChecker extends \Psalm\Plugin class StringChecker implements AfterExpressionAnalysisInterface
{ {
/** /**
* Called after an expression has been checked * Called after an expression has been checked

View File

@ -8,9 +8,10 @@ use Psalm\Context;
use Psalm\FileManipulation\FileManipulation; use Psalm\FileManipulation\FileManipulation;
use Psalm\IssueBuffer; use Psalm\IssueBuffer;
use Psalm\Issue\TypeCoercion; use Psalm\Issue\TypeCoercion;
use Psalm\Plugin\Hook\AfterStatementAnalysisInterface;
use Psalm\StatementsSource; use Psalm\StatementsSource;
class EchoChecker extends \Psalm\Plugin class EchoChecker implements AfterStatementAnalysisInterface
{ {
/** /**
* Called after a statement has been checked * Called after a statement has been checked

View File

@ -1,13 +1,13 @@
<?php <?php
namespace Psalm\Example\Plugin\ComposerBased; namespace Psalm\Example\Plugin\ComposerBased;
use Psalm\PluginApi; use Psalm\Plugin;
use SimpleXMLElement; use SimpleXMLElement;
class PluginEntryPoint implements PluginApi\PluginEntryPointInterface class PluginEntryPoint implements Plugin\PluginEntryPointInterface
{ {
/** @return void */ /** @return void */
public function __invoke(PluginApi\RegistrationInterface $registration, ?SimpleXMLElement $config = null) public function __invoke(Plugin\RegistrationInterface $registration, ?SimpleXMLElement $config = null)
{ {
require_once __DIR__ . '/EchoChecker.php'; require_once __DIR__ . '/EchoChecker.php';
$registration->registerHooksFromClass(EchoChecker::class); $registration->registerHooksFromClass(EchoChecker::class);

View File

@ -50,22 +50,14 @@
</MissingConstructor> </MissingConstructor>
<DeprecatedProperty errorLevel="suppress" /> <DeprecatedProperty errorLevel="suppress" />
<LessSpecificReturnType>
<errorLevel type="suppress">
<file name="src/Psalm/Plugin.php" />
</errorLevel>
</LessSpecificReturnType>
<UnusedParam> <UnusedParam>
<errorLevel type="suppress"> <errorLevel type="suppress">
<file name="src/Psalm/Plugin.php" />
<directory name="examples" /> <directory name="examples" />
</errorLevel> </errorLevel>
</UnusedParam> </UnusedParam>
<PossiblyUnusedParam> <PossiblyUnusedParam>
<errorLevel type="suppress"> <errorLevel type="suppress">
<file name="src/Psalm/Plugin.php" />
<directory name="examples" /> <directory name="examples" />
</errorLevel> </errorLevel>
</PossiblyUnusedParam> </PossiblyUnusedParam>
@ -96,8 +88,7 @@
<PossiblyUnusedMethod> <PossiblyUnusedMethod>
<errorLevel type="suppress"> <errorLevel type="suppress">
<directory name="tests" /> <directory name="tests" />
<directory name="src/Psalm/PluginApi" /> <directory name="src/Psalm/Plugin" />
<file name="src/Psalm/Plugin.php" />
<file name="src/Psalm/Internal/LanguageServer/Client/TextDocument.php" /> <file name="src/Psalm/Internal/LanguageServer/Client/TextDocument.php" />
<file name="src/Psalm/Internal/LanguageServer/Server/TextDocument.php" /> <file name="src/Psalm/Internal/LanguageServer/Server/TextDocument.php" />
<referencedMethod name="Psalm\Codebase::getParentInterfaces" /> <referencedMethod name="Psalm\Codebase::getParentInterfaces" />

View File

@ -116,8 +116,8 @@ return [
}, },
function ($filePath, $prefix, $contents) { function ($filePath, $prefix, $contents) {
$ret = str_replace( $ret = str_replace(
$prefix . '\Psalm\PluginApi', $prefix . '\Psalm\Plugin\\',
'Psalm\PluginApi', 'Psalm\Plugin\\',
$contents $contents
); );
return $ret; return $ret;
@ -125,6 +125,6 @@ return [
], ],
'whitelist' => [ 'whitelist' => [
\Composer\Autoload\ClassLoader::class, \Composer\Autoload\ClassLoader::class,
'Psalm\PluginApi\*', 'Psalm\Plugin\*',
] ]
]; ];

View File

@ -810,7 +810,7 @@ class Config
$plugin_class_name = $plugin_class_entry['class']; $plugin_class_name = $plugin_class_entry['class'];
$plugin_config = $plugin_class_entry['config']; $plugin_config = $plugin_class_entry['config'];
try { try {
/** @var PluginApi\PluginEntryPointInterface $plugin_object */ /** @var Plugin\PluginEntryPointInterface $plugin_object */
$plugin_object = new $plugin_class_name; $plugin_object = new $plugin_class_name;
$plugin_object($socket, $plugin_config); $plugin_object($socket, $plugin_config);
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -4,10 +4,9 @@ namespace Psalm;
use Psalm\Internal\Analyzer\ClassLikeAnalyzer; use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Internal\Analyzer\ProjectAnalyzer; use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\Scanner\FileScanner; use Psalm\Internal\Scanner\FileScanner;
use Psalm\PluginApi;
use SimpleXMLElement; use SimpleXMLElement;
class FileBasedPluginAdapter implements PluginApi\PluginEntryPointInterface class FileBasedPluginAdapter implements Plugin\PluginEntryPointInterface
{ {
/** @var string */ /** @var string */
private $path; private $path;
@ -26,9 +25,9 @@ class FileBasedPluginAdapter implements PluginApi\PluginEntryPointInterface
} }
/** @return void */ /** @return void */
public function __invoke(PluginApi\RegistrationInterface $registration, SimpleXMLElement $config = null) public function __invoke(Plugin\RegistrationInterface $registration, SimpleXMLElement $config = null)
{ {
$fq_class_name = $this->getPluginClassForPath($this->path, Plugin::class); $fq_class_name = $this->getPluginClassForPath($this->path);
/** @psalm-suppress UnresolvableInclude */ /** @psalm-suppress UnresolvableInclude */
require_once($this->path); require_once($this->path);
@ -36,7 +35,7 @@ class FileBasedPluginAdapter implements PluginApi\PluginEntryPointInterface
$registration->registerHooksFromClass($fq_class_name); $registration->registerHooksFromClass($fq_class_name);
} }
private function getPluginClassForPath(string $path, string $must_extend): string private function getPluginClassForPath(string $path): string
{ {
$codebase = $this->codebase; $codebase = $this->codebase;
@ -58,16 +57,6 @@ class FileBasedPluginAdapter implements PluginApi\PluginEntryPointInterface
$fq_class_name = reset($declared_classes); $fq_class_name = reset($declared_classes);
if (!$codebase->classExtends(
$fq_class_name,
$must_extend
)
) {
throw new \InvalidArgumentException(
'This plugin must extend ' . $must_extend . ' - ' . $path . ' does not'
);
}
return $fq_class_name; return $fq_class_name;
} }
} }

View File

@ -1,120 +0,0 @@
<?php
namespace Psalm;
use PhpParser;
use Psalm\FileManipulation\FileManipulation;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Type\Union;
abstract class Plugin
{
/**
* Called after an expression has been checked
*
* @param PhpParser\Node\Expr $expr
* @param Context $context
* @param StatementsSource $file_soure
* @param string[] $suppressed_issues
* @param FileManipulation[] $file_replacements
*
* @return null|false
*/
public static function afterExpressionAnalysis(
PhpParser\Node\Expr $expr,
Context $context,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
) {
return null;
}
/**
* Called after a statement has been checked
*
* @param string[] $suppressed_issues
* @param FileManipulation[] $file_replacements
*
* @return null|false
*/
public static function afterStatementAnalysis(
PhpParser\Node\Stmt $stmt,
Context $context,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
) {
return null;
}
/**
* @param FileManipulation[] $file_replacements
*
* @return void
*/
public static function afterClassLikeVisit(
PhpParser\Node\Stmt\ClassLike $stmt,
ClassLikeStorage $storage,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
) {
}
/**
* @param string $fq_class_name
* @param FileManipulation[] $file_replacements
*
* @return void
*/
public static function afterClassLikeExistenceCheck(
string $fq_class_name,
CodeLocation $code_location,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
) {
}
/**
* @param PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $expr
* @param string $method_id - the method id being checked
* @param string $appearing_method_id - the method id of the class that the method appears in
* @param string $declaring_method_id - the method id of the class or trait that declares the method
* @param string|null $var_id - a reference to the LHS of the variable
* @param PhpParser\Node\Arg[] $args
* @param FileManipulation[] $file_replacements
*
* @return void
*/
public static function afterMethodCallAnalysis(
PhpParser\Node\Expr $expr,
string $method_id,
string $appearing_method_id,
string $declaring_method_id,
Context $context,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = [],
Union $return_type_candidate = null
) {
}
/**
* @param string $function_id - the method id being checked
* @param PhpParser\Node\Arg[] $args
* @param FileManipulation[] $file_replacements
*
* @return void
*/
public static function afterFunctionCallAnalysis(
PhpParser\Node\Expr\FuncCall $expr,
string $function_id,
Context $context,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = [],
Union &$return_type_candidate = null
) {
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Psalm\Plugin\Hook;
use Psalm\CodeLocation;
use Psalm\Codebase;
use Psalm\FileManipulation\FileManipulation;
use Psalm\StatementsSource;
interface AfterClassLikeExistenceCheckInterface
{
/**
* @param FileManipulation[] $file_replacements
*
* @return void
*/
public static function afterClassLikeExistenceCheck(
string $fq_class_name,
CodeLocation $code_location,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
);
}

View File

@ -0,0 +1,24 @@
<?php
namespace Psalm\Plugin\Hook;
use PhpParser\Node\Stmt\ClassLike;
use Psalm\Codebase;
use Psalm\FileManipulation\FileManipulation;
use Psalm\StatementsSource;
use Psalm\Storage\ClassLikeStorage;
interface AfterClassLikeVisitInterface
{
/**
* @param FileManipulation[] $file_replacements
*
* @return void
*/
public static function afterClassLikeVisit(
ClassLike $stmt,
ClassLikeStorage $storage,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
);
}

View File

@ -0,0 +1,26 @@
<?php
namespace Psalm\Plugin\Hook;
use PhpParser\Node\Expr;
use Psalm\Codebase;
use Psalm\Context;
use Psalm\FileManipulation\FileManipulation;
use Psalm\StatementsSource;
interface AfterExpressionAnalysisInterface
{
/**
* Called after an expression has been checked
*
* @param FileManipulation[] $file_replacements
*
* @return null|false
*/
public static function afterExpressionAnalysis(
Expr $expr,
Context $context,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
);
}

View File

@ -0,0 +1,27 @@
<?php
namespace Psalm\Plugin\Hook;
use PhpParser\Node\Expr\FuncCall;
use Psalm\Codebase;
use Psalm\Context;
use Psalm\FileManipulation\FileManipulation;
use Psalm\StatementsSource;
use Psalm\Type\Union;
interface AfterFunctionCallAnalysisInterface
{
/**
* @param FileManipulation[] $file_replacements
*
* @return void
*/
public static function afterFunctionCallAnalysis(
FuncCall $expr,
string $function_id,
Context $context,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = [],
Union &$return_type_candidate = null
);
}

View File

@ -0,0 +1,32 @@
<?php
namespace Psalm\Plugin\Hook;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use Psalm\Codebase;
use Psalm\Context;
use Psalm\FileManipulation\FileManipulation;
use Psalm\StatementsSource;
use Psalm\Type\Union;
interface AfterMethodCallAnalysisInterface
{
/**
* @param MethodCall|StaticCall $expr
* @param FileManipulation[] $file_replacements
*
* @return void
*/
public static function afterMethodCallAnalysis(
Expr $expr,
string $method_id,
string $appearing_method_id,
string $declaring_method_id,
Context $context,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = [],
Union $return_type_candidate = null
);
}

View File

@ -0,0 +1,26 @@
<?php
namespace Psalm\Plugin\Hook;
use PhpParser\Node\Stmt;
use Psalm\Codebase;
use Psalm\Context;
use Psalm\FileManipulation\FileManipulation;
use Psalm\StatementsSource;
interface AfterStatementAnalysisInterface
{
/**
* Called after a statement has been checked
*
* @param FileManipulation[] $file_replacements
*
* @return null|false
*/
public static function afterStatementAnalysis(
Stmt $stmt,
Context $context,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
);
}

View File

@ -1,7 +1,8 @@
<?php <?php
namespace Psalm\PluginApi; namespace Psalm\Plugin;
use SimpleXMLElement; use SimpleXMLElement;
use Psalm\Plugin\RegistrationInterface;
interface PluginEntryPointInterface interface PluginEntryPointInterface
{ {

View File

@ -1,5 +1,5 @@
<?php <?php
namespace Psalm\PluginApi; namespace Psalm\Plugin;
interface RegistrationInterface interface RegistrationInterface
{ {

View File

@ -1,7 +1,8 @@
<?php <?php
namespace Psalm; namespace Psalm;
use Psalm\PluginApi\RegistrationInterface; use Psalm\Plugin\Hook;
use Psalm\Plugin\RegistrationInterface;
class PluginRegistrationSocket implements RegistrationInterface class PluginRegistrationSocket implements RegistrationInterface
{ {
@ -29,50 +30,27 @@ class PluginRegistrationSocket implements RegistrationInterface
throw new \InvalidArgumentException('Plugins must be loaded before registration'); throw new \InvalidArgumentException('Plugins must be loaded before registration');
} }
if (!is_subclass_of($handler, Plugin::class)) { if (is_subclass_of($handler, Hook\AfterMethodCallAnalysisInterface::class)) {
throw new \InvalidArgumentException(
'This handler must extend ' . Plugin::class . ' - ' . $handler . ' does not'
);
}
// check that handler class (or one of its ancestors, but not Plugin) actually redefines specific hooks,
// so that we don't register empty handlers provided by Plugin
$handlerClass = new \ReflectionClass($handler);
if ($handlerClass->getMethod('afterMethodCallAnalysis')->getDeclaringClass()->getName()
!== Plugin::class
) {
$this->config->after_method_checks[$handler] = $handler; $this->config->after_method_checks[$handler] = $handler;
} }
if ($handlerClass->getMethod('afterFunctionCallAnalysis')->getDeclaringClass()->getName() if (is_subclass_of($handler, Hook\AfterFunctionCallAnalysisInterface::class)) {
!== Plugin::class
) {
$this->config->after_function_checks[$handler] = $handler; $this->config->after_function_checks[$handler] = $handler;
} }
if ($handlerClass->getMethod('afterExpressionAnalysis')->getDeclaringClass()->getName() if (is_subclass_of($handler, Hook\AfterExpressionAnalysisInterface::class)) {
!== Plugin::class
) {
$this->config->after_expression_checks[$handler] = $handler; $this->config->after_expression_checks[$handler] = $handler;
} }
if ($handlerClass->getMethod('afterStatementAnalysis')->getDeclaringClass()->getName() if (is_subclass_of($handler, Hook\AfterStatementAnalysisInterface::class)) {
!== Plugin::class
) {
$this->config->after_statement_checks[$handler] = $handler; $this->config->after_statement_checks[$handler] = $handler;
} }
if ($handlerClass->getMethod('afterClassLikeExistenceCheck')->getDeclaringClass()->getName() if (is_subclass_of($handler, Hook\AfterClassLikeExistenceCheckInterface::class)) {
!== Plugin::class
) {
$this->config->after_classlike_exists_checks[$handler] = $handler; $this->config->after_classlike_exists_checks[$handler] = $handler;
} }
if ($handlerClass->getMethod('afterClassLikeVisit')->getDeclaringClass()->getName() if (is_subclass_of($handler, Hook\AfterClassLikeVisitInterface::class)) {
!== Plugin::class
) {
$this->config->after_visit_classlikes[$handler] = $handler; $this->config->after_visit_classlikes[$handler] = $handler;
} }
} }

View File

@ -5,9 +5,8 @@ use Prophecy\Argument;
use Psalm\Internal\Analyzer\FileAnalyzer; use Psalm\Internal\Analyzer\FileAnalyzer;
use Psalm\Config; use Psalm\Config;
use Psalm\Context; use Psalm\Context;
use Psalm\Plugin; use Psalm\Plugin\PluginEntryPointInterface;
use Psalm\PluginApi\PluginEntryPointInterface; use Psalm\Plugin\RegistrationInterface;
use Psalm\PluginApi\RegistrationInterface;
use SimpleXMLElement; use SimpleXMLElement;
class PluginTest extends TestCase class PluginTest extends TestCase

View File

@ -1,6 +1,8 @@
<?php <?php
class BasePlugin extends \Psalm\Plugin use Psalm\Plugin\Hook\AfterFunctionCallAnalysisInterface;
class BasePlugin implements Psalm\Plugin\Hook\AfterFunctionCallAnalysisInterface
{ {
public static function afterFunctionCallAnalysis( public static function afterFunctionCallAnalysis(
\PhpParser\Node\Expr\FuncCall $expr, \PhpParser\Node\Expr\FuncCall $expr,

View File

@ -1,7 +1,7 @@
<?php <?php
use Psalm\PluginApi\PluginEntryPointInterface; use Psalm\Plugin\PluginEntryPointInterface;
use Psalm\PluginApi\RegistrationInterface; use Psalm\Plugin\RegistrationInterface;
require_once __DIR__ . '/extending_plugin.php'; require_once __DIR__ . '/extending_plugin.php';