mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 12:24:49 +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:
parent
8b0f2579a5
commit
a338e76ef6
@ -23,12 +23,6 @@
|
||||
<MissingConstructor errorLevel="suppress" />
|
||||
<DeprecatedProperty errorLevel="suppress" />
|
||||
|
||||
<LessSpecificReturnType>
|
||||
<errorLevel type="suppress">
|
||||
<file name="src/Psalm/Plugin.php" />
|
||||
</errorLevel>
|
||||
</LessSpecificReturnType>
|
||||
|
||||
<UnusedProperty>
|
||||
<errorLevel type="info">
|
||||
<file name="src/Psalm/FileManipulation/FunctionDocblockManipulator.php" />
|
||||
@ -44,7 +38,6 @@
|
||||
<PossiblyUnusedMethod>
|
||||
<errorLevel type="suppress">
|
||||
<file name="src/Psalm/Type/Atomic/GenericTrait.php" />
|
||||
<file name="src/Psalm/Plugin.php" />
|
||||
</errorLevel>
|
||||
<errorLevel type="info">
|
||||
<file name="src/Psalm/Codebase.php" />
|
||||
|
@ -28,7 +28,7 @@
|
||||
"bin": ["psalm", "psalter", "psalm-language-server"],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psalm\\PluginApi\\": "src/Psalm/PluginApi",
|
||||
"Psalm\\Plugin\\": "src/Psalm/Plugin",
|
||||
"Psalm\\": "src/Psalm"
|
||||
}
|
||||
},
|
||||
|
@ -2,24 +2,25 @@
|
||||
|
||||
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
|
||||
class SomePlugin extends \Psalm\Plugin
|
||||
class SomePlugin implements \Psalm\Plugin\Hook\AfterStatementAnalysisInterface
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
`Psalm\Plugin` offers six methods that you can override:
|
||||
- `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
|
||||
`Psalm\Plugin\Hook\*` offers six interfaces that you can implement:
|
||||
|
||||
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):
|
||||
```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`
|
||||
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
|
||||
|
||||
@ -63,10 +64,10 @@ Run `composer create-project weirdan/psalm-plugin-skeleton:dev-master your-plugi
|
||||
|
||||
### 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
|
||||
|
||||
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.
|
||||
|
@ -5,10 +5,11 @@ use Psalm\Codebase;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\FileManipulation\FileManipulation;
|
||||
use Psalm\FileSource;
|
||||
use Psalm\Plugin\Hook\AfterClassLikeExistenceCheckInterface;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
|
||||
class ClassUnqualifier extends \Psalm\Plugin
|
||||
class ClassUnqualifier implements AfterClassLikeExistenceCheckInterface
|
||||
{
|
||||
/**
|
||||
* @param string $fq_class_name
|
||||
|
@ -10,9 +10,10 @@ use Psalm\Context;
|
||||
use Psalm\FileManipulation\FileManipulation;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Issue\TypeCoercion;
|
||||
use Psalm\Plugin\Hook\AfterExpressionAnalysisInterface;
|
||||
use Psalm\StatementsSource;
|
||||
|
||||
class StringChecker extends \Psalm\Plugin
|
||||
class StringChecker implements AfterExpressionAnalysisInterface
|
||||
{
|
||||
/**
|
||||
* Called after an expression has been checked
|
||||
|
@ -8,9 +8,10 @@ use Psalm\Context;
|
||||
use Psalm\FileManipulation\FileManipulation;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Issue\TypeCoercion;
|
||||
use Psalm\Plugin\Hook\AfterStatementAnalysisInterface;
|
||||
use Psalm\StatementsSource;
|
||||
|
||||
class EchoChecker extends \Psalm\Plugin
|
||||
class EchoChecker implements AfterStatementAnalysisInterface
|
||||
{
|
||||
/**
|
||||
* Called after a statement has been checked
|
||||
|
@ -1,13 +1,13 @@
|
||||
<?php
|
||||
namespace Psalm\Example\Plugin\ComposerBased;
|
||||
|
||||
use Psalm\PluginApi;
|
||||
use Psalm\Plugin;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class PluginEntryPoint implements PluginApi\PluginEntryPointInterface
|
||||
class PluginEntryPoint implements Plugin\PluginEntryPointInterface
|
||||
{
|
||||
/** @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';
|
||||
$registration->registerHooksFromClass(EchoChecker::class);
|
||||
|
@ -50,22 +50,14 @@
|
||||
</MissingConstructor>
|
||||
<DeprecatedProperty errorLevel="suppress" />
|
||||
|
||||
<LessSpecificReturnType>
|
||||
<errorLevel type="suppress">
|
||||
<file name="src/Psalm/Plugin.php" />
|
||||
</errorLevel>
|
||||
</LessSpecificReturnType>
|
||||
|
||||
<UnusedParam>
|
||||
<errorLevel type="suppress">
|
||||
<file name="src/Psalm/Plugin.php" />
|
||||
<directory name="examples" />
|
||||
</errorLevel>
|
||||
</UnusedParam>
|
||||
|
||||
<PossiblyUnusedParam>
|
||||
<errorLevel type="suppress">
|
||||
<file name="src/Psalm/Plugin.php" />
|
||||
<directory name="examples" />
|
||||
</errorLevel>
|
||||
</PossiblyUnusedParam>
|
||||
@ -96,8 +88,7 @@
|
||||
<PossiblyUnusedMethod>
|
||||
<errorLevel type="suppress">
|
||||
<directory name="tests" />
|
||||
<directory name="src/Psalm/PluginApi" />
|
||||
<file name="src/Psalm/Plugin.php" />
|
||||
<directory name="src/Psalm/Plugin" />
|
||||
<file name="src/Psalm/Internal/LanguageServer/Client/TextDocument.php" />
|
||||
<file name="src/Psalm/Internal/LanguageServer/Server/TextDocument.php" />
|
||||
<referencedMethod name="Psalm\Codebase::getParentInterfaces" />
|
||||
|
@ -116,8 +116,8 @@ return [
|
||||
},
|
||||
function ($filePath, $prefix, $contents) {
|
||||
$ret = str_replace(
|
||||
$prefix . '\Psalm\PluginApi',
|
||||
'Psalm\PluginApi',
|
||||
$prefix . '\Psalm\Plugin\\',
|
||||
'Psalm\Plugin\\',
|
||||
$contents
|
||||
);
|
||||
return $ret;
|
||||
@ -125,6 +125,6 @@ return [
|
||||
],
|
||||
'whitelist' => [
|
||||
\Composer\Autoload\ClassLoader::class,
|
||||
'Psalm\PluginApi\*',
|
||||
'Psalm\Plugin\*',
|
||||
]
|
||||
];
|
||||
|
@ -810,7 +810,7 @@ class Config
|
||||
$plugin_class_name = $plugin_class_entry['class'];
|
||||
$plugin_config = $plugin_class_entry['config'];
|
||||
try {
|
||||
/** @var PluginApi\PluginEntryPointInterface $plugin_object */
|
||||
/** @var Plugin\PluginEntryPointInterface $plugin_object */
|
||||
$plugin_object = new $plugin_class_name;
|
||||
$plugin_object($socket, $plugin_config);
|
||||
} catch (\Throwable $e) {
|
||||
|
@ -4,10 +4,9 @@ namespace Psalm;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
||||
use Psalm\Internal\Scanner\FileScanner;
|
||||
use Psalm\PluginApi;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class FileBasedPluginAdapter implements PluginApi\PluginEntryPointInterface
|
||||
class FileBasedPluginAdapter implements Plugin\PluginEntryPointInterface
|
||||
{
|
||||
/** @var string */
|
||||
private $path;
|
||||
@ -26,9 +25,9 @@ class FileBasedPluginAdapter implements PluginApi\PluginEntryPointInterface
|
||||
}
|
||||
|
||||
/** @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 */
|
||||
require_once($this->path);
|
||||
@ -36,7 +35,7 @@ class FileBasedPluginAdapter implements PluginApi\PluginEntryPointInterface
|
||||
$registration->registerHooksFromClass($fq_class_name);
|
||||
}
|
||||
|
||||
private function getPluginClassForPath(string $path, string $must_extend): string
|
||||
private function getPluginClassForPath(string $path): string
|
||||
{
|
||||
$codebase = $this->codebase;
|
||||
|
||||
@ -58,16 +57,6 @@ class FileBasedPluginAdapter implements PluginApi\PluginEntryPointInterface
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
) {
|
||||
}
|
||||
}
|
@ -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 = []
|
||||
);
|
||||
}
|
24
src/Psalm/Plugin/Hook/AfterClassLikeVisitInterface.php
Normal file
24
src/Psalm/Plugin/Hook/AfterClassLikeVisitInterface.php
Normal 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 = []
|
||||
);
|
||||
}
|
26
src/Psalm/Plugin/Hook/AfterExpressionAnalysisInterface.php
Normal file
26
src/Psalm/Plugin/Hook/AfterExpressionAnalysisInterface.php
Normal 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 = []
|
||||
);
|
||||
}
|
27
src/Psalm/Plugin/Hook/AfterFunctionCallAnalysisInterface.php
Normal file
27
src/Psalm/Plugin/Hook/AfterFunctionCallAnalysisInterface.php
Normal 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
|
||||
);
|
||||
}
|
32
src/Psalm/Plugin/Hook/AfterMethodCallAnalysisInterface.php
Normal file
32
src/Psalm/Plugin/Hook/AfterMethodCallAnalysisInterface.php
Normal 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
|
||||
);
|
||||
}
|
26
src/Psalm/Plugin/Hook/AfterStatementAnalysisInterface.php
Normal file
26
src/Psalm/Plugin/Hook/AfterStatementAnalysisInterface.php
Normal 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 = []
|
||||
);
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace Psalm\PluginApi;
|
||||
namespace Psalm\Plugin;
|
||||
|
||||
use SimpleXMLElement;
|
||||
use Psalm\Plugin\RegistrationInterface;
|
||||
|
||||
interface PluginEntryPointInterface
|
||||
{
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Psalm\PluginApi;
|
||||
namespace Psalm\Plugin;
|
||||
|
||||
interface RegistrationInterface
|
||||
{
|
@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace Psalm;
|
||||
|
||||
use Psalm\PluginApi\RegistrationInterface;
|
||||
use Psalm\Plugin\Hook;
|
||||
use Psalm\Plugin\RegistrationInterface;
|
||||
|
||||
class PluginRegistrationSocket implements RegistrationInterface
|
||||
{
|
||||
@ -29,50 +30,27 @@ class PluginRegistrationSocket implements RegistrationInterface
|
||||
throw new \InvalidArgumentException('Plugins must be loaded before registration');
|
||||
}
|
||||
|
||||
if (!is_subclass_of($handler, Plugin::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
|
||||
) {
|
||||
if (is_subclass_of($handler, Hook\AfterMethodCallAnalysisInterface::class)) {
|
||||
$this->config->after_method_checks[$handler] = $handler;
|
||||
}
|
||||
|
||||
if ($handlerClass->getMethod('afterFunctionCallAnalysis')->getDeclaringClass()->getName()
|
||||
!== Plugin::class
|
||||
) {
|
||||
if (is_subclass_of($handler, Hook\AfterFunctionCallAnalysisInterface::class)) {
|
||||
$this->config->after_function_checks[$handler] = $handler;
|
||||
}
|
||||
|
||||
if ($handlerClass->getMethod('afterExpressionAnalysis')->getDeclaringClass()->getName()
|
||||
!== Plugin::class
|
||||
) {
|
||||
if (is_subclass_of($handler, Hook\AfterExpressionAnalysisInterface::class)) {
|
||||
$this->config->after_expression_checks[$handler] = $handler;
|
||||
}
|
||||
|
||||
if ($handlerClass->getMethod('afterStatementAnalysis')->getDeclaringClass()->getName()
|
||||
!== Plugin::class
|
||||
) {
|
||||
if (is_subclass_of($handler, Hook\AfterStatementAnalysisInterface::class)) {
|
||||
$this->config->after_statement_checks[$handler] = $handler;
|
||||
}
|
||||
|
||||
if ($handlerClass->getMethod('afterClassLikeExistenceCheck')->getDeclaringClass()->getName()
|
||||
!== Plugin::class
|
||||
) {
|
||||
if (is_subclass_of($handler, Hook\AfterClassLikeExistenceCheckInterface::class)) {
|
||||
$this->config->after_classlike_exists_checks[$handler] = $handler;
|
||||
}
|
||||
|
||||
if ($handlerClass->getMethod('afterClassLikeVisit')->getDeclaringClass()->getName()
|
||||
!== Plugin::class
|
||||
) {
|
||||
if (is_subclass_of($handler, Hook\AfterClassLikeVisitInterface::class)) {
|
||||
$this->config->after_visit_classlikes[$handler] = $handler;
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,8 @@ use Prophecy\Argument;
|
||||
use Psalm\Internal\Analyzer\FileAnalyzer;
|
||||
use Psalm\Config;
|
||||
use Psalm\Context;
|
||||
use Psalm\Plugin;
|
||||
use Psalm\PluginApi\PluginEntryPointInterface;
|
||||
use Psalm\PluginApi\RegistrationInterface;
|
||||
use Psalm\Plugin\PluginEntryPointInterface;
|
||||
use Psalm\Plugin\RegistrationInterface;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class PluginTest extends TestCase
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
class BasePlugin extends \Psalm\Plugin
|
||||
use Psalm\Plugin\Hook\AfterFunctionCallAnalysisInterface;
|
||||
|
||||
class BasePlugin implements Psalm\Plugin\Hook\AfterFunctionCallAnalysisInterface
|
||||
{
|
||||
public static function afterFunctionCallAnalysis(
|
||||
\PhpParser\Node\Expr\FuncCall $expr,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Psalm\PluginApi\PluginEntryPointInterface;
|
||||
use Psalm\PluginApi\RegistrationInterface;
|
||||
use Psalm\Plugin\PluginEntryPointInterface;
|
||||
use Psalm\Plugin\RegistrationInterface;
|
||||
|
||||
require_once __DIR__ . '/extending_plugin.php';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user