1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-14 18:36:58 +01:00
psalm/src/Psalm/Internal/Codebase/ClassLikes.php

2332 lines
88 KiB
PHP
Raw Normal View History

2018-02-04 00:52:35 +01:00
<?php
namespace Psalm\Internal\Codebase;
2018-02-04 00:52:35 +01:00
2021-12-03 21:40:18 +01:00
use InvalidArgumentException;
2018-02-04 00:52:35 +01:00
use PhpParser;
2021-12-03 20:11:20 +01:00
use PhpParser\NodeTraverser;
2018-02-04 00:52:35 +01:00
use Psalm\CodeLocation;
2021-12-03 20:11:20 +01:00
use Psalm\Codebase;
2018-02-04 00:52:35 +01:00
use Psalm\Config;
2019-07-05 22:24:00 +02:00
use Psalm\Exception\UnpopulatedClasslikeException;
2021-12-03 20:11:20 +01:00
use Psalm\FileManipulation;
2019-07-05 22:24:00 +02:00
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
2021-12-03 20:11:20 +01:00
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Codebase\ConstantTypeResolver;
use Psalm\Internal\FileManipulation\ClassDocblockManipulator;
2021-12-03 20:11:20 +01:00
use Psalm\Internal\FileManipulation\CodeMigration;
2021-06-08 04:55:21 +02:00
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
2021-12-03 20:11:20 +01:00
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\PhpVisitor\TraitFinder;
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;
2021-12-03 20:11:20 +01:00
use Psalm\Internal\Type\TypeExpander;
2018-02-04 00:52:35 +01:00
use Psalm\Issue\PossiblyUnusedMethod;
use Psalm\Issue\PossiblyUnusedParam;
use Psalm\Issue\PossiblyUnusedProperty;
2021-12-03 20:11:20 +01:00
use Psalm\Issue\PossiblyUnusedReturnValue;
2018-02-04 00:52:35 +01:00
use Psalm\Issue\UnusedClass;
use Psalm\Issue\UnusedConstructor;
2018-02-04 00:52:35 +01:00
use Psalm\Issue\UnusedMethod;
2021-12-03 20:11:20 +01:00
use Psalm\Issue\UnusedParam;
2018-02-04 00:52:35 +01:00
use Psalm\Issue\UnusedProperty;
2021-12-03 20:11:20 +01:00
use Psalm\Issue\UnusedReturnValue;
2018-02-04 00:52:35 +01:00
use Psalm\IssueBuffer;
use Psalm\Node\VirtualNode;
use Psalm\Progress\Progress;
use Psalm\Progress\VoidProgress;
2021-12-03 20:11:20 +01:00
use Psalm\StatementsSource;
2021-12-04 03:37:19 +01:00
use Psalm\Storage\ClassConstantStorage;
2018-02-04 00:52:35 +01:00
use Psalm\Storage\ClassLikeStorage;
use Psalm\Type;
2021-12-13 04:45:57 +01:00
use Psalm\Type\Atomic\TEnumCase;
2021-12-13 16:28:14 +01:00
use Psalm\Type\Union;
2021-12-03 21:40:18 +01:00
use ReflectionClass;
use ReflectionProperty;
2021-12-03 21:40:18 +01:00
use UnexpectedValueException;
2021-06-08 04:55:21 +02:00
2021-12-03 21:07:25 +01:00
use function array_filter;
2021-06-08 04:55:21 +02:00
use function array_merge;
use function array_pop;
use function count;
use function end;
use function explode;
use function get_declared_classes;
use function get_declared_interfaces;
use function implode;
use function preg_match;
use function preg_quote;
use function preg_replace;
2019-07-05 22:24:00 +02:00
use function strlen;
use function strrpos;
use function strtolower;
use function substr;
2021-06-08 04:55:21 +02:00
use const PHP_EOL;
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;
/**
* @var FileReferenceProvider
*/
public $file_reference_provider;
2018-02-04 00:52:35 +01:00
/**
* @var array<lowercase-string, bool>
2018-02-04 00:52:35 +01:00
*/
private $existing_classlikes_lc = [];
/**
* @var array<lowercase-string, bool>
2018-02-04 00:52:35 +01:00
*/
private $existing_classes_lc = [];
/**
* @var array<string, bool>
*/
private $existing_classes = [];
/**
* @var array<lowercase-string, bool>
2018-02-04 00:52:35 +01:00
*/
private $existing_interfaces_lc = [];
/**
* @var array<string, bool>
*/
private $existing_interfaces = [];
/**
* @var array<lowercase-string, bool>
2018-02-04 00:52:35 +01:00
*/
private $existing_traits_lc = [];
/**
* @var array<string, bool>
*/
private $existing_traits = [];
/**
* @var array<lowercase-string, bool>
*/
private $existing_enums_lc = [];
/**
* @var array<string, bool>
*/
private $existing_enums = [];
2018-02-04 00:52:35 +01:00
/**
* @var array<lowercase-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;
/**
* @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,
FileReferenceProvider $file_reference_provider,
2020-03-03 04:27:54 +01:00
StatementsProvider $statements_provider,
Scanner $scanner
2018-02-04 00:52:35 +01:00
) {
$this->config = $config;
$this->classlike_storage_provider = $storage_provider;
$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();
}
private function collectPredefinedClassLikes(): void
2018-02-04 00:52:35 +01:00
{
/** @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);
/** @psalm-suppress ArgumentTypeCoercion */
2021-12-03 21:40:18 +01:00
$reflection_class = new ReflectionClass($predefined_class);
2018-02-04 00:52:35 +01:00
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;
$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);
/** @psalm-suppress ArgumentTypeCoercion */
2021-12-03 21:40:18 +01:00
$reflection_class = new ReflectionClass($predefined_interface);
2018-02-04 00:52:35 +01:00
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;
$this->existing_interfaces[$predefined_interface] = true;
2018-02-04 00:52:35 +01:00
}
}
}
public function addFullyQualifiedClassName(string $fq_class_name, ?string $file_path = null): void
2018-02-04 00:52:35 +01:00
{
$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_classes[$fq_class_name] = true;
2018-02-04 00:52:35 +01:00
$this->existing_traits_lc[$fq_class_name_lc] = false;
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
$this->existing_enums_lc[$fq_class_name_lc] = false;
2018-02-04 00:52:35 +01:00
if ($file_path) {
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
}
}
public function addFullyQualifiedInterfaceName(string $fq_class_name, ?string $file_path = null): void
2018-02-04 00:52:35 +01:00
{
$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_interfaces[$fq_class_name] = true;
2018-02-04 00:52:35 +01:00
$this->existing_classes_lc[$fq_class_name_lc] = false;
$this->existing_traits_lc[$fq_class_name_lc] = false;
$this->existing_enums_lc[$fq_class_name_lc] = false;
2018-02-04 00:52:35 +01:00
if ($file_path) {
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
}
}
public function addFullyQualifiedTraitName(string $fq_class_name, ?string $file_path = null): void
2018-02-04 00:52:35 +01:00
{
$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_traits[$fq_class_name] = true;
$this->existing_classes_lc[$fq_class_name_lc] = false;
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
$this->existing_enums[$fq_class_name] = false;
if ($file_path) {
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
}
}
public function addFullyQualifiedEnumName(string $fq_class_name, ?string $file_path = null): void
{
$fq_class_name_lc = strtolower($fq_class_name);
$this->existing_classlikes_lc[$fq_class_name_lc] = true;
$this->existing_enums_lc[$fq_class_name_lc] = true;
$this->existing_enums[$fq_class_name] = true;
$this->existing_traits_lc[$fq_class_name_lc] = false;
2018-02-04 00:52:35 +01:00
$this->existing_classes_lc[$fq_class_name_lc] = false;
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
if ($file_path) {
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
}
}
public function addFullyQualifiedClassLikeName(string $fq_class_name_lc, ?string $file_path = null): void
2018-02-04 00:52:35 +01:00
{
if ($file_path) {
$this->scanner->setClassLikeFilePath($fq_class_name_lc, $file_path);
}
}
/**
2020-10-17 18:36:44 +02:00
* @return list<string>
*/
public function getMatchingClassLikeNames(string $stub): array
{
$matching_classes = [];
if ($stub[0] === '*') {
$stub = substr($stub, 1);
}
$fully_qualified = false;
if ($stub[0] === '\\') {
$fully_qualified = true;
$stub = substr($stub, 1);
} else {
// for any not-fully-qualified class name the bit we care about comes after a dash
[, $stub] = explode('-', $stub);
}
$stub = preg_quote(strtolower($stub));
if ($fully_qualified) {
$stub = '^' . $stub;
} else {
$stub = '(^|\\\)' . $stub;
}
foreach ($this->existing_classes as $fq_classlike_name => $found) {
if (!$found) {
continue;
}
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;
}
}
return $matching_classes;
}
public function hasFullyQualifiedClassName(
string $fq_class_name,
?CodeLocation $code_location = null,
?string $calling_fq_class_name = null,
?string $calling_method_id = null
): bool {
$fq_class_name_lc = strtolower($this->getUnAliasedName($fq_class_name));
2018-11-29 06:05:56 +01:00
if ($code_location) {
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) {
$this->file_reference_provider->addNonMethodReferenceToClass(
$code_location->file_path,
$fq_class_name_lc
);
if ($calling_fq_class_name) {
$class_storage = $this->classlike_storage_provider->get($calling_fq_class_name);
if ($class_storage->location
&& $class_storage->location->file_path !== $code_location->file_path
) {
$this->file_reference_provider->addNonMethodReferenceToClass(
$class_storage->location->file_path,
$fq_class_name_lc
);
}
}
}
}
2018-02-04 00:52:35 +01: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]
)
&& !$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;
}
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;
}
public function hasFullyQualifiedInterfaceName(
string $fq_class_name,
?CodeLocation $code_location = null,
?string $calling_fq_class_name = null,
?string $calling_method_id = null
): bool {
$fq_class_name_lc = strtolower($this->getUnAliasedName($fq_class_name));
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]
2018-02-04 00:52:35 +01:00
)
&& !$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;
}
if ($this->collect_references && $code_location) {
if ($calling_method_id) {
$this->file_reference_provider->addMethodReferenceToClass(
$calling_method_id,
$fq_class_name_lc
);
} else {
$this->file_reference_provider->addNonMethodReferenceToClass(
$code_location->file_path,
$fq_class_name_lc
);
if ($calling_fq_class_name) {
$class_storage = $this->classlike_storage_provider->get($calling_fq_class_name);
if ($class_storage->location
&& $class_storage->location->file_path !== $code_location->file_path
) {
$this->file_reference_provider->addNonMethodReferenceToClass(
$class_storage->location->file_path,
$fq_class_name_lc
);
}
}
}
}
2018-02-04 00:52:35 +01: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;
}
public function hasFullyQualifiedEnumName(
string $fq_class_name,
?CodeLocation $code_location = null,
?string $calling_fq_class_name = null,
?string $calling_method_id = null
): bool {
$fq_class_name_lc = strtolower($this->getUnAliasedName($fq_class_name));
if (!isset($this->existing_enums_lc[$fq_class_name_lc])
|| !$this->existing_enums_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]
)
&& !$this->classlike_storage_provider->has($fq_class_name_lc)
) {
if (!isset($this->existing_enums_lc[$fq_class_name_lc])) {
$this->existing_enums_lc[$fq_class_name_lc] = false;
return false;
}
return $this->existing_enums_lc[$fq_class_name_lc];
}
return false;
}
if ($this->collect_references && $code_location) {
if ($calling_method_id) {
$this->file_reference_provider->addMethodReferenceToClass(
$calling_method_id,
$fq_class_name_lc
);
} else {
$this->file_reference_provider->addNonMethodReferenceToClass(
$code_location->file_path,
$fq_class_name_lc
);
if ($calling_fq_class_name) {
$class_storage = $this->classlike_storage_provider->get($calling_fq_class_name);
if ($class_storage->location
&& $class_storage->location->file_path !== $code_location->file_path
) {
$this->file_reference_provider->addNonMethodReferenceToClass(
$class_storage->location->file_path,
$fq_class_name_lc
);
}
}
}
}
if ($this->collect_locations && $code_location) {
$this->file_reference_provider->addCallingLocationForClass(
$code_location,
strtolower($fq_class_name)
);
}
return true;
}
public function hasFullyQualifiedTraitName(string $fq_class_name, ?CodeLocation $code_location = null): bool
2018-02-04 00:52:35 +01:00
{
$fq_class_name_lc = strtolower($this->getUnAliasedName($fq_class_name));
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;
}
if ($this->collect_references && $code_location) {
$this->file_reference_provider->addNonMethodReferenceToClass(
$code_location->file_path,
$fq_class_name_lc
);
2018-02-04 00:52:35 +01:00
}
return true;
}
/**
* Check whether a class/interface exists
*/
public function classOrInterfaceExists(
string $fq_class_name,
?CodeLocation $code_location = null,
?string $calling_fq_class_name = null,
?string $calling_method_id = null
): bool {
2021-09-25 02:34:21 +02:00
return $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);
2018-02-04 00:52:35 +01:00
}
/**
* Check whether a class/interface exists
*/
public function classOrInterfaceOrEnumExists(
string $fq_class_name,
?CodeLocation $code_location = null,
?string $calling_fq_class_name = null,
?string $calling_method_id = null
): bool {
2021-09-25 02:34:21 +02:00
return $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)
|| $this->enumExists($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 given class exists
*/
public function classExists(
string $fq_class_name,
?CodeLocation $code_location = null,
?string $calling_fq_class_name = null,
?string $calling_method_id = null
): bool {
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;
}
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
*
* @throws UnpopulatedClasslikeException when called on unpopulated class
2021-12-04 03:37:19 +01:00
* @throws InvalidArgumentException when class does not exist
2018-02-04 00:52:35 +01:00
*/
public function classExtends(string $fq_class_name, string $possible_parent, bool $from_api = false): bool
2018-02-04 00:52:35 +01:00
{
$unaliased_fq_class_name = $this->getUnAliasedName($fq_class_name);
$unaliased_fq_class_name_lc = strtolower($unaliased_fq_class_name);
2018-02-04 00:52:35 +01:00
if ($unaliased_fq_class_name_lc === 'generator') {
2018-02-04 00:52:35 +01:00
return false;
}
$class_storage = $this->classlike_storage_provider->get($unaliased_fq_class_name);
2018-11-29 06:05:56 +01:00
if ($from_api && !$class_storage->populated) {
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
*/
public function classImplements(string $fq_class_name, string $interface): bool
2018-02-04 00:52:35 +01:00
{
$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;
}
if ($interface_id === 'traversable' && $fq_class_name === 'iterator') {
return true;
}
if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[$interface_id])
|| isset(ClassLikeAnalyzer::SPECIAL_TYPES[$fq_class_name])
2018-02-04 00:52:35 +01:00
) {
return false;
}
$fq_class_name = $this->getUnAliasedName($fq_class_name);
2018-11-29 06:05:56 +01:00
if (!$this->classlike_storage_provider->has($fq_class_name)) {
return false;
}
2018-02-04 00:52:35 +01:00
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
if (isset($class_storage->class_implements[$interface_id])) {
return true;
}
foreach ($class_storage->class_implements as $implementing_interface_lc => $_) {
$aliased_interface_lc = strtolower(
$this->getUnAliasedName($implementing_interface_lc)
);
if ($aliased_interface_lc === $interface_id) {
return true;
}
}
return false;
2018-02-04 00:52:35 +01:00
}
public function interfaceExists(
string $fq_interface_name,
?CodeLocation $code_location = null,
?string $calling_fq_class_name = null,
?string $calling_method_id = null
): bool {
if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[strtolower($fq_interface_name)])) {
2018-02-04 00:52:35 +01:00
return false;
}
return $this->hasFullyQualifiedInterfaceName(
$fq_interface_name,
$code_location,
$calling_fq_class_name,
$calling_method_id
);
2018-02-04 00:52:35 +01:00
}
public function enumExists(
string $fq_enum_name,
?CodeLocation $code_location = null,
?string $calling_fq_class_name = null,
?string $calling_method_id = null
): bool {
if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[strtolower($fq_enum_name)])) {
return false;
}
return $this->hasFullyQualifiedEnumName(
$fq_enum_name,
$code_location,
$calling_fq_class_name,
$calling_method_id
);
}
public function interfaceExtends(string $interface_name, string $possible_parent): bool
2018-02-04 00:52:35 +01:00
{
return isset($this->getParentInterfaces($interface_name)[strtolower($possible_parent)]);
2018-02-04 00:52:35 +01:00
}
/**
* @return array<lowercase-string, string> all interfaces extended by $interface_name
2018-02-04 00:52:35 +01:00
*/
public function getParentInterfaces(string $fq_interface_name): array
2018-02-04 00:52:35 +01:00
{
$fq_interface_name = strtolower($fq_interface_name);
2021-09-25 02:34:21 +02:00
return $this->classlike_storage_provider->get($fq_interface_name)->parent_interfaces;
2018-02-04 00:52:35 +01:00
}
public function traitExists(string $fq_trait_name, ?CodeLocation $code_location = null): bool
2018-05-09 04:32:57 +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
*/
public function classHasCorrectCasing(string $fq_class_name): bool
2018-02-04 00:52:35 +01:00
{
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]);
}
public function interfaceHasCorrectCasing(string $fq_interface_name): bool
2018-02-04 00:52:35 +01:00
{
2018-11-29 06:05:56 +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]);
}
public function enumHasCorrectCasing(string $fq_enum_name): bool
{
if (isset($this->classlike_aliases[strtolower($fq_enum_name)])) {
return true;
}
return isset($this->existing_enums[$fq_enum_name]);
}
public function traitHasCorrectCase(string $fq_trait_name): bool
2018-02-04 00:52:35 +01:00
{
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]);
}
/**
* @param lowercase-string $fq_class_name
2018-02-04 00:52:35 +01:00
*/
2020-10-12 21:46:47 +02:00
public function isUserDefined(string $fq_class_name): bool
2018-02-04 00:52:35 +01:00
{
return $this->classlike_storage_provider->get($fq_class_name)->user_defined;
}
public function getTraitNode(string $fq_trait_name): PhpParser\Node\Stmt\Trait_
2018-02-04 00:52:35 +01:00
{
$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) {
2021-12-03 21:40:18 +01:00
throw new UnexpectedValueException('Storage should exist for ' . $fq_trait_name);
2020-03-03 04:27:54 +01:00
}
2020-08-09 22:23:43 +02:00
$file_statements = $this->statements_provider->getStatementsForFile($storage->location->file_path, '7.4');
2020-03-03 04:27:54 +01:00
2021-12-03 20:11:20 +01:00
$trait_finder = new TraitFinder($fq_trait_name);
2018-02-04 00:52:35 +01:00
2021-12-03 20:11:20 +01:00
$traverser = new NodeTraverser();
$traverser->addVisitor(
$trait_finder
);
2020-03-03 05:07:21 +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;
return $trait_node;
2018-02-04 00:52:35 +01:00
}
2021-12-03 21:40:18 +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
/**
* @param lowercase-string $alias_name
2018-11-29 06:05:56 +01:00
*/
public function addClassAlias(string $fq_class_name, string $alias_name): void
2018-11-29 06:05:56 +01:00
{
$this->classlike_aliases[$alias_name] = $fq_class_name;
2018-11-29 06:05:56 +01:00
}
2020-09-20 00:24:36 +02:00
public function getUnAliasedName(string $alias_name): string
2018-11-29 06:05:56 +01:00
{
$alias_name_lc = strtolower($alias_name);
if ($this->existing_classlikes_lc[$alias_name_lc] ?? false) {
return $alias_name;
}
$result = $this->classlike_aliases[$alias_name_lc] ?? $alias_name;
if ($result === $alias_name) {
return $result;
}
return $this->getUnAliasedName($result);
2018-11-29 06:05:56 +01:00
}
public function consolidateAnalyzedData(Methods $methods, ?Progress $progress, bool $find_unused_code): void
2018-02-04 00:52:35 +01:00
{
if ($progress === null) {
$progress = new VoidProgress();
}
$progress->debug('Checking class references' . PHP_EOL);
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
$codebase = $project_analyzer->getCodebase();
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);
2021-12-03 21:40:18 +01:00
} catch (InvalidArgumentException $e) {
2018-02-04 00:52:35 +01:00
continue;
}
if ($classlike_storage->location
&& $this->config->isInProjectDirs($classlike_storage->location->file_path)
&& !$classlike_storage->is_trait
2018-02-04 00:52:35 +01:00
) {
if ($find_unused_code) {
if (!$this->file_reference_provider->isClassReferenced($fq_class_name_lc)) {
IssueBuffer::maybeAdd(
new UnusedClass(
'Class ' . $classlike_storage->name . ' is never used',
$classlike_storage->location,
$classlike_storage->name
),
$classlike_storage->suppressed_issues
);
} else {
$this->checkMethodReferences($classlike_storage, $methods);
$this->checkPropertyReferences($classlike_storage);
2018-02-04 00:52:35 +01:00
}
}
$this->findPossibleMethodParamTypes($classlike_storage);
if ($codebase->alter_code
&& isset($project_analyzer->getIssuesToFix()['MissingImmutableAnnotation'])
&& !isset($codebase->analyzer->mutable_classes[$fq_class_name_lc])
&& !$classlike_storage->external_mutation_free
&& $classlike_storage->properties
&& isset($classlike_storage->methods['__construct'])
) {
$stmts = $codebase->getStatementsForFile(
$classlike_storage->location->file_path
);
foreach ($stmts as $stmt) {
if ($stmt instanceof PhpParser\Node\Stmt\Namespace_) {
foreach ($stmt->stmts as $namespace_stmt) {
if ($namespace_stmt instanceof PhpParser\Node\Stmt\Class_
2021-12-03 21:07:25 +01:00
&& strtolower((string) $stmt->name . '\\' . (string) $namespace_stmt->name)
=== $fq_class_name_lc
) {
self::makeImmutable(
$namespace_stmt,
$project_analyzer,
$classlike_storage->location->file_path
);
}
}
} elseif ($stmt instanceof PhpParser\Node\Stmt\Class_
2021-12-03 21:07:25 +01:00
&& strtolower((string) $stmt->name) === $fq_class_name_lc
) {
self::makeImmutable(
$stmt,
$project_analyzer,
$classlike_storage->location->file_path
);
}
}
}
2018-02-04 00:52:35 +01:00
}
}
}
2020-09-20 00:24:36 +02:00
public static function makeImmutable(
PhpParser\Node\Stmt\Class_ $class_stmt,
2021-12-03 20:11:20 +01:00
ProjectAnalyzer $project_analyzer,
string $file_path
): void {
$manipulator = ClassDocblockManipulator::getForClass(
$project_analyzer,
$file_path,
$class_stmt
);
$manipulator->makeImmutable();
}
public function moveMethods(Methods $methods, ?Progress $progress = null): void
{
if ($progress === null) {
$progress = new VoidProgress();
}
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
$codebase = $project_analyzer->getCodebase();
2019-06-02 18:02:32 +02:00
if (!$codebase->methods_to_move) {
return;
}
2019-06-04 06:32:19 +02:00
$progress->debug('Refactoring methods ' . PHP_EOL);
$code_migrations = [];
2019-06-04 06:32:19 +02:00
foreach ($codebase->methods_to_move as $source => $destination) {
$source_parts = explode('::', $source);
try {
$source_method_storage = $methods->getStorage(
2021-12-03 20:11:20 +01:00
new MethodIdentifier(...$source_parts)
);
2021-12-03 21:40:18 +01:00
} catch (InvalidArgumentException $e) {
continue;
}
[$destination_fq_class_name, $destination_name] = explode('::', $destination);
try {
2019-06-04 06:32:19 +02:00
$classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name);
2021-12-03 21:40:18 +01: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
) {
$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-04 06:32:19 +02:00
$old_method_name_bounds = $source_method_storage->location->getSelectionBounds();
FileManipulationBuffer::add(
2019-06-04 06:32:19 +02:00
$source_method_storage->stmt_location->file_path,
[
2021-12-03 20:11:20 +01:00
new 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
),
]
);
$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;
}
2021-12-03 20:11:20 +01:00
$code_migrations[] = new CodeMigration(
2019-06-04 06:32:19 +02:00
$source_method_storage->stmt_location->file_path,
$old_method_bounds[0],
$old_method_bounds[1],
$classlike_storage->stmt_location->file_path,
$new_class_bounds[0] + $insert_pos
);
}
}
FileManipulationBuffer::addCodeMigrations($code_migrations);
}
public function moveProperties(Properties $properties, ?Progress $progress = null): void
2019-06-04 06:32:19 +02:00
{
if ($progress === null) {
$progress = new VoidProgress();
}
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
2019-06-04 06:32:19 +02:00
$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);
2021-12-03 21:40:18 +01:00
} catch (InvalidArgumentException $e) {
2019-06-04 06:32:19 +02:00
continue;
}
[$source_fq_class_name] = explode('::$', $source);
[$destination_fq_class_name, $destination_name] = explode('::$', $destination);
2019-06-04 06:32:19 +02:00
$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();
2021-12-03 20:11:20 +01:00
$replace_type = TypeExpander::expandUnion(
2019-06-04 06:32:19 +02:00
$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,
[
2021-12-03 20:11:20 +01:00
new FileManipulation(
2019-06-04 06:32:19 +02:00
$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
}
2021-12-03 20:11:20 +01:00
$code_migrations[] = new CodeMigration(
2019-06-04 06:32:19 +02:00
$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);
}
public function moveClassConstants(?Progress $progress = null): void
2019-06-04 17:14:49 +02:00
{
if ($progress === null) {
$progress = new VoidProgress();
}
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
2019-06-04 17:14:49 +02:00
$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) {
[$source_fq_class_name, $source_const_name] = explode('::', $source);
[$destination_fq_class_name, $destination_name] = explode('::', $destination);
2019-06-04 17:14:49 +02:00
$source_classlike_storage = $this->classlike_storage_provider->get($source_fq_class_name);
$destination_classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name);
$constant_storage = $source_classlike_storage->constants[$source_const_name];
$source_const_stmt_location = $constant_storage->stmt_location;
$source_const_location = $constant_storage->location;
if (!$source_const_location || !$source_const_stmt_location) {
continue;
}
2019-06-04 17:14:49 +02:00
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,
[
2021-12-03 20:11:20 +01:00
new FileManipulation(
2019-06-04 17:14:49 +02:00
$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
}
2021-12-03 20:11:20 +01:00
$code_migrations[] = new CodeMigration(
2019-06-04 17:14:49 +02:00
$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);
}
/**
* @param lowercase-string|null $calling_method_id
*/
2019-06-04 22:36:32 +02:00
public function handleClassLikeReferenceInMigration(
2021-12-03 20:11:20 +01:00
Codebase $codebase,
StatementsSource $source,
2019-06-04 22:36:32 +02:00
PhpParser\Node $class_name_node,
string $fq_class_name,
?string $calling_method_id,
2019-11-17 01:59:08 +01:00
bool $force_change = false,
bool $was_self = false
): bool {
if ($class_name_node instanceof VirtualNode) {
return false;
}
2019-06-04 22:36:32 +02:00
$calling_fq_class_name = $source->getFQCLN();
// if we're inside a moved class static method
if ($codebase->methods_to_move
&& $calling_fq_class_name
&& $calling_method_id
&& isset($codebase->methods_to_move[$calling_method_id])
2019-06-04 22:36:32 +02: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'),
(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 = [];
2021-12-03 20:11:20 +01:00
$file_manipulations[] = new FileManipulation(
2019-06-04 22:36:32 +02:00
(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, -1);
2019-06-10 23:09:34 +02:00
$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 ($old_class_name === strtolower($alias)) {
2019-06-07 00:46:40 +02:00
$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
2021-12-03 20:11:20 +01:00
$file_manipulations[] = new FileManipulation(
2019-06-06 04:13:33 +02:00
(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
)
. ($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 = [];
2021-12-03 20:11:20 +01:00
$file_manipulations[] = new FileManipulation(
2019-06-10 23:09:34 +02:00
(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'),
(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;
}
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 = [];
2021-12-03 20:11:20 +01:00
$file_manipulations[] = new 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);
}
return true;
}
2019-06-04 22:36:32 +02:00
return false;
}
/**
* @param lowercase-string|null $calling_method_id
*/
2019-06-04 22:36:32 +02:00
public function handleDocblockTypeInMigration(
2021-12-03 20:11:20 +01:00
Codebase $codebase,
StatementsSource $source,
2021-12-13 16:28:14 +01:00
Union $type,
2019-06-04 22:36:32 +02:00
CodeLocation $type_location,
?string $calling_method_id
): void {
2019-06-04 22:36:32 +02:00
$calling_fq_class_name = $source->getFQCLN();
2021-10-13 18:29:25 +02:00
$fq_class_name_lc = strtolower($calling_fq_class_name ?? '');
2019-06-04 22:36:32 +02:00
$moved_type = false;
// if we're inside a moved class static method
if ($codebase->methods_to_move
&& $calling_fq_class_name
&& $calling_method_id
&& isset($codebase->methods_to_move[$calling_method_id])
2019-06-04 22:36:32 +02:00
) {
$bounds = $type_location->getSelectionBounds();
$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
2021-10-13 18:29:25 +02:00
&& isset($codebase->class_transforms[$fq_class_name_lc])
2019-06-11 16:33:52 +02:00
) {
2021-10-13 18:29:25 +02:00
$migrated_source_fqcln = $codebase->class_transforms[$fq_class_name_lc];
2019-06-11 16:33:52 +02:00
}
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, -1);
2019-06-11 16:33:52 +02:00
$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 ($old_class_name === strtolower($alias)) {
2019-06-11 16:33:52 +02:00
$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
2021-12-03 20:11:20 +01:00
$file_manipulations[] = new FileManipulation(
2019-06-04 22:36:32 +02:00
$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
2021-10-13 18:29:25 +02:00
&& isset($codebase->classes_to_move[$fq_class_name_lc])
2019-06-10 23:09:34 +02:00
) {
$bounds = $type_location->getSelectionBounds();
2021-10-13 18:29:25 +02:00
$destination_class = $codebase->classes_to_move[$fq_class_name_lc];
2019-06-10 23:09:34 +02:00
2021-10-13 18:29:25 +02:00
if ($type->containsClassLike($fq_class_name_lc)) {
2019-06-10 23:09:34 +02:00
$type = clone $type;
2021-10-13 18:29:25 +02:00
$type->replaceClassLike($fq_class_name_lc, $destination_class);
2019-06-10 23:09:34 +02:00
}
$this->airliftClassDefinedDocblockType(
$type,
$destination_class,
$source->getFilePath(),
$bounds[0],
$bounds[1]
);
}
2019-06-04 22:36:32 +02:00
}
public function airliftClassLikeReference(
string $fq_class_name,
string $destination_fq_class_name,
string $source_file_path,
int $source_start,
int $source_end,
2019-11-17 01:59:08 +01:00
bool $add_class_constant = false,
bool $allow_self = false
): void {
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
$codebase = $project_analyzer->getCodebase();
$destination_class_storage = $codebase->classlike_storage_provider->get($destination_fq_class_name);
if (!$destination_class_storage->aliases) {
2021-12-03 21:40:18 +01:00
throw new UnexpectedValueException('Aliases should not be null');
}
$file_manipulations = [];
2021-12-03 20:11:20 +01:00
$file_manipulations[] = new 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
) . ($add_class_constant ? '::class' : '')
);
FileManipulationBuffer::add(
$source_file_path,
$file_manipulations
);
}
2019-06-10 23:09:34 +02:00
public function airliftClassDefinedDocblockType(
2021-12-13 16:28:14 +01:00
Union $type,
2019-06-01 18:25:57 +02:00
string $destination_fq_class_name,
string $source_file_path,
int $source_start,
int $source_end
): void {
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
2019-06-01 18:25:57 +02:00
$codebase = $project_analyzer->getCodebase();
$destination_class_storage = $codebase->classlike_storage_provider->get($destination_fq_class_name);
if (!$destination_class_storage->aliases) {
2021-12-03 21:40:18 +01:00
throw new UnexpectedValueException('Aliases should not be null');
2019-06-01 18:25:57 +02:00
}
$file_manipulations = [];
2021-12-03 20:11:20 +01:00
$file_manipulations[] = new FileManipulation(
2019-06-01 18:25:57 +02:00
$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
);
}
/**
* @param ReflectionProperty::IS_PUBLIC|ReflectionProperty::IS_PROTECTED|ReflectionProperty::IS_PRIVATE
* $visibility
*
2021-12-04 03:37:19 +01:00
* @return array<string, ClassConstantStorage>
*/
public function getConstantsForClass(string $class_name, int $visibility): array
{
$class_name = strtolower($class_name);
$storage = $this->classlike_storage_provider->get($class_name);
if ($visibility === ReflectionProperty::IS_PUBLIC) {
2021-12-03 21:07:25 +01:00
return array_filter(
$storage->constants,
function ($constant) {
return $constant->type
&& $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC;
}
);
}
if ($visibility === ReflectionProperty::IS_PROTECTED) {
2021-12-03 21:07:25 +01:00
return array_filter(
$storage->constants,
function ($constant) {
return $constant->type
&& ($constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC
|| $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED);
}
);
}
2021-12-03 21:07:25 +01:00
return array_filter(
$storage->constants,
function ($constant) {
return $constant->type !== null;
}
);
}
/**
* @param ReflectionProperty::IS_PUBLIC|ReflectionProperty::IS_PROTECTED|ReflectionProperty::IS_PRIVATE
* $visibility
*/
public function getClassConstantType(
string $class_name,
string $constant_name,
int $visibility,
2021-12-03 20:11:20 +01:00
?StatementsAnalyzer $statements_analyzer = null,
array $visited_constant_ids = []
2021-12-13 16:28:14 +01:00
): ?Union {
$class_name = strtolower($class_name);
if (!$this->classlike_storage_provider->has($class_name)) {
return null;
}
$storage = $this->classlike_storage_provider->get($class_name);
if (isset($storage->constants[$constant_name])) {
$constant_storage = $storage->constants[$constant_name];
if ($visibility === ReflectionProperty::IS_PUBLIC
&& $constant_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PUBLIC
) {
return null;
}
if ($visibility === ReflectionProperty::IS_PROTECTED
&& $constant_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PUBLIC
&& $constant_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PROTECTED
) {
return null;
}
if ($constant_storage->unresolved_node) {
2021-12-13 16:28:14 +01:00
$constant_storage->type = new Union([ConstantTypeResolver::resolve(
$this,
$constant_storage->unresolved_node,
$statements_analyzer,
$visited_constant_ids
)]);
}
return $constant_storage->type;
} elseif (isset($storage->enum_cases[$constant_name])) {
2021-12-13 16:28:14 +01:00
return new Union([new TEnumCase($storage->name, $constant_name)]);
}
return null;
}
private function checkMethodReferences(ClassLikeStorage $classlike_storage, Methods $methods): void
2018-02-04 00:52:35 +01:00
{
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
$codebase = $project_analyzer->getCodebase();
foreach ($classlike_storage->appearing_method_ids as $method_name => $appearing_method_id) {
$appearing_fq_classlike_name = $appearing_method_id->fq_class_name;
if ($appearing_fq_classlike_name !== $classlike_storage->name) {
continue;
}
$method_id = $appearing_method_id;
2019-04-17 21:12:52 +02:00
$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];
$declaring_fq_classlike_name = $declaring_method_id->fq_class_name;
$declaring_method_name = $declaring_method_id->method_name;
try {
$declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name);
2021-12-03 21:40:18 +01:00
} 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;
}
$method_referenced = $this->file_reference_provider->isClassMethodReferenced(
strtolower((string) $method_id)
);
if (!$method_referenced
2018-02-04 00:52:35 +01:00
&& $method_storage->location
) {
if ($method_name !== '__destruct'
&& $method_name !== '__clone'
&& $method_name !== '__invoke'
&& $method_name !== '__unset'
&& $method_name !== '__isset'
&& $method_name !== '__sleep'
&& $method_name !== '__wakeup'
&& $method_name !== '__serialize'
&& $method_name !== '__unserialize'
&& $method_name !== '__set_state'
&& $method_name !== '__debuginfo'
&& $method_name !== '__tostring' // can be called in array_unique)
) {
$method_location = $method_storage->location;
$method_id = $classlike_storage->name . '::' . $method_storage->cased_name;
2018-02-04 00:52:35 +01:00
if ($method_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE) {
$has_parent_references = false;
2018-02-04 00:52:35 +01:00
if ($codebase->classImplements($classlike_storage->name, 'Serializable')
&& ($method_name === 'serialize' || $method_name === 'unserialize')
) {
continue;
}
$has_variable_calls = $codebase->analyzer->hasMixedMemberName($method_name)
|| $codebase->analyzer->hasMixedMemberName(strtolower($classlike_storage->name . '::'));
if (isset($classlike_storage->overridden_method_ids[$method_name])) {
foreach ($classlike_storage->overridden_method_ids[$method_name] as $parent_method_id) {
$parent_method_storage = $methods->getStorage($parent_method_id);
2018-02-04 00:52:35 +01:00
if ($parent_method_storage->location
&& !$project_analyzer->canReportIssues($parent_method_storage->location->file_path)
) {
// here we just dont know
$has_parent_references = true;
break;
}
$parent_method_referenced = $this->file_reference_provider->isClassMethodReferenced(
strtolower((string) $parent_method_id)
);
if (!$parent_method_storage->abstract || $parent_method_referenced) {
$has_parent_references = true;
break;
}
}
2018-02-04 00:52:35 +01:00
}
foreach ($classlike_storage->parent_classes as $parent_method_fqcln) {
if ($codebase->analyzer->hasMixedMemberName(
strtolower($parent_method_fqcln) . '::'
)) {
$has_variable_calls = true;
break;
}
}
foreach ($classlike_storage->class_implements as $fq_interface_name_lc => $_) {
try {
$interface_storage = $this->classlike_storage_provider->get($fq_interface_name_lc);
2021-12-03 21:40:18 +01:00
} catch (InvalidArgumentException $e) {
continue;
}
if ($codebase->analyzer->hasMixedMemberName(
$fq_interface_name_lc . '::'
)) {
$has_variable_calls = true;
}
if (isset($interface_storage->methods[$method_name])) {
$interface_method_referenced = $this->file_reference_provider->isClassMethodReferenced(
$fq_interface_name_lc . '::' . $method_name
);
if ($interface_method_referenced) {
$has_parent_references = true;
}
}
}
if (!$has_parent_references) {
$issue = new PossiblyUnusedMethod(
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
. ' calls to method ' . $method_id
. ($has_variable_calls ? ' (but did find some potential callers)' : ''),
$method_storage->location,
$method_id
);
2018-02-04 00:52:35 +01:00
if ($codebase->alter_code) {
if ($method_storage->stmt_location
&& !$declaring_classlike_storage->is_trait
&& isset($project_analyzer->getIssuesToFix()['PossiblyUnusedMethod'])
&& !$has_variable_calls
&& !IssueBuffer::isSuppressed($issue, $method_storage->suppressed_issues)
) {
FileManipulationBuffer::addForCodeLocation(
$method_storage->stmt_location,
'',
true
);
}
} elseif (IssueBuffer::accepts(
$issue,
$method_storage->suppressed_issues,
$method_storage->stmt_location
&& !$declaring_classlike_storage->is_trait
&& !$has_variable_calls
)) {
// fall through
2018-02-04 00:52:35 +01:00
}
}
} elseif (!isset($classlike_storage->declaring_method_ids['__call'])) {
$has_variable_calls = $codebase->analyzer->hasMixedMemberName(
strtolower($classlike_storage->name . '::')
) || $codebase->analyzer->hasMixedMemberName($method_name);
if ($method_name === '__construct') {
$issue = new UnusedConstructor(
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
. ' calls to private constructor ' . $method_id
. ($has_variable_calls ? ' (but did find some potential callers)' : ''),
$method_location,
$method_id
);
} else {
$issue = new UnusedMethod(
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
. ' calls to private method ' . $method_id
. ($has_variable_calls ? ' (but did find some potential callers)' : ''),
$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
&& isset($project_analyzer->getIssuesToFix()['UnusedMethod'])
&& !$has_variable_calls
&& !IssueBuffer::isSuppressed($issue, $method_storage->suppressed_issues)
) {
FileManipulationBuffer::addForCodeLocation(
$method_storage->stmt_location,
2019-04-17 19:56:47 +02:00
'',
true
);
}
} elseif (IssueBuffer::accepts(
$issue,
$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 {
if ($method_storage->return_type
&& $method_storage->return_type_location
&& !$method_storage->return_type->isVoid()
&& !$method_storage->return_type->isNever()
&& $method_id->method_name !== '__tostring'
2021-06-12 23:09:42 +02:00
&& ($method_storage->is_static || !$method_storage->probably_fluent)
) {
$method_return_referenced = $this->file_reference_provider->isMethodReturnReferenced(
strtolower((string) $method_id)
);
if (!$method_return_referenced) {
if ($method_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE) {
IssueBuffer::maybeAdd(
2021-12-03 20:11:20 +01:00
new UnusedReturnValue(
'The return value for this private method is never used',
$method_storage->return_type_location
),
$method_storage->suppressed_issues
);
} else {
IssueBuffer::maybeAdd(
2021-12-03 20:11:20 +01:00
new PossiblyUnusedReturnValue(
'The return value for this method is never used',
$method_storage->return_type_location
),
$method_storage->suppressed_issues
);
}
}
}
if ($method_storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE
&& !$classlike_storage->is_interface
) {
foreach ($method_storage->params as $offset => $param_storage) {
if (empty($classlike_storage->overridden_method_ids[$method_name])
&& $param_storage->location
&& !$param_storage->promoted_property
&& !$this->file_reference_provider->isMethodParamUsed(
strtolower((string) $method_id),
$offset
)
) {
if ($method_storage->final) {
IssueBuffer::maybeAdd(
2021-12-03 20:11:20 +01:00
new UnusedParam(
'Param #' . ($offset + 1) . ' is never referenced in this method',
$param_storage->location
),
$method_storage->suppressed_issues
);
} else {
IssueBuffer::maybeAdd(
new PossiblyUnusedParam(
'Param #' . ($offset + 1) . ' is never referenced in this method',
$param_storage->location
),
$method_storage->suppressed_issues
);
}
2018-02-04 00:52:35 +01:00
}
}
}
}
}
}
private function findPossibleMethodParamTypes(ClassLikeStorage $classlike_storage): void
{
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
$codebase = $project_analyzer->getCodebase();
foreach ($classlike_storage->appearing_method_ids as $method_name => $appearing_method_id) {
$appearing_fq_classlike_name = $appearing_method_id->fq_class_name;
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];
$declaring_fq_classlike_name = $declaring_method_id->fq_class_name;
$declaring_method_name = $declaring_method_id->method_name;
try {
$declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name);
2021-12-03 21:40:18 +01:00
} 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;
}
if ($declaring_classlike_storage->is_trait) {
continue;
}
$method_id_lc = strtolower((string) $method_id);
if (isset($codebase->analyzer->possible_method_param_types[$method_id_lc])) {
if ($method_storage->location) {
$possible_param_types
= $codebase->analyzer->possible_method_param_types[$method_id_lc];
if ($possible_param_types) {
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) {
2021-12-13 16:28:14 +01:00
if ($method_storage->params[$offset]->default_type instanceof Union) {
$default_type = clone $method_storage->params[$offset]->default_type;
} else {
2021-12-03 20:11:20 +01:00
$default_type_atomic = ConstantTypeResolver::resolve(
$codebase->classlikes,
$method_storage->params[$offset]->default_type,
null
);
2021-12-13 16:28:14 +01:00
$default_type = new Union([$default_type_atomic]);
}
2021-12-03 20:11:20 +01:00
$possible_type = Type::combineUnionTypes(
$possible_type,
$default_type
);
}
if ($codebase->alter_code
&& isset($project_analyzer->getIssuesToFix()['MissingParamType'])
) {
$function_analyzer = $project_analyzer->getFunctionLikeAnalyzer(
$method_id,
$method_storage->location->file_path
);
2019-12-03 07:59:36 +01:00
$has_variable_calls = $codebase->analyzer->hasMixedMemberName(
$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;
}
if ($function_analyzer) {
$function_analyzer->addOrUpdateParamType(
$project_analyzer,
$param_name,
$possible_type,
$possible_type->from_docblock
&& $project_analyzer->only_replace_php_types_with_non_docblock_types
);
}
} else {
IssueBuffer::addFixableIssue('MissingParamType');
}
}
}
}
}
}
}
private function checkPropertyReferences(ClassLikeStorage $classlike_storage): void
{
2021-12-03 20:11:20 +01:00
$project_analyzer = ProjectAnalyzer::getInstance();
$codebase = $project_analyzer->getCodebase();
2018-02-04 00:52:35 +01:00
foreach ($classlike_storage->properties as $property_name => $property_storage) {
$referenced_property_name = strtolower($classlike_storage->name) . '::$' . $property_name;
$property_referenced = $this->file_reference_provider->isClassPropertyReferenced(
$referenced_property_name
);
$property_constructor_referenced = false;
if ($property_referenced && $property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE) {
$all_method_references = $this->file_reference_provider->getAllMethodReferencesToClassProperties();
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
&& $property_storage->location
) {
$property_id = $classlike_storage->name . '::$' . $property_name;
if ($property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC
|| $property_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED
) {
$has_parent_references = isset($classlike_storage->overridden_property_ids[$property_name]);
$has_variable_calls = $codebase->analyzer->hasMixedMemberName('$' . $property_name)
|| $codebase->analyzer->hasMixedMemberName(strtolower($classlike_storage->name) . '::$');
foreach ($classlike_storage->parent_classes as $parent_method_fqcln) {
if ($codebase->analyzer->hasMixedMemberName(
strtolower($parent_method_fqcln) . '::$'
)) {
$has_variable_calls = true;
break;
}
}
foreach ($classlike_storage->class_implements as $fq_interface_name) {
if ($codebase->analyzer->hasMixedMemberName(
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']))
) {
$issue = new PossiblyUnusedProperty(
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
. ' references to property ' . $property_id
. ($has_variable_calls ? ' (but did find some potential references)' : ''),
$property_storage->location
);
if ($codebase->alter_code) {
if ($property_storage->stmt_location
&& isset($project_analyzer->getIssuesToFix()['PossiblyUnusedProperty'])
&& !$has_variable_calls
&& !IssueBuffer::isSuppressed($issue, $classlike_storage->suppressed_issues)
) {
FileManipulationBuffer::addForCodeLocation(
$property_storage->stmt_location,
'',
true
);
}
} elseif (IssueBuffer::accepts(
$issue,
$classlike_storage->suppressed_issues + $property_storage->suppressed_issues
)) {
// fall through
}
2018-02-04 00:52:35 +01:00
}
} elseif (!isset($classlike_storage->declaring_method_ids['__get'])) {
$has_variable_calls = $codebase->analyzer->hasMixedMemberName('$' . $property_name);
$issue = new UnusedProperty(
'Cannot find ' . ($has_variable_calls ? 'explicit' : 'any')
. ' references to private property ' . $property_id
. ($has_variable_calls ? ' (but did find some potential references)' : ''),
$property_storage->location
);
if ($codebase->alter_code) {
if (!$property_constructor_referenced
&& $property_storage->stmt_location
&& isset($project_analyzer->getIssuesToFix()['UnusedProperty'])
&& !$has_variable_calls
&& !IssueBuffer::isSuppressed($issue, $classlike_storage->suppressed_issues)
) {
FileManipulationBuffer::addForCodeLocation(
$property_storage->stmt_location,
2019-04-17 19:56:47 +02:00
'',
true
);
}
} elseif (IssueBuffer::accepts(
$issue,
$classlike_storage->suppressed_issues + $property_storage->suppressed_issues
2018-02-04 00:52:35 +01:00
)) {
// fall through
}
}
}
}
}
/**
* @param lowercase-string $fq_classlike_name_lc
2018-02-04 00:52:35 +01:00
*/
2020-10-12 21:46:47 +02:00
public function registerMissingClassLike(string $fq_classlike_name_lc): void
2018-02-04 00:52:35 +01:00
{
$this->existing_classlikes_lc[$fq_classlike_name_lc] = false;
}
/**
* @param lowercase-string $fq_classlike_name_lc
2018-02-04 00:52:35 +01:00
*/
2020-10-12 21:46:47 +02:00
public function isMissingClassLike(string $fq_classlike_name_lc): bool
2018-02-04 00:52:35 +01:00
{
return isset($this->existing_classlikes_lc[$fq_classlike_name_lc])
&& $this->existing_classlikes_lc[$fq_classlike_name_lc] === false;
}
/**
* @param lowercase-string $fq_classlike_name_lc
2018-02-04 00:52:35 +01:00
*/
2020-10-12 21:46:47 +02:00
public function doesClassLikeExist(string $fq_classlike_name_lc): bool
2018-02-04 00:52:35 +01:00
{
return isset($this->existing_classlikes_lc[$fq_classlike_name_lc])
&& $this->existing_classlikes_lc[$fq_classlike_name_lc];
}
public function forgetMissingClassLikes(): void
{
2021-12-03 21:07:25 +01:00
$this->existing_classlikes_lc = array_filter($this->existing_classlikes_lc);
}
public function removeClassLike(string $fq_class_name): void
{
$fq_class_name_lc = strtolower($fq_class_name);
unset(
$this->existing_classlikes_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],
$this->existing_enums_lc[$fq_class_name_lc],
$this->existing_enums[$fq_class_name],
$this->existing_interfaces_lc[$fq_class_name_lc],
2018-10-12 05:00:32 +02:00
$this->existing_interfaces[$fq_class_name],
$this->existing_classes_lc[$fq_class_name_lc],
$this->existing_classes[$fq_class_name],
2020-03-03 04:30:06 +01:00
$this->trait_nodes[$fq_class_name_lc]
);
2018-10-12 05:00:32 +02:00
$this->scanner->removeClassLike($fq_class_name_lc);
}
/**
* @return array{
* array<lowercase-string, bool>,
* array<lowercase-string, bool>,
* array<lowercase-string, bool>,
* array<string, bool>,
* array<lowercase-string, bool>,
* array<string, bool>,
* array<lowercase-string, bool>,
* array<string, bool>,
* array<string, bool>,
* }
*/
public function getThreadData(): array
{
return [
$this->existing_classlikes_lc,
$this->existing_classes_lc,
$this->existing_traits_lc,
$this->existing_traits,
$this->existing_enums_lc,
$this->existing_enums,
$this->existing_interfaces_lc,
$this->existing_interfaces,
$this->existing_classes,
];
}
/**
* @param array{
* 0: array<lowercase-string, bool>,
* 1: array<lowercase-string, bool>,
* 2: array<lowercase-string, bool>,
* 3: array<string, bool>,
* 4: array<lowercase-string, bool>,
* 5: array<string, bool>,
* 6: array<lowercase-string, bool>,
* 7: array<string, bool>,
* 8: array<string, bool>,
* } $thread_data
*
*/
public function addThreadData(array $thread_data): void
{
[
$existing_classlikes_lc,
$existing_classes_lc,
$existing_traits_lc,
$existing_traits,
$existing_enums_lc,
$existing_enums,
$existing_interfaces_lc,
$existing_interfaces,
$existing_classes
] = $thread_data;
$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_enums_lc = array_merge($existing_enums_lc, $this->existing_enums_lc);
$this->existing_enums = array_merge($existing_enums, $this->existing_enums);
$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);
}
public function getStorageFor(string $fq_class_name): ?ClassLikeStorage
{
$fq_class_name = $this->getUnAliasedName($fq_class_name);
try {
return $this->classlike_storage_provider->get($fq_class_name);
2021-12-03 21:40:18 +01:00
} catch (InvalidArgumentException $e) {
return null;
}
}
2018-02-04 00:52:35 +01:00
}