2019-10-13 05:59:52 +00:00
|
|
|
<?php
|
|
|
|
|
2021-05-09 20:15:43 +00:00
|
|
|
declare (strict_types=1);
|
2022-06-06 17:12:56 +00:00
|
|
|
namespace Rector\CodingStyle\Application;
|
2019-05-19 21:44:38 +00:00
|
|
|
|
2024-03-01 20:02:28 +00:00
|
|
|
use RectorPrefix202403\Nette\Utils\Strings;
|
2023-03-23 23:21:34 +00:00
|
|
|
use PhpParser\Node\Name;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node\Stmt;
|
|
|
|
use PhpParser\Node\Stmt\Declare_;
|
|
|
|
use PhpParser\Node\Stmt\Namespace_;
|
|
|
|
use PhpParser\Node\Stmt\Nop;
|
|
|
|
use PhpParser\Node\Stmt\Use_;
|
|
|
|
use PHPStan\Type\ObjectType;
|
|
|
|
use Rector\CodingStyle\ClassNameImport\UsedImportsResolver;
|
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
|
|
|
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
|
2022-06-06 17:12:56 +00:00
|
|
|
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
|
|
|
|
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
|
2019-05-19 21:44:38 +00:00
|
|
|
final class UseImportsAdder
|
|
|
|
{
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\CodingStyle\ClassNameImport\UsedImportsResolver
|
2019-05-19 21:44:38 +00:00
|
|
|
*/
|
|
|
|
private $usedImportsResolver;
|
2021-07-21 13:46:30 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-07-21 13:46:30 +00:00
|
|
|
* @var \Rector\NodeTypeResolver\PHPStan\Type\TypeFactory
|
|
|
|
*/
|
|
|
|
private $typeFactory;
|
2022-06-07 08:22:29 +00:00
|
|
|
public function __construct(UsedImportsResolver $usedImportsResolver, TypeFactory $typeFactory)
|
2019-05-19 21:44:38 +00:00
|
|
|
{
|
|
|
|
$this->usedImportsResolver = $usedImportsResolver;
|
2021-07-21 13:46:30 +00:00
|
|
|
$this->typeFactory = $typeFactory;
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param Stmt[] $stmts
|
2021-07-21 14:23:45 +00:00
|
|
|
* @param array<FullyQualifiedObjectType|AliasedObjectType> $useImportTypes
|
2023-07-28 13:10:47 +00:00
|
|
|
* @param array<FullyQualifiedObjectType|AliasedObjectType> $constantUseImportTypes
|
2021-07-21 14:23:45 +00:00
|
|
|
* @param array<FullyQualifiedObjectType|AliasedObjectType> $functionUseImportTypes
|
2020-11-20 00:26:04 +00:00
|
|
|
* @return Stmt[]
|
2019-05-19 21:44:38 +00:00
|
|
|
*/
|
2023-07-28 13:10:47 +00:00
|
|
|
public function addImportsToStmts(FileWithoutNamespace $fileWithoutNamespace, array $stmts, array $useImportTypes, array $constantUseImportTypes, array $functionUseImportTypes) : array
|
2019-05-19 21:44:38 +00:00
|
|
|
{
|
2023-09-12 12:38:05 +00:00
|
|
|
$usedImports = $this->usedImportsResolver->resolveForStmts($stmts);
|
|
|
|
$existingUseImportTypes = $usedImports->getUseImports();
|
|
|
|
$existingConstantUseImports = $usedImports->getConstantImports();
|
|
|
|
$existingFunctionUseImports = $usedImports->getFunctionImports();
|
2019-09-06 10:30:58 +00:00
|
|
|
$useImportTypes = $this->diffFullyQualifiedObjectTypes($useImportTypes, $existingUseImportTypes);
|
2023-07-28 13:10:47 +00:00
|
|
|
$constantUseImportTypes = $this->diffFullyQualifiedObjectTypes($constantUseImportTypes, $existingConstantUseImports);
|
2021-05-09 20:15:43 +00:00
|
|
|
$functionUseImportTypes = $this->diffFullyQualifiedObjectTypes($functionUseImportTypes, $existingFunctionUseImports);
|
2023-07-28 13:10:47 +00:00
|
|
|
$newUses = $this->createUses($useImportTypes, $constantUseImportTypes, $functionUseImportTypes, null);
|
2021-03-05 00:16:22 +00:00
|
|
|
if ($newUses === []) {
|
2023-11-16 14:51:30 +00:00
|
|
|
return [$fileWithoutNamespace];
|
2021-03-05 00:16:22 +00:00
|
|
|
}
|
2019-10-03 06:53:23 +00:00
|
|
|
// place after declare strict_types
|
|
|
|
foreach ($stmts as $key => $stmt) {
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($stmt instanceof Declare_) {
|
|
|
|
if (isset($stmts[$key + 1]) && $stmts[$key + 1] instanceof Use_) {
|
2020-11-27 12:39:01 +00:00
|
|
|
$nodesToAdd = $newUses;
|
|
|
|
} else {
|
|
|
|
// add extra space, if there are no new use imports to be added
|
2022-06-07 08:22:29 +00:00
|
|
|
$nodesToAdd = \array_merge([new Nop()], $newUses);
|
2020-11-27 12:39:01 +00:00
|
|
|
}
|
2023-03-08 15:24:58 +00:00
|
|
|
$this->mirrorUseComments($stmts, $newUses, $key + 1);
|
2021-05-09 20:15:43 +00:00
|
|
|
\array_splice($stmts, $key + 1, 0, $nodesToAdd);
|
2023-06-27 17:32:34 +00:00
|
|
|
$fileWithoutNamespace->stmts = $stmts;
|
|
|
|
$fileWithoutNamespace->stmts = \array_values($fileWithoutNamespace->stmts);
|
2023-11-16 14:51:30 +00:00
|
|
|
return [$fileWithoutNamespace];
|
2019-10-03 06:53:23 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-08 15:24:58 +00:00
|
|
|
$this->mirrorUseComments($stmts, $newUses);
|
2019-10-03 06:53:23 +00:00
|
|
|
// make use stmts first
|
2023-06-27 17:32:34 +00:00
|
|
|
$fileWithoutNamespace->stmts = \array_merge($newUses, $stmts);
|
|
|
|
$fileWithoutNamespace->stmts = \array_values($fileWithoutNamespace->stmts);
|
2023-11-16 14:51:30 +00:00
|
|
|
return [$fileWithoutNamespace];
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
|
|
|
/**
|
2019-09-06 10:30:58 +00:00
|
|
|
* @param FullyQualifiedObjectType[] $useImportTypes
|
2023-07-28 13:10:47 +00:00
|
|
|
* @param FullyQualifiedObjectType[] $constantUseImportTypes
|
2019-09-06 10:30:58 +00:00
|
|
|
* @param FullyQualifiedObjectType[] $functionUseImportTypes
|
2019-05-19 21:44:38 +00:00
|
|
|
*/
|
2023-07-28 13:10:47 +00:00
|
|
|
public function addImportsToNamespace(Namespace_ $namespace, array $useImportTypes, array $constantUseImportTypes, array $functionUseImportTypes) : void
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2019-05-19 21:44:38 +00:00
|
|
|
$namespaceName = $this->getNamespaceName($namespace);
|
2023-09-12 12:38:05 +00:00
|
|
|
$existingUsedImports = $this->usedImportsResolver->resolveForStmts($namespace->stmts);
|
|
|
|
$existingUseImportTypes = $existingUsedImports->getUseImports();
|
|
|
|
$existingConstantUseImportTypes = $existingUsedImports->getConstantImports();
|
|
|
|
$existingFunctionUseImportTypes = $existingUsedImports->getFunctionImports();
|
2021-07-21 14:23:45 +00:00
|
|
|
$existingUseImportTypes = $this->typeFactory->uniquateTypes($existingUseImportTypes);
|
2019-09-06 10:30:58 +00:00
|
|
|
$useImportTypes = $this->diffFullyQualifiedObjectTypes($useImportTypes, $existingUseImportTypes);
|
2023-07-28 13:10:47 +00:00
|
|
|
$constantUseImportTypes = $this->diffFullyQualifiedObjectTypes($constantUseImportTypes, $existingConstantUseImportTypes);
|
2021-05-09 20:15:43 +00:00
|
|
|
$functionUseImportTypes = $this->diffFullyQualifiedObjectTypes($functionUseImportTypes, $existingFunctionUseImportTypes);
|
2023-07-28 13:10:47 +00:00
|
|
|
$newUses = $this->createUses($useImportTypes, $constantUseImportTypes, $functionUseImportTypes, $namespaceName);
|
2023-03-07 13:36:57 +00:00
|
|
|
if ($newUses === []) {
|
|
|
|
return;
|
|
|
|
}
|
2023-03-08 15:24:58 +00:00
|
|
|
$this->mirrorUseComments($namespace->stmts, $newUses);
|
2021-05-09 20:15:43 +00:00
|
|
|
$namespace->stmts = \array_merge($newUses, $namespace->stmts);
|
2023-06-27 17:32:34 +00:00
|
|
|
$namespace->stmts = \array_values($namespace->stmts);
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
2023-03-12 00:37:04 +00:00
|
|
|
/**
|
|
|
|
* @param Stmt[] $stmts
|
|
|
|
* @param Use_[] $newUses
|
|
|
|
*/
|
|
|
|
private function mirrorUseComments(array $stmts, array $newUses, int $indexStmt = 0) : void
|
|
|
|
{
|
|
|
|
if ($stmts === []) {
|
|
|
|
return;
|
|
|
|
}
|
2023-05-29 14:02:39 +00:00
|
|
|
if (isset($stmts[$indexStmt]) && $stmts[$indexStmt] instanceof Use_) {
|
2023-03-12 00:37:04 +00:00
|
|
|
$comments = (array) $stmts[$indexStmt]->getAttribute(AttributeKey::COMMENTS);
|
|
|
|
if ($comments !== []) {
|
|
|
|
$newUses[0]->setAttribute(AttributeKey::COMMENTS, $stmts[$indexStmt]->getAttribute(AttributeKey::COMMENTS));
|
|
|
|
$stmts[$indexStmt]->setAttribute(AttributeKey::COMMENTS, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-30 09:49:07 +00:00
|
|
|
/**
|
2021-07-21 14:23:45 +00:00
|
|
|
* @param array<FullyQualifiedObjectType|AliasedObjectType> $mainTypes
|
|
|
|
* @param array<FullyQualifiedObjectType|AliasedObjectType> $typesToRemove
|
|
|
|
* @return array<FullyQualifiedObjectType|AliasedObjectType>
|
2019-10-30 09:49:07 +00:00
|
|
|
*/
|
2021-05-09 20:15:43 +00:00
|
|
|
private function diffFullyQualifiedObjectTypes(array $mainTypes, array $typesToRemove) : array
|
2019-05-19 21:44:38 +00:00
|
|
|
{
|
2019-10-30 09:49:07 +00:00
|
|
|
foreach ($mainTypes as $key => $mainType) {
|
|
|
|
foreach ($typesToRemove as $typeToRemove) {
|
|
|
|
if ($mainType->equals($typeToRemove)) {
|
|
|
|
unset($mainTypes[$key]);
|
|
|
|
}
|
|
|
|
}
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
2021-05-09 20:15:43 +00:00
|
|
|
return \array_values($mainTypes);
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
|
|
|
/**
|
2021-07-21 14:23:45 +00:00
|
|
|
* @param array<AliasedObjectType|FullyQualifiedObjectType> $useImportTypes
|
2023-07-28 13:10:47 +00:00
|
|
|
* @param array<FullyQualifiedObjectType|AliasedObjectType> $constantUseImportTypes
|
2021-07-21 14:23:45 +00:00
|
|
|
* @param array<FullyQualifiedObjectType|AliasedObjectType> $functionUseImportTypes
|
2019-05-19 21:44:38 +00:00
|
|
|
* @return Use_[]
|
|
|
|
*/
|
2023-07-28 13:10:47 +00:00
|
|
|
private function createUses(array $useImportTypes, array $constantUseImportTypes, array $functionUseImportTypes, ?string $namespaceName) : array
|
2019-05-19 21:44:38 +00:00
|
|
|
{
|
2019-09-06 10:30:58 +00:00
|
|
|
$newUses = [];
|
2023-09-12 20:17:09 +00:00
|
|
|
/** @var array<Use_::TYPE_*, array<AliasedObjectType|FullyQualifiedObjectType>> $importsMapping */
|
2023-07-28 13:10:47 +00:00
|
|
|
$importsMapping = [Use_::TYPE_NORMAL => $useImportTypes, Use_::TYPE_CONSTANT => $constantUseImportTypes, Use_::TYPE_FUNCTION => $functionUseImportTypes];
|
|
|
|
foreach ($importsMapping as $type => $importTypes) {
|
2023-09-12 20:17:09 +00:00
|
|
|
/** @var AliasedObjectType|FullyQualifiedObjectType $importType */
|
2023-07-28 13:10:47 +00:00
|
|
|
foreach ($importTypes as $importType) {
|
|
|
|
if ($namespaceName !== null && $this->isCurrentNamespace($namespaceName, $importType)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// already imported in previous cycle
|
|
|
|
$newUses[] = $importType->getUseNode($type);
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $newUses;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function getNamespaceName(Namespace_ $namespace) : ?string
|
2019-09-06 10:30:58 +00:00
|
|
|
{
|
2023-03-23 23:21:34 +00:00
|
|
|
if (!$namespace->name instanceof Name) {
|
2019-10-30 09:49:07 +00:00
|
|
|
return null;
|
2019-09-06 10:30:58 +00:00
|
|
|
}
|
2019-10-30 09:49:07 +00:00
|
|
|
return $namespace->name->toString();
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function isCurrentNamespace(string $namespaceName, ObjectType $objectType) : bool
|
2020-01-21 00:54:47 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$afterCurrentNamespace = Strings::after($objectType->getClassName(), $namespaceName . '\\');
|
2021-11-28 18:05:13 +00:00
|
|
|
if ($afterCurrentNamespace === null) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2019-10-30 09:49:07 +00:00
|
|
|
}
|
2021-05-29 22:10:59 +00:00
|
|
|
return \strpos($afterCurrentNamespace, '\\') === \false;
|
2019-09-06 10:30:58 +00:00
|
|
|
}
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|