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 = \RectorPrefix20220203\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; } }