1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-11 08:49:52 +01:00
psalm/src/Psalm/Codebase.php

766 lines
20 KiB
PHP
Raw Normal View History

<?php
namespace Psalm;
use PhpParser;
use Psalm\Checker\ProjectChecker;
use Psalm\Provider\ClassLikeStorageProvider;
use Psalm\Provider\FileProvider;
use Psalm\Provider\FileStorageProvider;
use Psalm\Provider\Providers;
use Psalm\Provider\StatementsProvider;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\FileStorage;
use Psalm\Storage\FunctionLikeStorage;
class Codebase
{
/**
2018-04-05 20:40:41 +02:00
* @var Config
*/
public $config;
/**
* A map of fully-qualified use declarations to the files
* that reference them (keyed by filename)
*
* @var array<string, array<string, array<int, \Psalm\CodeLocation>>>
*/
public $use_referencing_locations = [];
/**
* A map of file names to the classes that they contain explicit references to
* used in collaboration with use_referencing_locations
*
* @var array<string, array<string, bool>>
*/
public $use_referencing_files = [];
/**
* @var FileStorageProvider
*/
public $file_storage_provider;
/**
* @var ClassLikeStorageProvider
*/
public $classlike_storage_provider;
/**
* @var bool
*/
public $collect_references = false;
/**
* @var FileProvider
*/
private $file_provider;
/**
* @var StatementsProvider
*/
public $statements_provider;
/**
2018-02-04 00:52:35 +01:00
* @var bool
*/
2018-02-04 00:52:35 +01:00
private $debug_output = false;
/**
2018-02-04 00:52:35 +01:00
* @var array<string, Type\Union>
*/
2018-02-04 00:52:35 +01:00
private static $stubbed_constants = [];
/**
* Whether to register autoloaded information
*
* @var bool
*/
public $register_autoload_files = false;
/**
2018-02-04 00:52:35 +01:00
* Whether to log functions just at the file level or globally (for stubs)
*
* @var bool
*/
public $register_stub_files = false;
/**
* @var bool
*/
public $find_unused_code = false;
/**
2018-02-04 00:52:35 +01:00
* @var Codebase\Reflection
*/
2018-02-04 00:52:35 +01:00
private $reflection;
/**
2018-02-04 00:52:35 +01:00
* @var Codebase\Scanner
*/
2018-02-04 00:52:35 +01:00
public $scanner;
/**
2018-02-04 00:52:35 +01:00
* @var Codebase\Analyzer
*/
2018-02-04 00:52:35 +01:00
public $analyzer;
/**
2018-02-04 00:52:35 +01:00
* @var Codebase\Functions
*/
2018-02-04 00:52:35 +01:00
public $functions;
/**
2018-02-04 00:52:35 +01:00
* @var Codebase\ClassLikes
*/
2018-02-04 00:52:35 +01:00
public $classlikes;
/**
2018-02-04 00:52:35 +01:00
* @var Codebase\Methods
*/
2018-02-04 00:52:35 +01:00
public $methods;
/**
* @var Codebase\Properties
*/
public $properties;
/**
2018-02-04 00:52:35 +01:00
* @var Codebase\Populator
*/
2018-02-04 00:52:35 +01:00
public $populator;
/**
* @param bool $debug_output
*/
public function __construct(
Config $config,
Providers $providers,
$debug_output = false
) {
$this->config = $config;
$this->file_storage_provider = $providers->file_storage_provider;
$this->classlike_storage_provider = $providers->classlike_storage_provider;
$this->debug_output = $debug_output;
$this->file_provider = $providers->file_provider;
$this->statements_provider = $providers->statements_provider;
$this->debug_output = $debug_output;
self::$stubbed_constants = [];
$this->reflection = new Codebase\Reflection($providers->classlike_storage_provider, $this);
2018-02-04 00:52:35 +01:00
$this->scanner = new Codebase\Scanner(
$this,
$config,
$providers->file_storage_provider,
$providers->file_provider,
2018-02-04 00:52:35 +01:00
$this->reflection,
$providers->file_reference_provider,
2018-02-04 00:52:35 +01:00
$debug_output
);
$this->loadAnalyzer();
$this->functions = new Codebase\Functions($providers->file_storage_provider, $this->reflection);
$this->methods = new Codebase\Methods(
$config,
$providers->classlike_storage_provider,
$providers->file_reference_provider
);
$this->properties = new Codebase\Properties(
$providers->classlike_storage_provider,
$providers->file_reference_provider
);
2018-02-04 00:52:35 +01:00
$this->classlikes = new Codebase\ClassLikes(
$config,
$this,
$providers->classlike_storage_provider,
2018-02-04 00:52:35 +01:00
$this->scanner,
$this->methods,
$debug_output
);
$this->populator = new Codebase\Populator(
$config,
$providers->classlike_storage_provider,
$providers->file_storage_provider,
2018-02-04 00:52:35 +01:00
$this->classlikes,
$providers->file_reference_provider,
2018-02-04 00:52:35 +01:00
$debug_output
);
}
/**
* @return void
*/
private function loadAnalyzer()
{
$this->analyzer = new Codebase\Analyzer(
$this->config,
$this->file_provider,
$this->file_storage_provider,
2018-10-03 23:11:08 +02:00
$this->classlike_storage_provider,
$this->debug_output
);
}
/**
* @param array<string> $diff_files
*
* @return void
*/
public function reloadFiles(ProjectChecker $project_checker, array $diff_files)
{
$this->loadAnalyzer();
$project_checker->file_reference_provider->loadReferenceCache();
$referenced_files = $project_checker->getReferencedFilesFromDiff($diff_files);
foreach ($diff_files as $diff_file_path) {
$this->invalidateInformationForFile($diff_file_path);
}
foreach ($referenced_files as $referenced_file_path) {
if (in_array($referenced_file_path, $diff_files)) {
continue;
}
$file_storage = $this->file_storage_provider->get($referenced_file_path);
foreach ($file_storage->classlikes_in_file as $fq_classlike_name) {
$this->classlike_storage_provider->remove($fq_classlike_name);
$this->classlikes->removeClassLike($fq_classlike_name);
}
$this->file_storage_provider->remove($referenced_file_path);
$this->scanner->removeFile($referenced_file_path);
}
$referenced_files = array_combine($referenced_files, $referenced_files);
$this->scanner->addFilesToDeepScan($referenced_files);
$this->scanner->scanFiles($this->classlikes);
$project_checker->file_reference_provider->updateReferenceCache($project_checker, $referenced_files);
$this->populator->populateCodebase($this);
}
/**
* @return void
*/
2018-02-04 00:52:35 +01:00
public function collectReferences()
{
2018-02-04 00:52:35 +01:00
$this->collect_references = true;
$this->classlikes->collect_references = true;
$this->methods->collect_references = true;
$this->properties->collect_references = true;
}
/**
* @return void
*/
public function reportUnusedCode()
{
$this->collectReferences();
$this->find_unused_code = true;
}
/**
2018-02-04 00:52:35 +01:00
* @param array<string, string> $files_to_analyze
*
* @return void
*/
2018-02-04 00:52:35 +01:00
public function addFilesToAnalyze(array $files_to_analyze)
{
2018-02-04 00:52:35 +01:00
$this->scanner->addFilesToDeepScan($files_to_analyze);
$this->analyzer->addFiles($files_to_analyze);
}
/**
2018-02-04 00:52:35 +01:00
* Scans all files their related files
*
* @return void
*/
2018-02-04 00:52:35 +01:00
public function scanFiles()
{
2018-02-04 00:52:35 +01:00
$has_changes = $this->scanner->scanFiles($this->classlikes);
2018-02-04 00:52:35 +01:00
if ($has_changes) {
2018-06-28 03:53:25 +02:00
$this->populator->populateCodebase($this);
}
}
/**
2018-02-04 00:52:35 +01:00
* @param string $file_path
*
2018-02-04 00:52:35 +01:00
* @return string
*/
2018-02-04 00:52:35 +01:00
public function getFileContents($file_path)
{
2018-02-04 00:52:35 +01:00
return $this->file_provider->getContents($file_path);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $file_path
*
2018-02-04 00:52:35 +01:00
* @return array<int, PhpParser\Node\Stmt>
*/
2018-02-04 00:52:35 +01:00
public function getStatementsForFile($file_path)
{
2018-02-04 00:52:35 +01:00
return $this->statements_provider->getStatementsForFile(
$file_path,
$this->debug_output
);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $fq_classlike_name
*
2018-02-04 00:52:35 +01:00
* @return ClassLikeStorage
*/
2018-02-04 00:52:35 +01:00
public function createClassLikeStorage($fq_classlike_name)
{
2018-02-04 00:52:35 +01:00
return $this->classlike_storage_provider->create($fq_classlike_name);
}
2018-02-19 06:27:39 +01:00
/**
* @param string $file_path
*
* @return void
*/
public function cacheClassLikeStorage(ClassLikeStorage $classlike_storage, $file_path)
{
$file_contents = $this->file_provider->getContents($file_path);
if ($this->classlike_storage_provider->cache) {
$this->classlike_storage_provider->cache->writeToCache($classlike_storage, $file_path, $file_contents);
}
2018-02-19 06:27:39 +01:00
}
/**
* @param string $fq_classlike_name
* @param string $file_path
*
* @return void
*/
public function exhumeClassLikeStorage($fq_classlike_name, $file_path)
{
$file_contents = $this->file_provider->getContents($file_path);
$storage = $this->classlike_storage_provider->exhume($fq_classlike_name, $file_path, $file_contents);
if ($storage->is_trait) {
$this->classlikes->addFullyQualifiedTraitName($fq_classlike_name, $file_path);
} elseif ($storage->is_interface) {
$this->classlikes->addFullyQualifiedInterfaceName($fq_classlike_name, $file_path);
} else {
$this->classlikes->addFullyQualifiedClassName($fq_classlike_name, $file_path);
}
}
/**
2018-02-04 00:52:35 +01:00
* @param string $file_path
*
2018-02-04 00:52:35 +01:00
* @return FileStorage
*/
2018-02-04 00:52:35 +01:00
public function createFileStorageForPath($file_path)
{
2018-02-04 00:52:35 +01:00
return $this->file_storage_provider->create($file_path);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $symbol
*
2018-02-04 00:52:35 +01:00
* @return array<string, \Psalm\CodeLocation[]>
*/
2018-02-04 00:52:35 +01:00
public function findReferencesToSymbol($symbol)
{
2018-02-04 00:52:35 +01:00
if (!$this->collect_references) {
throw new \UnexpectedValueException('Should not be checking references');
}
if (strpos($symbol, '::') !== false) {
return $this->findReferencesToMethod($symbol);
}
return $this->findReferencesToClassLike($symbol);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $method_id
*
2018-02-04 00:52:35 +01:00
* @return array<string, \Psalm\CodeLocation[]>
*/
2018-02-04 00:52:35 +01:00
public function findReferencesToMethod($method_id)
{
2018-02-04 00:52:35 +01:00
list($fq_class_name, $method_name) = explode('::', $method_id);
2018-02-04 00:52:35 +01:00
try {
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
} catch (\InvalidArgumentException $e) {
die('Class ' . $fq_class_name . ' cannot be found' . PHP_EOL);
}
2018-02-04 00:52:35 +01:00
$method_name_lc = strtolower($method_name);
2018-02-04 00:52:35 +01:00
if (!isset($class_storage->methods[$method_name_lc])) {
die('Method ' . $method_id . ' cannot be found' . PHP_EOL);
}
2018-02-04 00:52:35 +01:00
$method_storage = $class_storage->methods[$method_name_lc];
2018-02-04 00:52:35 +01:00
if ($method_storage->referencing_locations === null) {
die('No references found for ' . $method_id . PHP_EOL);
}
2018-02-04 00:52:35 +01:00
return $method_storage->referencing_locations;
}
/**
2018-02-04 00:52:35 +01:00
* @param string $fq_class_name
*
2018-02-04 00:52:35 +01:00
* @return array<string, \Psalm\CodeLocation[]>
*/
2018-02-04 00:52:35 +01:00
public function findReferencesToClassLike($fq_class_name)
{
2018-02-04 00:52:35 +01:00
try {
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
} catch (\InvalidArgumentException $e) {
die('Class ' . $fq_class_name . ' cannot be found' . PHP_EOL);
}
2018-02-04 00:52:35 +01:00
if ($class_storage->referencing_locations === null) {
die('No references found for ' . $fq_class_name . PHP_EOL);
}
2018-02-04 00:52:35 +01:00
$classlike_references_by_file = $class_storage->referencing_locations;
2018-02-04 00:52:35 +01:00
$fq_class_name_lc = strtolower($fq_class_name);
2018-02-04 00:52:35 +01:00
if (isset($this->use_referencing_locations[$fq_class_name_lc])) {
foreach ($this->use_referencing_locations[$fq_class_name_lc] as $file_path => $locations) {
if (!isset($classlike_references_by_file[$file_path])) {
$classlike_references_by_file[$file_path] = $locations;
} else {
$classlike_references_by_file[$file_path] = array_merge(
$locations,
$classlike_references_by_file[$file_path]
);
}
}
}
2018-02-04 00:52:35 +01:00
return $classlike_references_by_file;
}
2018-01-31 23:09:09 +01:00
/**
* @param string $file_path
* @param string $closure_id
*
* @return FunctionLikeStorage
*/
public function getClosureStorage($file_path, $closure_id)
{
$file_storage = $this->file_storage_provider->get($file_path);
// closures can be returned here
if (isset($file_storage->functions[$closure_id])) {
return $file_storage->functions[$closure_id];
}
throw new \UnexpectedValueException(
'Expecting ' . $closure_id . ' to have storage in ' . $file_path
);
}
/**
* @param string $const_id
* @param Type\Union $type
*
* @return void
*/
public function addGlobalConstantType($const_id, Type\Union $type)
{
self::$stubbed_constants[$const_id] = $type;
}
/**
* @param string $const_id
*
* @return Type\Union|null
*/
public function getStubbedConstantType($const_id)
{
return isset(self::$stubbed_constants[$const_id]) ? self::$stubbed_constants[$const_id] : null;
}
/**
2018-02-04 00:52:35 +01:00
* @param string $file_path
*
2018-02-04 00:52:35 +01:00
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function fileExists($file_path)
{
2018-02-04 00:52:35 +01:00
return $this->file_provider->fileExists($file_path);
}
/**
2018-02-04 00:52:35 +01:00
* Check whether a class/interface exists
*
2018-02-04 00:52:35 +01:00
* @param string $fq_class_name
* @param CodeLocation $code_location
*
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function classOrInterfaceExists($fq_class_name, CodeLocation $code_location = null)
{
2018-02-04 00:52:35 +01:00
return $this->classlikes->classOrInterfaceExists($fq_class_name, $code_location);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $fq_class_name
* @param string $possible_parent
*
2018-02-04 00:52:35 +01:00
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function classExtendsOrImplements($fq_class_name, $possible_parent)
{
2018-02-04 00:52:35 +01:00
return $this->classlikes->classExtends($fq_class_name, $possible_parent)
|| $this->classlikes->classImplements($fq_class_name, $possible_parent);
}
/**
2018-02-04 00:52:35 +01:00
* Determine whether or not a given class exists
*
* @param string $fq_class_name
*
2018-02-04 00:52:35 +01:00
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function classExists($fq_class_name)
{
2018-02-04 00:52:35 +01:00
return $this->classlikes->classExists($fq_class_name);
}
/**
2018-02-04 00:52:35 +01:00
* Determine whether or not a class extends a parent
*
2018-02-04 00:52:35 +01:00
* @param string $fq_class_name
* @param string $possible_parent
*
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function classExtends($fq_class_name, $possible_parent)
{
2018-02-04 00:52:35 +01:00
return $this->classlikes->classExtends($fq_class_name, $possible_parent);
}
/**
2018-02-04 00:52:35 +01:00
* Check whether a class implements an interface
*
* @param string $fq_class_name
* @param string $interface
*
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function classImplements($fq_class_name, $interface)
{
2018-02-04 00:52:35 +01:00
return $this->classlikes->classImplements($fq_class_name, $interface);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $fq_interface_name
*
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function interfaceExists($fq_interface_name)
{
2018-02-04 00:52:35 +01:00
return $this->classlikes->interfaceExists($fq_interface_name);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $interface_name
* @param string $possible_parent
*
2018-02-04 00:52:35 +01:00
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function interfaceExtends($interface_name, $possible_parent)
{
2018-02-04 00:52:35 +01:00
return $this->classlikes->interfaceExtends($interface_name, $possible_parent);
}
2018-02-04 00:52:35 +01:00
/**
* @param string $fq_interface_name
*
* @return array<string> all interfaces extended by $interface_name
*/
public function getParentInterfaces($fq_interface_name)
{
return $this->classlikes->getParentInterfaces($fq_interface_name);
}
2018-02-04 00:52:35 +01:00
/**
* Determine whether or not a class has the correct casing
*
* @param string $fq_class_name
*
* @return bool
*/
public function classHasCorrectCasing($fq_class_name)
{
return $this->classlikes->classHasCorrectCasing($fq_class_name);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $fq_interface_name
*
2018-02-04 00:52:35 +01:00
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function interfaceHasCorrectCasing($fq_interface_name)
{
2018-02-04 00:52:35 +01:00
return $this->classlikes->interfaceHasCorrectCasing($fq_interface_name);
}
2018-02-04 00:52:35 +01:00
/**
* @param string $fq_trait_name
*
* @return bool
*/
public function traitHasCorrectCase($fq_trait_name)
{
return $this->classlikes->traitHasCorrectCase($fq_trait_name);
}
2018-02-04 00:52:35 +01:00
/**
* Whether or not a given method exists
*
* @param string $method_id
* @param CodeLocation|null $code_location
2018-09-27 19:32:08 +02:00
* @param string $calling_method_id
2018-02-04 00:52:35 +01:00
*
* @return bool
*/
2018-09-27 19:32:08 +02:00
public function methodExists($method_id, CodeLocation $code_location = null, $calling_method_id = null)
2018-02-04 00:52:35 +01:00
{
return $this->methods->methodExists($method_id, $calling_method_id, $code_location);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $method_id
*
* @return array<int, \Psalm\Storage\FunctionLikeParameter>
*/
2018-02-04 00:52:35 +01:00
public function getMethodParams($method_id)
{
2018-02-04 00:52:35 +01:00
return $this->methods->getMethodParams($method_id);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $method_id
*
* @return bool
*/
2018-02-04 00:52:35 +01:00
public function isVariadic($method_id)
{
2018-02-04 00:52:35 +01:00
return $this->methods->isVariadic($method_id);
}
2018-02-04 00:52:35 +01:00
/**
* @param string $method_id
* @param string $self_class
*
* @return Type\Union|null
*/
public function getMethodReturnType($method_id, &$self_class)
{
return $this->methods->getMethodReturnType($method_id, $self_class);
}
2018-02-04 00:52:35 +01:00
/**
* @param string $method_id
*
* @return bool
*/
public function getMethodReturnsByRef($method_id)
{
return $this->methods->getMethodReturnsByRef($method_id);
}
2018-02-04 00:52:35 +01:00
/**
* @param string $method_id
* @param CodeLocation|null $defined_location
*
* @return CodeLocation|null
*/
public function getMethodReturnTypeLocation(
$method_id,
CodeLocation &$defined_location = null
) {
return $this->methods->getMethodReturnTypeLocation($method_id, $defined_location);
}
2018-01-31 23:09:09 +01:00
/**
2018-02-04 00:52:35 +01:00
* @param string $method_id
*
* @return string|null
2018-01-31 23:09:09 +01:00
*/
2018-02-04 00:52:35 +01:00
public function getDeclaringMethodId($method_id)
2018-01-31 23:09:09 +01:00
{
2018-02-04 00:52:35 +01:00
return $this->methods->getDeclaringMethodId($method_id);
}
2018-01-31 23:09:09 +01:00
2018-02-04 00:52:35 +01:00
/**
* Get the class this method appears in (vs is declared in, which could give a trait)
*
* @param string $method_id
*
* @return string|null
*/
public function getAppearingMethodId($method_id)
{
return $this->methods->getAppearingMethodId($method_id);
2018-01-31 23:09:09 +01:00
}
/**
2018-02-04 00:52:35 +01:00
* @param string $method_id
*
* @return array<string>
*/
2018-02-04 00:52:35 +01:00
public function getOverriddenMethodIds($method_id)
{
2018-02-04 00:52:35 +01:00
return $this->methods->getOverriddenMethodIds($method_id);
}
/**
2018-02-04 00:52:35 +01:00
* @param string $method_id
*
* @return string
*/
2018-02-04 00:52:35 +01:00
public function getCasedMethodId($method_id)
{
2018-02-04 00:52:35 +01:00
return $this->methods->getCasedMethodId($method_id);
}
/**
* @param string $file_path
*
* @return void
*/
private function invalidateInformationForFile($file_path)
{
$this->scanner->removeFile($file_path);
try {
$file_storage = $this->file_storage_provider->get($file_path);
} catch (\InvalidArgumentException $e) {
return;
}
foreach ($file_storage->classlikes_in_file as $fq_classlike_name) {
$this->classlike_storage_provider->remove($fq_classlike_name);
$this->classlikes->removeClassLike($fq_classlike_name);
}
$this->file_storage_provider->remove($file_path);
}
}