Phpactor 2022.09.11

The Phpactor logo Phpactor 2022.09.11

Today I have tagged Phpactor 2022.09.11. The previous release was Phpactor 18 all the way back in January.

It has been a significant 8 months for Phpactor:

Converting Phpactor to a monorepo has accelerated development significantly, it has been far easier to fix bugs and add features. It is imaginable to say that more progress has been made in these 8 months than in the past 3 years and it’s still awful.

Conditional Types

CalVer

I’ve decided to use CalVer (i.e. tagging releases as YYYY.MM.DD) from now on. Previously Phpactor has been stuck in the long hell of 0.x releases, which begs the question: what would 1.0.0 look like? Semantic versioning doesn’t make any sense here, Phpactor is not a library, and since it is now a monorepo, it also does not offer extension points which plugins would depend on, and if it did, the API can be offered as a separate, semantically versioned, package.

Tagging Phpactor is, for me, a symbolic act - development is continouous. Some people were still using the 0.18 version. From now on I’ll try and tag a new version each month for the benefit of the changelog, people that require tagged versions and to give me something to blog about.

Removal of the extension manager

I made a big thing of Phpactor Extensions. The dream was you could start with only the extension manager package, and build your own Phpactor. In practice I created a monster that was tightly coupled to the internals of Composer 1.

As Composer 1 became more deprecated I was faced with the looming spectre of having to rewrite the extension manager from scratch. I didn’t have the time. In addition, having the monorepo meant that extensions would have to depend on the entire monorepo package for testing (no I have not yet, and probably never will, setup a repo split).

Finally I opted to simply remove the extension manager and bundle the extensions into Phpactor by default, they are just “available” to be activated with a config switch.

This decreases the maintainence burden by assuring that they always work, on other hand it increases it by requiring community initiatives to be merged into the monorepo, but so far this hasn’t been a huge issue.

Highlights

Some highlights from the changelog:

  • Completely new type system
  • Better constant support - indexing, goto def, find references, hover, etc.
  • Lazily resolve documentation for completion items
  • Generate constructor refactoring
  • Fill object refactoring
  • Remove unused imports diagnositcs and code transformation
  • Added native WR single-pass diagnostics #1700
  • Index clean command #1691 @mamazu
  • Support for mixins
  • Docblock completion
  • Support for type assertions via. is_*, instanceof etc
  • Array shape type support (types and basic completion)
  • Support for variadics
  • Send rename file request to client when renaming a PSR class @przepompownia
  • Generics mostly supported
  • PHPStan, Psalm and (experimental) Behat extensions are now included by default.
  • Enum support (requires 8.1 PHP runtime)
  • Many more things I didn’t even add to the changelog!
Generate Docblocks
Better inline completion documentation and keyword completion

Type System and Flow

Refactoring the Phpactor type system was the main motiviation for the change to the monorepo as it is something that affects many of the previous satellite packages (of which there were some 50 or so).

The new system gives it some level of parity with the Phpstan and Psalm type systems while the flow analysis is much better than it was, but still lacking when compared to the static analysers.

Type system in action

VS Code

The vs-code extension has had some attention. Phpactor is now automatically downloaded and installed on extension load and it is neatly packaged as a vsix file.

Many VSCode specific bugs have been fixed.

VSCode support is better than ever

Diagnostics

Phpactor now has a “single pass” diagnostics engine which is easily extensible, for development purposes Phpactor now also provides phpactor analyse which works just like PHPStan or Psalm:

new diagnostics phpactor worse:analyse

As it only has about 4 “rules” and is about 4x slower than your favourite SA tool I would not recommend using it for that purpose. The engine does however provide a good way to highlight when a code action can be applied, e.g. remove missing imports:

Unused Import Diagnostics and Code Action

Sponsorship

Marcel Pociot reached out to me to let me know that Tinkerwell uses Phpactor under the hood (albeit a modified fork), and has provided sponsorship of €100 a month for which I’m very grateful 💗 (and also to my other sponsors).

Working on a language server is an incredibly time consuming hobby, especially when it’s as badly written as Phpactor is, maybe one day I can get enough sponsorship to write it properly.

In addition I registered a Blackfire open source license to help improve Phpactor’s performance.

Docblock completion

Musing

The monorepository has made the project far easier to contribute to, but lots of the code is, I fear, difficult to understand and lacks some unifying principles (although progress has been made). The self-inflicted maintainence burden is still quite high, it’s still the case that a “quick” improvement can quickly expand into 4 or more hours of my day, and some “bugs” resurface again and again (looking at you, UnusedImportProvider). Saying that, it’s also the case that I’ve fixed 3 bugs in the hour before starting work, so it’s not all bad.

I increasingly feel that I am actively writing (tolerably well tested) legacy code, and perhaps more time needs to be spent on refactoring the code base.

One such refactoring would be to create a Intermediate Representation on top of the AST. Such a thing could be seen as a DOM document for code, and following in the footsteps of frontend libraries such as React (I think, I’m not a frontend person) it could be updated incrementally and not rebuilt from scratch all the time.

Nodes in the IR would be augmented with type information and the IR would also include Docblock nodes.

Ultimately it could mean:

  • Vastly improved performance for diagnostics, completion and just about anything else that depends on static analysis.
  • (theoretically) lossless abstraction (no more re-parsing/re-traversing the raw AST)
  • Can analyse Docblock and PHP nodes with the same API

I kind of imagine the document being broken down into blocks which are defined by scope (e.g. an if branch is a “block”) and atomic text edits would affect a single block, which may in turn have children which depend on it and are affected by the change, or not. But much more thought needs to be applied to this crazy theory.