1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-15 10:57:08 +01:00
psalm/src/Psalm/Provider/FileReferenceProvider.php

484 lines
13 KiB
PHP
Raw Normal View History

<?php
namespace Psalm\Provider;
use Psalm\Checker\ClassLikeChecker;
use Psalm\Checker\ProjectChecker;
2018-10-07 02:11:19 +02:00
use Psalm\Codebase;
use Psalm\Config;
/**
* @psalm-type IssueData = array{
* severity: string,
* line_from: int,
* line_to: int,
* type: string,
* message: string,
* file_name: string,
* file_path: string,
* snippet: string,
* from: int,
* to: int,
* snippet_from: int,
* snippet_to: int,
* column_from: int,
* column_to: int
* }
2018-10-07 02:11:19 +02:00
*
* @psalm-type TaggedCodeType = array<int, array{0: int, 1: string}>
*/
2017-02-18 23:49:34 +01:00
/**
* Used to determine which files reference other files, necessary for using the --diff
* option from the command line.
*/
class FileReferenceProvider
{
2018-09-30 05:51:06 +02:00
/**
* @var bool
*/
private $loaded_from_cache = false;
/**
* A lookup table used for getting all the files that reference a class
*
* @var array<string, array<string,bool>>
*/
private 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>>
*/
private static $referencing_files = [];
/**
* @var array<string, array<int,string>>
*/
private static $files_inheriting_classes = [];
/**
* A list of all files deleted since the last successful run
*
* @var array<int, string>|null
*/
private 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>}>
*/
private static $file_references = [];
/**
* @var array<string, array<string, bool>>
*/
private static $class_method_references = [];
2018-09-30 05:51:06 +02:00
/**
* @var array<string, array<string, int>>
2018-09-30 05:51:06 +02:00
*/
2018-11-02 02:52:39 +01:00
private static $analyzed_methods = [];
2018-09-30 05:51:06 +02:00
/**
* @var array<string, array<int, IssueData>>
*/
private static $issues = [];
/**
* @var array<string, array{0: array<int, array{0: int, 1: string}>, 1: array<int, array{0: int, 1: string}>}>
*/
private static $file_maps = [];
/**
* @var ?FileReferenceCacheProvider
*/
public $cache;
public function __construct(FileReferenceCacheProvider $cache = null)
{
$this->cache = $cache;
}
/**
* @return array<string>
*/
public function getDeletedReferencedFiles()
{
if (self::$deleted_files === null) {
self::$deleted_files = array_filter(
array_keys(self::$file_references),
/**
* @param string $file_name
2017-05-27 02:16:18 +02:00
*
* @return bool
*/
function ($file_name) {
return !file_exists($file_name);
}
);
}
return self::$deleted_files;
}
/**
* @param string $source_file
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 22:11:02 +02:00
* @param string $fq_class_name_lc
2017-05-27 02:16:18 +02:00
*
* @return void
*/
public function addFileReferenceToClass($source_file, $fq_class_name_lc)
{
self::$referencing_files[$source_file] = true;
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 22:11:02 +02:00
self::$file_references_to_class[$fq_class_name_lc][$source_file] = true;
}
/**
* @return array<string, array<string,bool>>
*/
public function getAllFileReferences()
{
return self::$file_references_to_class;
}
/**
* @param array<string, array<string,bool>> $references
* @psalm-suppress MixedTypeCoercion
*
* @return void
*/
public function addFileReferences(array $references)
{
self::$file_references_to_class = array_merge_recursive($references, self::$file_references_to_class);
}
/**
* @param string $source_file
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 22:11:02 +02:00
* @param string $fq_class_name_lc
2017-05-27 02:16:18 +02:00
*
* @return void
*/
public function addFileInheritanceToClass($source_file, $fq_class_name_lc)
{
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 22:11:02 +02:00
self::$files_inheriting_classes[$fq_class_name_lc][$source_file] = true;
}
/**
* @param string $file
2017-05-27 02:16:18 +02:00
*
* @return array
*/
2018-10-07 02:11:19 +02:00
private function calculateFilesReferencingFile(Codebase $codebase, $file)
{
$referenced_files = [];
2018-10-07 02:11:19 +02:00
$file_classes = ClassLikeChecker::getClassesForFile($codebase, $file);
foreach ($file_classes as $file_class_lc => $_) {
if (isset(self::$file_references_to_class[$file_class_lc])) {
$referenced_files = array_merge(
$referenced_files,
array_keys(self::$file_references_to_class[$file_class_lc])
);
}
}
return array_unique($referenced_files);
}
/**
* @param string $file
2017-05-27 02:16:18 +02:00
*
* @return array
*/
2018-10-07 02:11:19 +02:00
private function calculateFilesInheritingFile(Codebase $codebase, $file)
{
$referenced_files = [];
2018-10-07 02:11:19 +02:00
$file_classes = ClassLikeChecker::getClassesForFile($codebase, $file);
foreach ($file_classes as $file_class_lc => $_) {
if (isset(self::$files_inheriting_classes[$file_class_lc])) {
$referenced_files = array_merge(
$referenced_files,
array_keys(self::$files_inheriting_classes[$file_class_lc])
);
}
}
return array_unique($referenced_files);
}
/**
* @return void
*/
public function removeDeletedFilesFromReferences()
{
$deleted_files = self::getDeletedReferencedFiles();
if ($deleted_files) {
foreach ($deleted_files as $file) {
unset(self::$file_references[$file]);
}
if ($this->cache) {
$this->cache->setCachedFileReferences(self::$file_references);
}
}
}
/**
* @param string $file
2017-05-27 02:16:18 +02:00
*
* @return array<string>
*/
public function getFilesReferencingFile($file)
{
return isset(self::$file_references[$file]['a']) ? self::$file_references[$file]['a'] : [];
}
/**
* @param string $file
2017-05-27 02:16:18 +02:00
*
* @return array<string>
*/
public function getFilesInheritingFromFile($file)
{
return isset(self::$file_references[$file]['i']) ? self::$file_references[$file]['i'] : [];
}
/**
* @return array<string, array<string, bool>>
*/
public function getMethodsReferencing()
{
return self::$class_method_references;
}
/**
* @param bool $force_reload
* @return bool
* @psalm-suppress MixedAssignment
* @psalm-suppress MixedTypeCoercion
*/
public function loadReferenceCache($force_reload = true)
{
if ($this->cache && (!$this->loaded_from_cache || $force_reload)) {
2018-09-30 05:51:06 +02:00
$this->loaded_from_cache = true;
$file_references = $this->cache->getCachedFileReferences();
if ($file_references === null) {
return false;
}
self::$file_references = $file_references;
$class_method_references = $this->cache->getCachedMethodReferences();
2017-05-25 04:07:49 +02:00
if ($class_method_references === null) {
return false;
}
self::$class_method_references = $class_method_references;
2018-11-02 02:52:39 +01:00
$analyzed_methods = $this->cache->getAnalyzedMethodCache();
2018-10-07 02:11:19 +02:00
2018-11-02 02:52:39 +01:00
if ($analyzed_methods === false) {
2018-10-07 02:11:19 +02:00
return false;
}
2018-11-02 02:52:39 +01:00
self::$analyzed_methods = $analyzed_methods;
2018-10-07 02:11:19 +02:00
$issues = $this->cache->getCachedIssues();
if ($issues === null) {
return false;
}
self::$issues = $issues;
self::$file_maps = $this->cache->getFileMapCache() ?: [];
return true;
}
return false;
}
/**
* @param array<string, string|bool> $visited_files
2017-05-27 02:16:18 +02:00
*
* @return void
*/
2018-10-07 02:11:19 +02:00
public function updateReferenceCache(Codebase $codebase, array $visited_files)
{
foreach ($visited_files as $file => $_) {
$all_file_references = array_unique(
array_merge(
isset(self::$file_references[$file]['a']) ? self::$file_references[$file]['a'] : [],
2018-10-07 02:11:19 +02:00
$this->calculateFilesReferencingFile($codebase, $file)
)
);
$inheritance_references = array_unique(
array_merge(
isset(self::$file_references[$file]['i']) ? self::$file_references[$file]['i'] : [],
2018-10-07 02:11:19 +02:00
$this->calculateFilesInheritingFile($codebase, $file)
)
);
self::$file_references[$file] = [
'a' => $all_file_references,
'i' => $inheritance_references,
];
}
if ($this->cache) {
$this->cache->setCachedFileReferences(self::$file_references);
$this->cache->setCachedMethodReferences(self::$class_method_references);
$this->cache->setCachedIssues(self::$issues);
$this->cache->setFileMapCache(self::$file_maps);
2018-11-02 02:52:39 +01:00
$this->cache->setAnalyzedMethodCache(self::$analyzed_methods);
}
}
/**
2018-09-27 19:32:08 +02:00
* @param string $calling_method_id
* @param string $referenced_member_id
* @return void
*/
public function addReferenceToClassMethod($calling_method_id, $referenced_member_id)
{
if (!isset(self::$class_method_references[$referenced_member_id])) {
self::$class_method_references[$referenced_member_id] = [$calling_method_id => true];
} else {
self::$class_method_references[$referenced_member_id][$calling_method_id] = true;
}
}
/**
* @return array<string, array<string,bool>>
*/
public function getClassMethodReferences() : array
{
return self::$class_method_references;
}
/**
* @param array<string, array<string,bool>> $references
* @psalm-suppress MixedTypeCoercion
*
* @return void
*/
public function addClassMethodReferences(array $references)
{
foreach ($references as $referenced_member_id => $calling_method_ids) {
if (isset(self::$class_method_references[$referenced_member_id])) {
self::$class_method_references[$referenced_member_id] = array_merge(
self::$class_method_references[$referenced_member_id],
$calling_method_ids
);
} else {
self::$class_method_references[$referenced_member_id] = $calling_method_ids;
}
}
}
/**
* @return array<string, array<int, IssueData>>
*/
public function getExistingIssues() : array
{
return self::$issues;
}
/**
2018-09-30 05:51:06 +02:00
* @param string $file_path
* @return void
*/
2018-09-30 05:51:06 +02:00
public function clearExistingIssuesForFile($file_path)
{
2018-09-30 05:51:06 +02:00
unset(self::$issues[$file_path]);
}
/**
2018-09-27 19:32:08 +02:00
* @param string $file_path
* @param IssueData $issue
* @return void
*/
public function clearExistingFileMapsForFile($file_path)
{
unset(self::$file_maps[$file_path]);
}
/**
* @param string $file_path
* @return void
*/
public function addIssue($file_path, array $issue)
{
// dont save parse errors ever, as they're not responsive to AST diffing
if ($issue['type'] === 'ParseError') {
return;
}
if (!isset(self::$issues[$file_path])) {
self::$issues[$file_path] = [$issue];
} else {
self::$issues[$file_path][] = $issue;
}
}
2018-09-30 05:51:06 +02:00
/**
2018-11-02 02:52:39 +01:00
* @param array<string, array<string, int>> $analyzed_methods
2018-10-07 02:11:19 +02:00
* @return void
2018-09-30 05:51:06 +02:00
*/
2018-11-02 02:52:39 +01:00
public function setAnalyzedMethods(array $analyzed_methods)
2018-09-30 05:51:06 +02:00
{
2018-11-02 02:52:39 +01:00
self::$analyzed_methods = $analyzed_methods;
2018-09-30 05:51:06 +02:00
}
/**
* @param array<string, array{0: TaggedCodeType, 1: TaggedCodeType}> $file_maps
* @return void
*/
public function setFileMaps(array $file_maps)
{
self::$file_maps = $file_maps;
}
2018-09-30 05:51:06 +02:00
/**
* @return array<string, array<string, int>>
2018-09-30 05:51:06 +02:00
*/
2018-11-02 02:52:39 +01:00
public function getAnalyzedMethods()
2018-09-30 05:51:06 +02:00
{
2018-11-02 02:52:39 +01:00
return self::$analyzed_methods;
2018-09-30 05:51:06 +02:00
}
/**
* @return array<string, array{0: TaggedCodeType, 1: TaggedCodeType}>
*/
public function getFileMaps()
{
return self::$file_maps;
}
/**
* @return void
*/
public static function clearCache()
{
self::$file_references_to_class = [];
self::$referencing_files = [];
self::$files_inheriting_classes = [];
self::$deleted_files = null;
self::$file_references = [];
self::$class_method_references = [];
2018-11-02 02:52:39 +01:00
self::$analyzed_methods = [];
self::$issues = [];
self::$file_maps = [];
}
}