Description
Is your feature request related to a problem?
After migrating from PHPStorm to VS Code I noticed that the available PHP_CodeSniffer extensions lacked meaningful integration with editor features.
- phpcs: Despite being the most popular option, this extension only supports in-line warnings/errors. The extension provides no auto-fix or formatting options at all. It also runs
phpcs
synchronously for each file.- Although it continues to "work" depending on your use-case, this extension is currently unmaintained and abandoned.
- There is a fork that has a large number of downloads but it is also abandoned.
- PHP Sniffer & Beautifier: This extension provides support for in-line warnings/errors as well as document formatting. This extension does not support in-line fixes and selection document formatting.
- PHP Sniffer: Another extension that provides in-line warnings/errors and full/selection document formatting. It does not support in-line fixes.
- It supports selection document formatting by only running against the selected snippets and adding in a
<?php
tag. While in theory this is great, in practice, it means any warnings/errors registered by a token on an unselected line won't be included in the formatting.
- It supports selection document formatting by only running against the selected snippets and adding in a
Despite some getting most of the way there, none of these extensions fully utilize VS Code's language tools, nor do they provide feature parity with PHPStorm:
- Line/File Ignore: Support for automatically adding
phpcs:ignore
comments on lines or files using a right-click context menu. - Individual Fixes: Fix selected problem(s) using a right-click context menu.
- Performance: Even with a debounce, running
phpcs
against a large file with a lot of expensive sniffs can take a while.
To be clear, these are limitations imposed by phpcs
. These extensions take advantage of the json
report type and there is no other provided report that bridges the gap.
Describe the solution you'd like
As developers do, when I encountered these issues, my first instinct was (naturally) to create my own extension (obligatory xkcd). (Technically my first instinct was to contribute upstream but the extensions were all abandoned or tiny at the time.) This extension achieves parity with PHPStorm using a custom report integration that returns warnings/errors and supports both individual fixes and document/selection formatting. This approach allows for a great deal of control over the behavior of phpcs
, however, it requires extending internal classes. While the classes I've extended seem relatively safe, it's not ideal for compatibility.
Given how ubiquitous VS Code has become (along with its myriad of popular forks), it seems reasonable for us to invest in providing better support for these kinds of tools. I've got a few possible approaches that I'd like to discuss:
Implement Custom Reports
The meat of my custom integration is an extended Fixer
class and File
class. It works by ignoring warnings/errors that aren't specifically relevant to the requested report. For instance, individual fixes work by ignoring all warnings/errors other than the given sniff source and token.
The least contentious option would be to add similar functionality directly within PHP_CodeSniffer. This is agnostic to any specific editor or extension and opens the door for others to provide the same functionality anywhere they require it. The main negative I see with this approach is that it would only provide support with the latest version of phpcs
. This means any extension would still have to support the old report style moving forward.
Another avenue here could be to add this functionality in a separate Composer package. My extension does something similar to support use-cases where phpcs
is run on a guest OS via Docker or similar. This could be bundled with anything that wanted to use this extended functionality.
Language Server
One caveat to distributing a package (or bundling reports) is that it still requires custom extensions to be developed for each editor that wants to use the functionality. Microsoft's Language Server Protocol was created to provide developers a consistent interface for providing language features to any editor. We could provide a language server and then any extension would be free to use it.
Integrated Server
The most robust option is to build a language server into phpcs
directly. My assumption is that deeply integrating like this would allow us to take advantage of the benefits of having a long-lived process. For example, could we use an in-memory cache of open files with their tokens and associated messages to save time? I'm not totally sure and if this is something we're interested in then we should profile phpcs
and see what kind of savings we would get if we eliminated stuff we can cache.
The caveat is that this feels like a pretty big undertaking. There's a package with Language Server Protocol DTOs but we would still need to add our own RPC reading/writing. This also has the same problem of bundling in that it's only available for the latest version of phpcs
.
Separate Server
Microsoft provides a framework for building language servers that takes care of the server/client communication and server lifecycle. All we would need to do is provide an interface between phpcs
and the language server. This is essentially what my extension does already, however, it uses VS Code's API directly rather than providing an LSP interface.
The problem with a separate server is that compatibility becomes an issue again. In order to avoid bundling code in the latest phpcs
release we would need to provide a custom report type for the server. As the "official" language server we would need to make maintaining compatibility a priority. I haven't thought deeply about this in a while but one option could be a hybrid approach. We could start shipping a language server report in phpcs
and provide any necessary polyfills for older versions in the language server. This gives us forward compatibility (since the report and all related modifications are bundled) as well as backward compatibility (a little hacky perhaps but these versions aren't going to ever change).
I lean really heavily towards building a separate language server. It provides the most comprehensive compatibility and if we start bundling the language server report generation we can also get the benefits of deeper integration. As for the server itself, we can take advantage of asynchronous workers and caching to make it as performant as possible. In my experience this has worked out really well.
To be clear, I am totally on board with building the language server. I've had a lot of fun working on my VS Code extension and Microsoft's language server framework is pretty much 1:1 with VS Code's API. There's a few differences and I'd use the opportunity to re-write everything but I don't see this being a significant time investment.