1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-12 09:19:40 +01:00
psalm/docs/running_psalm/plugins/authoring_plugins.md

155 lines
8.5 KiB
Markdown
Raw Normal View History

2019-04-14 21:21:41 +02:00
# Authoring Plugins
2018-02-22 02:11:34 +01:00
2020-05-14 13:47:55 +02:00
## Quick start
### Using a template repository
Head over to [plugin template repository](https://github.com/weirdan/psalm-plugin-skeleton) on Github, login and click `Use this template` button.
2020-05-14 13:47:55 +02:00
### Using skeleton project
Run `composer create-project weirdan/psalm-plugin-skeleton:dev-master your-plugin-name` to quickly bootstrap a new plugin project in `your-plugin-name` folder. Make sure you adjust namespaces in `composer.json`, `Plugin.php` and `tests` folder.
## Stub files
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.
By convention, stub files have `.phpstub` extension to avoid IDEs treating them as actual php code.
## Generating stubs
Dev-require the library you want to tweak types for, e.g.
```
composer require --dev cakephp/chronos
```
Then generate the stubs
```
vendor/bin/psalm --generate-stubs=stubs/chronos.phpstub
```
Open the generated file and remove everything not related to the library you're stubbing. Tweak the docblocks to provide more accurate types.
## Registering stub files
Skeleton/template project includes the code to register all `.phpstub` files from the `stubs` directory.
To register a stub file manually use `Psalm\Plugin\RegistrationInterface::addStubFile()`.
## Registering custom scanners and analyzers
In addition to XML configuration node `<fileExtensions>` plugins can register their own custom scanner
and analyzer implementations for particular file extensions, e.g.
* `Psalm\Plugin\FileExtensionsInterface::addFileTypeScanner('html', CustomFileScanner::class)`
* `Psalm\Plugin\FileExtensionsInterface::addFileTypeAnalyzer('html', CustomFileAnalyzer::class)`
2020-05-14 13:47:55 +02:00
## Publishing your plugin on Packagist
Follow instructions on packagist.org under 'Publishing Packages' section.
## Advanced topics
### Starting from scratch
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\Plugin\PluginEntryPointInterface`
### Psalm API
Plugins may implement one of (or more than one of) `Psalm\Plugin\EventHandler\*` interface(s).
2018-02-22 02:11:34 +01:00
```php
<?php
class SomePlugin implements \Psalm\Plugin\EventHandler\AfterStatementAnalysisInterface
2018-02-22 02:11:34 +01:00
{
}
```
`Psalm\Plugin\EventHandler\*` offers the following interfaces that you can implement:
2018-02-22 02:11:34 +01:00
- `AfterAnalysisInterface` - called after Psalm has completed its analysis. Use this hook if you want to do something with the analysis results.
- `AfterClassLikeAnalysisInterface` - called after Psalm has completed its analysis of a given class.
- `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.
2020-11-16 02:28:51 +01:00
- `AfterFileAnalysisInterface` - called after Psalm analyzes a file.
- `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.
- `BeforeStatementAnalysisInterface` - called before Psalm evaluates an statement.
- `AfterStatementAnalysisInterface` - called after Psalm evaluates an statement.
- `BeforeAddIssueInterface` - called before Psalm adds an item to it's internal `IssueBuffer`, allows handling code issues individually
- `BeforeFileAnalysisInterface` - called before Psalm analyzes a file.
- `FunctionExistenceProviderInterface` - can be used to override Psalm's builtin function existence checks for one or more functions.
2019-06-28 19:59:09 +02:00
- `FunctionParamsProviderInterface.php` - can be used to override Psalm's builtin function parameter lookup for one or more functions.
- `FunctionReturnTypeProviderInterface` - can be used to override Psalm's builtin function return type lookup for one or more functions.
- `MethodExistenceProviderInterface` - can be used to override Psalm's builtin method existence checks for one or more classes.
2019-06-28 19:59:09 +02:00
- `MethodParamsProviderInterface` - can be used to override Psalm's builtin method parameter lookup for one or more classes.
- `MethodReturnTypeProviderInterface` - can be used to override Psalm's builtin method return type lookup for one or more classes.
- `MethodVisibilityProviderInterface` - can be used to override Psalm's builtin method visibility checks for one or more classes.
- `PropertyExistenceProviderInterface` - can be used to override Psalm's builtin property existence checks for one or more classes.
- `PropertyTypeProviderInterface` - can be used to override Psalm's builtin property type lookup for one or more classes.
- `PropertyVisibilityProviderInterface` - can be used to override Psalm's builtin property visibility checks for one or more classes.
- `DynamicFunctionStorageProviderInterface` - can be used to override signature for one or more functions. It means you can define variadic param list. Infer return type by input args. Define function templates. Also check out `Psalm\Plugin\DynamicFunctionStorage` to find out what api it brings for you to change function's definition.
Here are a couple of example plugins:
- [StringChecker](https://github.com/vimeo/psalm/blob/master/examples/plugins/StringChecker.php) - checks class references in strings
- [PreventFloatAssignmentChecker](https://github.com/vimeo/psalm/blob/master/examples/plugins/PreventFloatAssignmentChecker.php) - prevents assignment to floats
2019-01-07 15:34:16 +01:00
- [FunctionCasingChecker](https://github.com/vimeo/psalm/blob/master/examples/plugins/FunctionCasingChecker.php) - checks that your functions and methods are correctly-cased
2018-02-22 02:11:34 +01:00
2020-05-14 13:47:55 +02:00
To ensure your plugin runs when Psalm does, add it to your [config](../configuration.md) (not needed for composer-based plugins):
2019-05-03 16:27:09 +02:00
```xml
2018-02-22 02:11:34 +01:00
<plugins>
<plugin filename="src/plugins/SomePlugin.php" />
</plugins>
```
Plugin loading (#855) * add ability to load plugins by class names - Plugins need to implement `__invoke(PluginFacade $psalm):void` method - Plugins are enabled by adding `<pluginClass class="Qualified\Class\Name"/>` - `PluginFacade` provides a single point of contact with Psalm, so that plugins cannot become coupled to Psalm internals * added `psalm-plugin` cli tool to manage plugins Available commands: `psalm-plugin list` - lists available and enabled plugins `psalm-plugin enable 'Plugin\Class\Name'` - enables plugin (modifies `psalm.xml`) `psalm-plugin disable 'Plugin\Class\Name'` - disables plugin (modifies `psalm.xml`) Plugin installation: `composer install plugin-vendor/plugin-package-name` Plugin authoring: Plugins are identified by package `type` field, which should contain `psalm-plugin` string. `extra.pluginClass` should refer to the name of the class implementing `__invoke(PluginFacade $psalm):void` function Todo: - better config file search - better output for `psalm-plugin` - better formatting for modified xml file - composer skeleton project for plugins - ability to refer to plugins by package name (cli only) - composer plugin to (optionally) enable plugin upon installation - documentation on plugin installation and authoring - interfaces for plugin dependencies - interface for plugin entry point - migration path for legacy plugins * documented previously undocumented plugin methods * split legacy plugin registration into a wrapper class also added `PluginApi` namespace and `RegistrationInterface` * reuse psalm's config search algorithm * enable/disable plugins by composer package name * allow specifying alternative config file name * whitelist PluginApi namespace three times, but well, it works now * interface for plugin entry points * psalm-plugin as a symfony console app * fixed errors found by psalm * suppressed false positive UnusedMethods * cs fix * better psalm-plugin output * don't leave empty `plugins` node to avoid old schema violation * removed junk file that shouldn't be there * cs fix * fixed phpunit failure (constant redefinition) * work around missing docblock in on symfony console * php 7.0 compatibility * allow `pluginClass` child elements as plugin configuration * decouple console commands from undelying implementation - introduce PluginListFactory - add `PluginList::enable(string $class)` and `PluginList::disable(string $class)` * PluginList tests * ComposerLock test * droppped debugging statement * added part of console command tests * added tests for EnableCommand * added DisableCommand tests * ignore unused args * ConfigFile test * disable travis cache in attempt to fix builds * nah, that didn't work * update for upstream changes * rebase fixes * namespaced `extra` entry for entry point * s/PluginFacade/PluginRegistrationSocket/g * Added $config parameter to PluginEntryPointInterface::__invoke() * cs fixes * entry point interface php7.0 compatibility * cleaned up old cruft - dropped todos I'm not going to pursues - locked entry point to be a class implementing entry point interface * fixed legacy plugins docs * Added RegistrationInterface::registerHooksFromClass() It mimics the way old plugins were registered in Psalm\Config, so handler classes extending Psalm\Plugin should be fully compatible with it. Since Psalm\Plugin-style plugin registration was moved to RegistrationSocket, LegacyPlugin now only load file-based plugins, so it was renamed to FileBasedPluginAdapter. * Converted EchoChecker plugin to composer-based format - Its subfolder is registered as a local composer package in the root composer.json, so it's directly installable with ``` composer require psalm/echo-checker-plugin ``` - Migration is trivial: drop the plugin into a separate folder, then add simple composer.json and the entry point class. * Updated docs * Don't reject hook handlers that inherit handling methods * strip void return type in stub file
2018-11-11 05:23:36 +01:00
2019-05-03 16:27:09 +02:00
You can also specify an absolute path to your plugin:
```xml
<plugins>
<plugin filename="/path/to/SomePlugin.php" />
</plugins>
```
### Using Xdebug
As Psalm disables _Xdebug_ at runtime, if you need to debug your code step-by-step when authoring a plugin, you can allow the extension by running Psalm as following:
```console
$ PSALM_ALLOW_XDEBUG=1 path/to/psalm
```
2019-04-14 21:21:41 +02:00
## Type system
Understand how Psalm handles types by [reading this guide](plugins_type_system.md).
2019-03-19 16:21:31 +01:00
## Handling custom plugin issues
2019-06-09 06:54:13 +02:00
Plugins may sometimes need to emit their own issues (i.e. not emit one of the [existing issues](../issues.md)). If this is the case, they can emit an issue that extends `Psalm\Issue\PluginIssue`.
2019-06-09 06:54:13 +02:00
To suppress a custom plugin issue in docblocks you can just use its issue name (e.g. `/** @psalm-suppress NoFloatAssignment */`, but to [suppress it in Psalms config](../dealing_with_code_issues.md#config-suppression) you must use the pattern:
```xml
<PluginIssue name="NoFloatAssignment" errorLevel="suppress" />
```
You can also use more complex rules in the `<issueHandler />` element, as you can with any other issue type e.g.
```xml
<PluginIssue name="NoFloatAssignment">
<errorLevel type="suppress">
<directory name="tests" />
</errorLevel>
</PluginIssue>
```
2020-05-14 13:47:55 +02:00
## Upgrading file-based plugin to composer-based version
Plugin loading (#855) * add ability to load plugins by class names - Plugins need to implement `__invoke(PluginFacade $psalm):void` method - Plugins are enabled by adding `<pluginClass class="Qualified\Class\Name"/>` - `PluginFacade` provides a single point of contact with Psalm, so that plugins cannot become coupled to Psalm internals * added `psalm-plugin` cli tool to manage plugins Available commands: `psalm-plugin list` - lists available and enabled plugins `psalm-plugin enable 'Plugin\Class\Name'` - enables plugin (modifies `psalm.xml`) `psalm-plugin disable 'Plugin\Class\Name'` - disables plugin (modifies `psalm.xml`) Plugin installation: `composer install plugin-vendor/plugin-package-name` Plugin authoring: Plugins are identified by package `type` field, which should contain `psalm-plugin` string. `extra.pluginClass` should refer to the name of the class implementing `__invoke(PluginFacade $psalm):void` function Todo: - better config file search - better output for `psalm-plugin` - better formatting for modified xml file - composer skeleton project for plugins - ability to refer to plugins by package name (cli only) - composer plugin to (optionally) enable plugin upon installation - documentation on plugin installation and authoring - interfaces for plugin dependencies - interface for plugin entry point - migration path for legacy plugins * documented previously undocumented plugin methods * split legacy plugin registration into a wrapper class also added `PluginApi` namespace and `RegistrationInterface` * reuse psalm's config search algorithm * enable/disable plugins by composer package name * allow specifying alternative config file name * whitelist PluginApi namespace three times, but well, it works now * interface for plugin entry points * psalm-plugin as a symfony console app * fixed errors found by psalm * suppressed false positive UnusedMethods * cs fix * better psalm-plugin output * don't leave empty `plugins` node to avoid old schema violation * removed junk file that shouldn't be there * cs fix * fixed phpunit failure (constant redefinition) * work around missing docblock in on symfony console * php 7.0 compatibility * allow `pluginClass` child elements as plugin configuration * decouple console commands from undelying implementation - introduce PluginListFactory - add `PluginList::enable(string $class)` and `PluginList::disable(string $class)` * PluginList tests * ComposerLock test * droppped debugging statement * added part of console command tests * added tests for EnableCommand * added DisableCommand tests * ignore unused args * ConfigFile test * disable travis cache in attempt to fix builds * nah, that didn't work * update for upstream changes * rebase fixes * namespaced `extra` entry for entry point * s/PluginFacade/PluginRegistrationSocket/g * Added $config parameter to PluginEntryPointInterface::__invoke() * cs fixes * entry point interface php7.0 compatibility * cleaned up old cruft - dropped todos I'm not going to pursues - locked entry point to be a class implementing entry point interface * fixed legacy plugins docs * Added RegistrationInterface::registerHooksFromClass() It mimics the way old plugins were registered in Psalm\Config, so handler classes extending Psalm\Plugin should be fully compatible with it. Since Psalm\Plugin-style plugin registration was moved to RegistrationSocket, LegacyPlugin now only load file-based plugins, so it was renamed to FileBasedPluginAdapter. * Converted EchoChecker plugin to composer-based format - Its subfolder is registered as a local composer package in the root composer.json, so it's directly installable with ``` composer require psalm/echo-checker-plugin ``` - Migration is trivial: drop the plugin into a separate folder, then add simple composer.json and the entry point class. * Updated docs * Don't reject hook handlers that inherit handling methods * strip void return type in stub file
2018-11-11 05:23:36 +01:00
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/tree/master/examples/plugins/composer-based/echo-checker/).