1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-05 20:48:45 +01:00
psalm/src/Psalm/Provider/FileReferenceProvider.php
Matthew Brown e29dd140e3 Refactor scanning and analysis, introducing multithreading (#191)
* Add failing test

* Add visitor to soup up classlike references

* Move a whole bunch of code into the visitor

* Move some methods back, move onto analysis stage

* Use the getAliases method everywhere

* Fix refs

* Fix more refs

* Fix some tests

* Fix more tests

* Fix include tests

* Shift config class finding to project checker and fix bugs

* Fix a few more tests

* transition test to new syntax

* Remove var_dump

* Delete a bunch of code and fix mutation test

* Remove unnecessary visitation

* Transition to better mocked out file provider, breaking some cached statement loading

* Use different scheme for naming anonymous classes

* Fix anonymous class issues

* Refactor file/statement loading

* Add specific property types

* Fix mapped property assignment

* Improve how we deal with traits

* Fix trait checking

* Pass Psalm checks

* Add multi-process support

* Delay console output until the end

* Remove PHP 7 syntax

* Update file storage with classes

* Fix scanning individual files and add reflection return types

* Always turn XDebug off

* Add quicker method of getting method mutations

* Queue return types for crawling

* Interpret all strings as possible classes once we see a `get_class` call

* Check invalid return types again

* Fix template namespacing issues

* Default to class-insensitive file names for includes

* Don’t overwrite existing issues data

* Add var docblocks for scanning

* Add null check

* Fix loading of external classes in templates

* Only try to populate class when we haven’t yet seen it’s not a class

* Fix trait property accessibility

* Only ever improve docblock param type

* Make param replacement more robust

* Fix static const missing inferred type

* Fix a few more tests

* Register constant definitions

* Fix trait aliasing

* Skip constant type tests for now

* Fix linting issues

* Make sure caching is off for tests

* Remove unnecessary return

* Use emulative parser if on PHP 5.6

* Cache parser for faster first-time parse

* Fix constant resolution when scanning classes

* Remove test that’s beyond a practical scope

* Add back --diff support

* Add --help for --threads

* Remove unused vars
2017-07-25 16:11:02 -04:00

245 lines
6.8 KiB
PHP

<?php
namespace Psalm\Provider;
use Psalm\Checker\ClassLikeChecker;
use Psalm\Config;
/**
* Used to determine which files reference other files, necessary for using the --diff
* option from the command line.
*/
class FileReferenceProvider
{
const REFERENCE_CACHE_NAME = 'references';
/**
* A lookup table used for getting all the files that reference a class
*
* @var array<string, array<string,bool>>
*/
protected static $file_references_to_class = [];
/**
* A lookup table used for getting all the files that reference any other file
*
* @var array<string,array<string,bool>>
*/
protected static $referencing_files = [];
/**
* @var array<string, array<int,string>>
*/
protected static $files_inheriting_classes = [];
/**
* A list of all files deleted since the last successful run
*
* @var array<int, string>|null
*/
protected static $deleted_files = null;
/**
* A lookup table used for getting all the files referenced by a file
*
* @var array<string, array{a:array<int, string>, i:array<int, string>}>
*/
protected static $file_references = [];
/**
* @return array<string>
*/
public static function getDeletedReferencedFiles()
{
if (self::$deleted_files === null) {
self::$deleted_files = array_filter(
array_keys(self::$file_references),
/**
* @param string $file_name
*
* @return bool
*/
function ($file_name) {
return !file_exists($file_name);
}
);
}
return self::$deleted_files;
}
/**
* @param string $source_file
* @param string $fq_class_name_lc
*
* @return void
*/
public static function addFileReferenceToClass($source_file, $fq_class_name_lc)
{
self::$referencing_files[$source_file] = true;
self::$file_references_to_class[$fq_class_name_lc][$source_file] = true;
}
/**
* @param string $source_file
* @param string $fq_class_name_lc
*
* @return void
*/
public static function addFileInheritanceToClass($source_file, $fq_class_name_lc)
{
self::$files_inheriting_classes[$fq_class_name_lc][$source_file] = true;
}
/**
* @param string $file
*
* @return array
*/
public static function calculateFilesReferencingFile($file)
{
$referenced_files = [];
$file_classes = ClassLikeChecker::getClassesForFile($file);
foreach ($file_classes as $file_class) {
if (isset(self::$file_references_to_class[$file_class])) {
$referenced_files = array_merge(
$referenced_files,
array_keys(self::$file_references_to_class[$file_class])
);
}
}
return array_unique($referenced_files);
}
/**
* @param string $file
*
* @return array
*/
public static function calculateFilesInheritingFile($file)
{
$referenced_files = [];
$file_classes = ClassLikeChecker::getClassesForFile($file);
foreach ($file_classes as $file_class) {
if (isset(self::$files_inheriting_classes[$file_class])) {
$referenced_files = array_merge(
$referenced_files,
array_keys(self::$files_inheriting_classes[$file_class])
);
}
}
return array_unique($referenced_files);
}
/**
* @return void
*/
public static function removeDeletedFilesFromReferences()
{
$cache_directory = Config::getInstance()->getCacheDirectory();
$deleted_files = self::getDeletedReferencedFiles();
if ($deleted_files) {
foreach ($deleted_files as $file) {
unset(self::$file_references[$file]);
}
file_put_contents(
$cache_directory . DIRECTORY_SEPARATOR . self::REFERENCE_CACHE_NAME,
serialize(self::$file_references)
);
}
}
/**
* @param string $file
*
* @return array<string>
*/
public static function getFilesReferencingFile($file)
{
return isset(self::$file_references[$file]['a']) ? self::$file_references[$file]['a'] : [];
}
/**
* @param string $file
*
* @return array<string>
*/
public static function getFilesInheritingFromFile($file)
{
return isset(self::$file_references[$file]['i']) ? self::$file_references[$file]['i'] : [];
}
/**
* @return bool
* @psalm-suppress MixedAssignment
* @psalm-suppress InvalidPropertyAssignment
*/
public static function loadReferenceCache()
{
$cache_directory = Config::getInstance()->getCacheDirectory();
if ($cache_directory) {
$cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::REFERENCE_CACHE_NAME;
if (is_readable($cache_location)) {
$reference_cache = unserialize((string) file_get_contents($cache_location));
if (!is_array($reference_cache)) {
throw new \UnexpectedValueException('The reference cache must be an array');
}
self::$file_references = $reference_cache;
return true;
}
}
return false;
}
/**
* @param array<string, bool> $visited_files
*
* @return void
*/
public static function updateReferenceCache(array $visited_files)
{
$cache_directory = Config::getInstance()->getCacheDirectory();
if ($cache_directory) {
$cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::REFERENCE_CACHE_NAME;
foreach ($visited_files as $file => $_) {
$all_file_references = array_unique(
array_merge(
isset(self::$file_references[$file]['a']) ? self::$file_references[$file]['a'] : [],
FileReferenceProvider::calculateFilesReferencingFile($file)
)
);
$inheritance_references = array_unique(
array_merge(
isset(self::$file_references[$file]['i']) ? self::$file_references[$file]['i'] : [],
FileReferenceProvider::calculateFilesInheritingFile($file)
)
);
self::$file_references[$file] = [
'a' => $all_file_references,
'i' => $inheritance_references,
];
}
file_put_contents($cache_location, serialize(self::$file_references));
}
}
}