mirror of
https://github.com/danog/psalm.git
synced 2024-12-16 11:26:55 +01:00
e862e4ddb1
cc @TysonAndre
624 lines
16 KiB
PHP
624 lines
16 KiB
PHP
<?php
|
|
namespace Psalm;
|
|
|
|
use PhpParser;
|
|
use Psalm\Provider\ClassLikeStorageProvider;
|
|
use Psalm\Provider\FileProvider;
|
|
use Psalm\Provider\FileStorageProvider;
|
|
use Psalm\Provider\StatementsProvider;
|
|
use Psalm\Storage\ClassLikeStorage;
|
|
use Psalm\Storage\FileStorage;
|
|
use Psalm\Storage\FunctionLikeStorage;
|
|
|
|
class Codebase
|
|
{
|
|
/**
|
|
* @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
|
|
*/
|
|
private $statements_provider;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
private $debug_output = false;
|
|
|
|
/**
|
|
* @var array<string, Type\Union>
|
|
*/
|
|
private static $stubbed_constants = [];
|
|
|
|
/**
|
|
* Whether to log functions just at the file level or globally (for stubs)
|
|
*
|
|
* @var bool
|
|
*/
|
|
public $register_global_functions = false;
|
|
|
|
/**
|
|
* @var Codebase\Reflection
|
|
*/
|
|
private $reflection;
|
|
|
|
/**
|
|
* @var Codebase\Scanner
|
|
*/
|
|
public $scanner;
|
|
|
|
/**
|
|
* @var Codebase\Analyzer
|
|
*/
|
|
public $analyzer;
|
|
|
|
/**
|
|
* @var Codebase\Functions
|
|
*/
|
|
public $functions;
|
|
|
|
/**
|
|
* @var Codebase\ClassLikes
|
|
*/
|
|
public $classlikes;
|
|
|
|
/**
|
|
* @var Codebase\Methods
|
|
*/
|
|
public $methods;
|
|
|
|
/**
|
|
* @var Codebase\Properties
|
|
*/
|
|
public $properties;
|
|
|
|
/**
|
|
* @var Codebase\Populator
|
|
*/
|
|
public $populator;
|
|
|
|
/**
|
|
* @param bool $collect_references
|
|
* @param bool $debug_output
|
|
*/
|
|
public function __construct(
|
|
Config $config,
|
|
FileStorageProvider $file_storage_provider,
|
|
ClassLikeStorageProvider $classlike_storage_provider,
|
|
FileProvider $file_provider,
|
|
StatementsProvider $statements_provider,
|
|
$debug_output = false
|
|
) {
|
|
$this->config = $config;
|
|
$this->file_storage_provider = $file_storage_provider;
|
|
$this->classlike_storage_provider = $classlike_storage_provider;
|
|
$this->debug_output = $debug_output;
|
|
$this->file_provider = $file_provider;
|
|
$this->statements_provider = $statements_provider;
|
|
$this->debug_output = $debug_output;
|
|
|
|
self::$stubbed_constants = [];
|
|
|
|
$this->reflection = new Codebase\Reflection($classlike_storage_provider, $this);
|
|
|
|
$this->scanner = new Codebase\Scanner(
|
|
$this,
|
|
$config,
|
|
$file_storage_provider,
|
|
$statements_provider,
|
|
$this->reflection,
|
|
$debug_output
|
|
);
|
|
|
|
$this->analyzer = new Codebase\Analyzer($config, $file_provider, $debug_output);
|
|
|
|
$this->functions = new Codebase\Functions($file_storage_provider, $this->reflection);
|
|
$this->methods = new Codebase\Methods($classlike_storage_provider);
|
|
$this->properties = new Codebase\Properties($classlike_storage_provider);
|
|
$this->classlikes = new Codebase\ClassLikes(
|
|
$config,
|
|
$this,
|
|
$classlike_storage_provider,
|
|
$this->scanner,
|
|
$this->methods,
|
|
$debug_output
|
|
);
|
|
$this->populator = new Codebase\Populator(
|
|
$config,
|
|
$classlike_storage_provider,
|
|
$file_storage_provider,
|
|
$this->classlikes,
|
|
$this->methods,
|
|
$debug_output
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function collectReferences()
|
|
{
|
|
$this->collect_references = true;
|
|
$this->classlikes->collect_references = true;
|
|
$this->methods->collect_references = true;
|
|
$this->properties->collect_references = true;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string> $files_to_analyze
|
|
*
|
|
* @return void
|
|
*/
|
|
public function addFilesToAnalyze(array $files_to_analyze)
|
|
{
|
|
$this->scanner->addFilesToDeepScan($files_to_analyze);
|
|
$this->analyzer->addFiles($files_to_analyze);
|
|
}
|
|
|
|
/**
|
|
* Scans all files their related files
|
|
*
|
|
* @return void
|
|
*/
|
|
public function scanFiles()
|
|
{
|
|
$has_changes = $this->scanner->scanFiles($this->classlikes);
|
|
|
|
if ($has_changes) {
|
|
$this->populator->populateCodebase();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $file_path
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getFileContents($file_path)
|
|
{
|
|
return $this->file_provider->getContents($file_path);
|
|
}
|
|
|
|
/**
|
|
* @param string $file_path
|
|
*
|
|
* @return array<int, PhpParser\Node\Stmt>
|
|
*/
|
|
public function getStatementsForFile($file_path)
|
|
{
|
|
return $this->statements_provider->getStatementsForFile(
|
|
$file_path,
|
|
$this->debug_output
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $fq_classlike_name
|
|
*
|
|
* @return ClassLikeStorage
|
|
*/
|
|
public function createClassLikeStorage($fq_classlike_name)
|
|
{
|
|
return $this->classlike_storage_provider->create($fq_classlike_name);
|
|
}
|
|
|
|
/**
|
|
* @param string $file_path
|
|
*
|
|
* @return FileStorage
|
|
*/
|
|
public function createFileStorageForPath($file_path)
|
|
{
|
|
return $this->file_storage_provider->create($file_path);
|
|
}
|
|
|
|
/**
|
|
* @param string $symbol
|
|
*
|
|
* @return array<string, \Psalm\CodeLocation[]>
|
|
*/
|
|
public function findReferencesToSymbol($symbol)
|
|
{
|
|
if (!$this->collect_references) {
|
|
throw new \UnexpectedValueException('Should not be checking references');
|
|
}
|
|
|
|
if (strpos($symbol, '::') !== false) {
|
|
return $this->findReferencesToMethod($symbol);
|
|
}
|
|
|
|
return $this->findReferencesToClassLike($symbol);
|
|
}
|
|
|
|
/**
|
|
* @param string $method_id
|
|
*
|
|
* @return array<string, \Psalm\CodeLocation[]>
|
|
*/
|
|
public function findReferencesToMethod($method_id)
|
|
{
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
try {
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
} catch (\InvalidArgumentException $e) {
|
|
die('Class ' . $fq_class_name . ' cannot be found' . PHP_EOL);
|
|
}
|
|
|
|
$method_name_lc = strtolower($method_name);
|
|
|
|
if (!isset($class_storage->methods[$method_name_lc])) {
|
|
die('Method ' . $method_id . ' cannot be found' . PHP_EOL);
|
|
}
|
|
|
|
$method_storage = $class_storage->methods[$method_name_lc];
|
|
|
|
if ($method_storage->referencing_locations === null) {
|
|
die('No references found for ' . $method_id . PHP_EOL);
|
|
}
|
|
|
|
return $method_storage->referencing_locations;
|
|
}
|
|
|
|
/**
|
|
* @param string $fq_class_name
|
|
*
|
|
* @return array<string, \Psalm\CodeLocation[]>
|
|
*/
|
|
public function findReferencesToClassLike($fq_class_name)
|
|
{
|
|
try {
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
} catch (\InvalidArgumentException $e) {
|
|
die('Class ' . $fq_class_name . ' cannot be found' . PHP_EOL);
|
|
}
|
|
|
|
if ($class_storage->referencing_locations === null) {
|
|
die('No references found for ' . $fq_class_name . PHP_EOL);
|
|
}
|
|
|
|
$classlike_references_by_file = $class_storage->referencing_locations;
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
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]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $classlike_references_by_file;
|
|
}
|
|
|
|
/**
|
|
* @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 addStubbedConstantType($const_id, $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;
|
|
}
|
|
|
|
/**
|
|
* @param string $file_path
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function fileExists($file_path)
|
|
{
|
|
return $this->file_provider->fileExists($file_path);
|
|
}
|
|
|
|
/**
|
|
* Check whether a class/interface exists
|
|
*
|
|
* @param string $fq_class_name
|
|
* @param CodeLocation $code_location
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function classOrInterfaceExists($fq_class_name, CodeLocation $code_location = null)
|
|
{
|
|
return $this->classlikes->classOrInterfaceExists($fq_class_name, $code_location);
|
|
}
|
|
|
|
/**
|
|
* @param string $fq_class_name
|
|
* @param string $possible_parent
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function classExtendsOrImplements($fq_class_name, $possible_parent)
|
|
{
|
|
return $this->classlikes->classExtends($fq_class_name, $possible_parent)
|
|
|| $this->classlikes->classImplements($fq_class_name, $possible_parent);
|
|
}
|
|
|
|
/**
|
|
* Determine whether or not a given class exists
|
|
*
|
|
* @param string $fq_class_name
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function classExists($fq_class_name)
|
|
{
|
|
return $this->classlikes->classExists($fq_class_name);
|
|
}
|
|
|
|
/**
|
|
* Determine whether or not a class extends a parent
|
|
*
|
|
* @param string $fq_class_name
|
|
* @param string $possible_parent
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function classExtends($fq_class_name, $possible_parent)
|
|
{
|
|
return $this->classlikes->classExtends($fq_class_name, $possible_parent);
|
|
}
|
|
|
|
/**
|
|
* Check whether a class implements an interface
|
|
*
|
|
* @param string $fq_class_name
|
|
* @param string $interface
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function classImplements($fq_class_name, $interface)
|
|
{
|
|
return $this->classlikes->classImplements($fq_class_name, $interface);
|
|
}
|
|
|
|
/**
|
|
* @param string $fq_interface_name
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function interfaceExists($fq_interface_name)
|
|
{
|
|
return $this->classlikes->interfaceExists($fq_interface_name);
|
|
}
|
|
|
|
/**
|
|
* @param string $interface_name
|
|
* @param string $possible_parent
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function interfaceExtends($interface_name, $possible_parent)
|
|
{
|
|
return $this->classlikes->interfaceExtends($interface_name, $possible_parent);
|
|
}
|
|
|
|
/**
|
|
* @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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* @param string $fq_interface_name
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function interfaceHasCorrectCasing($fq_interface_name)
|
|
{
|
|
return $this->classlikes->interfaceHasCorrectCasing($fq_interface_name);
|
|
}
|
|
|
|
/**
|
|
* @param string $fq_trait_name
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function traitHasCorrectCase($fq_trait_name)
|
|
{
|
|
return $this->classlikes->traitHasCorrectCase($fq_trait_name);
|
|
}
|
|
|
|
/**
|
|
* Whether or not a given method exists
|
|
*
|
|
* @param string $method_id
|
|
* @param CodeLocation|null $code_location
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function methodExists($method_id, CodeLocation $code_location = null)
|
|
{
|
|
return $this->methods->methodExists($method_id, $code_location);
|
|
}
|
|
|
|
/**
|
|
* @param string $method_id
|
|
*
|
|
* @return array<int, \Psalm\FunctionLikeParameter>
|
|
*/
|
|
public function getMethodParams($method_id)
|
|
{
|
|
return $this->methods->getMethodParams($method_id);
|
|
}
|
|
|
|
/**
|
|
* @param string $method_id
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isVariadic($method_id)
|
|
{
|
|
return $this->methods->isVariadic($method_id);
|
|
}
|
|
|
|
/**
|
|
* @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);
|
|
}
|
|
|
|
/**
|
|
* @param string $method_id
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function getMethodReturnsByRef($method_id)
|
|
{
|
|
return $this->methods->getMethodReturnsByRef($method_id);
|
|
}
|
|
|
|
/**
|
|
* @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);
|
|
}
|
|
|
|
/**
|
|
* @param string $method_id
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getDeclaringMethodId($method_id)
|
|
{
|
|
return $this->methods->getDeclaringMethodId($method_id);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* @param string $method_id
|
|
*
|
|
* @return array<string>
|
|
*/
|
|
public function getOverriddenMethodIds($method_id)
|
|
{
|
|
return $this->methods->getOverriddenMethodIds($method_id);
|
|
}
|
|
|
|
/**
|
|
* @param string $method_id
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getCasedMethodId($method_id)
|
|
{
|
|
return $this->methods->getCasedMethodId($method_id);
|
|
}
|
|
}
|