2018-02-04 00:52:35 +01:00
|
|
|
|
<?php
|
2018-11-12 16:46:55 +01:00
|
|
|
|
namespace Psalm\Internal\Codebase;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use function array_merge;
|
|
|
|
|
use function array_pop;
|
2019-07-22 05:29:16 +02:00
|
|
|
|
use function count;
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use function end;
|
|
|
|
|
use function explode;
|
|
|
|
|
use function get_declared_classes;
|
|
|
|
|
use function get_declared_interfaces;
|
|
|
|
|
use function implode;
|
|
|
|
|
use const PHP_EOL;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
use PhpParser;
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use function preg_match;
|
|
|
|
|
use function preg_replace;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
use Psalm\Aliases;
|
|
|
|
|
use Psalm\CodeLocation;
|
|
|
|
|
use Psalm\Config;
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use Psalm\Exception\UnpopulatedClasslikeException;
|
|
|
|
|
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
2019-04-17 17:12:18 +02:00
|
|
|
|
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use Psalm\Internal\Provider\ClassLikeStorageProvider;
|
|
|
|
|
use Psalm\Internal\Provider\FileReferenceProvider;
|
2020-03-03 04:27:54 +01:00
|
|
|
|
use Psalm\Internal\Provider\StatementsProvider;
|
2019-09-14 20:26:31 +02:00
|
|
|
|
use Psalm\Internal\Scanner\UnresolvedConstant;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
use Psalm\Issue\PossiblyUnusedMethod;
|
|
|
|
|
use Psalm\Issue\PossiblyUnusedParam;
|
|
|
|
|
use Psalm\Issue\PossiblyUnusedProperty;
|
|
|
|
|
use Psalm\Issue\UnusedClass;
|
|
|
|
|
use Psalm\Issue\UnusedMethod;
|
|
|
|
|
use Psalm\Issue\UnusedProperty;
|
|
|
|
|
use Psalm\IssueBuffer;
|
2019-05-30 16:30:41 +02:00
|
|
|
|
use Psalm\Progress\Progress;
|
|
|
|
|
use Psalm\Progress\VoidProgress;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
use Psalm\Storage\ClassLikeStorage;
|
2018-02-09 00:14:28 +01:00
|
|
|
|
use Psalm\Type;
|
|
|
|
|
use ReflectionProperty;
|
2019-07-05 22:24:00 +02:00
|
|
|
|
use function strlen;
|
|
|
|
|
use function strrpos;
|
2019-06-26 22:52:29 +02:00
|
|
|
|
use function strtolower;
|
|
|
|
|
use function substr;
|
2019-09-17 17:29:41 +02:00
|
|
|
|
use Psalm\Internal\Scanner\UnresolvedConstantComponent;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2018-02-09 23:51:49 +01:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*
|
|
|
|
|
* Handles information about classes, interfaces and traits
|
|
|
|
|
*/
|
2018-02-04 00:52:35 +01:00
|
|
|
|
class ClassLikes
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* @var ClassLikeStorageProvider
|
|
|
|
|
*/
|
|
|
|
|
private $classlike_storage_provider;
|
|
|
|
|
|
2019-04-13 00:28:07 +02:00
|
|
|
|
/**
|
|
|
|
|
* @var FileReferenceProvider
|
|
|
|
|
*/
|
|
|
|
|
public $file_reference_provider;
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @var array<lowercase-string, bool>
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
|
|
|
|
private $existing_classlikes_lc = [];
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @var array<lowercase-string, bool>
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
|
|
|
|
private $existing_classes_lc = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var array<string, bool>
|
|
|
|
|
*/
|
|
|
|
|
private $existing_classes = [];
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @var array<lowercase-string, bool>
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
|
|
|
|
private $existing_interfaces_lc = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var array<string, bool>
|
|
|
|
|
*/
|
|
|
|
|
private $existing_interfaces = [];
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @var array<lowercase-string, bool>
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
|
|
|
|
private $existing_traits_lc = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var array<string, bool>
|
|
|
|
|
*/
|
|
|
|
|
private $existing_traits = [];
|
|
|
|
|
|
|
|
|
|
/**
|
2020-03-03 04:27:54 +01:00
|
|
|
|
* @var array<string, string>
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
2020-03-03 04:27:54 +01:00
|
|
|
|
private $classlike_aliases = [];
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2018-11-29 06:05:56 +01:00
|
|
|
|
/**
|
2020-03-03 04:27:54 +01:00
|
|
|
|
* @var array<string, PhpParser\Node\Stmt\Trait_>
|
2018-11-29 06:05:56 +01:00
|
|
|
|
*/
|
2020-03-03 04:27:54 +01:00
|
|
|
|
private $trait_nodes = [];
|
2018-11-29 06:05:56 +01:00
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
/**
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
public $collect_references = false;
|
|
|
|
|
|
2019-04-13 00:28:07 +02:00
|
|
|
|
/**
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
public $collect_locations = false;
|
|
|
|
|
|
2020-03-03 04:27:54 +01:00
|
|
|
|
/**
|
|
|
|
|
* @var StatementsProvider
|
|
|
|
|
*/
|
|
|
|
|
private $statements_provider;
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
/**
|
|
|
|
|
* @var Config
|
|
|
|
|
*/
|
|
|
|
|
private $config;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var Scanner
|
|
|
|
|
*/
|
|
|
|
|
private $scanner;
|
|
|
|
|
|
|
|
|
|
public function __construct(
|
|
|
|
|
Config $config,
|
|
|
|
|
ClassLikeStorageProvider $storage_provider,
|
2019-04-13 00:28:07 +02:00
|
|
|
|
FileReferenceProvider $file_reference_provider,
|
2020-03-03 04:27:54 +01:00
|
|
|
|
StatementsProvider $statements_provider,
|
2018-12-21 17:32:44 +01:00
|
|
|
|
Scanner $scanner
|
2018-02-04 00:52:35 +01:00
|
|
|
|
) {
|
|
|
|
|
$this->config = $config;
|
|
|
|
|
$this->classlike_storage_provider = $storage_provider;
|
2019-04-13 00:28:07 +02:00
|
|
|
|
$this->file_reference_provider = $file_reference_provider;
|
2020-03-03 04:27:54 +01:00
|
|
|
|
$this->statements_provider = $statements_provider;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$this->scanner = $scanner;
|
|
|
|
|
|
|
|
|
|
$this->collectPredefinedClassLikes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function collectPredefinedClassLikes()
|
|
|
|
|
{
|
|
|
|
|
/** @var array<int, string> */
|
|
|
|
|
$predefined_classes = get_declared_classes();
|
|
|
|
|
|
|
|
|
|
foreach ($predefined_classes as $predefined_class) {
|
2018-11-09 18:59:17 +01:00
|
|
|
|
$predefined_class = preg_replace('/^\\\/', '', $predefined_class);
|
2019-02-11 03:13:06 +01:00
|
|
|
|
/** @psalm-suppress TypeCoercion */
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$reflection_class = new \ReflectionClass($predefined_class);
|
|
|
|
|
|
|
|
|
|
if (!$reflection_class->isUserDefined()) {
|
|
|
|
|
$predefined_class_lc = strtolower($predefined_class);
|
|
|
|
|
$this->existing_classlikes_lc[$predefined_class_lc] = true;
|
|
|
|
|
$this->existing_classes_lc[$predefined_class_lc] = true;
|
2019-07-18 04:50:57 +02:00
|
|
|
|
$this->existing_classes[$predefined_class] = true;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @var array<int, string> */
|
|
|
|
|
$predefined_interfaces = get_declared_interfaces();
|
|
|
|
|
|
|
|
|
|
foreach ($predefined_interfaces as $predefined_interface) {
|
2018-11-09 18:59:17 +01:00
|
|
|
|
$predefined_interface = preg_replace('/^\\\/', '', $predefined_interface);
|
2019-02-11 03:13:06 +01:00
|
|
|
|
/** @psalm-suppress TypeCoercion */
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$reflection_class = new \ReflectionClass($predefined_interface);
|
|
|
|
|
|
|
|
|
|
if (!$reflection_class->isUserDefined()) {
|
|
|
|
|
$predefined_interface_lc = strtolower($predefined_interface);
|
|
|
|
|
$this->existing_classlikes_lc[$predefined_interface_lc] = true;
|
|
|
|
|
$this->existing_interfaces_lc[$predefined_interface_lc] = true;
|
2019-07-18 04:50:57 +02:00
|
|
|
|
$this->existing_interfaces[$predefined_interface] = true;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
* @param string|null $file_path
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function addFullyQualifiedClassName($fq_class_name, $file_path = null)
|
|
|
|
|
{
|
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
|
$this->existing_classlikes_lc[$fq_class_name_lc] = true;
|
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc] = true;
|
|
|
|
|
$this->existing_traits_lc[$fq_class_name_lc] = false;
|
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
|
|
|
|
|
$this->existing_classes[$fq_class_name] = true;
|
|
|
|
|
|
|
|
|
|
if ($file_path) {
|
|
|
|
|
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
* @param string|null $file_path
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function addFullyQualifiedInterfaceName($fq_class_name, $file_path = null)
|
|
|
|
|
{
|
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
|
$this->existing_classlikes_lc[$fq_class_name_lc] = true;
|
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc] = true;
|
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc] = false;
|
|
|
|
|
$this->existing_traits_lc[$fq_class_name_lc] = false;
|
|
|
|
|
$this->existing_interfaces[$fq_class_name] = true;
|
|
|
|
|
|
|
|
|
|
if ($file_path) {
|
|
|
|
|
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
* @param string|null $file_path
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function addFullyQualifiedTraitName($fq_class_name, $file_path = null)
|
|
|
|
|
{
|
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
|
$this->existing_classlikes_lc[$fq_class_name_lc] = true;
|
|
|
|
|
$this->existing_traits_lc[$fq_class_name_lc] = true;
|
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc] = false;
|
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
|
|
|
|
|
$this->existing_traits[$fq_class_name] = true;
|
|
|
|
|
|
|
|
|
|
if ($file_path) {
|
|
|
|
|
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_class_name_lc
|
|
|
|
|
* @param string|null $file_path
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function addFullyQualifiedClassLikeName($fq_class_name_lc, $file_path = null)
|
|
|
|
|
{
|
|
|
|
|
if ($file_path) {
|
|
|
|
|
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-21 23:10:35 +02:00
|
|
|
|
/**
|
|
|
|
|
* @return string[]
|
|
|
|
|
*/
|
|
|
|
|
public function getMatchingClassLikeNames(string $stub) : array
|
|
|
|
|
{
|
|
|
|
|
$matching_classes = [];
|
|
|
|
|
|
|
|
|
|
if ($stub[0] === '*') {
|
|
|
|
|
$stub = substr($stub, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$stub = strtolower($stub);
|
|
|
|
|
|
2019-07-18 04:50:57 +02:00
|
|
|
|
foreach ($this->existing_classes as $fq_classlike_name => $found) {
|
2019-06-21 23:10:35 +02:00
|
|
|
|
if (!$found) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-18 04:50:57 +02:00
|
|
|
|
if (preg_match('@(^|\\\)' . $stub . '.*@i', $fq_classlike_name)) {
|
|
|
|
|
$matching_classes[] = $fq_classlike_name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($this->existing_interfaces as $fq_classlike_name => $found) {
|
|
|
|
|
if (!$found) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (preg_match('@(^|\\\)' . $stub . '.*@i', $fq_classlike_name)) {
|
|
|
|
|
$matching_classes[] = $fq_classlike_name;
|
2019-06-21 23:10:35 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $matching_classes;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2020-02-11 22:39:33 +01:00
|
|
|
|
public function hasFullyQualifiedClassName(
|
|
|
|
|
$fq_class_name,
|
|
|
|
|
CodeLocation $code_location = null,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
?string $calling_fq_class_name = null,
|
|
|
|
|
?string $calling_method_id = null
|
2020-02-11 22:39:33 +01:00
|
|
|
|
) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
|
|
2018-11-29 06:05:56 +01:00
|
|
|
|
if (isset($this->classlike_aliases[$fq_class_name_lc])) {
|
|
|
|
|
$fq_class_name_lc = strtolower($this->classlike_aliases[$fq_class_name_lc]);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-28 21:30:56 +01:00
|
|
|
|
if ($code_location) {
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
|
|
|
|
$this->file_reference_provider->addMethodReferenceToClass(
|
|
|
|
|
$calling_method_id,
|
|
|
|
|
$fq_class_name_lc
|
|
|
|
|
);
|
2020-04-05 00:57:33 +02:00
|
|
|
|
} elseif (!$calling_fq_class_name || strtolower($calling_fq_class_name) !== $fq_class_name_lc) {
|
2020-04-02 23:17:55 +02:00
|
|
|
|
$this->file_reference_provider->addNonMethodReferenceToClass(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$code_location->file_path,
|
|
|
|
|
$fq_class_name_lc
|
|
|
|
|
);
|
2020-02-11 22:39:33 +01:00
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_fq_class_name) {
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($calling_fq_class_name);
|
2020-02-11 22:39:33 +01:00
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($class_storage->location
|
|
|
|
|
&& $class_storage->location->file_path !== $code_location->file_path
|
|
|
|
|
) {
|
2020-04-02 23:17:55 +02:00
|
|
|
|
$this->file_reference_provider->addNonMethodReferenceToClass(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$class_storage->location->file_path,
|
|
|
|
|
$fq_class_name_lc
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-02-11 22:39:33 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-13 21:38:09 +02:00
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-04-05 03:47:35 +02:00
|
|
|
|
if (!isset($this->existing_classes_lc[$fq_class_name_lc])
|
|
|
|
|
|| !$this->existing_classes_lc[$fq_class_name_lc]
|
|
|
|
|
|| !$this->classlike_storage_provider->has($fq_class_name_lc)
|
|
|
|
|
) {
|
|
|
|
|
if ((
|
|
|
|
|
!isset($this->existing_classes_lc[$fq_class_name_lc])
|
|
|
|
|
|| $this->existing_classes_lc[$fq_class_name_lc] === true
|
|
|
|
|
)
|
|
|
|
|
&& !$this->classlike_storage_provider->has($fq_class_name_lc)
|
|
|
|
|
) {
|
|
|
|
|
if (!isset($this->existing_classes_lc[$fq_class_name_lc])) {
|
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc] = false;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->existing_classes_lc[$fq_class_name_lc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-13 21:38:09 +02:00
|
|
|
|
if ($this->collect_locations && $code_location) {
|
|
|
|
|
$this->file_reference_provider->addCallingLocationForClass(
|
|
|
|
|
$code_location,
|
|
|
|
|
strtolower($fq_class_name)
|
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2020-02-11 22:39:33 +01:00
|
|
|
|
public function hasFullyQualifiedInterfaceName(
|
|
|
|
|
$fq_class_name,
|
|
|
|
|
CodeLocation $code_location = null,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
?string $calling_fq_class_name = null,
|
|
|
|
|
?string $calling_method_id = null
|
2020-02-11 22:39:33 +01:00
|
|
|
|
) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
|
|
2018-11-29 06:05:56 +01:00
|
|
|
|
if (isset($this->classlike_aliases[$fq_class_name_lc])) {
|
2018-12-21 15:29:23 +01:00
|
|
|
|
$fq_class_name_lc = strtolower($this->classlike_aliases[$fq_class_name_lc]);
|
2018-11-29 06:05:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
if (!isset($this->existing_interfaces_lc[$fq_class_name_lc])
|
|
|
|
|
|| !$this->existing_interfaces_lc[$fq_class_name_lc]
|
|
|
|
|
|| !$this->classlike_storage_provider->has($fq_class_name_lc)
|
|
|
|
|
) {
|
|
|
|
|
if ((
|
|
|
|
|
!isset($this->existing_classes_lc[$fq_class_name_lc])
|
|
|
|
|
|| $this->existing_classes_lc[$fq_class_name_lc] === true
|
|
|
|
|
)
|
|
|
|
|
&& !$this->classlike_storage_provider->has($fq_class_name_lc)
|
|
|
|
|
) {
|
|
|
|
|
if (!isset($this->existing_interfaces_lc[$fq_class_name_lc])) {
|
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->existing_interfaces_lc[$fq_class_name_lc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-13 21:38:09 +02:00
|
|
|
|
if ($this->collect_references && $code_location) {
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_method_id) {
|
|
|
|
|
$this->file_reference_provider->addMethodReferenceToClass(
|
|
|
|
|
$calling_method_id,
|
|
|
|
|
$fq_class_name_lc
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2020-04-02 23:17:55 +02:00
|
|
|
|
$this->file_reference_provider->addNonMethodReferenceToClass(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$code_location->file_path,
|
|
|
|
|
$fq_class_name_lc
|
|
|
|
|
);
|
2020-02-11 22:39:33 +01:00
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($calling_fq_class_name) {
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($calling_fq_class_name);
|
2020-02-11 22:39:33 +01:00
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if ($class_storage->location
|
|
|
|
|
&& $class_storage->location->file_path !== $code_location->file_path
|
|
|
|
|
) {
|
2020-04-02 23:17:55 +02:00
|
|
|
|
$this->file_reference_provider->addNonMethodReferenceToClass(
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$class_storage->location->file_path,
|
|
|
|
|
$fq_class_name_lc
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-02-11 22:39:33 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-13 21:38:09 +02:00
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2019-04-13 21:38:09 +02:00
|
|
|
|
if ($this->collect_locations && $code_location) {
|
|
|
|
|
$this->file_reference_provider->addCallingLocationForClass(
|
|
|
|
|
$code_location,
|
|
|
|
|
strtolower($fq_class_name)
|
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2019-04-13 21:38:09 +02:00
|
|
|
|
public function hasFullyQualifiedTraitName($fq_class_name, CodeLocation $code_location = null)
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
|
|
2018-11-29 06:05:56 +01:00
|
|
|
|
if (isset($this->classlike_aliases[$fq_class_name_lc])) {
|
2018-12-21 15:29:23 +01:00
|
|
|
|
$fq_class_name_lc = strtolower($this->classlike_aliases[$fq_class_name_lc]);
|
2018-11-29 06:05:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
if (!isset($this->existing_traits_lc[$fq_class_name_lc]) ||
|
|
|
|
|
!$this->existing_traits_lc[$fq_class_name_lc]
|
|
|
|
|
) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-13 21:38:09 +02:00
|
|
|
|
if ($this->collect_references && $code_location) {
|
2020-04-02 23:17:55 +02:00
|
|
|
|
$this->file_reference_provider->addNonMethodReferenceToClass(
|
2019-04-13 21:38:09 +02:00
|
|
|
|
$code_location->file_path,
|
|
|
|
|
$fq_class_name_lc
|
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether a class/interface exists
|
|
|
|
|
*
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
* @param CodeLocation $code_location
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function classOrInterfaceExists(
|
|
|
|
|
$fq_class_name,
|
2020-02-11 22:39:33 +01:00
|
|
|
|
CodeLocation $code_location = null,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
?string $calling_fq_class_name = null,
|
|
|
|
|
?string $calling_method_id = null
|
2018-02-04 00:52:35 +01:00
|
|
|
|
) {
|
2020-03-26 17:35:27 +01:00
|
|
|
|
if (!$this->classExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
|
|
|
|
&& !$this->interfaceExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
2019-04-13 21:38:09 +02:00
|
|
|
|
) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine whether or not a given class exists
|
|
|
|
|
*
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2020-02-11 22:39:33 +01:00
|
|
|
|
public function classExists(
|
|
|
|
|
$fq_class_name,
|
|
|
|
|
CodeLocation $code_location = null,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
?string $calling_fq_class_name = null,
|
|
|
|
|
?string $calling_method_id = null
|
2020-02-11 22:39:33 +01:00
|
|
|
|
) {
|
2019-01-05 22:23:18 +01:00
|
|
|
|
if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[$fq_class_name])) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($fq_class_name === 'Generator') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
return $this->hasFullyQualifiedClassName(
|
|
|
|
|
$fq_class_name,
|
|
|
|
|
$code_location,
|
|
|
|
|
$calling_fq_class_name,
|
|
|
|
|
$calling_method_id
|
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine whether or not a class extends a parent
|
|
|
|
|
*
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
* @param string $possible_parent
|
|
|
|
|
*
|
2019-03-16 23:03:37 +01:00
|
|
|
|
* @throws UnpopulatedClasslikeException when called on unpopulated class
|
|
|
|
|
* @throws \InvalidArgumentException when class does not exist
|
2019-07-05 22:24:00 +02:00
|
|
|
|
*
|
|
|
|
|
* @return bool
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
2019-03-17 22:11:04 +01:00
|
|
|
|
public function classExtends($fq_class_name, $possible_parent, bool $from_api = false)
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
2019-03-16 23:03:37 +01:00
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2019-03-16 23:03:37 +01:00
|
|
|
|
if ($fq_class_name_lc === 'generator') {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_class_name = $this->classlike_aliases[$fq_class_name_lc] ?? $fq_class_name;
|
2019-03-16 23:03:37 +01:00
|
|
|
|
|
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name_lc);
|
2018-11-29 06:05:56 +01:00
|
|
|
|
|
2019-03-17 22:11:04 +01:00
|
|
|
|
if ($from_api && !$class_storage->populated) {
|
2019-03-16 23:03:37 +01:00
|
|
|
|
throw new UnpopulatedClasslikeException($fq_class_name);
|
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
|
|
return isset($class_storage->parent_classes[strtolower($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)
|
|
|
|
|
{
|
|
|
|
|
$interface_id = strtolower($interface);
|
|
|
|
|
|
|
|
|
|
$fq_class_name = strtolower($fq_class_name);
|
|
|
|
|
|
|
|
|
|
if ($interface_id === 'callable' && $fq_class_name === 'closure') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($interface_id === 'traversable' && $fq_class_name === 'generator') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-26 22:58:49 +01:00
|
|
|
|
if ($interface_id === 'traversable' && $fq_class_name === 'iterator') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-05 22:23:18 +01:00
|
|
|
|
if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[$interface_id])
|
|
|
|
|
|| isset(ClassLikeAnalyzer::SPECIAL_TYPES[$fq_class_name])
|
2018-02-04 00:52:35 +01:00
|
|
|
|
) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-29 06:05:56 +01:00
|
|
|
|
if (isset($this->classlike_aliases[$fq_class_name])) {
|
|
|
|
|
$fq_class_name = $this->classlike_aliases[$fq_class_name];
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
|
|
|
|
|
return isset($class_storage->class_implements[$interface_id]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_interface_name
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2020-02-11 22:39:33 +01:00
|
|
|
|
public function interfaceExists(
|
|
|
|
|
$fq_interface_name,
|
|
|
|
|
CodeLocation $code_location = null,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
?string $calling_fq_class_name = null,
|
|
|
|
|
?string $calling_method_id = null
|
2020-02-11 22:39:33 +01:00
|
|
|
|
) {
|
2019-01-05 22:23:18 +01:00
|
|
|
|
if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[strtolower($fq_interface_name)])) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-11 22:39:33 +01:00
|
|
|
|
return $this->hasFullyQualifiedInterfaceName(
|
|
|
|
|
$fq_interface_name,
|
|
|
|
|
$code_location,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$calling_fq_class_name,
|
|
|
|
|
$calling_method_id
|
2020-02-11 22:39:33 +01:00
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $interface_name
|
|
|
|
|
* @param string $possible_parent
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function interfaceExtends($interface_name, $possible_parent)
|
|
|
|
|
{
|
2018-07-22 02:38:55 +02:00
|
|
|
|
return isset($this->getParentInterfaces($interface_name)[strtolower($possible_parent)]);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_interface_name
|
|
|
|
|
*
|
2018-07-22 02:38:55 +02:00
|
|
|
|
* @return array<string, string> all interfaces extended by $interface_name
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*/
|
|
|
|
|
public function getParentInterfaces($fq_interface_name)
|
|
|
|
|
{
|
|
|
|
|
$fq_interface_name = strtolower($fq_interface_name);
|
|
|
|
|
|
|
|
|
|
$storage = $this->classlike_storage_provider->get($fq_interface_name);
|
|
|
|
|
|
|
|
|
|
return $storage->parent_interfaces;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 04:32:57 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_trait_name
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2019-04-13 21:38:09 +02:00
|
|
|
|
public function traitExists($fq_trait_name, CodeLocation $code_location = null)
|
2018-05-09 04:32:57 +02:00
|
|
|
|
{
|
2019-04-13 21:38:09 +02:00
|
|
|
|
return $this->hasFullyQualifiedTraitName($fq_trait_name, $code_location);
|
2018-05-09 04:32:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
if ($fq_class_name === 'Generator') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-29 06:05:56 +01:00
|
|
|
|
if (isset($this->classlike_aliases[strtolower($fq_class_name)])) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return isset($this->existing_classes[$fq_class_name]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_interface_name
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function interfaceHasCorrectCasing($fq_interface_name)
|
|
|
|
|
{
|
2018-11-29 06:05:56 +01:00
|
|
|
|
if (isset($this->classlike_aliases[strtolower($fq_interface_name)])) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-21 15:29:23 +01:00
|
|
|
|
if (isset($this->classlike_aliases[strtolower($fq_interface_name)])) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return isset($this->existing_interfaces[$fq_interface_name]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_trait_name
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function traitHasCorrectCase($fq_trait_name)
|
|
|
|
|
{
|
2018-11-29 06:05:56 +01:00
|
|
|
|
if (isset($this->classlike_aliases[strtolower($fq_trait_name)])) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
return isset($this->existing_traits[$fq_trait_name]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @param lowercase-string $fq_class_name
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isUserDefined($fq_class_name)
|
|
|
|
|
{
|
|
|
|
|
return $this->classlike_storage_provider->get($fq_class_name)->user_defined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_trait_name
|
|
|
|
|
*
|
|
|
|
|
* @return PhpParser\Node\Stmt\Trait_
|
|
|
|
|
*/
|
|
|
|
|
public function getTraitNode($fq_trait_name)
|
|
|
|
|
{
|
|
|
|
|
$fq_trait_name_lc = strtolower($fq_trait_name);
|
|
|
|
|
|
|
|
|
|
if (isset($this->trait_nodes[$fq_trait_name_lc])) {
|
|
|
|
|
return $this->trait_nodes[$fq_trait_name_lc];
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 04:27:54 +01:00
|
|
|
|
$storage = $this->classlike_storage_provider->get($fq_trait_name);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-03-03 04:27:54 +01:00
|
|
|
|
if (!$storage->location) {
|
|
|
|
|
throw new \UnexpectedValueException('Storage should exist for ' . $fq_trait_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$file_statements = $this->statements_provider->getStatementsForFile($storage->location->file_path);
|
|
|
|
|
|
2020-03-15 04:54:42 +01:00
|
|
|
|
$trait_finder = new \Psalm\Internal\PhpVisitor\TraitFinder($fq_trait_name);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2020-03-09 21:41:40 +01:00
|
|
|
|
$traverser = new \PhpParser\NodeTraverser();
|
|
|
|
|
$traverser->addVisitor(
|
|
|
|
|
$trait_finder
|
|
|
|
|
);
|
2020-03-03 05:07:21 +01:00
|
|
|
|
|
2020-03-09 21:41:40 +01:00
|
|
|
|
$traverser->traverse($file_statements);
|
|
|
|
|
|
|
|
|
|
$trait_node = $trait_finder->getNode();
|
|
|
|
|
|
|
|
|
|
if ($trait_node) {
|
2020-03-12 03:14:21 +01:00
|
|
|
|
$this->trait_nodes[$fq_trait_name_lc] = $trait_node;
|
|
|
|
|
|
2020-03-09 21:41:40 +01:00
|
|
|
|
return $trait_node;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 04:27:54 +01:00
|
|
|
|
throw new \UnexpectedValueException('Could not locate trait statement');
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-29 06:05:56 +01:00
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @param lowercase-string $alias_name
|
2018-11-29 06:05:56 +01:00
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function addClassAlias(string $fq_class_name, string $alias_name)
|
|
|
|
|
{
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$this->classlike_aliases[$alias_name] = $fq_class_name;
|
2018-11-29 06:05:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getUnAliasedName(string $alias_name)
|
|
|
|
|
{
|
2019-07-18 02:33:44 +02:00
|
|
|
|
$alias_name_lc = strtolower($alias_name);
|
2019-08-15 16:17:27 +02:00
|
|
|
|
if ($this->existing_classlikes_lc[$alias_name_lc] ?? false) {
|
2019-07-18 02:33:44 +02:00
|
|
|
|
return $alias_name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->classlike_aliases[$alias_name_lc] ?? $alias_name;
|
2018-11-29 06:05:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
/**
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2019-12-24 02:02:34 +01:00
|
|
|
|
public function consolidateAnalyzedData(Methods $methods, ?Progress $progress, bool $find_unused_code)
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
2019-05-30 16:30:41 +02:00
|
|
|
|
if ($progress === null) {
|
|
|
|
|
$progress = new VoidProgress();
|
2019-04-17 19:15:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
|
$progress->debug('Checking class references' . PHP_EOL);
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
foreach ($this->existing_classlikes_lc as $fq_class_name_lc => $_) {
|
|
|
|
|
try {
|
|
|
|
|
$classlike_storage = $this->classlike_storage_provider->get($fq_class_name_lc);
|
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-10 05:10:42 +02:00
|
|
|
|
if ($classlike_storage->location
|
|
|
|
|
&& $this->config->isInProjectDirs($classlike_storage->location->file_path)
|
|
|
|
|
&& !$classlike_storage->is_trait
|
2018-02-04 00:52:35 +01:00
|
|
|
|
) {
|
2019-12-24 02:02:34 +01:00
|
|
|
|
if ($find_unused_code) {
|
2019-12-02 21:24:01 +01:00
|
|
|
|
if (!$this->file_reference_provider->isClassReferenced($fq_class_name_lc)) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new UnusedClass(
|
|
|
|
|
'Class ' . $classlike_storage->name . ' is never used',
|
|
|
|
|
$classlike_storage->location,
|
|
|
|
|
$classlike_storage->name
|
|
|
|
|
),
|
|
|
|
|
$classlike_storage->suppressed_issues
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$this->checkMethodReferences($classlike_storage, $methods);
|
|
|
|
|
$this->checkPropertyReferences($classlike_storage);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-02 21:24:01 +01:00
|
|
|
|
|
|
|
|
|
$this->findPossibleMethodParamTypes($classlike_storage);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 06:56:54 +02:00
|
|
|
|
/**
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2019-06-02 18:02:32 +02:00
|
|
|
|
public function moveMethods(Methods $methods, Progress $progress = null)
|
2019-06-01 06:56:54 +02:00
|
|
|
|
{
|
|
|
|
|
if ($progress === null) {
|
|
|
|
|
$progress = new VoidProgress();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
2019-06-02 18:02:32 +02:00
|
|
|
|
if (!$codebase->methods_to_move) {
|
2019-06-01 06:56:54 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 06:32:19 +02:00
|
|
|
|
$progress->debug('Refactoring methods ' . PHP_EOL);
|
2019-06-01 06:56:54 +02:00
|
|
|
|
|
|
|
|
|
$code_migrations = [];
|
|
|
|
|
|
2019-06-04 06:32:19 +02:00
|
|
|
|
foreach ($codebase->methods_to_move as $source => $destination) {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$source_parts = explode('::', $source);
|
|
|
|
|
|
2019-06-01 06:56:54 +02:00
|
|
|
|
try {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$source_method_storage = $methods->getStorage(
|
|
|
|
|
new \Psalm\Internal\MethodIdentifier(...$source_parts)
|
|
|
|
|
);
|
2019-06-01 06:56:54 +02:00
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 06:32:19 +02:00
|
|
|
|
list($destination_fq_class_name, $destination_name) = explode('::', $destination);
|
2019-06-01 06:56:54 +02:00
|
|
|
|
|
|
|
|
|
try {
|
2019-06-04 06:32:19 +02:00
|
|
|
|
$classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name);
|
2019-06-01 06:56:54 +02:00
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($classlike_storage->stmt_location
|
|
|
|
|
&& $this->config->isInProjectDirs($classlike_storage->stmt_location->file_path)
|
2019-06-04 06:32:19 +02:00
|
|
|
|
&& $source_method_storage->stmt_location
|
|
|
|
|
&& $source_method_storage->stmt_location->file_path
|
|
|
|
|
&& $source_method_storage->location
|
2019-06-01 06:56:54 +02:00
|
|
|
|
) {
|
|
|
|
|
$new_class_bounds = $classlike_storage->stmt_location->getSnippetBounds();
|
2019-06-04 06:32:19 +02:00
|
|
|
|
$old_method_bounds = $source_method_storage->stmt_location->getSnippetBounds();
|
2019-06-01 06:56:54 +02:00
|
|
|
|
|
2019-06-04 06:32:19 +02:00
|
|
|
|
$old_method_name_bounds = $source_method_storage->location->getSelectionBounds();
|
2019-06-01 06:56:54 +02:00
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add(
|
2019-06-04 06:32:19 +02:00
|
|
|
|
$source_method_storage->stmt_location->file_path,
|
2019-06-01 06:56:54 +02:00
|
|
|
|
[
|
|
|
|
|
new \Psalm\FileManipulation(
|
|
|
|
|
$old_method_name_bounds[0],
|
|
|
|
|
$old_method_name_bounds[1],
|
2019-06-04 06:32:19 +02:00
|
|
|
|
$destination_name
|
2019-07-05 22:24:00 +02:00
|
|
|
|
),
|
2019-06-01 06:56:54 +02:00
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$selection = $classlike_storage->stmt_location->getSnippet();
|
|
|
|
|
|
|
|
|
|
$insert_pos = strrpos($selection, "\n", -1);
|
|
|
|
|
|
|
|
|
|
if (!$insert_pos) {
|
|
|
|
|
$insert_pos = strlen($selection) - 1;
|
|
|
|
|
} else {
|
2019-07-05 22:24:00 +02:00
|
|
|
|
++$insert_pos;
|
2019-06-01 06:56:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$code_migrations[] = new \Psalm\Internal\FileManipulation\CodeMigration(
|
2019-06-04 06:32:19 +02:00
|
|
|
|
$source_method_storage->stmt_location->file_path,
|
2019-06-01 06:56:54 +02:00
|
|
|
|
$old_method_bounds[0],
|
|
|
|
|
$old_method_bounds[1],
|
|
|
|
|
$classlike_storage->stmt_location->file_path,
|
|
|
|
|
$new_class_bounds[0] + $insert_pos
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::addCodeMigrations($code_migrations);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 06:32:19 +02:00
|
|
|
|
/**
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function moveProperties(Properties $properties, Progress $progress = null)
|
|
|
|
|
{
|
|
|
|
|
if ($progress === null) {
|
|
|
|
|
$progress = new VoidProgress();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
|
|
|
|
if (!$codebase->properties_to_move) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$progress->debug('Refacting properties ' . PHP_EOL);
|
|
|
|
|
|
|
|
|
|
$code_migrations = [];
|
|
|
|
|
|
|
|
|
|
foreach ($codebase->properties_to_move as $source => $destination) {
|
|
|
|
|
try {
|
|
|
|
|
$source_property_storage = $properties->getStorage($source);
|
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 17:14:49 +02:00
|
|
|
|
list($source_fq_class_name) = explode('::$', $source);
|
2019-06-04 06:32:19 +02:00
|
|
|
|
list($destination_fq_class_name, $destination_name) = explode('::$', $destination);
|
|
|
|
|
|
|
|
|
|
$source_classlike_storage = $this->classlike_storage_provider->get($source_fq_class_name);
|
|
|
|
|
$destination_classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name);
|
|
|
|
|
|
|
|
|
|
if ($destination_classlike_storage->stmt_location
|
|
|
|
|
&& $this->config->isInProjectDirs($destination_classlike_storage->stmt_location->file_path)
|
|
|
|
|
&& $source_property_storage->stmt_location
|
|
|
|
|
&& $source_property_storage->stmt_location->file_path
|
|
|
|
|
&& $source_property_storage->location
|
|
|
|
|
) {
|
|
|
|
|
if ($source_property_storage->type
|
|
|
|
|
&& $source_property_storage->type_location
|
|
|
|
|
&& $source_property_storage->type_location !== $source_property_storage->signature_type_location
|
|
|
|
|
) {
|
|
|
|
|
$bounds = $source_property_storage->type_location->getSelectionBounds();
|
|
|
|
|
|
|
|
|
|
$replace_type = \Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::fleshOutType(
|
|
|
|
|
$codebase,
|
|
|
|
|
$source_property_storage->type,
|
|
|
|
|
$source_classlike_storage->name,
|
|
|
|
|
$source_classlike_storage->name,
|
|
|
|
|
$source_classlike_storage->parent_class
|
|
|
|
|
);
|
|
|
|
|
|
2019-06-10 23:09:34 +02:00
|
|
|
|
$this->airliftClassDefinedDocblockType(
|
2019-06-04 06:32:19 +02:00
|
|
|
|
$replace_type,
|
|
|
|
|
$destination_fq_class_name,
|
|
|
|
|
$source_property_storage->stmt_location->file_path,
|
|
|
|
|
$bounds[0],
|
|
|
|
|
$bounds[1]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$new_class_bounds = $destination_classlike_storage->stmt_location->getSnippetBounds();
|
|
|
|
|
$old_property_bounds = $source_property_storage->stmt_location->getSnippetBounds();
|
|
|
|
|
|
|
|
|
|
$old_property_name_bounds = $source_property_storage->location->getSelectionBounds();
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add(
|
|
|
|
|
$source_property_storage->stmt_location->file_path,
|
|
|
|
|
[
|
|
|
|
|
new \Psalm\FileManipulation(
|
|
|
|
|
$old_property_name_bounds[0],
|
|
|
|
|
$old_property_name_bounds[1],
|
|
|
|
|
'$' . $destination_name
|
2019-07-05 22:24:00 +02:00
|
|
|
|
),
|
2019-06-04 06:32:19 +02:00
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$selection = $destination_classlike_storage->stmt_location->getSnippet();
|
|
|
|
|
|
|
|
|
|
$insert_pos = strrpos($selection, "\n", -1);
|
|
|
|
|
|
|
|
|
|
if (!$insert_pos) {
|
|
|
|
|
$insert_pos = strlen($selection) - 1;
|
|
|
|
|
} else {
|
2019-07-05 22:24:00 +02:00
|
|
|
|
++$insert_pos;
|
2019-06-04 06:32:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$code_migrations[] = new \Psalm\Internal\FileManipulation\CodeMigration(
|
|
|
|
|
$source_property_storage->stmt_location->file_path,
|
|
|
|
|
$old_property_bounds[0],
|
|
|
|
|
$old_property_bounds[1],
|
|
|
|
|
$destination_classlike_storage->stmt_location->file_path,
|
|
|
|
|
$new_class_bounds[0] + $insert_pos
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::addCodeMigrations($code_migrations);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 17:14:49 +02:00
|
|
|
|
/**
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2019-06-04 22:36:32 +02:00
|
|
|
|
public function moveClassConstants(Progress $progress = null)
|
2019-06-04 17:14:49 +02:00
|
|
|
|
{
|
|
|
|
|
if ($progress === null) {
|
|
|
|
|
$progress = new VoidProgress();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
|
|
|
|
if (!$codebase->class_constants_to_move) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$progress->debug('Refacting constants ' . PHP_EOL);
|
|
|
|
|
|
|
|
|
|
$code_migrations = [];
|
|
|
|
|
|
|
|
|
|
foreach ($codebase->class_constants_to_move as $source => $destination) {
|
|
|
|
|
list($source_fq_class_name, $source_const_name) = explode('::', $source);
|
|
|
|
|
list($destination_fq_class_name, $destination_name) = explode('::', $destination);
|
|
|
|
|
|
|
|
|
|
$source_classlike_storage = $this->classlike_storage_provider->get($source_fq_class_name);
|
|
|
|
|
$destination_classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name);
|
|
|
|
|
|
|
|
|
|
$source_const_stmt_location = $source_classlike_storage->class_constant_stmt_locations[$source_const_name];
|
|
|
|
|
$source_const_location = $source_classlike_storage->class_constant_locations[$source_const_name];
|
|
|
|
|
|
|
|
|
|
if ($destination_classlike_storage->stmt_location
|
|
|
|
|
&& $this->config->isInProjectDirs($destination_classlike_storage->stmt_location->file_path)
|
|
|
|
|
&& $source_const_stmt_location->file_path
|
|
|
|
|
) {
|
|
|
|
|
$new_class_bounds = $destination_classlike_storage->stmt_location->getSnippetBounds();
|
|
|
|
|
$old_const_bounds = $source_const_stmt_location->getSnippetBounds();
|
|
|
|
|
|
|
|
|
|
$old_const_name_bounds = $source_const_location->getSelectionBounds();
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add(
|
|
|
|
|
$source_const_stmt_location->file_path,
|
|
|
|
|
[
|
|
|
|
|
new \Psalm\FileManipulation(
|
|
|
|
|
$old_const_name_bounds[0],
|
|
|
|
|
$old_const_name_bounds[1],
|
|
|
|
|
$destination_name
|
2019-07-05 22:24:00 +02:00
|
|
|
|
),
|
2019-06-04 17:14:49 +02:00
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$selection = $destination_classlike_storage->stmt_location->getSnippet();
|
|
|
|
|
|
|
|
|
|
$insert_pos = strrpos($selection, "\n", -1);
|
|
|
|
|
|
|
|
|
|
if (!$insert_pos) {
|
|
|
|
|
$insert_pos = strlen($selection) - 1;
|
|
|
|
|
} else {
|
2019-07-05 22:24:00 +02:00
|
|
|
|
++$insert_pos;
|
2019-06-04 17:14:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$code_migrations[] = new \Psalm\Internal\FileManipulation\CodeMigration(
|
|
|
|
|
$source_const_stmt_location->file_path,
|
|
|
|
|
$old_const_bounds[0],
|
|
|
|
|
$old_const_bounds[1],
|
|
|
|
|
$destination_classlike_storage->stmt_location->file_path,
|
|
|
|
|
$new_class_bounds[0] + $insert_pos
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::addCodeMigrations($code_migrations);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param lowercase-string|null $calling_method_id
|
|
|
|
|
*/
|
2019-06-04 22:36:32 +02:00
|
|
|
|
public function handleClassLikeReferenceInMigration(
|
|
|
|
|
\Psalm\Codebase $codebase,
|
|
|
|
|
\Psalm\StatementsSource $source,
|
|
|
|
|
PhpParser\Node $class_name_node,
|
|
|
|
|
string $fq_class_name,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
?string $calling_method_id,
|
2019-11-17 01:59:08 +01:00
|
|
|
|
bool $force_change = false,
|
|
|
|
|
bool $was_self = false
|
2019-06-04 22:36:32 +02:00
|
|
|
|
) : bool {
|
|
|
|
|
$calling_fq_class_name = $source->getFQCLN();
|
|
|
|
|
|
|
|
|
|
// if we're inside a moved class static method
|
|
|
|
|
if ($codebase->methods_to_move
|
|
|
|
|
&& $calling_fq_class_name
|
2020-03-26 17:35:27 +01:00
|
|
|
|
&& $calling_method_id
|
|
|
|
|
&& isset($codebase->methods_to_move[$calling_method_id])
|
2019-06-04 22:36:32 +02:00
|
|
|
|
) {
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$destination_class = explode('::', $codebase->methods_to_move[$calling_method_id])[0];
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
|
|
|
|
$intended_fq_class_name = strtolower($calling_fq_class_name) === strtolower($fq_class_name)
|
|
|
|
|
&& isset($codebase->classes_to_move[strtolower($calling_fq_class_name)])
|
|
|
|
|
? $destination_class
|
|
|
|
|
: $fq_class_name;
|
|
|
|
|
|
|
|
|
|
$this->airliftClassLikeReference(
|
|
|
|
|
$intended_fq_class_name,
|
|
|
|
|
$destination_class,
|
|
|
|
|
$source->getFilePath(),
|
|
|
|
|
(int) $class_name_node->getAttribute('startFilePos'),
|
2019-06-14 21:54:15 +02:00
|
|
|
|
(int) $class_name_node->getAttribute('endFilePos') + 1,
|
2019-11-17 01:59:08 +01:00
|
|
|
|
$class_name_node instanceof PhpParser\Node\Scalar\MagicConst\Class_,
|
|
|
|
|
$was_self
|
2019-06-04 22:36:32 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-10 23:09:34 +02:00
|
|
|
|
// if we're outside a moved class, but we're changing all references to a class
|
|
|
|
|
if (isset($codebase->class_transforms[strtolower($fq_class_name)])) {
|
|
|
|
|
$new_fq_class_name = $codebase->class_transforms[strtolower($fq_class_name)];
|
|
|
|
|
$file_manipulations = [];
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
|
|
|
|
if ($class_name_node instanceof PhpParser\Node\Identifier) {
|
2019-06-10 23:09:34 +02:00
|
|
|
|
$destination_parts = explode('\\', $new_fq_class_name);
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
|
|
|
|
$destination_class_name = array_pop($destination_parts);
|
|
|
|
|
$file_manipulations = [];
|
|
|
|
|
|
|
|
|
|
$file_manipulations[] = new \Psalm\FileManipulation(
|
|
|
|
|
(int) $class_name_node->getAttribute('startFilePos'),
|
|
|
|
|
(int) $class_name_node->getAttribute('endFilePos') + 1,
|
|
|
|
|
$destination_class_name
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add($source->getFilePath(), $file_manipulations);
|
|
|
|
|
|
2019-06-10 23:09:34 +02:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-06-06 04:13:33 +02:00
|
|
|
|
|
|
|
|
|
$uses_flipped = $source->getAliasedClassesFlipped();
|
|
|
|
|
$uses_flipped_replaceable = $source->getAliasedClassesFlippedReplaceable();
|
|
|
|
|
|
2019-06-07 00:46:40 +02:00
|
|
|
|
$old_fq_class_name = strtolower($fq_class_name);
|
|
|
|
|
|
2019-06-10 23:09:34 +02:00
|
|
|
|
$migrated_source_fqcln = $calling_fq_class_name;
|
|
|
|
|
|
|
|
|
|
if ($calling_fq_class_name
|
|
|
|
|
&& isset($codebase->class_transforms[strtolower($calling_fq_class_name)])
|
|
|
|
|
) {
|
|
|
|
|
$migrated_source_fqcln = $codebase->class_transforms[strtolower($calling_fq_class_name)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$source_namespace = $source->getNamespace();
|
|
|
|
|
|
|
|
|
|
if ($migrated_source_fqcln && $calling_fq_class_name !== $migrated_source_fqcln) {
|
|
|
|
|
$new_source_parts = explode('\\', $migrated_source_fqcln);
|
|
|
|
|
array_pop($new_source_parts);
|
|
|
|
|
$source_namespace = implode('\\', $new_source_parts);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-07 00:46:40 +02:00
|
|
|
|
if (isset($uses_flipped_replaceable[$old_fq_class_name])) {
|
|
|
|
|
$alias = $uses_flipped_replaceable[$old_fq_class_name];
|
2019-06-11 16:33:52 +02:00
|
|
|
|
unset($uses_flipped[$old_fq_class_name]);
|
2019-06-07 00:46:40 +02:00
|
|
|
|
$old_class_name_parts = explode('\\', $old_fq_class_name);
|
|
|
|
|
$old_class_name = end($old_class_name_parts);
|
|
|
|
|
if (strtolower($old_class_name) === strtolower($alias)) {
|
|
|
|
|
$new_class_name_parts = explode('\\', $new_fq_class_name);
|
|
|
|
|
$new_class_name = end($new_class_name_parts);
|
|
|
|
|
$uses_flipped[strtolower($new_fq_class_name)] = $new_class_name;
|
|
|
|
|
} else {
|
|
|
|
|
$uses_flipped[strtolower($new_fq_class_name)] = $alias;
|
|
|
|
|
}
|
2019-06-06 04:13:33 +02:00
|
|
|
|
}
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
2019-06-06 04:13:33 +02:00
|
|
|
|
$file_manipulations[] = new \Psalm\FileManipulation(
|
|
|
|
|
(int) $class_name_node->getAttribute('startFilePos'),
|
|
|
|
|
(int) $class_name_node->getAttribute('endFilePos') + 1,
|
|
|
|
|
Type::getStringFromFQCLN(
|
|
|
|
|
$new_fq_class_name,
|
2019-06-10 23:09:34 +02:00
|
|
|
|
$source_namespace,
|
2019-06-06 04:13:33 +02:00
|
|
|
|
$uses_flipped,
|
2019-11-17 01:59:08 +01:00
|
|
|
|
$migrated_source_fqcln,
|
|
|
|
|
$was_self
|
2019-06-06 04:13:33 +02:00
|
|
|
|
)
|
2019-06-14 21:54:15 +02:00
|
|
|
|
. ($class_name_node instanceof PhpParser\Node\Scalar\MagicConst\Class_ ? '::class' : '')
|
2019-06-06 04:13:33 +02:00
|
|
|
|
);
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
2019-06-06 04:13:33 +02:00
|
|
|
|
FileManipulationBuffer::add($source->getFilePath(), $file_manipulations);
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
2019-06-06 04:13:33 +02:00
|
|
|
|
return true;
|
2019-06-04 22:36:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-10 23:09:34 +02:00
|
|
|
|
// if we're inside a moved class (could be a method, could be a property/class const default)
|
|
|
|
|
if ($codebase->classes_to_move
|
|
|
|
|
&& $calling_fq_class_name
|
|
|
|
|
&& isset($codebase->classes_to_move[strtolower($calling_fq_class_name)])
|
|
|
|
|
) {
|
|
|
|
|
$destination_class = $codebase->classes_to_move[strtolower($calling_fq_class_name)];
|
|
|
|
|
|
|
|
|
|
if ($class_name_node instanceof PhpParser\Node\Identifier) {
|
|
|
|
|
$destination_parts = explode('\\', $destination_class);
|
|
|
|
|
|
|
|
|
|
$destination_class_name = array_pop($destination_parts);
|
|
|
|
|
$file_manipulations = [];
|
|
|
|
|
|
|
|
|
|
$file_manipulations[] = new \Psalm\FileManipulation(
|
|
|
|
|
(int) $class_name_node->getAttribute('startFilePos'),
|
|
|
|
|
(int) $class_name_node->getAttribute('endFilePos') + 1,
|
|
|
|
|
$destination_class_name
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add($source->getFilePath(), $file_manipulations);
|
|
|
|
|
} else {
|
|
|
|
|
$this->airliftClassLikeReference(
|
|
|
|
|
strtolower($calling_fq_class_name) === strtolower($fq_class_name)
|
|
|
|
|
? $destination_class
|
|
|
|
|
: $fq_class_name,
|
|
|
|
|
$destination_class,
|
|
|
|
|
$source->getFilePath(),
|
|
|
|
|
(int) $class_name_node->getAttribute('startFilePos'),
|
2019-06-14 21:54:15 +02:00
|
|
|
|
(int) $class_name_node->getAttribute('endFilePos') + 1,
|
|
|
|
|
$class_name_node instanceof PhpParser\Node\Scalar\MagicConst\Class_
|
2019-06-10 23:09:34 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-14 21:54:15 +02:00
|
|
|
|
if ($force_change) {
|
|
|
|
|
if ($calling_fq_class_name) {
|
|
|
|
|
$this->airliftClassLikeReference(
|
|
|
|
|
$fq_class_name,
|
|
|
|
|
$calling_fq_class_name,
|
|
|
|
|
$source->getFilePath(),
|
|
|
|
|
(int) $class_name_node->getAttribute('startFilePos'),
|
|
|
|
|
(int) $class_name_node->getAttribute('endFilePos') + 1
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$file_manipulations = [];
|
|
|
|
|
|
|
|
|
|
$file_manipulations[] = new \Psalm\FileManipulation(
|
|
|
|
|
(int) $class_name_node->getAttribute('startFilePos'),
|
|
|
|
|
(int) $class_name_node->getAttribute('endFilePos') + 1,
|
|
|
|
|
Type::getStringFromFQCLN(
|
|
|
|
|
$fq_class_name,
|
|
|
|
|
$source->getNamespace(),
|
|
|
|
|
$source->getAliasedClassesFlipped(),
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add($source->getFilePath(), $file_manipulations);
|
|
|
|
|
}
|
2019-06-14 20:41:36 +02:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 22:36:32 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param lowercase-string|null $calling_method_id
|
|
|
|
|
*/
|
2019-06-04 22:36:32 +02:00
|
|
|
|
public function handleDocblockTypeInMigration(
|
|
|
|
|
\Psalm\Codebase $codebase,
|
|
|
|
|
\Psalm\StatementsSource $source,
|
|
|
|
|
Type\Union $type,
|
|
|
|
|
CodeLocation $type_location,
|
2020-03-26 17:35:27 +01:00
|
|
|
|
?string $calling_method_id
|
2019-06-04 22:36:32 +02:00
|
|
|
|
) : void {
|
|
|
|
|
$calling_fq_class_name = $source->getFQCLN();
|
|
|
|
|
|
|
|
|
|
$moved_type = false;
|
|
|
|
|
|
|
|
|
|
// if we're inside a moved class static method
|
|
|
|
|
if ($codebase->methods_to_move
|
|
|
|
|
&& $calling_fq_class_name
|
2020-03-26 17:35:27 +01:00
|
|
|
|
&& $calling_method_id
|
|
|
|
|
&& isset($codebase->methods_to_move[$calling_method_id])
|
2019-06-04 22:36:32 +02:00
|
|
|
|
) {
|
|
|
|
|
$bounds = $type_location->getSelectionBounds();
|
|
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
|
$destination_class = explode('::', $codebase->methods_to_move[$calling_method_id])[0];
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
2019-06-10 23:09:34 +02:00
|
|
|
|
$this->airliftClassDefinedDocblockType(
|
2019-06-04 22:36:32 +02:00
|
|
|
|
$type,
|
|
|
|
|
$destination_class,
|
|
|
|
|
$source->getFilePath(),
|
|
|
|
|
$bounds[0],
|
|
|
|
|
$bounds[1]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$moved_type = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we're outside a moved class, but we're changing all references to a class
|
2019-06-11 16:33:52 +02:00
|
|
|
|
if (!$moved_type && $codebase->class_transforms) {
|
|
|
|
|
$uses_flipped = $source->getAliasedClassesFlipped();
|
|
|
|
|
$uses_flipped_replaceable = $source->getAliasedClassesFlippedReplaceable();
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
|
$migrated_source_fqcln = $calling_fq_class_name;
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
|
if ($calling_fq_class_name
|
|
|
|
|
&& isset($codebase->class_transforms[strtolower($calling_fq_class_name)])
|
|
|
|
|
) {
|
|
|
|
|
$migrated_source_fqcln = $codebase->class_transforms[strtolower($calling_fq_class_name)];
|
|
|
|
|
}
|
2019-06-04 22:36:32 +02:00
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
|
$source_namespace = $source->getNamespace();
|
2019-06-06 04:13:33 +02:00
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
|
if ($migrated_source_fqcln && $calling_fq_class_name !== $migrated_source_fqcln) {
|
|
|
|
|
$new_source_parts = explode('\\', $migrated_source_fqcln);
|
|
|
|
|
array_pop($new_source_parts);
|
|
|
|
|
$source_namespace = implode('\\', $new_source_parts);
|
|
|
|
|
}
|
2019-06-10 23:09:34 +02:00
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
|
foreach ($codebase->class_transforms as $old_fq_class_name => $new_fq_class_name) {
|
|
|
|
|
if (isset($uses_flipped_replaceable[$old_fq_class_name])) {
|
|
|
|
|
$alias = $uses_flipped_replaceable[$old_fq_class_name];
|
|
|
|
|
unset($uses_flipped[$old_fq_class_name]);
|
|
|
|
|
$old_class_name_parts = explode('\\', $old_fq_class_name);
|
|
|
|
|
$old_class_name = end($old_class_name_parts);
|
|
|
|
|
if (strtolower($old_class_name) === strtolower($alias)) {
|
|
|
|
|
$new_class_name_parts = explode('\\', $new_fq_class_name);
|
|
|
|
|
$new_class_name = end($new_class_name_parts);
|
|
|
|
|
$uses_flipped[strtolower($new_fq_class_name)] = $new_class_name;
|
|
|
|
|
} else {
|
|
|
|
|
$uses_flipped[strtolower($new_fq_class_name)] = $alias;
|
2019-06-10 23:09:34 +02:00
|
|
|
|
}
|
2019-06-11 16:33:52 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($codebase->class_transforms as $old_fq_class_name => $new_fq_class_name) {
|
|
|
|
|
if ($type->containsClassLike($old_fq_class_name)) {
|
|
|
|
|
$type = clone $type;
|
2019-06-10 23:09:34 +02:00
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
|
$type->replaceClassLike($old_fq_class_name, $new_fq_class_name);
|
2019-06-10 23:09:34 +02:00
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
|
$bounds = $type_location->getSelectionBounds();
|
2019-06-10 23:09:34 +02:00
|
|
|
|
|
2019-06-11 16:33:52 +02:00
|
|
|
|
$file_manipulations = [];
|
2019-06-06 04:13:33 +02:00
|
|
|
|
|
2019-06-04 22:36:32 +02:00
|
|
|
|
$file_manipulations[] = new \Psalm\FileManipulation(
|
|
|
|
|
$bounds[0],
|
|
|
|
|
$bounds[1],
|
|
|
|
|
$type->toNamespacedString(
|
2019-06-10 23:09:34 +02:00
|
|
|
|
$source_namespace,
|
2019-06-06 04:13:33 +02:00
|
|
|
|
$uses_flipped,
|
2019-06-10 23:09:34 +02:00
|
|
|
|
$migrated_source_fqcln,
|
2019-06-04 22:36:32 +02:00
|
|
|
|
false
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add(
|
|
|
|
|
$source->getFilePath(),
|
|
|
|
|
$file_manipulations
|
|
|
|
|
);
|
2019-06-10 23:09:34 +02:00
|
|
|
|
|
|
|
|
|
$moved_type = true;
|
2019-06-04 22:36:32 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-10 23:09:34 +02:00
|
|
|
|
|
|
|
|
|
// if we're inside a moved class (could be a method, could be a property/class const default)
|
|
|
|
|
if (!$moved_type
|
|
|
|
|
&& $codebase->classes_to_move
|
|
|
|
|
&& $calling_fq_class_name
|
|
|
|
|
&& isset($codebase->classes_to_move[strtolower($calling_fq_class_name)])
|
|
|
|
|
) {
|
|
|
|
|
$bounds = $type_location->getSelectionBounds();
|
|
|
|
|
|
|
|
|
|
$destination_class = $codebase->classes_to_move[strtolower($calling_fq_class_name)];
|
|
|
|
|
|
|
|
|
|
if ($type->containsClassLike(strtolower($calling_fq_class_name))) {
|
|
|
|
|
$type = clone $type;
|
|
|
|
|
|
|
|
|
|
$type->replaceClassLike(strtolower($calling_fq_class_name), $destination_class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->airliftClassDefinedDocblockType(
|
|
|
|
|
$type,
|
|
|
|
|
$destination_class,
|
|
|
|
|
$source->getFilePath(),
|
|
|
|
|
$bounds[0],
|
|
|
|
|
$bounds[1]
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-06-04 22:36:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 16:07:38 +02:00
|
|
|
|
public function airliftClassLikeReference(
|
|
|
|
|
string $fq_class_name,
|
|
|
|
|
string $destination_fq_class_name,
|
|
|
|
|
string $source_file_path,
|
|
|
|
|
int $source_start,
|
2019-06-14 21:54:15 +02:00
|
|
|
|
int $source_end,
|
2019-11-17 01:59:08 +01:00
|
|
|
|
bool $add_class_constant = false,
|
|
|
|
|
bool $allow_self = false
|
2019-06-01 16:07:38 +02:00
|
|
|
|
) : void {
|
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
|
|
|
|
$destination_class_storage = $codebase->classlike_storage_provider->get($destination_fq_class_name);
|
|
|
|
|
|
|
|
|
|
if (!$destination_class_storage->aliases) {
|
|
|
|
|
throw new \UnexpectedValueException('Aliases should not be null');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$file_manipulations = [];
|
|
|
|
|
|
|
|
|
|
$file_manipulations[] = new \Psalm\FileManipulation(
|
|
|
|
|
$source_start,
|
|
|
|
|
$source_end,
|
|
|
|
|
Type::getStringFromFQCLN(
|
|
|
|
|
$fq_class_name,
|
|
|
|
|
$destination_class_storage->aliases->namespace,
|
|
|
|
|
$destination_class_storage->aliases->uses_flipped,
|
2019-11-17 01:59:08 +01:00
|
|
|
|
$destination_class_storage->name,
|
|
|
|
|
$allow_self
|
2019-06-14 21:54:15 +02:00
|
|
|
|
) . ($add_class_constant ? '::class' : '')
|
2019-06-01 16:07:38 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add(
|
|
|
|
|
$source_file_path,
|
|
|
|
|
$file_manipulations
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-10 23:09:34 +02:00
|
|
|
|
public function airliftClassDefinedDocblockType(
|
2019-06-01 18:25:57 +02:00
|
|
|
|
Type\Union $type,
|
|
|
|
|
string $destination_fq_class_name,
|
|
|
|
|
string $source_file_path,
|
|
|
|
|
int $source_start,
|
|
|
|
|
int $source_end
|
|
|
|
|
) : void {
|
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
|
|
|
|
$destination_class_storage = $codebase->classlike_storage_provider->get($destination_fq_class_name);
|
|
|
|
|
|
|
|
|
|
if (!$destination_class_storage->aliases) {
|
|
|
|
|
throw new \UnexpectedValueException('Aliases should not be null');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$file_manipulations = [];
|
|
|
|
|
|
|
|
|
|
$file_manipulations[] = new \Psalm\FileManipulation(
|
|
|
|
|
$source_start,
|
|
|
|
|
$source_end,
|
|
|
|
|
$type->toNamespacedString(
|
|
|
|
|
$destination_class_storage->aliases->namespace,
|
|
|
|
|
$destination_class_storage->aliases->uses_flipped,
|
|
|
|
|
$destination_class_storage->name,
|
|
|
|
|
false
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FileManipulationBuffer::add(
|
|
|
|
|
$source_file_path,
|
|
|
|
|
$file_manipulations
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-09 00:18:34 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param string $class_name
|
|
|
|
|
* @param mixed $visibility
|
|
|
|
|
*
|
|
|
|
|
* @return array<string,Type\Union>
|
|
|
|
|
*/
|
|
|
|
|
public function getConstantsForClass($class_name, $visibility)
|
|
|
|
|
{
|
|
|
|
|
$class_name = strtolower($class_name);
|
|
|
|
|
|
|
|
|
|
$storage = $this->classlike_storage_provider->get($class_name);
|
|
|
|
|
|
|
|
|
|
if ($visibility === ReflectionProperty::IS_PUBLIC) {
|
|
|
|
|
return $storage->public_class_constants;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($visibility === ReflectionProperty::IS_PROTECTED) {
|
|
|
|
|
return array_merge(
|
|
|
|
|
$storage->public_class_constants,
|
|
|
|
|
$storage->protected_class_constants
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($visibility === ReflectionProperty::IS_PRIVATE) {
|
|
|
|
|
return array_merge(
|
|
|
|
|
$storage->public_class_constants,
|
|
|
|
|
$storage->protected_class_constants,
|
|
|
|
|
$storage->private_class_constants
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new \InvalidArgumentException('Must specify $visibility');
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-14 20:26:31 +02:00
|
|
|
|
public function getConstantForClass(
|
|
|
|
|
string $class_name,
|
|
|
|
|
string $constant_name,
|
|
|
|
|
int $visibility,
|
2019-12-10 22:16:44 +01:00
|
|
|
|
?\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null,
|
|
|
|
|
array $visited_constant_ids = []
|
2019-09-14 20:26:31 +02:00
|
|
|
|
) : ?Type\Union {
|
|
|
|
|
$class_name = strtolower($class_name);
|
|
|
|
|
$storage = $this->classlike_storage_provider->get($class_name);
|
|
|
|
|
|
|
|
|
|
if ($visibility === ReflectionProperty::IS_PUBLIC) {
|
|
|
|
|
$type_candidates = $storage->public_class_constants;
|
|
|
|
|
|
|
|
|
|
$fallbacks = $storage->public_class_constant_nodes;
|
|
|
|
|
} elseif ($visibility === ReflectionProperty::IS_PROTECTED) {
|
|
|
|
|
$type_candidates = array_merge(
|
|
|
|
|
$storage->public_class_constants,
|
|
|
|
|
$storage->protected_class_constants
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$fallbacks = array_merge(
|
|
|
|
|
$storage->public_class_constant_nodes,
|
|
|
|
|
$storage->protected_class_constant_nodes
|
|
|
|
|
);
|
|
|
|
|
} elseif ($visibility === ReflectionProperty::IS_PRIVATE) {
|
|
|
|
|
$type_candidates = array_merge(
|
|
|
|
|
$storage->public_class_constants,
|
|
|
|
|
$storage->protected_class_constants,
|
|
|
|
|
$storage->private_class_constants
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$fallbacks = array_merge(
|
|
|
|
|
$storage->public_class_constant_nodes,
|
|
|
|
|
$storage->protected_class_constant_nodes,
|
|
|
|
|
$storage->private_class_constant_nodes
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
throw new \InvalidArgumentException('Must specify $visibility');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isset($fallbacks[$constant_name])) {
|
2019-12-10 22:16:44 +01:00
|
|
|
|
return new Type\Union([
|
|
|
|
|
$this->resolveConstantType(
|
|
|
|
|
$fallbacks[$constant_name],
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids
|
|
|
|
|
)
|
|
|
|
|
]);
|
2019-09-14 20:26:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-18 18:39:00 +02:00
|
|
|
|
if (isset($type_candidates[$constant_name])) {
|
|
|
|
|
return $type_candidates[$constant_name];
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-14 20:26:31 +02:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string|int|float|bool|null $value
|
|
|
|
|
*/
|
|
|
|
|
private static function getLiteralTypeFromScalarValue($value) : Type\Atomic
|
|
|
|
|
{
|
|
|
|
|
if (\is_string($value)) {
|
|
|
|
|
return new Type\Atomic\TLiteralString($value);
|
|
|
|
|
} elseif (\is_int($value)) {
|
|
|
|
|
return new Type\Atomic\TLiteralInt($value);
|
|
|
|
|
} elseif (\is_float($value)) {
|
|
|
|
|
return new Type\Atomic\TLiteralFloat($value);
|
|
|
|
|
} elseif ($value === false) {
|
|
|
|
|
return new Type\Atomic\TFalse;
|
|
|
|
|
} elseif ($value === true) {
|
|
|
|
|
return new Type\Atomic\TTrue;
|
|
|
|
|
} else {
|
|
|
|
|
return new Type\Atomic\TNull;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveConstantType(
|
|
|
|
|
\Psalm\Internal\Scanner\UnresolvedConstantComponent $c,
|
2019-12-10 22:16:44 +01:00
|
|
|
|
\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null,
|
|
|
|
|
array $visited_constant_ids = []
|
2019-09-14 20:26:31 +02:00
|
|
|
|
) : Type\Atomic {
|
2019-12-10 22:16:44 +01:00
|
|
|
|
$c_id = \spl_object_id($c);
|
|
|
|
|
|
|
|
|
|
if (isset($visited_constant_ids[$c_id])) {
|
|
|
|
|
throw new \Psalm\Exception\CircularReferenceException('Found a circular reference');
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-14 20:26:31 +02:00
|
|
|
|
if ($c instanceof UnresolvedConstant\ScalarValue) {
|
|
|
|
|
return self::getLiteralTypeFromScalarValue($c->value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($c instanceof UnresolvedConstant\UnresolvedBinaryOp) {
|
2019-12-10 22:16:44 +01:00
|
|
|
|
$left = $this->resolveConstantType(
|
|
|
|
|
$c->left,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
);
|
|
|
|
|
$right = $this->resolveConstantType(
|
|
|
|
|
$c->right,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
);
|
2019-09-14 20:26:31 +02:00
|
|
|
|
|
|
|
|
|
if ($left instanceof Type\Atomic\TMixed || $right instanceof Type\Atomic\TMixed) {
|
|
|
|
|
return new Type\Atomic\TMixed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($c instanceof UnresolvedConstant\UnresolvedConcatOp) {
|
|
|
|
|
if ($left instanceof Type\Atomic\TLiteralString && $right instanceof Type\Atomic\TLiteralString) {
|
|
|
|
|
return new Type\Atomic\TLiteralString($left->value . $right->value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Type\Atomic\TMixed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($c instanceof UnresolvedConstant\UnresolvedAdditionOp
|
|
|
|
|
|| $c instanceof UnresolvedConstant\UnresolvedSubtractionOp
|
|
|
|
|
|| $c instanceof UnresolvedConstant\UnresolvedDivisionOp
|
|
|
|
|
|| $c instanceof UnresolvedConstant\UnresolvedMultiplicationOp
|
2020-03-09 05:54:26 +01:00
|
|
|
|
|| $c instanceof UnresolvedConstant\UnresolvedBitwiseOr
|
2019-09-14 20:26:31 +02:00
|
|
|
|
) {
|
|
|
|
|
if (($left instanceof Type\Atomic\TLiteralFloat || $left instanceof Type\Atomic\TLiteralInt)
|
|
|
|
|
&& ($right instanceof Type\Atomic\TLiteralFloat || $right instanceof Type\Atomic\TLiteralInt)
|
|
|
|
|
) {
|
|
|
|
|
if ($c instanceof UnresolvedConstant\UnresolvedAdditionOp) {
|
|
|
|
|
return self::getLiteralTypeFromScalarValue($left->value + $right->value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($c instanceof UnresolvedConstant\UnresolvedSubtractionOp) {
|
|
|
|
|
return self::getLiteralTypeFromScalarValue($left->value - $right->value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($c instanceof UnresolvedConstant\UnresolvedDivisionOp) {
|
|
|
|
|
return self::getLiteralTypeFromScalarValue($left->value / $right->value);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-09 05:54:26 +01:00
|
|
|
|
if ($c instanceof UnresolvedConstant\UnresolvedBitwiseOr) {
|
2019-09-14 20:26:31 +02:00
|
|
|
|
return self::getLiteralTypeFromScalarValue($left->value | $right->value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self::getLiteralTypeFromScalarValue($left->value * $right->value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Type\Atomic\TMixed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Type\Atomic\TMixed;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-17 17:29:41 +02:00
|
|
|
|
if ($c instanceof UnresolvedConstant\UnresolvedTernary) {
|
2019-12-10 22:16:44 +01:00
|
|
|
|
$cond = $this->resolveConstantType(
|
|
|
|
|
$c->cond,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
);
|
|
|
|
|
$if = $c->if ? $this->resolveConstantType(
|
|
|
|
|
$c->if,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
) : null;
|
|
|
|
|
$else = $this->resolveConstantType(
|
|
|
|
|
$c->else,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
);
|
2019-09-17 17:29:41 +02:00
|
|
|
|
|
|
|
|
|
if ($cond instanceof Type\Atomic\TLiteralFloat
|
|
|
|
|
|| $cond instanceof Type\Atomic\TLiteralInt
|
|
|
|
|
|| $cond instanceof Type\Atomic\TLiteralString
|
|
|
|
|
) {
|
|
|
|
|
if ($cond->value) {
|
|
|
|
|
return $if ? $if : $cond;
|
|
|
|
|
}
|
|
|
|
|
} elseif ($cond instanceof Type\Atomic\TFalse || $cond instanceof Type\Atomic\TNull) {
|
|
|
|
|
return $else;
|
|
|
|
|
} elseif ($cond instanceof Type\Atomic\TTrue) {
|
|
|
|
|
return $if ? $if : $cond;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-14 20:26:31 +02:00
|
|
|
|
if ($c instanceof UnresolvedConstant\ArrayValue) {
|
|
|
|
|
$properties = [];
|
|
|
|
|
|
2019-09-27 15:05:56 +02:00
|
|
|
|
if (!$c->entries) {
|
|
|
|
|
return new Type\Atomic\TArray([Type::getEmpty(), Type::getEmpty()]);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-15 17:54:01 +01:00
|
|
|
|
$is_list = true;
|
|
|
|
|
|
2019-09-14 20:26:31 +02:00
|
|
|
|
foreach ($c->entries as $i => $entry) {
|
|
|
|
|
if ($entry->key) {
|
2019-12-10 22:16:44 +01:00
|
|
|
|
$key_type = $this->resolveConstantType(
|
|
|
|
|
$entry->key,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
);
|
2020-01-15 17:54:01 +01:00
|
|
|
|
|
|
|
|
|
if (!$key_type instanceof Type\Atomic\TLiteralInt
|
|
|
|
|
|| $key_type->value !== $i
|
|
|
|
|
) {
|
|
|
|
|
$is_list = false;
|
|
|
|
|
}
|
2019-09-14 20:26:31 +02:00
|
|
|
|
} else {
|
|
|
|
|
$key_type = new Type\Atomic\TLiteralInt($i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($key_type instanceof Type\Atomic\TLiteralInt
|
|
|
|
|
|| $key_type instanceof Type\Atomic\TLiteralString
|
|
|
|
|
) {
|
|
|
|
|
$key_value = $key_type->value;
|
|
|
|
|
} else {
|
|
|
|
|
return new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-10 22:16:44 +01:00
|
|
|
|
$value_type = new Type\Union([$this->resolveConstantType(
|
|
|
|
|
$entry->value,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
)]);
|
2019-09-14 20:26:31 +02:00
|
|
|
|
|
|
|
|
|
$properties[$key_value] = $value_type;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-15 17:54:01 +01:00
|
|
|
|
$objectlike = new Type\Atomic\ObjectLike($properties);
|
|
|
|
|
|
|
|
|
|
$objectlike->is_list = $is_list;
|
2020-03-23 21:28:20 +01:00
|
|
|
|
$objectlike->sealed = true;
|
2020-01-15 17:54:01 +01:00
|
|
|
|
|
|
|
|
|
return $objectlike;
|
2019-09-14 20:26:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($c instanceof UnresolvedConstant\ClassConstant) {
|
2019-12-04 19:33:50 +01:00
|
|
|
|
if ($c->name === 'class') {
|
|
|
|
|
return new Type\Atomic\TLiteralClassString($c->fqcln);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-14 20:26:31 +02:00
|
|
|
|
$found_type = $this->getConstantForClass(
|
|
|
|
|
$c->fqcln,
|
|
|
|
|
$c->name,
|
|
|
|
|
ReflectionProperty::IS_PRIVATE,
|
2019-12-10 22:16:44 +01:00
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
2019-09-14 20:26:31 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($found_type) {
|
2020-01-04 18:20:26 +01:00
|
|
|
|
return \array_values($found_type->getAtomicTypes())[0];
|
2019-09-14 20:26:31 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-18 18:39:00 +02:00
|
|
|
|
if ($c instanceof UnresolvedConstant\ArrayOffsetFetch) {
|
|
|
|
|
$var_type = $this->resolveConstantType(
|
|
|
|
|
$c->array,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$offset_type = $this->resolveConstantType(
|
|
|
|
|
$c->offset,
|
|
|
|
|
$statements_analyzer,
|
|
|
|
|
$visited_constant_ids + [$c_id => true]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_type instanceof Type\Atomic\ObjectLike
|
|
|
|
|
&& ($offset_type instanceof Type\Atomic\TLiteralInt
|
|
|
|
|
|| $offset_type instanceof Type\Atomic\TLiteralString)
|
|
|
|
|
) {
|
|
|
|
|
$union = $var_type->properties[$offset_type->value] ?? null;
|
|
|
|
|
|
|
|
|
|
if ($union && $union->isSingle()) {
|
|
|
|
|
return \array_values($union->getAtomicTypes())[0];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-14 20:26:31 +02:00
|
|
|
|
if ($c instanceof UnresolvedConstant\Constant) {
|
|
|
|
|
if ($statements_analyzer) {
|
|
|
|
|
$found_type = $statements_analyzer->getConstType(
|
|
|
|
|
$c->name,
|
|
|
|
|
$c->is_fully_qualified,
|
|
|
|
|
null
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($found_type) {
|
2020-01-04 18:20:26 +01:00
|
|
|
|
return \array_values($found_type->getAtomicTypes())[0];
|
2019-09-14 20:26:31 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Type\Atomic\TMixed;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-09 00:14:28 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param string $class_name
|
|
|
|
|
* @param string $const_name
|
|
|
|
|
* @param Type\Union $type
|
|
|
|
|
* @param int $visibility
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function setConstantType(
|
|
|
|
|
$class_name,
|
|
|
|
|
$const_name,
|
|
|
|
|
Type\Union $type,
|
|
|
|
|
$visibility
|
|
|
|
|
) {
|
|
|
|
|
$storage = $this->classlike_storage_provider->get($class_name);
|
|
|
|
|
|
|
|
|
|
if ($visibility === ReflectionProperty::IS_PUBLIC) {
|
|
|
|
|
$storage->public_class_constants[$const_name] = $type;
|
|
|
|
|
} elseif ($visibility === ReflectionProperty::IS_PROTECTED) {
|
|
|
|
|
$storage->protected_class_constants[$const_name] = $type;
|
|
|
|
|
} elseif ($visibility === ReflectionProperty::IS_PRIVATE) {
|
|
|
|
|
$storage->private_class_constants[$const_name] = $type;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
/**
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-12-21 17:32:44 +01:00
|
|
|
|
private function checkMethodReferences(ClassLikeStorage $classlike_storage, Methods $methods)
|
2018-02-04 00:52:35 +01:00
|
|
|
|
{
|
2019-04-17 17:12:18 +02:00
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
2018-06-10 05:10:42 +02:00
|
|
|
|
foreach ($classlike_storage->appearing_method_ids as $method_name => $appearing_method_id) {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$appearing_fq_classlike_name = $appearing_method_id->fq_class_name;
|
2018-06-10 05:10:42 +02:00
|
|
|
|
|
|
|
|
|
if ($appearing_fq_classlike_name !== $classlike_storage->name) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-13 00:28:07 +02:00
|
|
|
|
$method_id = $appearing_method_id;
|
|
|
|
|
|
2019-04-17 21:12:52 +02:00
|
|
|
|
$declaring_classlike_storage = $classlike_storage;
|
|
|
|
|
|
2018-06-10 05:10:42 +02:00
|
|
|
|
if (isset($classlike_storage->methods[$method_name])) {
|
|
|
|
|
$method_storage = $classlike_storage->methods[$method_name];
|
|
|
|
|
} else {
|
|
|
|
|
$declaring_method_id = $classlike_storage->declaring_method_ids[$method_name];
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$declaring_fq_classlike_name = $declaring_method_id->fq_class_name;
|
|
|
|
|
$declaring_method_name = $declaring_method_id->method_name;
|
2018-06-10 05:10:42 +02:00
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name);
|
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-01 19:33:23 +01:00
|
|
|
|
$method_storage = $declaring_classlike_storage->methods[$declaring_method_name];
|
2019-04-13 00:28:07 +02:00
|
|
|
|
$method_id = $declaring_method_id;
|
2018-06-10 05:10:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-01 22:52:40 +02:00
|
|
|
|
if ($method_storage->location
|
|
|
|
|
&& !$project_analyzer->canReportIssues($method_storage->location->file_path)
|
|
|
|
|
&& !$codebase->analyzer->canReportIssues($method_storage->location->file_path)
|
|
|
|
|
) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$method_referenced = $this->file_reference_provider->isClassMethodReferenced(
|
|
|
|
|
strtolower((string) $method_id)
|
|
|
|
|
);
|
2019-04-13 00:28:07 +02:00
|
|
|
|
|
|
|
|
|
if (!$method_referenced
|
2018-02-04 00:52:35 +01:00
|
|
|
|
&& (substr($method_name, 0, 2) !== '__' || $method_name === '__construct')
|
|
|
|
|
&& $method_storage->location
|
|
|
|
|
) {
|
2018-04-07 00:28:22 +02:00
|
|
|
|
$method_location = $method_storage->location;
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$method_id = $classlike_storage->name . '::' . $method_storage->cased_name;
|
|
|
|
|
|
2018-12-13 23:20:29 +01:00
|
|
|
|
if ($method_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$has_parent_references = false;
|
|
|
|
|
|
2019-12-31 13:56:35 +01:00
|
|
|
|
if ($codebase->classImplements($classlike_storage->name, 'Serializable')
|
|
|
|
|
&& ($method_name === 'serialize' || $method_name === 'unserialize')
|
|
|
|
|
) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$has_variable_calls = $codebase->analyzer->hasMixedMemberName($method_name)
|
2019-04-22 19:18:19 +02:00
|
|
|
|
|| $codebase->analyzer->hasMixedMemberName(strtolower($classlike_storage->name . '::'));
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if (isset($classlike_storage->overridden_method_ids[$method_name])) {
|
|
|
|
|
foreach ($classlike_storage->overridden_method_ids[$method_name] as $parent_method_id) {
|
2018-12-21 17:32:44 +01:00
|
|
|
|
$parent_method_storage = $methods->getStorage($parent_method_id);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2019-09-01 22:52:40 +02:00
|
|
|
|
if ($parent_method_storage->location
|
|
|
|
|
&& !$project_analyzer->canReportIssues($parent_method_storage->location->file_path)
|
|
|
|
|
) {
|
|
|
|
|
// here we just don’t know
|
|
|
|
|
$has_parent_references = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-13 00:28:07 +02:00
|
|
|
|
$parent_method_referenced = $this->file_reference_provider->isClassMethodReferenced(
|
2020-02-15 02:54:26 +01:00
|
|
|
|
strtolower((string) $parent_method_id)
|
2019-04-13 00:28:07 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!$parent_method_storage->abstract || $parent_method_referenced) {
|
2018-06-10 05:10:42 +02:00
|
|
|
|
$has_parent_references = true;
|
2019-09-01 22:52:40 +02:00
|
|
|
|
break;
|
2018-06-10 05:10:42 +02:00
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 19:18:19 +02:00
|
|
|
|
foreach ($classlike_storage->parent_classes as $parent_method_fqcln) {
|
|
|
|
|
if ($codebase->analyzer->hasMixedMemberName(
|
|
|
|
|
strtolower($parent_method_fqcln) . '::'
|
|
|
|
|
)) {
|
|
|
|
|
$has_variable_calls = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-19 17:45:08 +02:00
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
foreach ($classlike_storage->class_implements as $fq_interface_name_lc => $_) {
|
2019-05-11 00:07:13 +02:00
|
|
|
|
try {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$interface_storage = $this->classlike_storage_provider->get($fq_interface_name_lc);
|
2019-05-11 00:07:13 +02:00
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-04-19 17:45:08 +02:00
|
|
|
|
|
2019-04-22 19:18:19 +02:00
|
|
|
|
if ($codebase->analyzer->hasMixedMemberName(
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_interface_name_lc . '::'
|
2019-04-19 17:45:08 +02:00
|
|
|
|
)) {
|
|
|
|
|
$has_variable_calls = true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
|
if (isset($interface_storage->methods[$method_name])) {
|
2019-04-13 00:28:07 +02:00
|
|
|
|
$interface_method_referenced = $this->file_reference_provider->isClassMethodReferenced(
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$fq_interface_name_lc . '::' . $method_name
|
2019-04-13 00:28:07 +02:00
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
2019-04-13 00:28:07 +02:00
|
|
|
|
if ($interface_method_referenced) {
|
2018-02-04 00:52:35 +01:00
|
|
|
|
$has_parent_references = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$has_parent_references) {
|
2019-04-17 17:12:18 +02:00
|
|
|
|
$issue = new PossiblyUnusedMethod(
|
2019-04-19 17:45:08 +02:00
|
|
|
|
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
|
|
|
|
|
. ' calls to method ' . $method_id
|
|
|
|
|
. ($has_variable_calls ? ' (but did find some potential callers)' : ''),
|
2019-04-17 17:12:18 +02:00
|
|
|
|
$method_storage->location,
|
|
|
|
|
$method_id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($codebase->alter_code) {
|
|
|
|
|
if ($method_storage->stmt_location
|
2019-04-17 21:12:52 +02:00
|
|
|
|
&& !$declaring_classlike_storage->is_trait
|
2019-04-17 17:12:18 +02:00
|
|
|
|
&& isset($project_analyzer->getIssuesToFix()['PossiblyUnusedMethod'])
|
2019-04-19 17:45:08 +02:00
|
|
|
|
&& !$has_variable_calls
|
2019-04-17 17:12:18 +02:00
|
|
|
|
&& !IssueBuffer::isSuppressed($issue, $method_storage->suppressed_issues)
|
|
|
|
|
) {
|
|
|
|
|
FileManipulationBuffer::addForCodeLocation(
|
|
|
|
|
$method_storage->stmt_location,
|
2019-04-17 19:56:47 +02:00
|
|
|
|
'',
|
|
|
|
|
true
|
2019-04-17 17:12:18 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} elseif (IssueBuffer::accepts(
|
|
|
|
|
$issue,
|
2019-12-02 21:24:01 +01:00
|
|
|
|
$method_storage->suppressed_issues,
|
|
|
|
|
$method_storage->stmt_location
|
|
|
|
|
&& !$declaring_classlike_storage->is_trait
|
|
|
|
|
&& !$has_variable_calls
|
2018-02-04 00:52:35 +01:00
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} elseif (!isset($classlike_storage->declaring_method_ids['__call'])) {
|
2019-04-19 18:03:52 +02:00
|
|
|
|
$has_variable_calls = $codebase->analyzer->hasMixedMemberName(
|
|
|
|
|
strtolower($classlike_storage->name . '::')
|
2020-02-15 02:54:26 +01:00
|
|
|
|
) || $codebase->analyzer->hasMixedMemberName($method_name);
|
2019-04-19 17:45:08 +02:00
|
|
|
|
|
2019-04-17 17:12:18 +02:00
|
|
|
|
$issue = new UnusedMethod(
|
2019-04-19 17:45:08 +02:00
|
|
|
|
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
|
|
|
|
|
. ' calls to private method ' . $method_id
|
|
|
|
|
. ($has_variable_calls ? ' (but did find some potential callers)' : ''),
|
2019-04-17 17:12:18 +02:00
|
|
|
|
$method_location,
|
|
|
|
|
$method_id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($codebase->alter_code) {
|
|
|
|
|
if ($method_storage->stmt_location
|
2019-04-17 21:12:52 +02:00
|
|
|
|
&& !$declaring_classlike_storage->is_trait
|
2019-04-17 17:12:18 +02:00
|
|
|
|
&& isset($project_analyzer->getIssuesToFix()['UnusedMethod'])
|
2019-04-22 19:18:19 +02:00
|
|
|
|
&& !$has_variable_calls
|
2019-04-17 17:12:18 +02:00
|
|
|
|
&& !IssueBuffer::isSuppressed($issue, $method_storage->suppressed_issues)
|
|
|
|
|
) {
|
|
|
|
|
FileManipulationBuffer::addForCodeLocation(
|
|
|
|
|
$method_storage->stmt_location,
|
2019-04-17 19:56:47 +02:00
|
|
|
|
'',
|
|
|
|
|
true
|
2019-04-17 17:12:18 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} elseif (IssueBuffer::accepts(
|
|
|
|
|
$issue,
|
2019-12-02 21:24:01 +01:00
|
|
|
|
$method_storage->suppressed_issues,
|
|
|
|
|
$method_storage->stmt_location
|
|
|
|
|
&& !$declaring_classlike_storage->is_trait
|
|
|
|
|
&& !$has_variable_calls
|
2018-02-04 00:52:35 +01:00
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2019-06-09 23:02:21 +02:00
|
|
|
|
if ($method_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE
|
|
|
|
|
&& !$classlike_storage->is_interface
|
|
|
|
|
) {
|
|
|
|
|
foreach ($method_storage->params as $offset => $param_storage) {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
if (!$this->file_reference_provider->isMethodParamUsed(
|
|
|
|
|
strtolower((string) $method_id),
|
|
|
|
|
$offset
|
|
|
|
|
)
|
2019-06-09 23:02:21 +02:00
|
|
|
|
&& $param_storage->location
|
|
|
|
|
) {
|
2020-03-31 21:49:43 +02:00
|
|
|
|
if ($method_storage->final) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new \Psalm\Issue\UnusedParam(
|
|
|
|
|
'Param #' . ($offset + 1) . ' is never referenced in this method',
|
|
|
|
|
$param_storage->location
|
|
|
|
|
),
|
|
|
|
|
$method_storage->suppressed_issues
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new PossiblyUnusedParam(
|
|
|
|
|
'Param #' . ($offset + 1) . ' is never referenced in this method',
|
|
|
|
|
$param_storage->location
|
|
|
|
|
),
|
|
|
|
|
$method_storage->suppressed_issues
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
2018-06-10 05:10:42 +02:00
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 22:41:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-02 21:24:01 +01:00
|
|
|
|
/**
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function findPossibleMethodParamTypes(ClassLikeStorage $classlike_storage)
|
|
|
|
|
{
|
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
|
|
|
|
foreach ($classlike_storage->appearing_method_ids as $method_name => $appearing_method_id) {
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$appearing_fq_classlike_name = $appearing_method_id->fq_class_name;
|
2019-12-02 21:24:01 +01:00
|
|
|
|
|
|
|
|
|
if ($appearing_fq_classlike_name !== $classlike_storage->name) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$method_id = $appearing_method_id;
|
|
|
|
|
|
|
|
|
|
$declaring_classlike_storage = $classlike_storage;
|
|
|
|
|
|
|
|
|
|
if (isset($classlike_storage->methods[$method_name])) {
|
|
|
|
|
$method_storage = $classlike_storage->methods[$method_name];
|
|
|
|
|
} else {
|
|
|
|
|
$declaring_method_id = $classlike_storage->declaring_method_ids[$method_name];
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$declaring_fq_classlike_name = $declaring_method_id->fq_class_name;
|
|
|
|
|
$declaring_method_name = $declaring_method_id->method_name;
|
2019-12-02 21:24:01 +01:00
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name);
|
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$method_storage = $declaring_classlike_storage->methods[$declaring_method_name];
|
|
|
|
|
$method_id = $declaring_method_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($method_storage->location
|
|
|
|
|
&& !$project_analyzer->canReportIssues($method_storage->location->file_path)
|
|
|
|
|
&& !$codebase->analyzer->canReportIssues($method_storage->location->file_path)
|
|
|
|
|
) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-10 23:32:39 +01:00
|
|
|
|
if ($declaring_classlike_storage->is_trait) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$method_id_lc = strtolower((string) $method_id);
|
|
|
|
|
|
|
|
|
|
if (isset($codebase->analyzer->possible_method_param_types[$method_id_lc])) {
|
2019-12-02 21:24:01 +01:00
|
|
|
|
if ($method_storage->location) {
|
|
|
|
|
$possible_param_types
|
2020-02-15 02:54:26 +01:00
|
|
|
|
= $codebase->analyzer->possible_method_param_types[$method_id_lc];
|
2019-12-02 21:24:01 +01:00
|
|
|
|
|
2019-12-03 06:57:26 +01:00
|
|
|
|
if ($possible_param_types) {
|
2019-12-02 21:24:01 +01:00
|
|
|
|
foreach ($possible_param_types as $offset => $possible_type) {
|
|
|
|
|
if (!isset($method_storage->params[$offset])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$param_name = $method_storage->params[$offset]->name;
|
|
|
|
|
|
|
|
|
|
if ($possible_type->hasMixed() || $possible_type->isNull()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($method_storage->params[$offset]->default_type) {
|
|
|
|
|
$possible_type = \Psalm\Type::combineUnionTypes(
|
|
|
|
|
$possible_type,
|
|
|
|
|
$method_storage->params[$offset]->default_type
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($codebase->alter_code
|
|
|
|
|
&& isset($project_analyzer->getIssuesToFix()['MissingParamType'])
|
|
|
|
|
) {
|
2019-12-03 06:57:26 +01:00
|
|
|
|
$function_analyzer = $project_analyzer->getFunctionLikeAnalyzer(
|
|
|
|
|
$method_id,
|
|
|
|
|
$method_storage->location->file_path
|
2019-12-02 21:24:01 +01:00
|
|
|
|
);
|
2019-12-03 06:57:26 +01:00
|
|
|
|
|
2019-12-03 07:59:36 +01:00
|
|
|
|
$has_variable_calls = $codebase->analyzer->hasMixedMemberName(
|
2020-02-15 02:54:26 +01:00
|
|
|
|
$method_name
|
2019-12-03 07:59:36 +01:00
|
|
|
|
)
|
|
|
|
|
|| $codebase->analyzer->hasMixedMemberName(
|
|
|
|
|
strtolower($classlike_storage->name . '::')
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($has_variable_calls) {
|
|
|
|
|
$possible_type->from_docblock = true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-03 06:57:26 +01:00
|
|
|
|
if ($function_analyzer) {
|
|
|
|
|
$function_analyzer->addOrUpdateParamType(
|
|
|
|
|
$project_analyzer,
|
|
|
|
|
$param_name,
|
|
|
|
|
$possible_type,
|
2019-12-03 07:49:43 +01:00
|
|
|
|
$possible_type->from_docblock
|
|
|
|
|
&& $project_analyzer->only_replace_php_types_with_non_docblock_types
|
2019-12-03 06:57:26 +01:00
|
|
|
|
);
|
|
|
|
|
}
|
2019-12-02 21:24:01 +01:00
|
|
|
|
} else {
|
|
|
|
|
IssueBuffer::addFixableIssue('MissingParamType');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-17 22:41:35 +02:00
|
|
|
|
/**
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function checkPropertyReferences(ClassLikeStorage $classlike_storage)
|
|
|
|
|
{
|
|
|
|
|
$project_analyzer = \Psalm\Internal\Analyzer\ProjectAnalyzer::getInstance();
|
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
|
|
foreach ($classlike_storage->properties as $property_name => $property_storage) {
|
2019-07-22 05:29:16 +02:00
|
|
|
|
$referenced_property_name = strtolower($classlike_storage->name) . '::$' . $property_name;
|
2019-04-13 00:28:07 +02:00
|
|
|
|
$property_referenced = $this->file_reference_provider->isClassPropertyReferenced(
|
2019-07-22 05:29:16 +02:00
|
|
|
|
$referenced_property_name
|
2019-04-13 00:28:07 +02:00
|
|
|
|
);
|
2019-04-16 22:07:48 +02:00
|
|
|
|
|
2019-07-22 05:29:16 +02:00
|
|
|
|
$property_constructor_referenced = false;
|
|
|
|
|
if ($property_referenced && $property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE) {
|
|
|
|
|
$all_method_references = $this->file_reference_provider->getAllMethodReferencesToClassMembers();
|
|
|
|
|
|
|
|
|
|
if (isset($all_method_references[$referenced_property_name])
|
|
|
|
|
&& count($all_method_references[$referenced_property_name]) === 1) {
|
|
|
|
|
$constructor_name = strtolower($classlike_storage->name) . '::__construct';
|
|
|
|
|
$property_references = $all_method_references[$referenced_property_name];
|
|
|
|
|
|
|
|
|
|
$property_constructor_referenced = isset($property_references[$constructor_name])
|
|
|
|
|
&& !$property_storage->is_static;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((!$property_referenced || $property_constructor_referenced)
|
2018-02-04 00:52:35 +01:00
|
|
|
|
&& (substr($property_name, 0, 2) !== '__' || $property_name === '__construct')
|
|
|
|
|
&& $property_storage->location
|
|
|
|
|
) {
|
|
|
|
|
$property_id = $classlike_storage->name . '::$' . $property_name;
|
|
|
|
|
|
2019-04-19 17:45:08 +02:00
|
|
|
|
if ($property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC
|
|
|
|
|
|| $property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED
|
|
|
|
|
) {
|
2019-04-17 22:41:35 +02:00
|
|
|
|
$has_parent_references = isset($classlike_storage->overridden_property_ids[$property_name]);
|
2019-04-17 17:12:18 +02:00
|
|
|
|
|
2019-04-19 17:45:08 +02:00
|
|
|
|
$has_variable_calls = $codebase->analyzer->hasMixedMemberName('$' . $property_name)
|
|
|
|
|
|| $codebase->analyzer->hasMixedMemberName(strtolower($classlike_storage->name) . '::$');
|
|
|
|
|
|
2019-04-22 19:18:19 +02:00
|
|
|
|
foreach ($classlike_storage->parent_classes as $parent_method_fqcln) {
|
|
|
|
|
if ($codebase->analyzer->hasMixedMemberName(
|
|
|
|
|
strtolower($parent_method_fqcln) . '::$'
|
|
|
|
|
)) {
|
|
|
|
|
$has_variable_calls = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-19 17:45:08 +02:00
|
|
|
|
foreach ($classlike_storage->class_implements as $fq_interface_name) {
|
2019-04-22 19:18:19 +02:00
|
|
|
|
if ($codebase->analyzer->hasMixedMemberName(
|
2019-04-19 17:45:08 +02:00
|
|
|
|
strtolower($fq_interface_name) . '::$'
|
|
|
|
|
)) {
|
|
|
|
|
$has_variable_calls = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$has_parent_references
|
|
|
|
|
&& ($property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC
|
|
|
|
|
|| !isset($classlike_storage->declaring_method_ids['__get']))
|
|
|
|
|
) {
|
2019-04-17 22:41:35 +02:00
|
|
|
|
$issue = new PossiblyUnusedProperty(
|
2019-04-19 17:45:08 +02:00
|
|
|
|
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
|
|
|
|
|
. ' references to property ' . $property_id
|
|
|
|
|
. ($has_variable_calls ? ' (but did find some potential references)' : ''),
|
2019-04-17 22:41:35 +02:00
|
|
|
|
$property_storage->location
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($codebase->alter_code) {
|
|
|
|
|
if ($property_storage->stmt_location
|
|
|
|
|
&& isset($project_analyzer->getIssuesToFix()['PossiblyUnusedProperty'])
|
2019-04-19 17:45:08 +02:00
|
|
|
|
&& !$has_variable_calls
|
2019-04-17 22:41:35 +02:00
|
|
|
|
&& !IssueBuffer::isSuppressed($issue, $classlike_storage->suppressed_issues)
|
|
|
|
|
) {
|
|
|
|
|
FileManipulationBuffer::addForCodeLocation(
|
|
|
|
|
$property_storage->stmt_location,
|
|
|
|
|
'',
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} elseif (IssueBuffer::accepts(
|
|
|
|
|
$issue,
|
|
|
|
|
$classlike_storage->suppressed_issues
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
2019-04-17 17:12:18 +02:00
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|
2019-04-19 17:45:08 +02:00
|
|
|
|
} elseif (!isset($classlike_storage->declaring_method_ids['__get'])) {
|
|
|
|
|
$has_variable_calls = $codebase->analyzer->hasMixedMemberName('$' . $property_name);
|
|
|
|
|
|
2019-04-17 17:12:18 +02:00
|
|
|
|
$issue = new UnusedProperty(
|
2019-04-19 17:45:08 +02:00
|
|
|
|
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
|
|
|
|
|
. ' references to private property ' . $property_id
|
|
|
|
|
. ($has_variable_calls ? ' (but did find some potential references)' : ''),
|
2019-04-17 17:12:18 +02:00
|
|
|
|
$property_storage->location
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($codebase->alter_code) {
|
2019-07-22 05:29:16 +02:00
|
|
|
|
if (!$property_constructor_referenced
|
|
|
|
|
&& $property_storage->stmt_location
|
2019-04-17 17:12:18 +02:00
|
|
|
|
&& isset($project_analyzer->getIssuesToFix()['UnusedProperty'])
|
2019-04-19 17:45:08 +02:00
|
|
|
|
&& !$has_variable_calls
|
2019-04-17 17:12:18 +02:00
|
|
|
|
&& !IssueBuffer::isSuppressed($issue, $classlike_storage->suppressed_issues)
|
|
|
|
|
) {
|
|
|
|
|
FileManipulationBuffer::addForCodeLocation(
|
|
|
|
|
$property_storage->stmt_location,
|
2019-04-17 19:56:47 +02:00
|
|
|
|
'',
|
|
|
|
|
true
|
2019-04-17 17:12:18 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} elseif (IssueBuffer::accepts(
|
|
|
|
|
$issue,
|
|
|
|
|
$classlike_storage->suppressed_issues
|
2018-02-04 00:52:35 +01:00
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @param lowercase-string $fq_classlike_name_lc
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function registerMissingClassLike($fq_classlike_name_lc)
|
|
|
|
|
{
|
|
|
|
|
$this->existing_classlikes_lc[$fq_classlike_name_lc] = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @param lowercase-string $fq_classlike_name_lc
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isMissingClassLike($fq_classlike_name_lc)
|
|
|
|
|
{
|
|
|
|
|
return isset($this->existing_classlikes_lc[$fq_classlike_name_lc])
|
|
|
|
|
&& $this->existing_classlikes_lc[$fq_classlike_name_lc] === false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* @param lowercase-string $fq_classlike_name_lc
|
2018-02-04 00:52:35 +01:00
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function doesClassLikeExist($fq_classlike_name_lc)
|
|
|
|
|
{
|
|
|
|
|
return isset($this->existing_classlikes_lc[$fq_classlike_name_lc])
|
|
|
|
|
&& $this->existing_classlikes_lc[$fq_classlike_name_lc];
|
|
|
|
|
}
|
2018-09-28 22:18:45 +02:00
|
|
|
|
|
2020-05-10 21:55:44 +02:00
|
|
|
|
public function forgetMissingClassLikes() : void
|
|
|
|
|
{
|
|
|
|
|
$this->existing_classlikes_lc = \array_filter($this->existing_classlikes_lc);
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-28 22:18:45 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param string $fq_class_name
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function removeClassLike($fq_class_name)
|
|
|
|
|
{
|
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2020-02-15 02:54:26 +01:00
|
|
|
|
|
2018-09-28 22:18:45 +02:00
|
|
|
|
unset(
|
|
|
|
|
$this->existing_classlikes_lc[$fq_class_name_lc],
|
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc],
|
|
|
|
|
$this->existing_traits_lc[$fq_class_name_lc],
|
2018-10-12 05:00:32 +02:00
|
|
|
|
$this->existing_traits[$fq_class_name],
|
2018-09-28 22:18:45 +02:00
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc],
|
2018-10-12 05:00:32 +02:00
|
|
|
|
$this->existing_interfaces[$fq_class_name],
|
2018-09-28 22:18:45 +02:00
|
|
|
|
$this->existing_classes[$fq_class_name],
|
2020-03-03 04:30:06 +01:00
|
|
|
|
$this->trait_nodes[$fq_class_name_lc]
|
2018-09-28 22:18:45 +02:00
|
|
|
|
);
|
2018-10-12 05:00:32 +02:00
|
|
|
|
|
|
|
|
|
$this->scanner->removeClassLike($fq_class_name_lc);
|
2018-09-28 22:18:45 +02:00
|
|
|
|
}
|
2018-10-11 19:58:39 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return array{
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* 0: array<lowercase-string, bool>,
|
|
|
|
|
* 1: array<lowercase-string, bool>,
|
|
|
|
|
* 2: array<lowercase-string, bool>,
|
2018-10-11 19:58:39 +02:00
|
|
|
|
* 3: array<string, bool>,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* 4: array<lowercase-string, bool>,
|
2018-10-11 19:58:39 +02:00
|
|
|
|
* 5: array<string, bool>,
|
|
|
|
|
* 6: array<string, bool>,
|
|
|
|
|
* }
|
|
|
|
|
*/
|
|
|
|
|
public function getThreadData()
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
$this->existing_classlikes_lc,
|
|
|
|
|
$this->existing_classes_lc,
|
|
|
|
|
$this->existing_traits_lc,
|
|
|
|
|
$this->existing_traits,
|
|
|
|
|
$this->existing_interfaces_lc,
|
|
|
|
|
$this->existing_interfaces,
|
|
|
|
|
$this->existing_classes,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param array{
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* 0: array<lowercase-string, bool>,
|
|
|
|
|
* 1: array<lowercase-string, bool>,
|
|
|
|
|
* 2: array<lowercase-string, bool>,
|
2018-10-11 19:58:39 +02:00
|
|
|
|
* 3: array<string, bool>,
|
2020-02-15 02:54:26 +01:00
|
|
|
|
* 4: array<lowercase-string, bool>,
|
2018-10-11 19:58:39 +02:00
|
|
|
|
* 5: array<string, bool>,
|
|
|
|
|
* 6: array<string, bool>,
|
|
|
|
|
* } $thread_data
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function addThreadData(array $thread_data)
|
|
|
|
|
{
|
2019-07-05 22:24:00 +02:00
|
|
|
|
list(
|
2018-10-11 19:58:39 +02:00
|
|
|
|
$existing_classlikes_lc,
|
|
|
|
|
$existing_classes_lc,
|
|
|
|
|
$existing_traits_lc,
|
|
|
|
|
$existing_traits,
|
|
|
|
|
$existing_interfaces_lc,
|
|
|
|
|
$existing_interfaces,
|
2020-03-03 04:27:54 +01:00
|
|
|
|
$existing_classes) = $thread_data;
|
2018-10-11 19:58:39 +02:00
|
|
|
|
|
|
|
|
|
$this->existing_classlikes_lc = array_merge($existing_classlikes_lc, $this->existing_classlikes_lc);
|
|
|
|
|
$this->existing_classes_lc = array_merge($existing_classes_lc, $this->existing_classes_lc);
|
|
|
|
|
$this->existing_traits_lc = array_merge($existing_traits_lc, $this->existing_traits_lc);
|
|
|
|
|
$this->existing_traits = array_merge($existing_traits, $this->existing_traits);
|
|
|
|
|
$this->existing_interfaces_lc = array_merge($existing_interfaces_lc, $this->existing_interfaces_lc);
|
|
|
|
|
$this->existing_interfaces = array_merge($existing_interfaces, $this->existing_interfaces);
|
|
|
|
|
$this->existing_classes = array_merge($existing_classes, $this->existing_classes);
|
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
}
|