2021-04-06 17:33:09 +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\NodeTypeResolver\PhpDocNodeVisitor;
|
2021-04-06 17:33:09 +00:00
|
|
|
|
2023-06-01 08:56:46 +00:00
|
|
|
use RectorPrefix202306\Nette\Utils\Strings;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node as PhpParserNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Node;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
2022-09-01 19:50:06 +00:00
|
|
|
use PHPStan\Reflection\ReflectionProvider;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PHPStan\Type\ObjectType;
|
|
|
|
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
|
|
|
|
use Rector\BetterPhpDocParser\PhpDoc\SpacelessPhpDocTagNode;
|
|
|
|
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
|
|
|
|
use Rector\CodingStyle\ClassNameImport\ClassNameImportSkipper;
|
|
|
|
use Rector\Core\Configuration\Option;
|
2022-09-01 19:50:06 +00:00
|
|
|
use Rector\Core\Configuration\Parameter\ParameterProvider;
|
2022-06-06 17:12:56 +00:00
|
|
|
use Rector\Core\Exception\ShouldNotHappenException;
|
|
|
|
use Rector\Core\Provider\CurrentFileProvider;
|
|
|
|
use Rector\Core\ValueObject\Application\File;
|
2022-08-29 20:49:28 +00:00
|
|
|
use Rector\PhpDocParser\PhpDocParser\PhpDocNodeVisitor\AbstractPhpDocNodeVisitor;
|
2022-06-06 17:12:56 +00:00
|
|
|
use Rector\PostRector\Collector\UseNodesToAddCollector;
|
|
|
|
use Rector\StaticTypeMapper\StaticTypeMapper;
|
|
|
|
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
|
2022-06-07 08:22:29 +00:00
|
|
|
final class NameImportingPhpDocNodeVisitor extends AbstractPhpDocNodeVisitor
|
2021-04-06 17:33:09 +00:00
|
|
|
{
|
|
|
|
/**
|
2021-11-05 23:24:22 +00:00
|
|
|
* @var PhpParserNode|null
|
2021-05-10 23:39:21 +00:00
|
|
|
*/
|
|
|
|
private $currentPhpParserNode;
|
|
|
|
/**
|
2021-12-04 12:47:17 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\StaticTypeMapper\StaticTypeMapper
|
2021-04-06 17:33:09 +00:00
|
|
|
*/
|
|
|
|
private $staticTypeMapper;
|
|
|
|
/**
|
2021-12-04 12:47:17 +00:00
|
|
|
* @readonly
|
2022-09-01 19:50:06 +00:00
|
|
|
* @var \Rector\Core\Configuration\Parameter\ParameterProvider
|
2021-04-06 17:33:09 +00:00
|
|
|
*/
|
|
|
|
private $parameterProvider;
|
|
|
|
/**
|
2021-12-04 12:47:17 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\CodingStyle\ClassNameImport\ClassNameImportSkipper
|
2021-04-06 17:33:09 +00:00
|
|
|
*/
|
|
|
|
private $classNameImportSkipper;
|
|
|
|
/**
|
2021-12-04 12:47:17 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\PostRector\Collector\UseNodesToAddCollector
|
2021-04-06 17:33:09 +00:00
|
|
|
*/
|
|
|
|
private $useNodesToAddCollector;
|
2021-07-21 13:46:30 +00:00
|
|
|
/**
|
2021-12-04 12:47:17 +00:00
|
|
|
* @readonly
|
2021-07-21 13:46:30 +00:00
|
|
|
* @var \Rector\Core\Provider\CurrentFileProvider
|
|
|
|
*/
|
|
|
|
private $currentFileProvider;
|
2021-09-22 17:53:44 +00:00
|
|
|
/**
|
2021-12-04 12:47:17 +00:00
|
|
|
* @readonly
|
2022-09-01 19:50:06 +00:00
|
|
|
* @var \PHPStan\Reflection\ReflectionProvider
|
2021-09-22 17:53:44 +00:00
|
|
|
*/
|
2022-09-01 19:50:06 +00:00
|
|
|
private $reflectionProvider;
|
|
|
|
public function __construct(StaticTypeMapper $staticTypeMapper, ParameterProvider $parameterProvider, ClassNameImportSkipper $classNameImportSkipper, UseNodesToAddCollector $useNodesToAddCollector, CurrentFileProvider $currentFileProvider, ReflectionProvider $reflectionProvider)
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2021-04-06 17:33:09 +00:00
|
|
|
$this->staticTypeMapper = $staticTypeMapper;
|
|
|
|
$this->parameterProvider = $parameterProvider;
|
|
|
|
$this->classNameImportSkipper = $classNameImportSkipper;
|
|
|
|
$this->useNodesToAddCollector = $useNodesToAddCollector;
|
2021-07-21 13:46:30 +00:00
|
|
|
$this->currentFileProvider = $currentFileProvider;
|
2022-09-01 19:50:06 +00:00
|
|
|
$this->reflectionProvider = $reflectionProvider;
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function beforeTraverse(Node $node) : void
|
2021-04-06 17:33:09 +00:00
|
|
|
{
|
2023-03-23 23:21:34 +00:00
|
|
|
if (!$this->currentPhpParserNode instanceof PhpParserNode) {
|
2022-06-07 08:22:29 +00:00
|
|
|
throw new ShouldNotHappenException('Set "$currentPhpParserNode" first');
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function enterNode(Node $node) : ?Node
|
2021-04-06 17:33:09 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($node instanceof SpacelessPhpDocTagNode) {
|
2021-06-22 10:43:16 +00:00
|
|
|
return $this->enterSpacelessPhpDocTagNode($node);
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($node instanceof DoctrineAnnotationTagValueNode) {
|
2021-06-22 10:43:16 +00:00
|
|
|
$this->processDoctrineAnnotationTagValueNode($node);
|
|
|
|
return $node;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$node instanceof IdentifierTypeNode) {
|
2021-04-06 17:33:09 +00:00
|
|
|
return null;
|
|
|
|
}
|
2021-05-09 20:15:43 +00:00
|
|
|
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($node, $this->currentPhpParserNode);
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$staticType instanceof FullyQualifiedObjectType) {
|
2021-04-06 17:33:09 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
// Importing root namespace classes (like \DateTime) is optional
|
|
|
|
if ($this->shouldSkipShortClassName($staticType)) {
|
|
|
|
return null;
|
|
|
|
}
|
2021-07-21 13:46:30 +00:00
|
|
|
$file = $this->currentFileProvider->getFile();
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$file instanceof File) {
|
2021-07-21 13:46:30 +00:00
|
|
|
return null;
|
|
|
|
}
|
2023-03-23 23:21:34 +00:00
|
|
|
if (!$this->currentPhpParserNode instanceof PhpParserNode) {
|
2022-06-07 08:22:29 +00:00
|
|
|
throw new ShouldNotHappenException();
|
2021-10-23 21:09:26 +00:00
|
|
|
}
|
2021-07-21 13:46:30 +00:00
|
|
|
return $this->processFqnNameImport($this->currentPhpParserNode, $node, $staticType, $file);
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function setCurrentNode(PhpParserNode $phpParserNode) : void
|
2021-04-06 17:33:09 +00:00
|
|
|
{
|
|
|
|
$this->currentPhpParserNode = $phpParserNode;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function processFqnNameImport(PhpParserNode $phpParserNode, IdentifierTypeNode $identifierTypeNode, FullyQualifiedObjectType $fullyQualifiedObjectType, File $file) : ?IdentifierTypeNode
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2022-03-28 09:18:40 +00:00
|
|
|
if (\strncmp($fullyQualifiedObjectType->getClassName(), '@', \strlen('@')) === 0) {
|
2022-06-07 08:22:29 +00:00
|
|
|
$fullyQualifiedObjectType = new FullyQualifiedObjectType(\ltrim($fullyQualifiedObjectType->getClassName(), '@'));
|
2022-03-28 09:18:40 +00:00
|
|
|
}
|
2021-07-21 13:46:30 +00:00
|
|
|
if ($this->classNameImportSkipper->shouldSkipNameForFullyQualifiedObjectType($file, $phpParserNode, $fullyQualifiedObjectType)) {
|
|
|
|
return null;
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
2022-08-09 13:39:17 +00:00
|
|
|
$parentNode = $identifierTypeNode->getAttribute(PhpDocAttributeKey::PARENT);
|
|
|
|
if ($parentNode instanceof TemplateTagValueNode) {
|
2021-04-06 17:33:09 +00:00
|
|
|
// might break
|
|
|
|
return null;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$newNode = new IdentifierTypeNode($fullyQualifiedObjectType->getShortName());
|
2021-04-06 17:33:09 +00:00
|
|
|
// should skip because its already used
|
2021-07-21 13:46:30 +00:00
|
|
|
if ($this->useNodesToAddCollector->isShortImported($file, $fullyQualifiedObjectType)) {
|
|
|
|
if (!$this->useNodesToAddCollector->isImportShortable($file, $fullyQualifiedObjectType)) {
|
|
|
|
return null;
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
2021-09-22 17:53:44 +00:00
|
|
|
if ($this->shouldImport($newNode, $identifierTypeNode, $fullyQualifiedObjectType)) {
|
2021-07-21 13:46:30 +00:00
|
|
|
$this->useNodesToAddCollector->addUseImport($fullyQualifiedObjectType);
|
|
|
|
return $newNode;
|
|
|
|
}
|
|
|
|
return null;
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
2021-09-22 17:53:44 +00:00
|
|
|
if ($this->shouldImport($newNode, $identifierTypeNode, $fullyQualifiedObjectType)) {
|
2021-07-21 13:46:30 +00:00
|
|
|
$this->useNodesToAddCollector->addUseImport($fullyQualifiedObjectType);
|
2021-04-09 21:00:57 +00:00
|
|
|
return $newNode;
|
|
|
|
}
|
2021-07-21 13:46:30 +00:00
|
|
|
return null;
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function shouldImport(IdentifierTypeNode $newNode, IdentifierTypeNode $identifierTypeNode, FullyQualifiedObjectType $fullyQualifiedObjectType) : bool
|
2021-09-22 17:53:44 +00:00
|
|
|
{
|
|
|
|
if ($newNode->name === $identifierTypeNode->name) {
|
|
|
|
return \false;
|
|
|
|
}
|
2021-09-23 08:01:36 +00:00
|
|
|
if (\strncmp($identifierTypeNode->name, '\\', \strlen('\\')) === 0) {
|
|
|
|
return \true;
|
|
|
|
}
|
2021-09-22 17:53:44 +00:00
|
|
|
$className = $fullyQualifiedObjectType->getClassName();
|
2022-09-01 19:50:06 +00:00
|
|
|
if (!$this->reflectionProvider->hasClass($className)) {
|
2021-09-23 08:01:36 +00:00
|
|
|
return \false;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$firstPath = Strings::before($identifierTypeNode->name, '\\' . $newNode->name);
|
2021-09-23 08:01:36 +00:00
|
|
|
if ($firstPath === null) {
|
|
|
|
return \true;
|
|
|
|
}
|
|
|
|
if ($firstPath === '') {
|
|
|
|
return \true;
|
|
|
|
}
|
|
|
|
$namespaceParts = \explode('\\', \ltrim($firstPath, '\\'));
|
|
|
|
return \count($namespaceParts) > 1;
|
2021-09-22 17:53:44 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function shouldSkipShortClassName(FullyQualifiedObjectType $fullyQualifiedObjectType) : bool
|
2021-04-06 17:33:09 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$importShortClasses = $this->parameterProvider->provideBoolParameter(Option::IMPORT_SHORT_CLASSES);
|
2021-04-06 17:33:09 +00:00
|
|
|
if ($importShortClasses) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
2021-05-09 20:15:43 +00:00
|
|
|
return \substr_count($fullyQualifiedObjectType->getClassName(), '\\') === 0;
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function processDoctrineAnnotationTagValueNode(DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode) : void
|
2021-06-22 10:43:16 +00:00
|
|
|
{
|
2021-10-23 21:09:26 +00:00
|
|
|
$currentPhpParserNode = $this->currentPhpParserNode;
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$currentPhpParserNode instanceof PhpParserNode) {
|
|
|
|
throw new ShouldNotHappenException();
|
2021-10-23 21:09:26 +00:00
|
|
|
}
|
2021-06-22 10:43:16 +00:00
|
|
|
$identifierTypeNode = $doctrineAnnotationTagValueNode->identifierTypeNode;
|
2021-10-23 21:09:26 +00:00
|
|
|
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($identifierTypeNode, $currentPhpParserNode);
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$staticType instanceof FullyQualifiedObjectType) {
|
|
|
|
if (!$staticType instanceof ObjectType) {
|
2021-06-22 10:43:16 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$staticType = new FullyQualifiedObjectType($staticType->getClassName());
|
2021-06-22 10:43:16 +00:00
|
|
|
}
|
2021-07-21 13:46:30 +00:00
|
|
|
$file = $this->currentFileProvider->getFile();
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$file instanceof File) {
|
2021-07-21 13:46:30 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-10-23 21:09:26 +00:00
|
|
|
$shortentedIdentifierTypeNode = $this->processFqnNameImport($currentPhpParserNode, $identifierTypeNode, $staticType, $file);
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$shortentedIdentifierTypeNode instanceof IdentifierTypeNode) {
|
2021-06-22 10:43:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
$doctrineAnnotationTagValueNode->identifierTypeNode = $shortentedIdentifierTypeNode;
|
|
|
|
$doctrineAnnotationTagValueNode->markAsChanged();
|
|
|
|
}
|
2022-08-11 14:29:35 +00:00
|
|
|
private function enterSpacelessPhpDocTagNode(SpacelessPhpDocTagNode $spacelessPhpDocTagNode) : ?\Rector\BetterPhpDocParser\PhpDoc\SpacelessPhpDocTagNode
|
2021-06-22 10:43:16 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$spacelessPhpDocTagNode->value instanceof DoctrineAnnotationTagValueNode) {
|
2021-06-22 10:43:16 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
// special case for doctrine annotation
|
|
|
|
if (\strncmp($spacelessPhpDocTagNode->name, '@', \strlen('@')) !== 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$attributeClass = \ltrim($spacelessPhpDocTagNode->name, '@\\');
|
2022-06-07 08:22:29 +00:00
|
|
|
$identifierTypeNode = new IdentifierTypeNode($attributeClass);
|
2021-10-23 21:09:26 +00:00
|
|
|
$currentPhpParserNode = $this->currentPhpParserNode;
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$currentPhpParserNode instanceof PhpParserNode) {
|
|
|
|
throw new ShouldNotHappenException();
|
2021-10-23 21:09:26 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType(new IdentifierTypeNode($attributeClass), $currentPhpParserNode);
|
|
|
|
if (!$staticType instanceof FullyQualifiedObjectType) {
|
|
|
|
if (!$staticType instanceof ObjectType) {
|
2021-06-22 10:43:16 +00:00
|
|
|
return null;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$staticType = new FullyQualifiedObjectType($staticType->getClassName());
|
2021-06-22 10:43:16 +00:00
|
|
|
}
|
2021-07-21 13:46:30 +00:00
|
|
|
$file = $this->currentFileProvider->getFile();
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$file instanceof File) {
|
2021-07-21 13:46:30 +00:00
|
|
|
return null;
|
|
|
|
}
|
2021-10-23 21:09:26 +00:00
|
|
|
$importedName = $this->processFqnNameImport($currentPhpParserNode, $identifierTypeNode, $staticType, $file);
|
2023-03-23 23:21:34 +00:00
|
|
|
if ($importedName instanceof IdentifierTypeNode) {
|
2021-06-22 10:43:16 +00:00
|
|
|
$spacelessPhpDocTagNode->name = '@' . $importedName->name;
|
|
|
|
return $spacelessPhpDocTagNode;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2021-04-06 17:33:09 +00:00
|
|
|
}
|