Static Code Analysis

The Jetpack monorepo uses Phan to perform static code analysis. While PHPCS can catch some surface-level issues and enforces coding standards, static analysis does a deep analysis of the code flows across the whole project, helping to identify problems like unused variables, dead code, and mismatched data types. Catching these issues early prevents bugs from making it into production.

Currently static analysis runs on all commits pushed to the monorepo.

Configuration for a project resides in the .phan/config.php within the project, which should generally build on top of the .phan/config.base.php from the monorepo root. A baseline file also resides at .phan/baseline.php to allow for incremental fixing of errors.

FAQs

How can I fix my types?

Where you can’t directly declare the types in PHP, you can use PHPDoc tags to declare what types things are supposed to be:

  • @var on class properties.
  • @param, @return, and @throws on functions and methods.

You can also use @phan-var, @phan-param, and so on if using the unprefixed tags would confuse other things.

Many of the existing type errors Phan is raising can be fixed by improving the PHPDoc. For example, code may be documented as @var WP_Error, which is being interpreted as Some\Namespace\WP_Error; this should instead be documented as @var \WP_Error.

Inline documentation like /** @var MyClass $var */ will not work, as php-ast does not preserve these comments. Phan can infer from if ( $var instanceof MyClass ) { ... } that $var is a MyClass inside the body of the if, which may be a beneficial check to add. Or, if you know for sure the type in a specific instance, Phan allows for using no-op string literal statements like '@var MyClass $var'; directly after the assignment to $var.

How do I deal with false positives?

First, be sure it’s really a false positive. If it really is a false positive, Phan recognizes inline comments like // @phan-suppress-current-line PhanUndeclaredVariable or // @phan-suppress-next-line PhanUndeclaredVariable, PhanUndeclaredFunction to silence issues on a particular line.

You can also use @phan-suppress in the doc block for the nearest enclosing class, function, or method to suppress issues within that scope, or @phan-file-suppress to suppress issues for an entire file. Use these sparingly so as to not accidentally ignore issues other than the one you intend.

GitHub keeps reporting new errors

GitHub limits each CI check to 10 inline annotations. If you think there might be more, you’d have to look at the log output for the check run or run Phan locally (see below).

Running Phan locally

Phan in the monorepo should be run locally via Jetpack’s CLI tool as jetpack phan. Note that Phan soft-requires the PHP ast extension; while on Linux installing this is often as easy as sudo apt-get install php8.2-ast, some Mac users have reported needing further steps.

Set up PHP’s AST extension

You can check whether the ast extension is already installed in your environment by running php --ri ast. If it prints something like this, you should be good (unless you need a newer version; see Phan’s README for version requirements):

ast support => enabled
extension version => 1.1.1 AST version => Current version is 90.
All versions (including experimental): {50, 60, 70, 80, 85, 90, 100}

Instructions for Mac users

This assumes you have PHP installed via Homebrew (e.g. you’ve done brew install php@8.2).

  1. You may need to brew install pkg-config zlib to install some necessary dependencies.
  2. Update the list of available extensions: pecl channel-update pecl.php.net
  3. Build the extension: pecl install ast
    • If the build process fails due to mkdir errors with the pecl directory, you might try mkdir -p /opt/homebrew/lib/php/pecl and running the install again.
  4. You may also need to tell PHP where to find the newly-installed extension.
    1. Run pecl config-get ext_dir to find where pecl installs extensions.
    2. Run php -r 'echo ini_get( "extension_dir" ) . "\n";' to find where PHP currently expects extensions to live.
    3. If those are the same, great! If not, you have two options:
      • If PHP’s current directory is empty, you could find your php.ini file (php --ini) and change extension_dir to pecl’s location.
      • Or else, pecl probably added extension=ast.so to an ini file somewhere. You could change the ast.so value to be the full path inside pecl’s directory.

If you can’t install the ast extension, you can still run Phan with the --allow-polyfill-parser option, but note that this may cause false positives and cannot be used to update baseline files. Alternatively, you can run Phan inside the Docker development environment.