simpleCallableNodeTraverser = $simpleCallableNodeTraverser; $this->nodeNameResolver = $nodeNameResolver; $this->nodeFinder = $nodeFinder; $this->reflectionProvider = $reflectionProvider; $this->betterNodeFinder = $betterNodeFinder; } // Avoids circular reference /** * @required */ public function autowireShortNameResolver(\Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory $phpDocInfoFactory) : void { $this->phpDocInfoFactory = $phpDocInfoFactory; } /** * @return array */ public function resolveForNode(\Rector\Core\ValueObject\Application\File $file) : array { $smartFileInfo = $file->getSmartFileInfo(); $nodeRealPath = $smartFileInfo->getRealPath(); if (isset($this->shortNamesByFilePath[$nodeRealPath])) { return $this->shortNamesByFilePath[$nodeRealPath]; } $shortNamesToFullyQualifiedNames = $this->resolveForStmts($file->getNewStmts()); $this->shortNamesByFilePath[$nodeRealPath] = $shortNamesToFullyQualifiedNames; return $shortNamesToFullyQualifiedNames; } /** * Collects all "class ", "trait " and "interface " * @return string[] */ public function resolveShortClassLikeNamesForNode(\PhpParser\Node $node) : array { $namespace = $this->betterNodeFinder->findParentType($node, \PhpParser\Node\Stmt\Namespace_::class); if (!$namespace instanceof \PhpParser\Node\Stmt\Namespace_) { // only handle namespace nodes return []; } /** @var ClassLike[] $classLikes */ $classLikes = $this->nodeFinder->findInstanceOf($namespace, \PhpParser\Node\Stmt\ClassLike::class); $shortClassLikeNames = []; foreach ($classLikes as $classLike) { $shortClassLikeNames[] = $this->nodeNameResolver->getShortName($classLike); } return \array_unique($shortClassLikeNames); } /** * @param Node[] $stmts * @return array */ private function resolveForStmts(array $stmts) : array { $shortNamesToFullyQualifiedNames = []; $this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, function (\PhpParser\Node $node) use(&$shortNamesToFullyQualifiedNames) : void { // class name is used! if ($node instanceof \PhpParser\Node\Stmt\ClassLike && $node->name instanceof \PhpParser\Node\Identifier) { $fullyQualifiedName = $this->nodeNameResolver->getName($node); if ($fullyQualifiedName === null) { return; } $shortNamesToFullyQualifiedNames[$node->name->toString()] = $fullyQualifiedName; return; } if (!$node instanceof \PhpParser\Node\Name) { return; } $originalName = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::ORIGINAL_NAME); if (!$originalName instanceof \PhpParser\Node\Name) { return; } // already short if (\strpos($originalName->toString(), '\\') !== \false) { return; } $fullyQualifiedName = $this->nodeNameResolver->getName($node); $shortNamesToFullyQualifiedNames[$originalName->toString()] = $fullyQualifiedName; }); $docBlockShortNamesToFullyQualifiedNames = $this->resolveFromDocBlocks($stmts); return \array_merge($shortNamesToFullyQualifiedNames, $docBlockShortNamesToFullyQualifiedNames); } /** * @param Node[] $stmts * @return array */ private function resolveFromDocBlocks(array $stmts) : array { $reflectionClass = $this->resolveNativeClassReflection($stmts); $shortNamesToFullyQualifiedNames = []; $this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, function (\PhpParser\Node $node) use(&$shortNamesToFullyQualifiedNames, $reflectionClass) : void { $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); foreach ($phpDocInfo->getPhpDocNode()->children as $phpDocChildNode) { /** @var PhpDocChildNode $phpDocChildNode */ $shortTagName = $this->resolveShortTagNameFromPhpDocChildNode($phpDocChildNode); if ($shortTagName === null) { continue; } if ($reflectionClass !== null) { $fullyQualifiedTagName = \RectorPrefix20210602\Nette\Utils\Reflection::expandClassName($shortTagName, $reflectionClass); } else { $fullyQualifiedTagName = $shortTagName; } $shortNamesToFullyQualifiedNames[$shortTagName] = $fullyQualifiedTagName; } }); return $shortNamesToFullyQualifiedNames; } private function resolveShortTagNameFromPhpDocChildNode(\PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode $phpDocChildNode) : ?string { if (!$phpDocChildNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode) { return null; } $tagName = \ltrim($phpDocChildNode->name, '@'); // is annotation class - big letter? if (\RectorPrefix20210602\Nette\Utils\Strings::match($tagName, self::BIG_LETTER_START_REGEX)) { return $tagName; } if (!$this->isValueNodeWithType($phpDocChildNode->value)) { return null; } $typeNode = $phpDocChildNode->value->type; if (!$typeNode instanceof \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode) { return null; } if (\strpos($typeNode->name, '\\') !== \false) { return null; } return $typeNode->name; } private function isValueNodeWithType(\PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode $phpDocTagValueNode) : bool { return $phpDocTagValueNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode || $phpDocTagValueNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode || $phpDocTagValueNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode || $phpDocTagValueNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode || $phpDocTagValueNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode; } /** * @param Node[] $stmts */ private function resolveNativeClassReflection(array $stmts) : ?\ReflectionClass { $firstClassLike = $this->nodeFinder->findFirstInstanceOf($stmts, \PhpParser\Node\Stmt\ClassLike::class); if (!$firstClassLike instanceof \PhpParser\Node\Stmt\ClassLike) { return null; } $className = $this->nodeNameResolver->getName($firstClassLike); if (!$className) { return null; } if (!$this->reflectionProvider->hasClass($className)) { return null; } $classReflection = $this->reflectionProvider->getClass($className); return $classReflection->getNativeReflection(); } }