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 and click `Use this template` button.
### 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()` .
2021-06-04 21:32:53 +02:00
## 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\RegistrationInterface::addFileTypeScanner('html', CustomFileScanner::class)`
* `Psalm\Plugin\RegistrationInterface::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
2018-11-12 16:11:08 +01:00
Plugins may implement one of (or more than one of) `Psalm\Plugin\Hook\*` interface(s).
2018-02-22 02:11:34 +01:00
```php
< ?php
2018-11-12 16:11:08 +01:00
class SomePlugin implements \Psalm\Plugin\Hook\AfterStatementAnalysisInterface
2018-02-22 02:11:34 +01:00
{
}
```
2020-05-14 13:47:55 +02:00
`Psalm\Plugin\Hook\*` offers the following interfaces that you can implement:
2018-02-22 02:11:34 +01:00
2019-05-16 20:32:44 +02: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.
2020-02-13 13:04:02 +01:00
- `AfterEveryFunctionCallAnalysisInterface` - called after Psalm evaluates any function call. Cannot influence the call further.
2019-05-16 20:32:44 +02:00
- `AfterExpressionAnalysisInterface` - called after Psalm evaluates an expression.
2020-11-16 02:28:51 +01:00
- `AfterFileAnalysisInterface` - called after Psalm analyzes a file.
2020-02-13 13:04:02 +01:00
- `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.
2020-04-28 21:30:51 +02:00
- `AfterFunctionLikeAnalysisInterface` - called after Psalm has completed its analysis of a given function-like.
2019-05-16 20:32:44 +02:00
- `AfterMethodCallAnalysisInterface` - called after Psalm analyzes a method call.
- `AfterStatementAnalysisInterface` - called after Psalm evaluates an statement.
2020-08-10 16:49:53 +02:00
- `BeforeFileAnalysisInterface` - called before Psalm analyzes a file.
2019-05-16 20:32:44 +02:00
- `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.
2019-05-16 20:32:44 +02:00
- `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.
2019-05-16 20:32:44 +02:00
- `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.
2018-11-12 16:11:08 +01:00
2019-01-07 14:38:56 +01:00
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 >
```
2020-09-12 05:35:37 +02:00
### 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-01-07 14:38:56 +01:00
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-01-07 14:38:56 +01:00
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 Psalm’ s config ](../dealing_with_code_issues.md#config-suppression ) you must use the pattern:
2019-01-07 14:38:56 +01:00
```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
2021-06-04 21:32:53 +02: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/ ).