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:
parent
8b0f2579a5
commit
a338e76ef6
@ -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" />
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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" />
|
||||||
|
@ -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\*',
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
<?php
|
||||||
namespace Psalm\PluginApi;
|
namespace Psalm\Plugin;
|
||||||
|
|
||||||
use SimpleXMLElement;
|
use SimpleXMLElement;
|
||||||
|
use Psalm\Plugin\RegistrationInterface;
|
||||||
|
|
||||||
interface PluginEntryPointInterface
|
interface PluginEntryPointInterface
|
||||||
{
|
{
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Psalm\PluginApi;
|
namespace Psalm\Plugin;
|
||||||
|
|
||||||
interface RegistrationInterface
|
interface RegistrationInterface
|
||||||
{
|
{
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user