1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Display target PHP version

Historically it was often not quite clear to users what PHP version
Psalm assumes, and why. This PR addresses this issue by printing the
version and where we got it from right before scanning the files.
This commit is contained in:
Bruce Weirdan 2021-11-27 02:06:33 +02:00
parent 72de3b66cc
commit d19aad7db1
No known key found for this signature in database
GPG Key ID: CFC3AAB181751B0D
31 changed files with 172 additions and 74 deletions

View File

@ -278,6 +278,9 @@ class Codebase
*/
public $php_minor_version = PHP_MINOR_VERSION;
/** @var 'cli'|'config'|'composer'|'tests'|'runtime' */
public $php_version_source = 'runtime';
/**
* @var bool
*/

View File

@ -2225,7 +2225,12 @@ class Config
public function getPhpVersion(): ?string
{
return $this->configured_php_version ?? $this->getPHPVersionFromComposerJson();
return $this->getPhpVersionFromConfig() ?? $this->getPHPVersionFromComposerJson();
}
public function getPhpVersionFromConfig(): ?string
{
return $this->configured_php_version;
}
private function setBooleanAttribute(string $name, bool $value): void
@ -2237,7 +2242,7 @@ class Config
* @psalm-suppress MixedAssignment
* @psalm-suppress MixedArrayAccess
*/
private function getPHPVersionFromComposerJson(): ?string
public function getPHPVersionFromComposerJson(): ?string
{
$composer_json_path = Composer::getJsonFilePath($this->base_dir);

View File

@ -544,6 +544,33 @@ class ProjectAnalyzer
return isset($this->project_files[$file_path]);
}
private function generatePHPVersionMessage(): string
{
$codebase = $this->codebase;
$version = $codebase->php_major_version . '.' . $codebase->php_minor_version;
switch ($codebase->php_version_source) {
case 'cli':
$source = '(set by CLI argument)';
break;
case 'config':
$source = '(set by config file)';
break;
case 'composer':
$source = '(inferred from composer.json)';
break;
case 'tests':
$source = '(set by tests)';
break;
case 'runtime':
$source = '(inferred from current PHP version)';
break;
}
return "Target PHP version: $version $source\n";
}
public function check(string $base_dir, bool $is_diff = false): void
{
$start_checks = (int)microtime(true);
@ -574,6 +601,7 @@ class ProjectAnalyzer
}
}
$this->progress->write($this->generatePHPVersionMessage());
$this->progress->startScanningFiles();
$diff_no_files = false;
@ -992,6 +1020,7 @@ class ProjectAnalyzer
$this->checkDirWithConfig($dir_name, $this->config, true);
$this->progress->write($this->generatePHPVersionMessage());
$this->progress->startScanningFiles();
$this->config->initializePlugins($this);
@ -1123,6 +1152,7 @@ class ProjectAnalyzer
$this->file_reference_provider->loadReferenceCache();
$this->progress->write($this->generatePHPVersionMessage());
$this->progress->startScanningFiles();
$this->config->initializePlugins($this);
@ -1164,6 +1194,7 @@ class ProjectAnalyzer
$this->file_reference_provider->loadReferenceCache();
$this->progress->write($this->generatePHPVersionMessage());
$this->progress->startScanningFiles();
$this->config->initializePlugins($this);
@ -1262,7 +1293,10 @@ class ProjectAnalyzer
$this->show_issues = false;
}
public function setPhpVersion(string $version): void
/**
* @param 'cli'|'config'|'composer'|'tests' $source
*/
public function setPhpVersion(string $version, string $source): void
{
if (!preg_match('/^(5\.[456]|7\.[01234]|8\.[01])(\..*)?$/', $version)) {
throw new \UnexpectedValueException('Expecting a version number in the format x.y');
@ -1283,6 +1317,7 @@ class ProjectAnalyzer
$this->codebase->php_major_version = $php_major_version;
$this->codebase->php_minor_version = $php_minor_version;
$this->codebase->php_version_source = $source;
}
/**

View File

@ -332,8 +332,7 @@ final class Psalm
$progress
);
self::initPhpVersion($options, $config, $project_analyzer);
CliUtils::initPhpVersion($options, $config, $project_analyzer);
$start_time = microtime(true);
@ -1071,21 +1070,6 @@ final class Psalm
}
}
private static function initPhpVersion(array $options, Config $config, ProjectAnalyzer $project_analyzer): void
{
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);
}
$project_analyzer->setPhpVersion($options['php-version']);
}
}
/** @return false|'always'|'auto' */
private static function shouldFindUnusedCode(array $options, Config $config)
{

View File

@ -281,17 +281,7 @@ HELP;
$keyed_issues = [];
}
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);
}
$project_analyzer->setPhpVersion($options['php-version']);
}
CliUtils::initPhpVersion($options, $config, $project_analyzer);
if (isset($options['codeowner'])) {
$codeowner_files = self::loadCodeowners($providers);

View File

@ -5,6 +5,7 @@ namespace Psalm\Internal;
use Composer\Autoload\ClassLoader;
use Phar;
use Psalm\Config;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\Composer;
use function assert;
@ -622,4 +623,25 @@ HELP;
return (int)$limit;
}
public static function initPhpVersion(array $options, Config $config, ProjectAnalyzer $project_analyzer): void
{
$source = null;
if (isset($options['php-version'])) {
if (!is_string($options['php-version'])) {
die('Expecting a version number in the format x.y' . PHP_EOL);
}
$version = $options['php-version'];
$source = 'cli';
} elseif ($version = $config->getPhpVersionFromConfig()) {
$source = 'config';
} elseif ($version = $config->getPHPVersionFromComposerJson()) {
$source = 'composer';
}
if ($version !== null && $source !== null) {
$project_analyzer->setPhpVersion($version, $source);
}
}
}

View File

@ -67,7 +67,7 @@ class ConfigTest extends \Psalm\Tests\TestCase
)
);
$p->setPhpVersion('7.3');
$p->setPhpVersion('7.3', 'tests');
return $p;
}

View File

@ -136,7 +136,7 @@ class DocumentationTest extends TestCase
)
);
$this->project_analyzer->setPhpVersion('8.0');
$this->project_analyzer->setPhpVersion('8.0', 'tests');
}
public function testAllIssuesCoveredInConfigSchema(): void
@ -204,7 +204,7 @@ class DocumentationTest extends TestCase
$this->markTestSkipped();
}
$this->project_analyzer->setPhpVersion($php_version);
$this->project_analyzer->setPhpVersion($php_version, 'tests');
if ($check_references) {
$this->project_analyzer->getCodebase()->reportUnusedCode();

View File

@ -115,6 +115,10 @@ class PsalmEndToEndTest extends TestCase
{
$this->runPsalmInit(1);
$result = $this->runPsalm([], self::$tmpDir, true);
$this->assertStringContainsString(
'Target PHP version: 7.1 (inferred from composer.json)',
$result['STDERR']
);
$this->assertStringContainsString('UnusedParam', $result['STDOUT']);
$this->assertStringContainsString('InvalidReturnType', $result['STDOUT']);
$this->assertStringContainsString('InvalidReturnStatement', $result['STDOUT']);
@ -122,6 +126,26 @@ class PsalmEndToEndTest extends TestCase
$this->assertSame(2, $result['CODE']);
}
public function testPsalmWithPHPVersionOverride(): void
{
$this->runPsalmInit(1);
$result = $this->runPsalm(['--php-version=8.0'], self::$tmpDir, true);
$this->assertStringContainsString(
'Target PHP version: 8.0 (set by CLI argument)',
$result['STDERR']
);
}
public function testPsalmWithPHPVersionFromConfig(): void
{
$this->runPsalmInit(1, '7.4');
$result = $this->runPsalm([], self::$tmpDir, true);
$this->assertStringContainsString(
'Target PHP version: 7.4 (set by config file)',
$result['STDERR']
);
}
public function testPsalmDiff(): void
{
if (PHP_VERSION_ID < 70400) {
@ -212,7 +236,7 @@ class PsalmEndToEndTest extends TestCase
/**
* @return array{STDOUT: string, STDERR: string, CODE: int|null}
*/
private function runPsalmInit(?int $level = null): array
private function runPsalmInit(?int $level = null, ?string $php_version = null): array
{
$args = ['--init'];
@ -226,7 +250,9 @@ class PsalmEndToEndTest extends TestCase
$psalm_config_contents = file_get_contents(self::$tmpDir . '/psalm.xml');
$psalm_config_contents = \str_replace(
'errorLevel="1"',
'errorLevel="1" cacheDirectory="' . self::$tmpDir . '/cache"',
'errorLevel="1" '
. 'cacheDirectory="' . self::$tmpDir . '/cache" '
. ($php_version ? ('phpVersion="' . $php_version . '"') : ''),
$psalm_config_contents
);
file_put_contents(self::$tmpDir . '/psalm.xml', $psalm_config_contents);

View File

@ -68,7 +68,7 @@ abstract class FileManipulationTestCase extends \Psalm\Tests\TestCase
$input_code
);
$this->project_analyzer->setPhpVersion($php_version);
$this->project_analyzer->setPhpVersion($php_version, 'tests');
$keyed_issues_to_fix = [];

View File

@ -29,7 +29,7 @@ class FileReferenceTest extends TestCase
);
$this->project_analyzer->getCodebase()->collectLocations();
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
}
/**

View File

@ -36,7 +36,7 @@ class AnalyzedMethodTest extends \Psalm\Tests\TestCase
$config,
$providers
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
}
/**

View File

@ -36,7 +36,7 @@ class CachedStorageTest extends \Psalm\Tests\TestCase
$config,
$providers
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
}
public function testValidInclude(): void

View File

@ -37,7 +37,7 @@ class ErrorAfterUpdateTest extends \Psalm\Tests\TestCase
$config,
$providers
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
}
/**

View File

@ -37,7 +37,7 @@ class ErrorFixTest extends \Psalm\Tests\TestCase
$config,
$providers
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
}
/**

View File

@ -39,7 +39,7 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
$config,
$providers
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
}
/**

View File

@ -361,7 +361,7 @@ class ForbiddenCodeTest extends TestCase
)
);
$p->setPhpVersion('7.4');
$p->setPhpVersion('7.4', 'tests');
return $p;
}

View File

@ -35,7 +35,7 @@ class CompletionTest extends \Psalm\Tests\TestCase
$config,
$providers
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
$this->project_analyzer->getCodebase()->store_node_types = true;
}

View File

@ -31,7 +31,7 @@ class FileMapTest extends \Psalm\Tests\TestCase
$config,
$providers
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
$this->project_analyzer->getCodebase()->store_node_types = true;
}

View File

@ -34,7 +34,7 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase
$providers
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
$this->project_analyzer->getCodebase()->store_node_types = true;
}

View File

@ -103,7 +103,7 @@ class MethodSignatureTest extends TestCase
$this->expectExceptionMessage('MethodSignatureMismatch');
$this->expectException(\Psalm\Exception\CodeException::class);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
$this->addFile(
'somefile.php',
@ -127,7 +127,7 @@ class MethodSignatureTest extends TestCase
public function testMismatchingCovariantReturnIn74(): void
{
$this->project_analyzer->setPhpVersion('7.4');
$this->project_analyzer->setPhpVersion('7.4', 'tests');
$this->addFile(
'somefile.php',
@ -154,7 +154,7 @@ class MethodSignatureTest extends TestCase
$this->expectExceptionMessage('MethodSignatureMismatch');
$this->expectException(\Psalm\Exception\CodeException::class);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
$this->addFile(
'somefile.php',
@ -176,7 +176,7 @@ class MethodSignatureTest extends TestCase
public function testMismatchingCovariantReturnIn74WithSelf(): void
{
$this->project_analyzer->setPhpVersion('7.4');
$this->project_analyzer->setPhpVersion('7.4', 'tests');
$this->addFile(
'somefile.php',
@ -201,7 +201,7 @@ class MethodSignatureTest extends TestCase
$this->expectExceptionMessage('MethodSignatureMismatch');
$this->expectException(\Psalm\Exception\CodeException::class);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
$this->addFile(
'somefile.php',
@ -222,7 +222,7 @@ class MethodSignatureTest extends TestCase
public function testMismatchingCovariantParamIn74(): void
{
$this->project_analyzer->setPhpVersion('7.4');
$this->project_analyzer->setPhpVersion('7.4', 'tests');
$this->addFile(
'somefile.php',

View File

@ -2,6 +2,7 @@
namespace Psalm\Tests;
use Psalm\Config;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\IncludeCollector;
use Psalm\Internal\Provider\FakeFileProvider;
use Psalm\Internal\RuntimeCaches;
@ -27,7 +28,7 @@ class ProjectCheckerTest extends TestCase
/** @var TestConfig */
protected static $config;
/** @var \Psalm\Internal\Analyzer\ProjectAnalyzer */
/** @var ProjectAnalyzer */
protected $project_analyzer;
public static function setUpBeforeClass() : void
@ -49,7 +50,7 @@ class ProjectCheckerTest extends TestCase
$this->file_provider = new FakeFileProvider();
}
private function getProjectAnalyzerWithConfig(Config $config): \Psalm\Internal\Analyzer\ProjectAnalyzer
private function getProjectAnalyzerWithConfig(Config $config): ProjectAnalyzer
{
$config->setIncludeCollector(new IncludeCollector());
return new \Psalm\Internal\Analyzer\ProjectAnalyzer(
@ -79,6 +80,7 @@ class ProjectCheckerTest extends TestCase
</psalm>'
)
);
$this->project_analyzer->setPhpVersion('8.1', 'tests');
$this->project_analyzer->progress = new EchoProgress();
@ -86,7 +88,13 @@ class ProjectCheckerTest extends TestCase
$this->project_analyzer->check('tests/fixtures/DummyProject');
$output = ob_get_clean();
$this->assertSame('Scanning files...' . "\n" . 'Analyzing files...' . "\n\n", $output);
$this->assertSame(
'Target PHP version: 8.1 (set by tests)' . "\n"
. 'Scanning files...' . "\n"
. 'Analyzing files...' . "\n"
. "\n",
$output
);
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
@ -261,13 +269,21 @@ class Bat
)
);
$this->project_analyzer->setPhpVersion('8.1', 'tests');
$this->project_analyzer->progress = new EchoProgress();
ob_start();
$this->project_analyzer->checkDir('tests/fixtures/DummyProject');
$output = ob_get_clean();
$this->assertSame('Scanning files...' . "\n" . 'Analyzing files...' . "\n\n", $output);
$this->assertSame(
'Target PHP version: 8.1 (set by tests)' . "\n"
. 'Scanning files...' . "\n"
. 'Analyzing files...' . "\n"
. "\n",
$output
);
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
@ -293,6 +309,8 @@ class Bat
)
);
$this->project_analyzer->setPhpVersion('8.1', 'tests');
$this->project_analyzer->progress = new EchoProgress();
ob_start();
@ -304,7 +322,13 @@ class Bat
]);
$output = ob_get_clean();
$this->assertSame('Scanning files...' . "\n" . 'Analyzing files...' . "\n\n", $output);
$this->assertSame(
'Target PHP version: 8.1 (set by tests)' . "\n"
. 'Scanning files...' . "\n"
. 'Analyzing files...' . "\n"
. "\n",
$output
);
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
@ -330,6 +354,8 @@ class Bat
)
);
$this->project_analyzer->setPhpVersion('8.1', 'tests');
$this->project_analyzer->progress = new EchoProgress();
ob_start();
@ -341,7 +367,13 @@ class Bat
]);
$output = ob_get_clean();
$this->assertSame('Scanning files...' . "\n" . 'Analyzing files...' . "\n\n", $output);
$this->assertSame(
'Target PHP version: 8.1 (set by tests)' . "\n"
. 'Scanning files...' . "\n"
. 'Analyzing files...' . "\n"
. "\n",
$output
);
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());

View File

@ -50,7 +50,7 @@ class StubTest extends TestCase
new Provider\FakeParserCacheProvider()
)
);
$project_analyzer->setPhpVersion('7.4');
$project_analyzer->setPhpVersion('7.4', 'tests');
$config->setIncludeCollector(new IncludeCollector());
$config->visitComposerAutoloadFiles($project_analyzer, null);
@ -852,7 +852,7 @@ class StubTest extends TestCase
</psalm>'
)
);
$this->project_analyzer->setPhpVersion($php_version);
$this->project_analyzer->setPhpVersion($php_version, 'tests');
$file_path = getcwd() . '/src/somefile.php';

View File

@ -82,7 +82,7 @@ class TestCase extends BaseTestCase
$providers
);
$this->project_analyzer->setPhpVersion('7.4');
$this->project_analyzer->setPhpVersion('7.4', 'tests');
}
public function tearDown() : void
@ -180,13 +180,13 @@ class TestCase extends BaseTestCase
parent::assertRegExp($pattern, $string, $message);
}
}
public static function assertArrayKeysAreStrings(array $array, string $message = ''): void
{
$validKeys = array_filter($array, 'is_string', ARRAY_FILTER_USE_KEY);
self::assertTrue(count($array) === count($validKeys), $message);
}
public static function assertArrayKeysAreZeroOrString(array $array, string $message = ''): void
{
$isZeroOrString = /** @param mixed $key */ function ($key): bool {
@ -195,19 +195,19 @@ class TestCase extends BaseTestCase
$validKeys = array_filter($array, $isZeroOrString, ARRAY_FILTER_USE_KEY);
self::assertTrue(count($array) === count($validKeys), $message);
}
public static function assertArrayValuesAreArrays(array $array, string $message = ''): void
{
$validValues = array_filter($array, 'is_array');
self::assertTrue(count($array) === count($validValues), $message);
}
public static function assertArrayValuesAreStrings(array $array, string $message = ''): void
{
$validValues = array_filter($array, 'is_string');
self::assertTrue(count($array) === count($validValues), $message);
}
public static function assertStringIsParsableType(string $type, string $message = ''): void
{
if ($type === '') {

View File

@ -66,7 +66,7 @@ trait InvalidCodeAnalysisTestTrait
Config::getInstance()->setCustomErrorLevel($issue_name, $error_level);
}
$this->project_analyzer->setPhpVersion($php_version);
$this->project_analyzer->setPhpVersion($php_version, 'tests');
$file_path = self::$src_dir_path . 'somefile.php';

View File

@ -68,7 +68,7 @@ trait ValidCodeAnalysisTestTrait
$context = new Context();
$this->project_analyzer->setPhpVersion($php_version);
$this->project_analyzer->setPhpVersion($php_version, 'tests');
$codebase = $this->project_analyzer->getCodebase();
$codebase->config->visitPreloadedStubFiles($codebase);

View File

@ -23,7 +23,7 @@ class ValueTest extends \Psalm\Tests\TestCase
)
);
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
}
/**

View File

@ -32,7 +32,7 @@ class UnusedCodeTest extends TestCase
);
$this->project_analyzer->getCodebase()->reportUnusedCode();
$this->project_analyzer->setPhpVersion('7.3');
$this->project_analyzer->setPhpVersion('7.3', 'tests');
}
/**
@ -56,7 +56,7 @@ class UnusedCodeTest extends TestCase
$code
);
$this->project_analyzer->setPhpVersion('8.0');
$this->project_analyzer->setPhpVersion('8.0', 'tests');
foreach ($error_levels as $error_level) {
$this->project_analyzer->getCodebase()->config->setCustomErrorLevel($error_level, Config::REPORT_SUPPRESS);

View File

@ -31,7 +31,7 @@ class UnusedVariableTest extends TestCase
)
);
$this->project_analyzer->setPhpVersion('7.4');
$this->project_analyzer->setPhpVersion('7.4', 'tests');
$this->project_analyzer->getCodebase()->reportUnusedVariables();
}

View File

@ -136,7 +136,7 @@ class VariadicTest extends TestCase
new Provider\FakeParserCacheProvider()
)
);
$project_analyzer->setPhpVersion('7.3');
$project_analyzer->setPhpVersion('7.3', 'tests');
$config->setIncludeCollector(new IncludeCollector());
$config->visitComposerAutoloadFiles($project_analyzer, null);

View File

@ -3,6 +3,7 @@
"description": "A sample project to be used when testing Psalm",
"type": "project",
"require": {
"php": ">= 7.1",
"vimeo/psalm": "^4.3"
},
"autoload": {