Skip to content

Commit

Permalink
Add conditional baselines for PHPStan
Browse files Browse the repository at this point in the history
Based on the PHP version and installed package versions.

Added conditionally included baselines for PHP 5–7 and 8+, Composer 1 and 2, Dotenv 3–5.

This conditional functionality can be greatly reduced if we drop support for older versions of PHP, Composer, and other dependencies.

To resolve these conditional baselines, we have to replace the initial data of {@see \Composer\InstalledVersions} which contains PHPStan's dependencies, since the context is its PHAR.

To accomplish this, we have to retrieve the contents of the project's installed dependencies from either:

1. `vendor/composer/installed.php` — Composer v2 PHP format.
2. `vendor/composer/installed.json` — Either Composer v1 or v2 JSON format.

The JSON format is tricky because it changes drastically between Composer v1 and v2 and the v2 PHP format. Both JSON formats must be remapped to the PHP format expected by `InstalledVersions`.

If the project's installed dependencies cannot be loaded, this file returns only the PHP baselines.

If the project's installed dependencies can be loaded, the extra baselines are resolved and the initial data of `InstalledVersions` is restored.
  • Loading branch information
mcaskill committed Mar 16, 2023
1 parent 542bfe6 commit 9b27a98
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 0 deletions.
41 changes: 41 additions & 0 deletions build/baseline-composer-1.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
parameters:
ignoreErrors:
-
message: "#^Call to an undefined method Composer\\\\Plugin\\\\PreFileDownloadEvent\\:\\:getContext\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\Plugin\\\\PreFileDownloadEvent\\:\\:getType\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\Plugin\\\\PreFileDownloadEvent\\:\\:setCustomCacheKey\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\Plugin\\\\PreFileDownloadEvent\\:\\:setProcessedUrl\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\DependencyResolver\\\\Operation\\\\OperationInterface\\:\\:getPackage\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\DependencyResolver\\\\Operation\\\\OperationInterface\\:\\:getTargetPackage\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^If condition is always true\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Unreachable statement \\- code above always terminates\\.$#"
count: 1
path: ../src/Installer.php
31 changes: 31 additions & 0 deletions build/baseline-composer-2.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
parameters:
ignoreErrors:
-
message: "#^If condition is always false\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\DependencyResolver\\\\Operation\\\\OperationInterface\\:\\:getJobType\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\DependencyResolver\\\\Operation\\\\OperationInterface\\:\\:getPackage\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\DependencyResolver\\\\Operation\\\\OperationInterface\\:\\:getTargetPackage\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\Plugin\\\\PreFileDownloadEvent\\:\\:getRemoteFilesystem\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined method Composer\\\\Plugin\\\\PreFileDownloadEvent\\:\\:setRemoteFilesystem\\(\\)\\.$#"
count: 1
path: ../src/Installer.php
21 changes: 21 additions & 0 deletions build/baseline-php-7.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
parameters:
ignoreErrors:
-
message: "#^Parameter \\#1 \\$ch of function curl_close expects resource, CurlHandle\\|resource given\\.$#"
count: 1
path: ../src/Http.php

-
message: "#^Parameter \\#1 \\$ch of function curl_errno expects resource, CurlHandle\\|resource given\\.$#"
count: 1
path: ../src/Http.php

-
message: "#^Parameter \\#1 \\$ch of function curl_exec expects resource, CurlHandle\\|resource given\\.$#"
count: 1
path: ../src/Http.php

-
message: "#^Parameter \\$curl_handle of method Junaidbhura\\\\Composer\\\\WPProPlugins\\\\Http\\:\\:request\\(\\) has invalid type CurlHandle\\.$#"
count: 1
path: ../src/Http.php
16 changes: 16 additions & 0 deletions build/baseline-php-8.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
parameters:
ignoreErrors:
-
message: "#^Parameter \\#1 \\$handle of function curl_close expects CurlHandle, CurlHandle\\|resource given\\.$#"
count: 1
path: ../src/Http.php

-
message: "#^Parameter \\#1 \\$handle of function curl_errno expects CurlHandle, CurlHandle\\|resource given\\.$#"
count: 1
path: ../src/Http.php

-
message: "#^Parameter \\#1 \\$handle of function curl_exec expects CurlHandle, CurlHandle\\|resource given\\.$#"
count: 1
path: ../src/Http.php
11 changes: 11 additions & 0 deletions build/baseline-phpdotenv-3.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
parameters:
ignoreErrors:
-
message: "#^Call to an undefined static method Dotenv\\\\Dotenv\\:\\:createImmutable\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Call to an undefined static method Dotenv\\\\Dotenv\\:\\:createUnsafeImmutable\\(\\)\\.$#"
count: 1
path: ../src/Installer.php
16 changes: 16 additions & 0 deletions build/baseline-phpdotenv-4.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
parameters:
ignoreErrors:
-
message: "#^Call to an undefined static method Dotenv\\\\Dotenv\\:\\:createUnsafeImmutable\\(\\)\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Parameter \\#1 \\$repository of static method Dotenv\\\\Dotenv\\:\\:create\\(\\) expects Dotenv\\\\Repository\\\\RepositoryInterface, string given\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Static method Dotenv\\\\Dotenv\\:\\:create\\(\\) invoked with 1 parameter, 2\\-4 required\\.$#"
count: 1
path: ../src/Installer.php
11 changes: 11 additions & 0 deletions build/baseline-phpdotenv-5.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
parameters:
ignoreErrors:
-
message: "#^Parameter \\#1 \\$repository of static method Dotenv\\\\Dotenv\\:\\:create\\(\\) expects Dotenv\\\\Repository\\\\RepositoryInterface, string given\\.$#"
count: 1
path: ../src/Installer.php

-
message: "#^Static method Dotenv\\\\Dotenv\\:\\:create\\(\\) invoked with 1 parameter, 2\\-5 required\\.$#"
count: 1
path: ../src/Installer.php
130 changes: 130 additions & 0 deletions build/ignore-errors-by-installed-versions.neon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

/**
* Resolves additional baselines and configurations to apply to PHPStan's
* analysis based on the version of PHP, Composer, and the project's packages.
*
* To resolve these conditional baselines, we have to replace the initial data
* of {@see \Composer\InstalledVersions} which contains PHPStan's dependencies,
* since the context is its PHAR.
*
* To accomplish this, we have to retrieve the contents of the project's
* installed dependencies from either:
*
* 1. 'vendor/composer/installed.php' — Composer v2 PHP format.
* 2. 'vendor/composer/installed.json' — Either Composer v2 or v1 JSON format.
*
* The JSON format is tricky because it changes drastically between Composer v1
* and v2 and the v2 PHP format. Both JSON formats must be remapped to the PHP
* format expected by `InstalledVersions`.
*
* If the project's installed dependencies cannot be loaded, this file returns
* only the PHP baselines.
*
* If the project's installed dependencies can be loaded, the extra baselines
* are resolved and the initial data of `InstalledVersions` is restored.
*/

use Composer\InstalledVersions;
use Composer\Semver\VersionParser;

$config = array();
$config['includes'] = array();
$config['parameters']['phpVersion'] = PHP_VERSION_ID;

if ( PHP_VERSION_ID >= 80000 ) {
$config['includes'][] = __DIR__ . '/baseline-php-8.neon';
} else {
$config['includes'][] = __DIR__ . '/baseline-php-7.neon';
}

$cwd = getcwd();
if ( ! is_string( $cwd ) ) {
return $config;
}

if ( file_exists( $cwd . '/vendor/composer/installed.php' ) ) {
$projectInstalled = require $cwd . '/vendor/composer/installed.php';
} elseif ( file_exists( $cwd . '/vendor/composer/installed.json' ) ) {
$json = file_get_contents( $cwd . '/vendor/composer/installed.json' );
if ( ! is_string( $json ) ) {
return $config;
}

$installed = json_decode( $json, true );
if ( ! is_array( $installed ) ) {
return $config;
}

$projectInstalled = array(
'root' => array(),
'versions' => array(),
);

$packages = isset( $installed['packages'] ) ? $installed['packages'] : $installed;

if ( is_array( $packages ) ) {
foreach ( $packages as $package ) {
$projectInstalled['versions'][ $package['name'] ] = array(
'pretty_version' => $package['version'],
'version' => $package['version_normalized'],
'reference' => (
isset( $package['dist']['reference'] )
? $package['dist']['reference']
: (
isset( $package['source']['reference'] )
? $package['source']['reference']
: null
)
),
'type' => $package['type'],
'install_path' => (
isset( $package['install-path'] )
? __DIR__ . '/' . $package['install-path']
: null
),
'aliases' => (
isset( $package['extra']['branch-alias'] )
? array_values( $package['extra']['branch-alias'] )
: array()
),
'dev_requirement' => (
isset( $installed['dev-package-names'] )
? in_array( $package['name'], $installed['dev-package-names'], true )
: false
),
);
}
}
}

if ( empty( $projectInstalled['versions'] ) ) {
return $config;
}

$pharInstalled = InstalledVersions::getAllRawData();
InstalledVersions::reload( $projectInstalled );

$versionParser = new VersionParser();

if ( InstalledVersions::isInstalled( 'composer/composer' ) ) {
if ( InstalledVersions::satisfies( $versionParser, 'composer/composer', '^1') ) {
$config['includes'][] = __DIR__ . '/baseline-composer-1.neon';
} else {
$config['includes'][] = __DIR__ . '/baseline-composer-2.neon';
}
}

if ( InstalledVersions::isInstalled( 'vlucas/phpdotenv' ) ) {
if ( InstalledVersions::satisfies( $versionParser, 'vlucas/phpdotenv', '^3') ) {
$config['includes'][] = __DIR__ . '/baseline-phpdotenv-3.neon';
} elseif ( InstalledVersions::satisfies( $versionParser, 'vlucas/phpdotenv', '^4') ) {
$config['includes'][] = __DIR__ . '/baseline-phpdotenv-4.neon';
} else {
$config['includes'][] = __DIR__ . '/baseline-phpdotenv-5.neon';
}
}

InstalledVersions::reload( $pharInstalled ? end( $pharInstalled[0] ) : array() );

return $config;
2 changes: 2 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
includes:
- build/ignore-errors-by-installed-versions.neon.php
parameters:
level: max
paths:
Expand Down

0 comments on commit 9b27a98

Please sign in to comment.