mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 12:55:26 +01:00
d19aad7db1
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.
227 lines
6.5 KiB
PHP
227 lines
6.5 KiB
PHP
<?php
|
|
namespace Psalm\Tests;
|
|
|
|
use PHPUnit\Framework\TestCase as BaseTestCase;
|
|
use Psalm\Config;
|
|
use Psalm\Internal\Analyzer\FileAnalyzer;
|
|
use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
|
use Psalm\Internal\Provider\FakeFileProvider;
|
|
use Psalm\Internal\Provider\Providers;
|
|
use Psalm\Internal\RuntimeCaches;
|
|
use Psalm\Internal\Type\TypeParser;
|
|
use Psalm\Internal\Type\TypeTokenizer;
|
|
use Psalm\Tests\Internal\Provider;
|
|
use Psalm\Type\Union;
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
use function array_filter;
|
|
use function count;
|
|
use function define;
|
|
use function defined;
|
|
use function getcwd;
|
|
use function ini_set;
|
|
use function is_string;
|
|
use function method_exists;
|
|
|
|
use const ARRAY_FILTER_USE_KEY;
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
class TestCase extends BaseTestCase
|
|
{
|
|
/** @var string */
|
|
protected static $src_dir_path;
|
|
|
|
/** @var ProjectAnalyzer */
|
|
protected $project_analyzer;
|
|
|
|
/** @var FakeFileProvider */
|
|
protected $file_provider;
|
|
|
|
/** @var Config */
|
|
protected $testConfig;
|
|
|
|
public static function setUpBeforeClass() : void
|
|
{
|
|
ini_set('memory_limit', '-1');
|
|
|
|
if (!defined('PSALM_VERSION')) {
|
|
define('PSALM_VERSION', '4.0.0');
|
|
}
|
|
|
|
if (!defined('PHP_PARSER_VERSION')) {
|
|
define('PHP_PARSER_VERSION', '4.0.0');
|
|
}
|
|
|
|
parent::setUpBeforeClass();
|
|
self::$src_dir_path = getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR;
|
|
}
|
|
|
|
protected function makeConfig() : Config
|
|
{
|
|
return new TestConfig();
|
|
}
|
|
|
|
public function setUp() : void
|
|
{
|
|
parent::setUp();
|
|
|
|
RuntimeCaches::clearAll();
|
|
|
|
$this->file_provider = new FakeFileProvider();
|
|
|
|
$this->testConfig = $this->makeConfig();
|
|
|
|
$providers = new Providers(
|
|
$this->file_provider,
|
|
new Provider\FakeParserCacheProvider()
|
|
);
|
|
|
|
$this->project_analyzer = new ProjectAnalyzer(
|
|
$this->testConfig,
|
|
$providers
|
|
);
|
|
|
|
$this->project_analyzer->setPhpVersion('7.4', 'tests');
|
|
}
|
|
|
|
public function tearDown() : void
|
|
{
|
|
unset($this->project_analyzer, $this->file_provider, $this->testConfig);
|
|
RuntimeCaches::clearAll();
|
|
}
|
|
|
|
/**
|
|
* @param string $file_path
|
|
* @param string $contents
|
|
*
|
|
*/
|
|
public function addFile($file_path, $contents): void
|
|
{
|
|
$this->file_provider->registerFile($file_path, $contents);
|
|
$this->project_analyzer->getCodebase()->scanner->addFileToShallowScan($file_path);
|
|
}
|
|
|
|
/**
|
|
* @param string $file_path
|
|
*
|
|
*/
|
|
public function analyzeFile($file_path, \Psalm\Context $context, bool $track_unused_suppressions = true, bool $taint_flow_tracking = false): void
|
|
{
|
|
$codebase = $this->project_analyzer->getCodebase();
|
|
|
|
if ($taint_flow_tracking) {
|
|
$this->project_analyzer->trackTaintedInputs();
|
|
}
|
|
|
|
$codebase->addFilesToAnalyze([$file_path => $file_path]);
|
|
|
|
$codebase->scanFiles();
|
|
|
|
$codebase->config->visitStubFiles($codebase);
|
|
|
|
if ($codebase->alter_code) {
|
|
$this->project_analyzer->interpretRefactors();
|
|
}
|
|
|
|
$this->project_analyzer->trackUnusedSuppressions();
|
|
|
|
$file_analyzer = new FileAnalyzer(
|
|
$this->project_analyzer,
|
|
$file_path,
|
|
$codebase->config->shortenFileName($file_path)
|
|
);
|
|
$file_analyzer->analyze($context);
|
|
|
|
if ($codebase->taint_flow_graph) {
|
|
$codebase->taint_flow_graph->connectSinksAndSources();
|
|
}
|
|
|
|
if ($track_unused_suppressions) {
|
|
\Psalm\IssueBuffer::processUnusedSuppressions($codebase->file_provider);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param bool $withDataSet
|
|
*
|
|
*/
|
|
protected function getTestName($withDataSet = true): string
|
|
{
|
|
$name = parent::getName($withDataSet);
|
|
/**
|
|
* @psalm-suppress TypeDoesNotContainNull PHPUnit 8.2 made it non-nullable again
|
|
*/
|
|
if (null === $name) {
|
|
throw new RuntimeException('anonymous test - shouldn\'t happen');
|
|
}
|
|
|
|
return $name;
|
|
}
|
|
|
|
/**
|
|
* Compatibility alias
|
|
*/
|
|
public function expectExceptionMessageRegExp(string $regexp): void
|
|
{
|
|
if (method_exists($this, 'expectExceptionMessageMatches')) {
|
|
$this->expectExceptionMessageMatches($regexp);
|
|
} else {
|
|
/** @psalm-suppress UndefinedMethod */
|
|
parent::expectExceptionMessageRegExp($regexp);
|
|
}
|
|
}
|
|
|
|
public static function assertRegExp(string $pattern, string $string, string $message = ''): void
|
|
{
|
|
if (method_exists(self::class, 'assertMatchesRegularExpression')) {
|
|
self::assertMatchesRegularExpression($pattern, $string, $message);
|
|
} else {
|
|
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 {
|
|
return $key === 0 || is_string($key);
|
|
};
|
|
$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 === '') {
|
|
// Ignore empty types for now, as these are quite common for pecl libraries
|
|
self::assertTrue(true);
|
|
} else {
|
|
$union = null;
|
|
try {
|
|
$tokens = TypeTokenizer::tokenize($type);
|
|
$union = TypeParser::parseTokens($tokens);
|
|
} catch (Throwable $_e) {
|
|
}
|
|
self::assertInstanceOf(Union::class, $union, $message);
|
|
}
|
|
}
|
|
}
|