Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow setting php version from config or composer.json #2715

Merged
merged 2 commits into from Jan 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -15,13 +15,14 @@
],
"require": {
"php": "^7.1.3|^8",
"ext-SimpleXML": "*",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-SimpleXML": "*",
"ext-tokenizer": "*",
"amphp/amp": "^2.1",
"amphp/byte-stream": "^1.5",
"composer/semver": "^1.5",
"composer/xdebug-handler": "^1.1",
"felixfbecker/advanced-json-rpc": "^3.0.3",
"felixfbecker/language-server-protocol": "^1.4",
Expand Down
1 change: 1 addition & 0 deletions config.xsd
Expand Up @@ -26,6 +26,7 @@
<xs:attribute name="errorBaseline" type="xs:string" />
<xs:attribute name="maxStringLength" type="xs:string" />
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="phpVersion" type="xs:string" />
<xs:attribute name="serializer" type="xs:string" />

<xs:attribute name="addParamDefaultToDocblockType" type="xs:boolean" default="false" />
Expand Down
10 changes: 10 additions & 0 deletions docs/running_psalm/configuration.md
Expand Up @@ -244,6 +244,16 @@ Setting to `false` prevents the stub from loading.
```
When `true`, Psalm will complain when referencing an explicit string offset on an array e.g. `$arr['foo']` without a user first asserting that it exists (either via an `isset` check or via an object-like array). Defaults to `false`.

#### phpVersion
```xml
<psalm
phpVersion="[string]"
>
```
Set the php version psalm should assume when checking and/or fixing the project. If this attribute is not set, psalm uses the declaration in `composer.json` if one is present. It will check against the earliest version of PHP that satisfies the declared `php` dependency

This can be overridden on the command-line using the `--php-version=` flag which takes the highest precedence over both the `phpVersion` setting and the version derived from `composer.json`.

### Running Psalm

#### autoloader
Expand Down
46 changes: 46 additions & 0 deletions src/Psalm/Config.php
@@ -1,6 +1,7 @@
<?php
namespace Psalm;

use Composer\Semver\Semver;
use Webmozart\PathUtil\Path;
use function array_merge;
use function array_pop;
Expand Down Expand Up @@ -182,6 +183,13 @@ class Config
*/
public $base_dir;

/**
* The PHP version to assume as declared in the config file
*
* @var string|null
*/
private $configured_php_version;

/**
* @var array<int, string>
*/
Expand Down Expand Up @@ -745,6 +753,10 @@ private static function fromXmlAndPaths(string $base_dir, string $file_contents,
$base_dir = $current_dir;
}

if (isset($config_xml['phpVersion'])) {
$config->configured_php_version = (string) $config_xml['phpVersion'];
}

if (isset($config_xml['autoloader'])) {
$autoloader_path = $config->base_dir . DIRECTORY_SEPARATOR . $config_xml['autoloader'];

Expand Down Expand Up @@ -1861,8 +1873,42 @@ public function addStubFile(string $stub_file)
$this->stub_files[] = $stub_file;
}

public function getPhpVersion(): ?string
{
if (isset($this->configured_php_version)) {
return $this->configured_php_version;
}

return $this->getPHPVersionFromComposerJson();
}

private function setBooleanAttribute(string $name, bool $value): void
{
$this->$name = $value;
}

/**
* @psalm-suppress MixedAssignment
* @psalm-suppress MixedArrayAccess
*/
private function getPHPVersionFromComposerJson(): ?string
{
$composer_json_path = $this->base_dir . DIRECTORY_SEPARATOR. 'composer.json';

if (file_exists($composer_json_path)) {
if (!$composer_json = json_decode(file_get_contents($composer_json_path), true)) {
throw new \UnexpectedValueException('Invalid composer.json at ' . $composer_json_path);
}
$php_version = $composer_json['require']['php'] ?? null;

if ($php_version) {
foreach (['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] as $candidate) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like having to do this loop here hard-coding versions, but IMHO there's no way to deterministically reverse a composer range to end up with a minimum version.

What still is a problem is that the knowledge of what versions of PHP psalm has actual support for isn't centralized but so far only existed in this regex here:

if (!preg_match('/^(5\.[456]|7\.[01234]|8\.[0])(\..*)?$/', $version)) {

maybe for the future it would be worth to actually have proper API to get to this information so further duplication isn't needed.

if (Semver::satisfies($candidate, (string)$php_version)) {
return $candidate;
}
}
}
}
return null;
}
}
4 changes: 4 additions & 0 deletions src/psalm.php
Expand Up @@ -493,6 +493,10 @@ function ($arg) {
$progress
);

if (!isset($options['php-version'])) {
$options['php-version'] = $config->getPhpVersion();
}

if (isset($options['php-version'])) {
if (!is_string($options['php-version'])) {
die('Expecting a version number in the format x.y' . PHP_EOL);
Expand Down
26 changes: 26 additions & 0 deletions tests/Config/ConfigTest.php
Expand Up @@ -1451,4 +1451,30 @@ public function testGetPossiblePsr4Path()
$config->getPotentialComposerFilePathForClassLike('Psalm\\Tests\\Foo')
);
}

/**
* @return void
*/
public function testTakesPhpVersionFromConfigFile()
{
$cfg = Config::loadFromXML(
dirname(__DIR__, 2),
'<?xml version="1.0"?><psalm phpVersion="7.1"></psalm>'
);
$this->assertSame('7.1', $cfg->getPhpVersion());
}

/**
* @return void
*/
public function testReadsComposerJsonForPhpVersion()
{

$root = __DIR__ . '/../fixtures/ComposerPhpVersion';
$cfg = Config::loadFromXML($root, "<?xml version=\"1.0\"?><psalm></psalm>");
$this->assertSame('7.2', $cfg->getPhpVersion());

$cfg = Config::loadFromXML($root, "<?xml version=\"1.0\"?><psalm phpVersion='8.0'></psalm>");
$this->assertSame('8.0', $cfg->getPhpVersion());
}
}
5 changes: 5 additions & 0 deletions tests/fixtures/ComposerPhpVersion/composer.json
@@ -0,0 +1,5 @@
{
"require": {
"php": "7.2|7.3,<8"
}
}