2017-07-25 22:11:02 +02:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Provider;
|
|
|
|
|
|
|
|
use PhpParser;
|
|
|
|
|
|
|
|
class StatementsProvider
|
|
|
|
{
|
2018-01-21 19:38:51 +01:00
|
|
|
/**
|
|
|
|
* @var FileProvider
|
|
|
|
*/
|
|
|
|
private $file_provider;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var ParserCacheProvider
|
|
|
|
*/
|
|
|
|
private $cache_provider;
|
|
|
|
|
2018-03-12 05:01:21 +01:00
|
|
|
/**
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
private $this_modified_time;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var FileStorageCacheProvider
|
|
|
|
*/
|
|
|
|
private $file_storage_cache_provider;
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, array<string, bool>>
|
|
|
|
*/
|
|
|
|
private $unchanged_members = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, array<string, bool>>
|
|
|
|
*/
|
|
|
|
private $unchanged_signature_members = [];
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
/**
|
|
|
|
* @var PhpParser\Parser|null
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
protected static $parser;
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
/**
|
|
|
|
* @var PhpParser\NodeTraverser|null
|
|
|
|
*/
|
|
|
|
protected static $node_traverser;
|
|
|
|
|
2018-03-12 05:01:21 +01:00
|
|
|
public function __construct(
|
|
|
|
FileProvider $file_provider,
|
|
|
|
ParserCacheProvider $cache_provider,
|
|
|
|
FileStorageCacheProvider $file_storage_cache_provider
|
|
|
|
) {
|
2018-01-21 19:38:51 +01:00
|
|
|
$this->file_provider = $file_provider;
|
|
|
|
$this->cache_provider = $cache_provider;
|
2018-03-12 05:01:21 +01:00
|
|
|
$this->this_modified_time = filemtime(__FILE__);
|
|
|
|
$this->file_storage_cache_provider = $file_storage_cache_provider;
|
2018-01-21 19:38:51 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param bool $debug_output
|
|
|
|
*
|
|
|
|
* @return array<int, \PhpParser\Node\Stmt>
|
|
|
|
*/
|
2018-01-21 19:38:51 +01:00
|
|
|
public function getStatementsForFile($file_path, $debug_output = false)
|
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$from_cache = false;
|
|
|
|
|
2018-04-24 13:07:04 +02:00
|
|
|
$version = (string) PHP_PARSER_VERSION . $this->this_modified_time;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$file_contents = $this->file_provider->getContents($file_path);
|
|
|
|
$modified_time = $this->file_provider->getModifiedTime($file_path);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
|
|
|
$file_content_hash = md5($version . $file_contents);
|
2018-02-19 17:14:07 +01:00
|
|
|
$file_cache_key = $this->cache_provider->getParserCacheKey($file_path, $this->cache_provider->use_igbinary);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$stmts = $this->cache_provider->loadStatementsFromCache(
|
2017-07-25 22:11:02 +02:00
|
|
|
$modified_time,
|
|
|
|
$file_content_hash,
|
|
|
|
$file_cache_key
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($stmts === null) {
|
|
|
|
if ($debug_output) {
|
2018-04-13 01:42:24 +02:00
|
|
|
echo 'Parsing ' . $file_path . "\n";
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
2018-04-22 04:13:10 +02:00
|
|
|
$stmts = self::parseStatements($file_contents);
|
2018-09-26 00:37:24 +02:00
|
|
|
|
|
|
|
$existing_file_contents = $this->cache_provider->loadExistingFileContentsFromCache($file_cache_key);
|
|
|
|
|
|
|
|
if ($existing_file_contents) {
|
|
|
|
$existing_statements = $this->cache_provider->loadExistingStatementsFromCache($file_cache_key);
|
|
|
|
|
|
|
|
if ($existing_statements) {
|
|
|
|
list($unchanged_members, $unchanged_signature_members) = \Psalm\Diff\FileStatementsDiffer::diff(
|
|
|
|
$existing_statements,
|
|
|
|
$stmts,
|
|
|
|
$existing_file_contents,
|
|
|
|
$file_contents
|
|
|
|
);
|
|
|
|
|
|
|
|
$unchanged_members = array_map(
|
|
|
|
function (int $_) : bool {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
array_flip($unchanged_members)
|
|
|
|
);
|
|
|
|
|
|
|
|
$unchanged_signature_members = array_map(
|
|
|
|
function (int $_) : bool {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
array_flip($unchanged_signature_members)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (isset($this->unchanged_members[$file_path])) {
|
|
|
|
$this->unchanged_members[$file_path] = array_intersect_key(
|
|
|
|
$this->unchanged_members[$file_path],
|
|
|
|
$unchanged_members
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$this->unchanged_members[$file_path] = $unchanged_members;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($this->unchanged_signature_members[$file_path])) {
|
|
|
|
$this->unchanged_signature_members[$file_path] = array_intersect_key(
|
|
|
|
$this->unchanged_signature_members[$file_path],
|
|
|
|
$unchanged_signature_members
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$this->unchanged_signature_members[$file_path] = $unchanged_signature_members;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-12 05:01:21 +01:00
|
|
|
$this->file_storage_cache_provider->removeCacheForFile($file_path);
|
2018-09-26 00:37:24 +02:00
|
|
|
$this->cache_provider->cacheFileContents($file_cache_key, $file_contents);
|
2017-07-25 22:11:02 +02:00
|
|
|
} else {
|
|
|
|
$from_cache = true;
|
|
|
|
}
|
|
|
|
|
2018-01-21 19:38:51 +01:00
|
|
|
$this->cache_provider->saveStatementsToCache($file_cache_key, $file_content_hash, $stmts, $from_cache);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
|
|
|
if (!$stmts) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stmts;
|
|
|
|
}
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
/**
|
|
|
|
* @return array<string, array<string, bool>>
|
|
|
|
*/
|
|
|
|
public function getUnchangedMembers()
|
|
|
|
{
|
|
|
|
return $this->unchanged_members;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<string, array<string, bool>>
|
|
|
|
*/
|
|
|
|
public function getUnchangedSignatureMembers()
|
|
|
|
{
|
|
|
|
return $this->unchanged_signature_members;
|
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
/**
|
|
|
|
* @param string $file_contents
|
|
|
|
*
|
|
|
|
* @return array<int, \PhpParser\Node\Stmt>
|
|
|
|
*/
|
2018-04-22 04:13:10 +02:00
|
|
|
public static function parseStatements($file_contents)
|
2017-07-25 22:11:02 +02:00
|
|
|
{
|
|
|
|
if (!self::$parser) {
|
2018-04-24 13:08:14 +02:00
|
|
|
$lexer = new PhpParser\Lexer([
|
|
|
|
'usedAttributes' => [
|
|
|
|
'comments', 'startLine', 'startFilePos', 'endFilePos',
|
|
|
|
],
|
|
|
|
]);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
|
|
|
self::$parser = (new PhpParser\ParserFactory())->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer);
|
|
|
|
}
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
if (!self::$node_traverser) {
|
|
|
|
self::$node_traverser = new PhpParser\NodeTraverser;
|
|
|
|
$name_resolver = new \Psalm\Visitor\SimpleNameResolver;
|
|
|
|
self::$node_traverser->addVisitor($name_resolver);
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
try {
|
|
|
|
/** @var array<int, \PhpParser\Node\Stmt> */
|
|
|
|
$stmts = self::$parser->parse($file_contents);
|
|
|
|
} catch (PhpParser\Error $e) {
|
|
|
|
throw $e;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
/** @var array<int, \PhpParser\Node\Stmt> */
|
|
|
|
$stmts = self::$node_traverser->traverse($stmts);
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
return $stmts;
|
|
|
|
}
|
|
|
|
}
|