rector/packages/NodeTypeResolver/PhpDocNodeVisitor/NameImportingPhpDocNodeVisitor.php

224 lines
11 KiB
PHP
Raw Normal View History

<?php
declare (strict_types=1);
namespace Rector\NodeTypeResolver\PhpDocNodeVisitor;
use RectorPrefix20211204\Nette\Utils\Strings;
use PhpParser\Node as PhpParserNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
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;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Provider\CurrentFileProvider;
use Rector\Core\ValueObject\Application\File;
use Rector\PostRector\Collector\UseNodesToAddCollector;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use RectorPrefix20211204\Symplify\PackageBuilder\Parameter\ParameterProvider;
use RectorPrefix20211204\Symplify\PackageBuilder\Reflection\ClassLikeExistenceChecker;
use RectorPrefix20211204\Symplify\SimplePhpDocParser\PhpDocNodeVisitor\AbstractPhpDocNodeVisitor;
final class NameImportingPhpDocNodeVisitor extends \RectorPrefix20211204\Symplify\SimplePhpDocParser\PhpDocNodeVisitor\AbstractPhpDocNodeVisitor
{
/**
* @var PhpParserNode|null
*/
private $currentPhpParserNode;
/**
* @var \Rector\StaticTypeMapper\StaticTypeMapper
*/
private $staticTypeMapper;
/**
* @var \Symplify\PackageBuilder\Parameter\ParameterProvider
*/
private $parameterProvider;
/**
* @var \Rector\CodingStyle\ClassNameImport\ClassNameImportSkipper
*/
private $classNameImportSkipper;
/**
* @var \Rector\PostRector\Collector\UseNodesToAddCollector
*/
private $useNodesToAddCollector;
/**
* @var \Rector\Core\Provider\CurrentFileProvider
*/
private $currentFileProvider;
/**
* @var \Symplify\PackageBuilder\Reflection\ClassLikeExistenceChecker
*/
private $classLikeExistenceChecker;
public function __construct(\Rector\StaticTypeMapper\StaticTypeMapper $staticTypeMapper, \RectorPrefix20211204\Symplify\PackageBuilder\Parameter\ParameterProvider $parameterProvider, \Rector\CodingStyle\ClassNameImport\ClassNameImportSkipper $classNameImportSkipper, \Rector\PostRector\Collector\UseNodesToAddCollector $useNodesToAddCollector, \Rector\Core\Provider\CurrentFileProvider $currentFileProvider, \RectorPrefix20211204\Symplify\PackageBuilder\Reflection\ClassLikeExistenceChecker $classLikeExistenceChecker)
{
$this->staticTypeMapper = $staticTypeMapper;
$this->parameterProvider = $parameterProvider;
$this->classNameImportSkipper = $classNameImportSkipper;
$this->useNodesToAddCollector = $useNodesToAddCollector;
$this->currentFileProvider = $currentFileProvider;
$this->classLikeExistenceChecker = $classLikeExistenceChecker;
}
public function beforeTraverse(\PHPStan\PhpDocParser\Ast\Node $node) : void
{
if ($this->currentPhpParserNode === null) {
throw new \Rector\Core\Exception\ShouldNotHappenException('Set "$currentPhpParserNode" first');
}
}
public function enterNode(\PHPStan\PhpDocParser\Ast\Node $node) : ?\PHPStan\PhpDocParser\Ast\Node
{
if ($node instanceof \Rector\BetterPhpDocParser\PhpDoc\SpacelessPhpDocTagNode) {
return $this->enterSpacelessPhpDocTagNode($node);
}
if ($node instanceof \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode) {
$this->processDoctrineAnnotationTagValueNode($node);
return $node;
}
if (!$node instanceof \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode) {
return null;
}
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($node, $this->currentPhpParserNode);
if (!$staticType instanceof \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType) {
return null;
}
// Importing root namespace classes (like \DateTime) is optional
if ($this->shouldSkipShortClassName($staticType)) {
return null;
}
$file = $this->currentFileProvider->getFile();
if (!$file instanceof \Rector\Core\ValueObject\Application\File) {
return null;
}
if ($this->currentPhpParserNode === null) {
throw new \Rector\Core\Exception\ShouldNotHappenException();
}
return $this->processFqnNameImport($this->currentPhpParserNode, $node, $staticType, $file);
}
public function setCurrentNode(\PhpParser\Node $phpParserNode) : void
{
$this->currentPhpParserNode = $phpParserNode;
}
private function processFqnNameImport(\PhpParser\Node $phpParserNode, \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode $identifierTypeNode, \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType $fullyQualifiedObjectType, \Rector\Core\ValueObject\Application\File $file) : ?\PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode
{
if ($this->classNameImportSkipper->shouldSkipNameForFullyQualifiedObjectType($file, $phpParserNode, $fullyQualifiedObjectType)) {
return null;
}
$parent = $identifierTypeNode->getAttribute(\Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey::PARENT);
if ($parent instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode) {
// might break
return null;
}
$newNode = new \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode($fullyQualifiedObjectType->getShortName());
// should skip because its already used
if ($this->useNodesToAddCollector->isShortImported($file, $fullyQualifiedObjectType)) {
if (!$this->useNodesToAddCollector->isImportShortable($file, $fullyQualifiedObjectType)) {
return null;
}
if ($this->shouldImport($newNode, $identifierTypeNode, $fullyQualifiedObjectType)) {
$this->useNodesToAddCollector->addUseImport($fullyQualifiedObjectType);
return $newNode;
}
return null;
}
if ($this->shouldImport($newNode, $identifierTypeNode, $fullyQualifiedObjectType)) {
$this->useNodesToAddCollector->addUseImport($fullyQualifiedObjectType);
return $newNode;
}
return null;
}
private function shouldImport(\PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode $newNode, \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode $identifierTypeNode, \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType $fullyQualifiedObjectType) : bool
{
if ($newNode->name === $identifierTypeNode->name) {
return \false;
}
if (\strncmp($identifierTypeNode->name, '\\', \strlen('\\')) === 0) {
return \true;
}
$className = $fullyQualifiedObjectType->getClassName();
if (!$this->classLikeExistenceChecker->doesClassLikeInsensitiveExists($className)) {
return \false;
}
$firstPath = \RectorPrefix20211204\Nette\Utils\Strings::before($identifierTypeNode->name, '\\' . $newNode->name);
if ($firstPath === null) {
return \true;
}
if ($firstPath === '') {
return \true;
}
$namespaceParts = \explode('\\', \ltrim($firstPath, '\\'));
return \count($namespaceParts) > 1;
}
private function shouldSkipShortClassName(\Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType $fullyQualifiedObjectType) : bool
{
$importShortClasses = $this->parameterProvider->provideBoolParameter(\Rector\Core\Configuration\Option::IMPORT_SHORT_CLASSES);
if ($importShortClasses) {
return \false;
}
return \substr_count($fullyQualifiedObjectType->getClassName(), '\\') === 0;
}
private function processDoctrineAnnotationTagValueNode(\Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode) : void
{
$currentPhpParserNode = $this->currentPhpParserNode;
if (!$currentPhpParserNode instanceof \PhpParser\Node) {
throw new \Rector\Core\Exception\ShouldNotHappenException();
}
$identifierTypeNode = $doctrineAnnotationTagValueNode->identifierTypeNode;
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($identifierTypeNode, $currentPhpParserNode);
if (!$staticType instanceof \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType) {
if (!$staticType instanceof \PHPStan\Type\ObjectType) {
return;
}
$staticType = new \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType($staticType->getClassName());
}
$file = $this->currentFileProvider->getFile();
if (!$file instanceof \Rector\Core\ValueObject\Application\File) {
return;
}
$shortentedIdentifierTypeNode = $this->processFqnNameImport($currentPhpParserNode, $identifierTypeNode, $staticType, $file);
if (!$shortentedIdentifierTypeNode instanceof \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode) {
return;
}
$doctrineAnnotationTagValueNode->identifierTypeNode = $shortentedIdentifierTypeNode;
$doctrineAnnotationTagValueNode->markAsChanged();
}
/**
* @return \Rector\BetterPhpDocParser\PhpDoc\SpacelessPhpDocTagNode|null
*/
private function enterSpacelessPhpDocTagNode(\Rector\BetterPhpDocParser\PhpDoc\SpacelessPhpDocTagNode $spacelessPhpDocTagNode)
{
if (!$spacelessPhpDocTagNode->value instanceof \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode) {
return null;
}
// special case for doctrine annotation
if (\strncmp($spacelessPhpDocTagNode->name, '@', \strlen('@')) !== 0) {
return null;
}
$attributeClass = \ltrim($spacelessPhpDocTagNode->name, '@\\');
$identifierTypeNode = new \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode($attributeClass);
$currentPhpParserNode = $this->currentPhpParserNode;
if (!$currentPhpParserNode instanceof \PhpParser\Node) {
throw new \Rector\Core\Exception\ShouldNotHappenException();
}
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType(new \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode($attributeClass), $currentPhpParserNode);
if (!$staticType instanceof \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType) {
if (!$staticType instanceof \PHPStan\Type\ObjectType) {
return null;
}
$staticType = new \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType($staticType->getClassName());
}
$file = $this->currentFileProvider->getFile();
if (!$file instanceof \Rector\Core\ValueObject\Application\File) {
return null;
}
$importedName = $this->processFqnNameImport($currentPhpParserNode, $identifierTypeNode, $staticType, $file);
if ($importedName !== null) {
$spacelessPhpDocTagNode->name = '@' . $importedName->name;
return $spacelessPhpDocTagNode;
}
return null;
}
}