From 3d634b603d84ee1ad822204cc6fe19bf787c86ed Mon Sep 17 00:00:00 2001 From: Philip Hofstetter Date: Thu, 30 Jan 2020 08:20:23 +0100 Subject: [PATCH 1/2] allow setting php version from config or composer.json if a composer.json is present and a PHP version requirement is configured, we set the php version to the minimal PHP version that satisfies the composer requirement. Additionally, this adds a `phpVersion` attribute to the tag. If that's set, it takes precedence over what has been detected in composer.json. And finally, the --php-version command line flag continues to work and takes precedence over the setting in the tag this fixes #2628 --- composer.json | 3 +- config.xsd | 1 + src/Psalm/Config.php | 46 +++++++++++++++++++ src/psalm.php | 4 ++ tests/Config/ConfigTest.php | 26 +++++++++++ .../fixtures/ComposerPhpVersion/composer.json | 5 ++ 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/ComposerPhpVersion/composer.json diff --git a/composer.json b/composer.json index 698454e8d73..89a5611067e 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/config.xsd b/config.xsd index 07150200d2f..61b65875062 100644 --- a/config.xsd +++ b/config.xsd @@ -26,6 +26,7 @@ + diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index eeedfd36cd9..c2444bceae5 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -1,6 +1,7 @@ */ @@ -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']; @@ -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) { + if (Semver::satisfies($candidate, (string)$php_version)) { + return $candidate; + } + } + } + } + return null; + } } diff --git a/src/psalm.php b/src/psalm.php index df23c9d1d8b..fcbcd09e06e 100644 --- a/src/psalm.php +++ b/src/psalm.php @@ -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); diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index 443807b8798..39186831979 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -1451,4 +1451,30 @@ public function testGetPossiblePsr4Path() $config->getPotentialComposerFilePathForClassLike('Psalm\\Tests\\Foo') ); } + + /** + * @return void + */ + public function testTakesPhpVersionFromConfigFile() + { + $cfg = Config::loadFromXML( + dirname(__DIR__, 2), + '' + ); + $this->assertSame('7.1', $cfg->getPhpVersion()); + } + + /** + * @return void + */ + public function testReadsComposerJsonForPhpVersion() + { + + $root = __DIR__ . '/../fixtures/ComposerPhpVersion'; + $cfg = Config::loadFromXML($root, ""); + $this->assertSame('7.2', $cfg->getPhpVersion()); + + $cfg = Config::loadFromXML($root, ""); + $this->assertSame('8.0', $cfg->getPhpVersion()); + } } diff --git a/tests/fixtures/ComposerPhpVersion/composer.json b/tests/fixtures/ComposerPhpVersion/composer.json new file mode 100644 index 00000000000..e41b6f1b9f5 --- /dev/null +++ b/tests/fixtures/ComposerPhpVersion/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "7.2|7.3,<8" + } +} From 2be6bbfeed45cbe6912c0db96aa354591fd58aa7 Mon Sep 17 00:00:00 2001 From: Philip Hofstetter Date: Thu, 30 Jan 2020 08:28:48 +0100 Subject: [PATCH 2/2] document `phpVersion` setting --- docs/running_psalm/configuration.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/running_psalm/configuration.md b/docs/running_psalm/configuration.md index c619d28d668..c85dd87f69b 100644 --- a/docs/running_psalm/configuration.md +++ b/docs/running_psalm/configuration.md @@ -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 + +``` +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