2019-10-13 05:59:52 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2019-05-19 21:44:38 +00:00
|
|
|
|
2020-11-20 13:37:53 +00:00
|
|
|
namespace Rector\CodingStyle\ClassNameImport;
|
2019-05-19 21:44:38 +00:00
|
|
|
|
|
|
|
use Nette\Utils\Strings;
|
|
|
|
use PhpParser\Node;
|
2019-11-02 18:38:24 +00:00
|
|
|
use PhpParser\Node\Identifier;
|
2019-05-19 21:44:38 +00:00
|
|
|
use PhpParser\Node\Name;
|
2019-11-02 18:38:24 +00:00
|
|
|
use PhpParser\Node\Stmt\ClassLike;
|
2020-07-12 16:35:56 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
2021-01-19 21:32:28 +00:00
|
|
|
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
|
2021-02-05 12:40:28 +00:00
|
|
|
use Rector\CodingStyle\Naming\ClassNaming;
|
2020-06-06 23:18:09 +00:00
|
|
|
use Rector\NodeTypeResolver\FileSystem\CurrentFileInfoProvider;
|
2019-05-19 21:44:38 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2021-01-16 21:45:18 +00:00
|
|
|
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
|
2020-06-06 23:18:09 +00:00
|
|
|
use Symplify\SmartFileSystem\SmartFileInfo;
|
2019-05-19 21:44:38 +00:00
|
|
|
|
|
|
|
final class ShortNameResolver
|
|
|
|
{
|
2020-09-23 09:16:40 +00:00
|
|
|
/**
|
|
|
|
* @var string
|
2020-10-29 18:04:33 +00:00
|
|
|
* @see https://regex101.com/r/KphLd2/1
|
2020-09-23 09:16:40 +00:00
|
|
|
*/
|
|
|
|
private const BIG_LETTER_START_REGEX = '#^[A-Z]#';
|
|
|
|
|
2019-05-19 21:44:38 +00:00
|
|
|
/**
|
2020-02-01 16:04:38 +00:00
|
|
|
* @var string[][]
|
2019-05-19 21:44:38 +00:00
|
|
|
*/
|
2020-06-06 23:18:09 +00:00
|
|
|
private $shortNamesByFilePath = [];
|
2019-05-19 21:44:38 +00:00
|
|
|
|
2019-06-04 15:23:39 +00:00
|
|
|
/**
|
2021-01-16 21:45:18 +00:00
|
|
|
* @var SimpleCallableNodeTraverser
|
2019-06-04 15:23:39 +00:00
|
|
|
*/
|
2021-01-16 21:45:18 +00:00
|
|
|
private $simpleCallableNodeTraverser;
|
2019-06-04 15:23:39 +00:00
|
|
|
|
2020-06-06 23:18:09 +00:00
|
|
|
/**
|
|
|
|
* @var CurrentFileInfoProvider
|
|
|
|
*/
|
|
|
|
private $currentFileInfoProvider;
|
|
|
|
|
2021-01-19 21:32:28 +00:00
|
|
|
/**
|
|
|
|
* @var PhpDocInfoFactory
|
|
|
|
*/
|
|
|
|
private $phpDocInfoFactory;
|
|
|
|
|
2021-02-05 12:40:28 +00:00
|
|
|
/**
|
|
|
|
* @var ClassNaming
|
|
|
|
*/
|
|
|
|
private $classNaming;
|
|
|
|
|
2020-06-06 23:18:09 +00:00
|
|
|
public function __construct(
|
2021-01-16 21:45:18 +00:00
|
|
|
SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
|
2021-01-19 21:32:28 +00:00
|
|
|
CurrentFileInfoProvider $currentFileInfoProvider,
|
2021-02-05 12:40:28 +00:00
|
|
|
PhpDocInfoFactory $phpDocInfoFactory,
|
|
|
|
ClassNaming $classNaming
|
2020-06-06 23:18:09 +00:00
|
|
|
) {
|
2021-01-16 21:45:18 +00:00
|
|
|
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
|
2020-06-06 23:18:09 +00:00
|
|
|
$this->currentFileInfoProvider = $currentFileInfoProvider;
|
2021-01-19 21:32:28 +00:00
|
|
|
$this->phpDocInfoFactory = $phpDocInfoFactory;
|
2021-02-05 12:40:28 +00:00
|
|
|
$this->classNaming = $classNaming;
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function resolveForNode(Node $node): array
|
|
|
|
{
|
2020-06-06 23:18:09 +00:00
|
|
|
$realPath = $this->getNodeRealPath($node);
|
2019-06-04 15:23:39 +00:00
|
|
|
|
2020-06-06 23:18:09 +00:00
|
|
|
if (isset($this->shortNamesByFilePath[$realPath])) {
|
|
|
|
return $this->shortNamesByFilePath[$realPath];
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 23:18:09 +00:00
|
|
|
$currentStmts = $this->currentFileInfoProvider->getCurrentStmts();
|
|
|
|
|
|
|
|
$shortNames = $this->resolveForStmts($currentStmts);
|
|
|
|
$this->shortNamesByFilePath[$realPath] = $shortNames;
|
2019-09-06 10:30:58 +00:00
|
|
|
|
|
|
|
return $shortNames;
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
|
|
|
|
2019-11-14 18:27:04 +00:00
|
|
|
/**
|
|
|
|
* Collects all "class <SomeClass>", "trait <SomeTrait>" and "interface <SomeInterface>"
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function resolveShortClassLikeNamesForNode(Node $node): array
|
|
|
|
{
|
|
|
|
$namespace = $node->getAttribute(AttributeKey::NAMESPACE_NODE);
|
|
|
|
if ($namespace === null) {
|
|
|
|
// only handle namespace nodes
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$shortClassLikeNames = [];
|
2021-01-16 21:45:18 +00:00
|
|
|
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($namespace, function (Node $node) use (
|
2019-11-14 18:27:04 +00:00
|
|
|
&$shortClassLikeNames
|
|
|
|
) {
|
|
|
|
// ...
|
|
|
|
if (! $node instanceof ClassLike) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($node->name === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-02-05 12:40:28 +00:00
|
|
|
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
|
|
|
if (is_string($className)) {
|
|
|
|
$shortClassLikeNames[] = $this->classNaming->getShortName($className);
|
|
|
|
}
|
2019-11-14 18:27:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return array_unique($shortClassLikeNames);
|
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
private function getNodeRealPath(Node $node): ?string
|
|
|
|
{
|
|
|
|
/** @var SmartFileInfo|null $fileInfo */
|
|
|
|
$fileInfo = $node->getAttribute(AttributeKey::FILE_INFO);
|
|
|
|
if ($fileInfo !== null) {
|
|
|
|
return $fileInfo->getRealPath();
|
|
|
|
}
|
|
|
|
|
2020-10-29 21:25:35 +00:00
|
|
|
$smartFileInfo = $this->currentFileInfoProvider->getSmartFileInfo();
|
|
|
|
if ($smartFileInfo !== null) {
|
|
|
|
return $smartFileInfo->getRealPath();
|
2020-08-05 20:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-05-19 21:44:38 +00:00
|
|
|
/**
|
2020-06-06 23:18:09 +00:00
|
|
|
* @param Node[] $stmts
|
2019-05-19 21:44:38 +00:00
|
|
|
* @return string[]
|
|
|
|
*/
|
2020-06-06 23:18:09 +00:00
|
|
|
private function resolveForStmts(array $stmts): array
|
2019-05-19 21:44:38 +00:00
|
|
|
{
|
|
|
|
$shortNames = [];
|
|
|
|
|
2021-01-16 21:45:18 +00:00
|
|
|
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, function (Node $node) use (
|
2019-05-19 21:44:38 +00:00
|
|
|
&$shortNames
|
|
|
|
): void {
|
2019-11-02 18:38:24 +00:00
|
|
|
// class name is used!
|
2020-01-19 19:45:01 +00:00
|
|
|
if ($node instanceof ClassLike && $node->name instanceof Identifier) {
|
|
|
|
$shortNames[$node->name->toString()] = $node->name->toString();
|
|
|
|
return;
|
2019-11-02 18:38:24 +00:00
|
|
|
}
|
|
|
|
|
2019-05-19 21:44:38 +00:00
|
|
|
if (! $node instanceof Name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-13 20:43:48 +00:00
|
|
|
$originalName = $node->getAttribute(AttributeKey::ORIGINAL_NAME);
|
2019-05-19 21:44:38 +00:00
|
|
|
if (! $originalName instanceof Name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// already short
|
|
|
|
if (Strings::contains($originalName->toString(), '\\')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$shortNames[$originalName->toString()] = $node->toString();
|
|
|
|
});
|
|
|
|
|
2020-07-12 16:35:56 +00:00
|
|
|
$docBlockShortNames = $this->resolveFromDocBlocks($stmts);
|
|
|
|
|
|
|
|
return array_merge($shortNames, $docBlockShortNames);
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|
2020-06-06 23:18:09 +00:00
|
|
|
|
2020-07-12 16:35:56 +00:00
|
|
|
/**
|
|
|
|
* @param Node[] $stmts
|
2021-03-06 21:16:18 +00:00
|
|
|
* @return array<string, string>
|
2020-07-12 16:35:56 +00:00
|
|
|
*/
|
|
|
|
private function resolveFromDocBlocks(array $stmts): array
|
|
|
|
{
|
|
|
|
$shortNames = [];
|
|
|
|
|
2021-01-19 21:32:28 +00:00
|
|
|
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, function (Node $node) use (
|
|
|
|
&$shortNames
|
|
|
|
): void {
|
|
|
|
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
|
2020-07-12 16:35:56 +00:00
|
|
|
|
|
|
|
foreach ($phpDocInfo->getPhpDocNode()->children as $phpDocChildNode) {
|
2021-03-06 21:16:18 +00:00
|
|
|
/** @var PhpDocChildNode $phpDocChildNode */
|
2020-07-12 16:35:56 +00:00
|
|
|
$shortTagName = $this->resolveShortTagNameFromPhpDocChildNode($phpDocChildNode);
|
|
|
|
if ($shortTagName === null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$shortNames[$shortTagName] = $shortTagName;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return $shortNames;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function resolveShortTagNameFromPhpDocChildNode(PhpDocChildNode $phpDocChildNode): ?string
|
|
|
|
{
|
|
|
|
if (! $phpDocChildNode instanceof PhpDocTagNode) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$tagName = ltrim($phpDocChildNode->name, '@');
|
|
|
|
|
|
|
|
// is annotation class - big letter?
|
2020-09-23 09:16:40 +00:00
|
|
|
if (Strings::match($tagName, self::BIG_LETTER_START_REGEX)) {
|
2020-07-12 16:35:56 +00:00
|
|
|
return $tagName;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! $this->isValueNodeWithType($phpDocChildNode->value)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$typeNode = $phpDocChildNode->value->type;
|
|
|
|
if (! $typeNode instanceof IdentifierTypeNode) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Strings::contains($typeNode->name, '\\')) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $typeNode->name;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function isValueNodeWithType(PhpDocTagValueNode $phpDocTagValueNode): bool
|
|
|
|
{
|
|
|
|
return $phpDocTagValueNode instanceof PropertyTagValueNode ||
|
|
|
|
$phpDocTagValueNode instanceof ReturnTagValueNode ||
|
|
|
|
$phpDocTagValueNode instanceof ParamTagValueNode ||
|
|
|
|
$phpDocTagValueNode instanceof VarTagValueNode ||
|
|
|
|
$phpDocTagValueNode instanceof ThrowsTagValueNode;
|
|
|
|
}
|
2019-05-19 21:44:38 +00:00
|
|
|
}
|