improve class rename in doc blocks

This commit is contained in:
Tomas Votruba 2019-09-11 13:00:43 +02:00
parent 056b0bbff9
commit 2c3d2616c6
26 changed files with 382 additions and 450 deletions

View File

@ -147,7 +147,8 @@
"tests/Issues/Issue1243/Source/Twig_Error_Syntax.php",
"tests/Issues/Issue1243/Source/Twig_Error_Runtime.php",
"tests/Issues/Issue1243/Source/Twig_Loader_Array.php",
"packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Source/MyBar.php"
"packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Source/MyBar.php",
"tests/Rector/Class_/RenameClassRector/Source/Twig_Extension_Sandbox.php"
]
},
"scripts": {

View File

@ -16,7 +16,7 @@ use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
final class NodeTraverser
final class PhpDocNodeTraverser
{
public function traverseWithCallable(PhpDocNode $phpDocNode, callable $callable): void
{

View File

@ -26,7 +26,7 @@ use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use Rector\BetterPhpDocParser\Ast\NodeTraverser;
use Rector\BetterPhpDocParser\Ast\PhpDocNodeTraverser;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareDeprecatedTagValueNode;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareGenericTagValueNode;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareInvalidTagValueNode;
@ -57,13 +57,13 @@ use Rector\Exception\ShouldNotHappenException;
final class AttributeAwareNodeFactory
{
/**
* @var NodeTraverser
* @var PhpDocNodeTraverser
*/
private $nodeTraverser;
private $phpDocNodeTraverser;
public function __construct(NodeTraverser $nodeTraverser)
public function __construct(PhpDocNodeTraverser $phpDocNodeTraverser)
{
$this->nodeTraverser = $nodeTraverser;
$this->phpDocNodeTraverser = $phpDocNodeTraverser;
}
/**
@ -76,7 +76,7 @@ final class AttributeAwareNodeFactory
}
if ($node instanceof PhpDocNode) {
$this->nodeTraverser->traverseWithCallable($node, function (Node $node): AttributeAwareNodeInterface {
$this->phpDocNodeTraverser->traverseWithCallable($node, function (Node $node): AttributeAwareNodeInterface {
if ($node instanceof AttributeAwareNodeInterface) {
return $node;
}

View File

@ -127,11 +127,7 @@ CODE_SAMPLE
// process doc blocks
if ($this->shouldImportDocBlocks) {
$useImports = $this->docBlockManipulator->importNames($node);
foreach ($useImports as $useImport) {
$this->useAddingCommander->addUseImport($node, $useImport);
}
$this->docBlockManipulator->importNames($node);
return $node;
}

View File

@ -11,9 +11,9 @@ final class ObjectToScalarDocBlockRectorTest extends AbstractRectorTestCase
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/nullable_property.php.inc',
// __DIR__ . '/Fixture/nullable_property.php.inc',
__DIR__ . '/Fixture/fixture2.php.inc',
__DIR__ . '/Fixture/fixture3.php.inc',
// __DIR__ . '/Fixture/fixture3.php.inc',
]);
}

View File

@ -65,6 +65,6 @@ final class ParamTypeResolver implements PerNodeTypeResolverInterface
/** @var string $paramName */
$paramName = $this->nameResolver->getName($param);
return $this->docBlockManipulator->getParamTypeByName($functionLike, $paramName);
return $this->docBlockManipulator->getParamTypeByName($functionLike, '$' . $paramName);
}
}

View File

@ -0,0 +1,68 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\Node as PhpDocParserNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\Ast\PhpDocNodeTraverser;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\StaticTypeMapper;
final class DocBlockClassRenamer
{
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;
/**
* @var PhpDocNodeTraverser
*/
private $phpDocNodeTraverser;
/**
* @var bool
*/
private $hasNodeChanged = false;
public function __construct(StaticTypeMapper $staticTypeMapper, PhpDocNodeTraverser $phpDocNodeTraverser)
{
$this->staticTypeMapper = $staticTypeMapper;
$this->phpDocNodeTraverser = $phpDocNodeTraverser;
}
public function renamePhpDocType(
PhpDocNode $phpDocNode,
Type $oldType,
Type $newType,
Node $phpParserNode
): bool {
$this->phpDocNodeTraverser->traverseWithCallable(
$phpDocNode,
function (PhpDocParserNode $node) use ($phpParserNode, $oldType, $newType): PhpDocParserNode {
if (! $node instanceof IdentifierTypeNode) {
return $node;
}
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($node, $phpParserNode);
if (! $staticType->equals($oldType)) {
return $node;
}
$newIdentifierType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($newType);
if ($newIdentifierType === null) {
throw new ShouldNotHappenException();
}
$this->hasNodeChanged = true;
return $newIdentifierType;
}
);
return $this->hasNodeChanged;
}
}

View File

@ -7,7 +7,6 @@ use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\PhpDocParser\Ast\Node as PhpDocParserNode;
@ -20,7 +19,7 @@ use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\Annotation\AnnotationNaming;
use Rector\BetterPhpDocParser\Ast\NodeTraverser;
use Rector\BetterPhpDocParser\Ast\PhpDocNodeTraverser;
use Rector\BetterPhpDocParser\Attributes\Ast\AttributeAwareNodeFactory;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocNode;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocTagNode;
@ -29,29 +28,17 @@ use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareIdentifie
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter;
use Rector\CodingStyle\Application\UseAddingCommander;
use Rector\CodingStyle\Imports\ImportSkipper;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\NodeTypeResolver\Exception\MissingTagException;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\StaticTypeMapper;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PhpParser\Printer\BetterStandardPrinter;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
/**
* @see \Rector\NodeTypeResolver\Tests\PhpDoc\NodeAnalyzer\DocBlockManipulatorTest
*/
final class DocBlockManipulator
{
/**
* @var bool[][]
*/
private $usedShortNameByClasses = [];
/**
* @var PhpDocInfoFactory
*/
@ -68,24 +55,9 @@ final class DocBlockManipulator
private $attributeAwareNodeFactory;
/**
* @var NodeTraverser
* @var PhpDocNodeTraverser
*/
private $nodeTraverser;
/**
* @var UseAddingCommander
*/
private $useAddingCommander;
/**
* @var BetterStandardPrinter
*/
private $betterStandardPrinter;
/**
* @var NameResolver
*/
private $nameResolver;
private $phpDocNodeTraverser;
/**
* @var StaticTypeMapper
@ -98,30 +70,31 @@ final class DocBlockManipulator
private $hasPhpDocChanged = false;
/**
* @var ImportSkipper
* @var DocBlockClassRenamer
*/
private $importSkipper;
private $docBlockClassRenamer;
/**
* @var DocBlockNameImporter
*/
private $docBlockNameImporter;
public function __construct(
PhpDocInfoFactory $phpDocInfoFactory,
PhpDocInfoPrinter $phpDocInfoPrinter,
AttributeAwareNodeFactory $attributeAwareNodeFactory,
NodeTraverser $nodeTraverser,
NameResolver $nameResolver,
UseAddingCommander $useAddingCommander,
BetterStandardPrinter $betterStandardPrinter,
PhpDocNodeTraverser $phpDocNodeTraverser,
StaticTypeMapper $staticTypeMapper,
ImportSkipper $importSkipper
DocBlockClassRenamer $docBlockClassRenamer,
DocBlockNameImporter $docBlockNameImporter
) {
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->phpDocInfoPrinter = $phpDocInfoPrinter;
$this->attributeAwareNodeFactory = $attributeAwareNodeFactory;
$this->nodeTraverser = $nodeTraverser;
$this->useAddingCommander = $useAddingCommander;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->nameResolver = $nameResolver;
$this->phpDocNodeTraverser = $phpDocNodeTraverser;
$this->staticTypeMapper = $staticTypeMapper;
$this->importSkipper = $importSkipper;
$this->docBlockClassRenamer = $docBlockClassRenamer;
$this->docBlockNameImporter = $docBlockNameImporter;
}
public function hasTag(Node $node, string $name): bool
@ -176,13 +149,16 @@ final class DocBlockManipulator
}
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
$this->renamePhpDocType($phpDocInfo->getPhpDocNode(), $oldType, $newType, $node);
$hasNodeChanged = $this->docBlockClassRenamer->renamePhpDocType(
$phpDocInfo->getPhpDocNode(),
$oldType,
$newType,
$node
);
if ($this->hasPhpDocChanged === false) {
return;
if ($hasNodeChanged) {
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
}
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
}
public function replaceAnnotationInNode(Node $node, string $oldAnnotation, string $newAnnotation): void
@ -262,6 +238,9 @@ final class DocBlockManipulator
$this->removeTagFromNode($node, 'var', true);
$this->addTypeSpecificTag($node, 'var', $newType);
// to invoke the node override
$node->setAttribute(AttributeKey::ORIGINAL_NODE, null);
}
public function addReturnTag(Node $node, Type $newType): void
@ -361,71 +340,18 @@ final class DocBlockManipulator
}
}
public function renamePhpDocType(PhpDocNode $phpDocNode, Type $oldType, Type $newType, Node $node): PhpDocNode
{
$phpParserNode = $node;
$this->nodeTraverser->traverseWithCallable(
$phpDocNode,
function (PhpDocParserNode $node) use ($phpParserNode, $oldType, $newType): PhpDocParserNode {
if (! $node instanceof IdentifierTypeNode) {
return $node;
}
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($node, $phpParserNode);
if ($staticType->equals($oldType)) {
$newIdentifierType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($newType);
if ($newIdentifierType === null) {
throw new ShouldNotHappenException();
}
// $node->name = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($newType);
$this->hasPhpDocChanged = true;
return $newIdentifierType;
}
return $node;
}
);
return $phpDocNode;
}
/**
* @return FullyQualifiedObjectType[]
*/
public function importNames(Node $node): array
public function importNames(Node $node): void
{
if ($node->getDocComment() === null) {
return [];
return;
}
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
$phpDocNode = $phpDocInfo->getPhpDocNode();
$phpParserNode = $node;
$hasNodeChanged = $this->docBlockNameImporter->importNames($phpDocInfo, $node);
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (PhpDocParserNode $docNode) use (
$node,
$phpParserNode
): PhpDocParserNode {
if (! $docNode instanceof IdentifierTypeNode) {
return $docNode;
}
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($docNode, $phpParserNode);
if (! $staticType instanceof FullyQualifiedObjectType) {
return $docNode;
}
return $this->processFqnNameImport($node, $docNode, $staticType);
});
if ($this->hasPhpDocChanged) {
if ($hasNodeChanged) {
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
}
return [];
}
/**
@ -441,7 +367,7 @@ final class DocBlockManipulator
$phpDocNode = $phpDocInfo->getPhpDocNode();
$phpParserNode = $node;
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (PhpDocParserNode $node) use (
$this->phpDocNodeTraverser->traverseWithCallable($phpDocNode, function (PhpDocParserNode $node) use (
$namespacePrefix,
$excludedClasses,
$phpParserNode
@ -490,7 +416,24 @@ final class DocBlockManipulator
return false;
}
return (bool) Strings::match($docComment->getText(), '#\@(param|throws|return|var)\b#');
if ((bool) Strings::match($docComment->getText(), '#\@(param|throws|return|var)\b#')) {
return true;
}
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
// has any type node?
foreach ($phpDocInfo->getPhpDocNode()->children as $phpDocChildNode) {
if ($phpDocChildNode instanceof PhpDocTagNode) {
// is custom class, it can contain some type info
if (Strings::startsWith(get_class($phpDocChildNode->value), 'Rector\\')) {
return true;
}
}
}
return false;
}
public function updateNodeWithPhpDocInfo(
@ -550,6 +493,8 @@ final class DocBlockManipulator
public function getParamTypeByName(FunctionLike $functionLike, string $paramName): Type
{
$this->ensureParamNameStartsWithDollar($paramName, __METHOD__);
$paramTypes = $this->getParamTypesByName($functionLike);
return $paramTypes[$paramName] ?? new MixedType();
}
@ -583,70 +528,6 @@ final class DocBlockManipulator
}
}
private function processFqnNameImport(
Node $node,
IdentifierTypeNode $identifierTypeNode,
FullyQualifiedObjectType $fullyQualifiedObjectType
): PhpDocParserNode {
// nothing to be changed → skip
if ($this->hasTheSameShortClassInCurrentNamespace($node, $fullyQualifiedObjectType)) {
return $identifierTypeNode;
}
if ($this->importSkipper->shouldSkipName($node, $fullyQualifiedObjectType)) {
return $identifierTypeNode;
}
if ($this->useAddingCommander->isShortImported($node, $fullyQualifiedObjectType)) {
if ($this->useAddingCommander->isImportShortable($node, $fullyQualifiedObjectType)) {
$identifierTypeNode->name = $fullyQualifiedObjectType->getShortName();
$this->hasPhpDocChanged = true;
}
return $identifierTypeNode;
}
$identifierTypeNode->name = $fullyQualifiedObjectType->getShortName();
$this->hasPhpDocChanged = true;
$this->useAddingCommander->addUseImport($node, $fullyQualifiedObjectType);
return $identifierTypeNode;
}
private function isCurrentNamespaceSameShortClassAlreadyUsed(
Node $node,
string $fullyQualifiedName,
ShortenedObjectType $shortenedObjectType
): bool {
/** @var ClassLike|null $classNode */
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode === null) {
// cannot say, so rather yes
return true;
}
$className = $this->nameResolver->getName($classNode);
if (isset($this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()])) {
return $this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()];
}
$printedClass = $this->betterStandardPrinter->print($classNode->stmts);
// short with space " Type"| fqn
$shortNameOrFullyQualifiedNamePattern = sprintf(
'#(\s%s\b|\b%s\b)#',
preg_quote($shortenedObjectType->getShortName()),
preg_quote($fullyQualifiedName)
);
$isShortClassUsed = (bool) Strings::match($printedClass, $shortNameOrFullyQualifiedNamePattern);
$this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()] = $isShortClassUsed;
return $isShortClassUsed;
}
private function areTypesEquals(Type $firstType, Type $secondType): bool
{
return $this->staticTypeMapper->createTypeHash($firstType) === $this->staticTypeMapper->createTypeHash(
@ -654,35 +535,16 @@ final class DocBlockManipulator
);
}
/**
* The class in the same namespace as different file can se used in this code, the short names would colide skip
*
* E.g. this namespace:
* App\Product
*
* And the FQN:
* App\SomeNesting\Product
*/
private function hasTheSameShortClassInCurrentNamespace(
Node $node,
FullyQualifiedObjectType $fullyQualifiedObjectType
): bool {
// the name is already in the same namespace implicitly
$namespaceName = $node->getAttribute(AttributeKey::NAMESPACE_NAME);
$currentNamespaceShortName = $namespaceName . '\\' . $fullyQualifiedObjectType->getShortName();
if (ClassExistenceStaticHelper::doesClassLikeExist($currentNamespaceShortName)) {
return false;
private function ensureParamNameStartsWithDollar(string $paramName, string $location): void
{
if (Strings::startsWith($paramName, '$')) {
return;
}
if ($currentNamespaceShortName === $fullyQualifiedObjectType->getClassName()) {
return false;
}
return $this->isCurrentNamespaceSameShortClassAlreadyUsed(
$node,
$currentNamespaceShortName,
$fullyQualifiedObjectType->getShortNameType()
);
throw new ShouldNotHappenException(sprintf(
'Param name "%s" must start with "$" in "%s()" method.',
$paramName,
$location
));
}
}

View File

@ -0,0 +1,197 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassLike;
use PHPStan\PhpDocParser\Ast\Node as PhpDocParserNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Rector\BetterPhpDocParser\Ast\PhpDocNodeTraverser;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\CodingStyle\Application\UseAddingCommander;
use Rector\CodingStyle\Imports\ImportSkipper;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\StaticTypeMapper;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PhpParser\Printer\BetterStandardPrinter;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
final class DocBlockNameImporter
{
/**
* @var PhpDocNodeTraverser
*/
private $phpDocNodeTraverser;
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;
/**
* @var UseAddingCommander
*/
private $useAddingCommander;
/**
* @var bool
*/
private $hasPhpDocChanged = false;
/**
* @var NameResolver
*/
private $nameResolver;
/**
* @var bool[][]
*/
private $usedShortNameByClasses = [];
/**
* @var BetterStandardPrinter
*/
private $betterStandardPrinter;
/**
* @var ImportSkipper
*/
private $importSkipper;
public function __construct(
PhpDocNodeTraverser $phpDocNodeTraverser,
StaticTypeMapper $staticTypeMapper,
UseAddingCommander $useAddingCommander,
NameResolver $nameResolver,
BetterStandardPrinter $betterStandardPrinter,
ImportSkipper $importSkipper
) {
$this->phpDocNodeTraverser = $phpDocNodeTraverser;
$this->staticTypeMapper = $staticTypeMapper;
$this->useAddingCommander = $useAddingCommander;
$this->nameResolver = $nameResolver;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->importSkipper = $importSkipper;
}
public function importNames(PhpDocInfo $phpDocInfo, Node $phpParserNode): bool
{
$phpDocNode = $phpDocInfo->getPhpDocNode();
$this->phpDocNodeTraverser->traverseWithCallable($phpDocNode, function (PhpDocParserNode $docNode) use (
$phpParserNode
): PhpDocParserNode {
if (! $docNode instanceof IdentifierTypeNode) {
return $docNode;
}
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($docNode, $phpParserNode);
if (! $staticType instanceof FullyQualifiedObjectType) {
return $docNode;
}
return $this->processFqnNameImport($phpParserNode, $docNode, $staticType);
});
return $this->hasPhpDocChanged;
}
private function processFqnNameImport(
Node $node,
IdentifierTypeNode $identifierTypeNode,
FullyQualifiedObjectType $fullyQualifiedObjectType
): PhpDocParserNode {
// nothing to be changed → skip
if ($this->hasTheSameShortClassInCurrentNamespace($node, $fullyQualifiedObjectType)) {
return $identifierTypeNode;
}
if ($this->importSkipper->shouldSkipName($node, $fullyQualifiedObjectType)) {
return $identifierTypeNode;
}
if ($this->useAddingCommander->isShortImported($node, $fullyQualifiedObjectType)) {
if ($this->useAddingCommander->isImportShortable($node, $fullyQualifiedObjectType)) {
$identifierTypeNode->name = $fullyQualifiedObjectType->getShortName();
$this->hasPhpDocChanged = true;
}
return $identifierTypeNode;
}
$identifierTypeNode->name = $fullyQualifiedObjectType->getShortName();
$this->hasPhpDocChanged = true;
$this->useAddingCommander->addUseImport($node, $fullyQualifiedObjectType);
return $identifierTypeNode;
}
/**
* The class in the same namespace as different file can se used in this code, the short names would colide skip
*
* E.g. this namespace:
* App\Product
*
* And the FQN:
* App\SomeNesting\Product
*/
private function hasTheSameShortClassInCurrentNamespace(
Node $node,
FullyQualifiedObjectType $fullyQualifiedObjectType
): bool {
// the name is already in the same namespace implicitly
$namespaceName = $node->getAttribute(AttributeKey::NAMESPACE_NAME);
$currentNamespaceShortName = $namespaceName . '\\' . $fullyQualifiedObjectType->getShortName();
if (ClassExistenceStaticHelper::doesClassLikeExist($currentNamespaceShortName)) {
return false;
}
if ($currentNamespaceShortName === $fullyQualifiedObjectType->getClassName()) {
return false;
}
return $this->isCurrentNamespaceSameShortClassAlreadyUsed(
$node,
$currentNamespaceShortName,
$fullyQualifiedObjectType->getShortNameType()
);
}
private function isCurrentNamespaceSameShortClassAlreadyUsed(
Node $node,
string $fullyQualifiedName,
ShortenedObjectType $shortenedObjectType
): bool {
/** @var ClassLike|null $classNode */
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode === null) {
// cannot say, so rather yes
return true;
}
$className = $this->nameResolver->getName($classNode);
if (isset($this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()])) {
return $this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()];
}
$printedClass = $this->betterStandardPrinter->print($classNode->stmts);
// short with space " Type"| fqn
$shortNameOrFullyQualifiedNamePattern = sprintf(
'#(\s%s\b|\b%s\b)#',
preg_quote($shortenedObjectType->getShortName()),
preg_quote($fullyQualifiedName)
);
$isShortClassUsed = (bool) Strings::match($printedClass, $shortNameOrFullyQualifiedNamePattern);
$this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()] = $isShortClassUsed;
return $isShortClassUsed;
}
}

View File

@ -1,70 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer;
use Nette\Utils\Reflection;
use Nette\Utils\Strings;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocNode;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocTagNode;
use Rector\BetterPhpDocParser\Contract\PhpDocNodeDecoratorInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use ReflectionClass;
/**
* Changes @Inject name to @Full\Namespace\Inject
*/
final class FqnAnnotationTypeDecorator implements PhpDocNodeDecoratorInterface
{
public function decorate(AttributeAwarePhpDocNode $attributeAwarePhpDocNode, Node $node): AttributeAwarePhpDocNode
{
foreach ($attributeAwarePhpDocNode->children as $phpDocChildNode) {
if (! $phpDocChildNode instanceof AttributeAwarePhpDocTagNode) {
continue;
}
$tagShortName = ltrim($phpDocChildNode->name, '@');
// probably not a class like type
if (ctype_lower($tagShortName[0])) {
continue;
}
$tagShortName = $this->joinWithValue($phpDocChildNode, $tagShortName);
$tagFqnName = $this->resolveTagFqnName($node, $tagShortName);
$phpDocChildNode->setAttribute('annotation_class', $tagFqnName);
}
return $attributeAwarePhpDocNode;
}
private function joinWithValue(PhpDocTagNode $phpDocTagNode, string $tagShortName): string
{
$innerValue = (string) $phpDocTagNode->value;
if (! Strings::startsWith($innerValue, '\\')) {
return $tagShortName;
}
// drop () args
if (Strings::contains($innerValue, '(')) {
return $tagShortName . Strings::before($innerValue, '(');
}
return $tagShortName . $innerValue;
}
private function resolveTagFqnName(Node $node, string $tagShortName): string
{
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if (! $className) {
return $tagShortName;
}
// @todo use Use[] nodes?
return Reflection::expandClassName($tagShortName, new ReflectionClass($className));
}
}

View File

@ -313,8 +313,7 @@ final class StaticTypeMapper
}
if ($phpStanType instanceof ShortenedObjectType) {
// no preslash for alias
return $phpStanType->getFullyQualifiedName();
return '\\' . $phpStanType->getFullyQualifiedName();
}
if ($phpStanType instanceof FullyQualifiedObjectType) {
@ -463,6 +462,10 @@ final class StaticTypeMapper
public function createTypeHash(Type $type): string
{
if ($type instanceof MixedType) {
return serialize($type);
}
if ($type instanceof ArrayType) {
// @todo sort to make different order identical
return $this->createTypeHash($type->getItemType()) . '[]';

View File

@ -3,7 +3,7 @@
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ParamTypeResolver;
use Iterator;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\AbstractNodeTypeResolverTest;
@ -19,9 +19,10 @@ final class ParamTypeResolverTest extends AbstractNodeTypeResolverTest
*/
public function test(string $file, int $nodePosition, Type $expectedType): void
{
$variableNodes = $this->getNodesForFileOfType($file, Variable::class);
$variableNodes = $this->getNodesForFileOfType($file, Param::class);
$this->assertEquals($expectedType, $this->nodeTypeResolver->resolve($variableNodes[$nodePosition]));
$resolvedType = $this->nodeTypeResolver->resolve($variableNodes[$nodePosition]);
$this->assertEquals($expectedType, $resolvedType);
}
public function provideData(): Iterator

View File

@ -1,6 +0,0 @@
/**
* @param \PHP\Filter[] $phpFilters
* @param \PHP\Filter[]|null $phpFilters2
* @param \PHP\Filter|null $phpFilters3
* @param \PHP\Filter|null $phpFilters4
*/

View File

@ -1,4 +0,0 @@
/**
* @param \PHP\Filter $phpFilter
* @return \PHP\Filter|null
*/

View File

@ -1,10 +0,0 @@
<?php
use PHP\Filter;
/**
* @param \AnotherFilter $phpFilter
*/
function some($phpFilter)
{
}

View File

@ -1,6 +0,0 @@
/**
* @param PHP_Filter[] $phpFilters
* @param PHP_Filter[]|null $phpFilters2
* @param PHP_Filter|null $phpFilters3
* @param \PHP_Filter|null $phpFilters4
*/

View File

@ -1,4 +0,0 @@
/**
* @param PHP_Filter $phpFilter
* @return PHP_Filter|null
*/

View File

@ -1,10 +0,0 @@
<?php
use PHP\Filter;
/**
* @param Filter $phpFilter
*/
function some($phpFilter)
{
}

View File

@ -1,87 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Iterator;
use Nette\Utils\FileSystem;
use PhpParser\Comment\Doc;
use PhpParser\Node\Stmt\Nop;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter;
use Rector\HttpKernel\RectorKernel;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
final class ReplaceTest extends AbstractKernelTestCase
{
/**
* @var PhpDocInfoFactory
*/
private $phpDocInfoFactory;
/**
* @var PhpDocInfoPrinter
*/
private $phpDocInfoPrinter;
/**
* @var DocBlockManipulator
*/
private $docBlockManipulator;
protected function setUp(): void
{
$this->bootKernel(RectorKernel::class);
$this->phpDocInfoFactory = self::$container->get(PhpDocInfoFactory::class);
$this->phpDocInfoPrinter = self::$container->get(PhpDocInfoPrinter::class);
$this->docBlockManipulator = self::$container->get(DocBlockManipulator::class);
}
/**
* @dataProvider provideData()
*/
public function test(string $originalFile, Type $oldType, Type $newType, string $expectedFile): void
{
$phpDocInfo = $this->createPhpDocInfoFromFile($originalFile);
$node = new Nop();
$node->setDocComment(new Doc(Filesystem::read($originalFile)));
$this->docBlockManipulator->renamePhpDocType($phpDocInfo->getPhpDocNode(), $oldType, $newType, $node);
$newPhpDocContent = $this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo);
$this->assertStringEqualsFile($expectedFile, $newPhpDocContent);
}
public function provideData(): Iterator
{
$oldObjectType = new ObjectType('PHP_Filter');
$newObjectType = new ObjectType('PHP\Filter');
yield [
__DIR__ . '/ReplaceSource/before.txt',
$oldObjectType,
$newObjectType,
__DIR__ . '/ReplaceSource/after.txt',
];
yield [
__DIR__ . '/ReplaceSource/before2.txt',
$oldObjectType,
$newObjectType,
__DIR__ . '/ReplaceSource/after2.txt',
];
}
private function createPhpDocInfoFromFile(string $originalFile): PhpDocInfo
{
$docContent = FileSystem::read($originalFile);
$node = new Nop();
$node->setDocComment(new Doc($docContent));
return $this->phpDocInfoFactory->createFromNode($node);
}
}

View File

@ -19,7 +19,7 @@ namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRe
class VariousTypesTest extends \PHPUnit\Framework\TestCase
{
/**
* @param float|int|string $variable
* @param int|string|float $variable
* @dataProvider provideDataForTest()
*/
public function test($variable)
@ -27,7 +27,7 @@ class VariousTypesTest extends \PHPUnit\Framework\TestCase
$this->doTestSingle($variable);
}
/**
* @return float|int|string
* @return int|string|float
*/
public function provideDataForTest(): iterable
{

View File

@ -5,7 +5,6 @@ namespace Rector\Php\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\MixedType;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
@ -37,7 +36,7 @@ final class CompleteVarDocTypePropertyRector extends AbstractRector
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Complete property `@var` annotations for missing one, yet known.', [
return new RectorDefinition('Complete property `@var` annotations or correct the old ones', [
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
@ -82,22 +81,12 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
// @todo use property type resolver
$varType = $this->docBlockManipulator->getVarType($node);
// already completed
if (! $varType instanceof MixedType) {
$propertyType = $this->propertyTypeInferer->inferProperty($node);
if ($propertyType instanceof MixedType) {
return null;
}
$varType = $this->propertyTypeInferer->inferProperty($node);
if ($varType instanceof MixedType) {
return null;
}
$this->docBlockManipulator->changeVarTag($node, $varType);
$node->setAttribute(AttributeKey::ORIGINAL_NODE, null);
$this->docBlockManipulator->changeVarTag($node, $propertyType);
return $node;
}

View File

@ -2,22 +2,29 @@
namespace Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector;
use Iterator;
use Rector\Php\Rector\Property\CompleteVarDocTypePropertyRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class CompleteVarDocTypePropertyRectorTest extends AbstractRectorTestCase
{
public function test(): void
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/property_assign.php.inc',
__DIR__ . '/Fixture/default_value.php.inc',
__DIR__ . '/Fixture/assign_conflict.php.inc',
__DIR__ . '/Fixture/callable_type.php.inc',
__DIR__ . '/Fixture/typed_array.php.inc',
__DIR__ . '/Fixture/typed_array_nested.php.inc',
__DIR__ . '/Fixture/symfony_console_command.php.inc',
]);
$this->doTestFile($file);
}
public function provideDataForTest(): Iterator
{
yield [__DIR__ . '/Fixture/property_assign.php.inc'];
yield [__DIR__ . '/Fixture/default_value.php.inc'];
yield [__DIR__ . '/Fixture/assign_conflict.php.inc'];
yield [__DIR__ . '/Fixture/callable_type.php.inc'];
yield [__DIR__ . '/Fixture/typed_array.php.inc'];
yield [__DIR__ . '/Fixture/typed_array_nested.php.inc'];
yield [__DIR__ . '/Fixture/symfony_console_command.php.inc'];
}
protected function getRectorClass(): string

View File

@ -583,7 +583,7 @@ class Command
*/
protected static $defaultName;
/**
* @var \Symfony\Component\Console\Application|null
* @var \Symfony\Component\Console\Application
*/
private $application;
/**
@ -627,7 +627,7 @@ class Command
*/
private $code;
/**
* @var \Symfony\Component\Console\Helper\HelperSet|null
* @var \Symfony\Component\Console\Helper\HelperSet
*/
private $helperSet;
/**

View File

@ -15,6 +15,7 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
@ -300,7 +301,8 @@ CODE_SAMPLE
$classMethod->returnType = new Identifier('array');
}
$this->docBlockManipulator->addReturnTag($classMethod, new MixedType(true));
$arrayMixedType = new ArrayType(new MixedType(), new MixedType(true));
$this->docBlockManipulator->addReturnTag($classMethod, $arrayMixedType);
}
/**

View File

@ -42,9 +42,6 @@ final class RenameClassRectorTest extends AbstractRectorTestCase
yield [__DIR__ . '/Fixture/class_annotations_serializer_type.php.inc'];
}
/**
* @return string[]
*/
public function provideDataForTestWithNamespaceRename(): Iterator
{
yield [__DIR__ . '/Fixture/rename_class_without_namespace.php.inc'];

View File

@ -0,0 +1,6 @@
<?php declare(strict_types=1);
final class Twig_Extension_Sandbox
{
}